summaryrefslogtreecommitdiffstats
path: root/src/util
diff options
context:
space:
mode:
Diffstat (limited to '')
l---------src/util/.indent.pro1
-rw-r--r--src/util/.printfck25
-rw-r--r--src/util/Makefile.in2926
-rw-r--r--src/util/allascii.c65
-rw-r--r--src/util/alldig.c73
-rw-r--r--src/util/allprint.c50
-rw-r--r--src/util/allspace.c50
-rw-r--r--src/util/argv.c721
-rw-r--r--src/util/argv.h78
-rw-r--r--src/util/argv_attr.h43
-rw-r--r--src/util/argv_attr_print.c73
-rw-r--r--src/util/argv_attr_scan.c93
-rw-r--r--src/util/argv_split.c112
-rw-r--r--src/util/argv_split_at.c124
-rw-r--r--src/util/argv_splitq.c118
-rw-r--r--src/util/attr.h188
-rw-r--r--src/util/attr_clnt.c300
-rw-r--r--src/util/attr_clnt.h60
-rw-r--r--src/util/attr_print0.c256
-rw-r--r--src/util/attr_print64.c297
-rw-r--r--src/util/attr_print_plain.c252
-rw-r--r--src/util/attr_scan.ref36
-rw-r--r--src/util/attr_scan0.c596
-rw-r--r--src/util/attr_scan0.ref79
-rw-r--r--src/util/attr_scan64.c665
-rw-r--r--src/util/attr_scan64.ref79
-rw-r--r--src/util/attr_scan_plain.c643
-rw-r--r--src/util/attr_scan_plain.ref79
-rw-r--r--src/util/auto_clnt.c372
-rw-r--r--src/util/auto_clnt.h51
-rw-r--r--src/util/balpar.c56
-rw-r--r--src/util/base32_code.c266
-rw-r--r--src/util/base32_code.h41
-rw-r--r--src/util/base64_code.c228
-rw-r--r--src/util/base64_code.h49
-rw-r--r--src/util/basename.c49
-rw-r--r--src/util/binhash.c447
-rw-r--r--src/util/binhash.h65
-rw-r--r--src/util/byte_mask.c306
-rw-r--r--src/util/byte_mask.h64
-rw-r--r--src/util/byte_mask.in7
-rw-r--r--src/util/byte_mask.ref014
-rw-r--r--src/util/byte_mask.ref122
-rw-r--r--src/util/byte_mask.ref210
-rw-r--r--src/util/cache.in26
-rw-r--r--src/util/casefold.c359
-rw-r--r--src/util/casefold_test.in24
-rw-r--r--src/util/casefold_test.ref47
-rw-r--r--src/util/check_arg.h157
-rw-r--r--src/util/chroot_uid.c88
-rw-r--r--src/util/chroot_uid.h29
-rw-r--r--src/util/cidr_match.c316
-rw-r--r--src/util/cidr_match.h82
-rw-r--r--src/util/clean_env.c165
-rw-r--r--src/util/clean_env.h36
-rw-r--r--src/util/close_on_exec.c60
-rw-r--r--src/util/compat_va_copy.h44
-rw-r--r--src/util/concatenate.c73
-rw-r--r--src/util/connect.h43
-rw-r--r--src/util/ctable.c321
-rw-r--r--src/util/ctable.h41
-rw-r--r--src/util/ctable.in39
-rw-r--r--src/util/ctable.ref99
-rw-r--r--src/util/dict.c670
-rw-r--r--src/util/dict.h346
-rw-r--r--src/util/dict_alloc.c196
-rw-r--r--src/util/dict_cache.c1127
-rw-r--r--src/util/dict_cache.h67
-rw-r--r--src/util/dict_cdb.c446
-rw-r--r--src/util/dict_cdb.h44
-rw-r--r--src/util/dict_cidr.c362
-rw-r--r--src/util/dict_cidr.h43
-rw-r--r--src/util/dict_cidr.in23
-rw-r--r--src/util/dict_cidr.map50
-rw-r--r--src/util/dict_cidr.ref63
-rw-r--r--src/util/dict_cidr_file.in3
-rw-r--r--src/util/dict_cidr_file.map3
-rw-r--r--src/util/dict_cidr_file.ref8
-rw-r--r--src/util/dict_db.c880
-rw-r--r--src/util/dict_db.h58
-rw-r--r--src/util/dict_dbm.c503
-rw-r--r--src/util/dict_dbm.h44
-rw-r--r--src/util/dict_debug.c150
-rw-r--r--src/util/dict_env.c112
-rw-r--r--src/util/dict_env.h37
-rw-r--r--src/util/dict_fail.c115
-rw-r--r--src/util/dict_fail.h42
-rw-r--r--src/util/dict_file.c231
-rw-r--r--src/util/dict_ht.c171
-rw-r--r--src/util/dict_ht.h38
-rw-r--r--src/util/dict_inline.c150
-rw-r--r--src/util/dict_inline.h37
-rw-r--r--src/util/dict_inline.ref24
-rw-r--r--src/util/dict_inline_cidr.ref4
-rw-r--r--src/util/dict_inline_file.ref12
-rw-r--r--src/util/dict_inline_pcre.ref4
-rw-r--r--src/util/dict_inline_regexp.ref4
-rw-r--r--src/util/dict_lmdb.c706
-rw-r--r--src/util/dict_lmdb.h50
-rw-r--r--src/util/dict_ni.c194
-rw-r--r--src/util/dict_ni.h34
-rw-r--r--src/util/dict_nis.c247
-rw-r--r--src/util/dict_nis.h37
-rw-r--r--src/util/dict_nisplus.c304
-rw-r--r--src/util/dict_nisplus.h37
-rw-r--r--src/util/dict_open.c617
-rw-r--r--src/util/dict_pcre.c1121
-rw-r--r--src/util/dict_pcre.h43
-rw-r--r--src/util/dict_pcre.in15
-rw-r--r--src/util/dict_pcre.map28
-rw-r--r--src/util/dict_pcre.ref44
-rw-r--r--src/util/dict_pcre_file.in4
-rw-r--r--src/util/dict_pcre_file.map6
-rw-r--r--src/util/dict_pcre_file.ref12
-rw-r--r--src/util/dict_pipe.c189
-rw-r--r--src/util/dict_pipe.h37
-rw-r--r--src/util/dict_pipe_test.in9
-rw-r--r--src/util/dict_pipe_test.ref14
-rw-r--r--src/util/dict_random.c179
-rw-r--r--src/util/dict_random.h37
-rw-r--r--src/util/dict_random.ref20
-rw-r--r--src/util/dict_random_file.ref10
-rw-r--r--src/util/dict_regexp.c874
-rw-r--r--src/util/dict_regexp.h42
-rw-r--r--src/util/dict_regexp.in17
-rw-r--r--src/util/dict_regexp.map27
-rw-r--r--src/util/dict_regexp.ref46
-rw-r--r--src/util/dict_regexp_file.in3
-rw-r--r--src/util/dict_regexp_file.map3
-rw-r--r--src/util/dict_regexp_file.ref8
-rw-r--r--src/util/dict_sdbm.c483
-rw-r--r--src/util/dict_sdbm.h44
-rw-r--r--src/util/dict_seq.in11
-rw-r--r--src/util/dict_seq.ref22
-rw-r--r--src/util/dict_sockmap.c384
-rw-r--r--src/util/dict_sockmap.h37
-rw-r--r--src/util/dict_static.c151
-rw-r--r--src/util/dict_static.h35
-rw-r--r--src/util/dict_static.ref14
-rw-r--r--src/util/dict_static_file.ref10
-rw-r--r--src/util/dict_stream.c274
-rw-r--r--src/util/dict_stream.ref13
-rw-r--r--src/util/dict_surrogate.c180
-rw-r--r--src/util/dict_tcp.c315
-rw-r--r--src/util/dict_tcp.h37
-rw-r--r--src/util/dict_test.c166
-rw-r--r--src/util/dict_test.in10
-rw-r--r--src/util/dict_test.ref20
-rw-r--r--src/util/dict_thash.c255
-rw-r--r--src/util/dict_thash.h37
-rw-r--r--src/util/dict_thash.in5
-rw-r--r--src/util/dict_thash.map17
-rw-r--r--src/util/dict_thash.ref6
-rw-r--r--src/util/dict_union.c202
-rw-r--r--src/util/dict_union.h37
-rw-r--r--src/util/dict_union_test.in7
-rw-r--r--src/util/dict_union_test.ref10
-rw-r--r--src/util/dict_unix.c204
-rw-r--r--src/util/dict_unix.h37
-rw-r--r--src/util/dict_utf8.c300
-rw-r--r--src/util/dict_utf8_test.in14
-rw-r--r--src/util/dict_utf8_test.ref18
-rw-r--r--src/util/dir_forest.c110
-rw-r--r--src/util/dir_forest.h35
-rw-r--r--src/util/doze.c71
-rw-r--r--src/util/dummy_read.c61
-rw-r--r--src/util/dummy_write.c61
-rw-r--r--src/util/dup2_pass_on_exec.c64
-rw-r--r--src/util/duplex_pipe.c49
-rw-r--r--src/util/edit_file.c353
-rw-r--r--src/util/edit_file.h53
-rw-r--r--src/util/environ.c156
-rw-r--r--src/util/events.c1261
-rw-r--r--src/util/events.h67
-rw-r--r--src/util/exec_command.c112
-rw-r--r--src/util/exec_command.h30
-rw-r--r--src/util/extpar.c109
-rw-r--r--src/util/fifo_listen.c111
-rw-r--r--src/util/fifo_open.c67
-rw-r--r--src/util/fifo_rdonly_bug.c127
-rw-r--r--src/util/fifo_rdwr_bug.c89
-rw-r--r--src/util/fifo_trigger.c168
-rw-r--r--src/util/file_limit.c96
-rw-r--r--src/util/find_inet.c253
-rw-r--r--src/util/find_inet.h33
-rw-r--r--src/util/find_inet.ref5
-rw-r--r--src/util/format_tv.c160
-rw-r--r--src/util/format_tv.h35
-rw-r--r--src/util/format_tv.in68
-rw-r--r--src/util/format_tv.ref120
-rw-r--r--src/util/fsspace.c127
-rw-r--r--src/util/fsspace.h33
-rw-r--r--src/util/fullname.c111
-rw-r--r--src/util/fullname.h29
-rw-r--r--src/util/gccw.c58
-rw-r--r--src/util/gccw.ref18
-rw-r--r--src/util/get_domainname.c66
-rw-r--r--src/util/get_domainname.h29
-rw-r--r--src/util/get_hostname.c84
-rw-r--r--src/util/get_hostname.h29
-rw-r--r--src/util/hash_fnv.c237
-rw-r--r--src/util/hash_fnv.h40
-rw-r--r--src/util/hex_code.c243
-rw-r--r--src/util/hex_code.h54
-rw-r--r--src/util/hex_quote.c153
-rw-r--r--src/util/hex_quote.h36
-rw-r--r--src/util/host_port.c203
-rw-r--r--src/util/host_port.h35
-rw-r--r--src/util/host_port.in16
-rw-r--r--src/util/host_port.ref30
-rw-r--r--src/util/htable.c439
-rw-r--r--src/util/htable.h70
-rw-r--r--src/util/inet_addr_host.c173
-rw-r--r--src/util/inet_addr_host.h35
-rw-r--r--src/util/inet_addr_list.c186
-rw-r--r--src/util/inet_addr_list.h44
-rw-r--r--src/util/inet_addr_list.in9
-rw-r--r--src/util/inet_addr_list.ref15
-rw-r--r--src/util/inet_addr_local.c621
-rw-r--r--src/util/inet_addr_local.h35
-rw-r--r--src/util/inet_addr_sizes.c77
-rw-r--r--src/util/inet_addr_sizes.h36
-rw-r--r--src/util/inet_connect.c190
-rw-r--r--src/util/inet_listen.c189
-rw-r--r--src/util/inet_prefix_top.c172
-rw-r--r--src/util/inet_prefix_top.h27
-rw-r--r--src/util/inet_proto.c328
-rw-r--r--src/util/inet_proto.h56
-rw-r--r--src/util/inet_trigger.c130
-rw-r--r--src/util/inet_windowsize.c82
-rw-r--r--src/util/iostuff.h73
-rw-r--r--src/util/ip_match.c676
-rw-r--r--src/util/ip_match.h38
-rw-r--r--src/util/ip_match.in26
-rw-r--r--src/util/ip_match.ref69
-rw-r--r--src/util/killme_after.c69
-rw-r--r--src/util/killme_after.h30
-rw-r--r--src/util/known_tcp_ports.c253
-rw-r--r--src/util/known_tcp_ports.h38
-rw-r--r--src/util/known_tcp_ports.ref6
-rw-r--r--src/util/ldseed.c138
-rw-r--r--src/util/ldseed.h30
-rw-r--r--src/util/line_number.c71
-rw-r--r--src/util/line_number.h35
-rw-r--r--src/util/line_wrap.c122
-rw-r--r--src/util/line_wrap.h31
-rw-r--r--src/util/listen.h53
-rw-r--r--src/util/lmdb_cache_test_1.sh55
-rw-r--r--src/util/lmdb_cache_test_2.sh42
-rw-r--r--src/util/load_file.c80
-rw-r--r--src/util/load_file.h32
-rw-r--r--src/util/load_lib.c146
-rw-r--r--src/util/load_lib.h46
-rw-r--r--src/util/logwriter.c124
-rw-r--r--src/util/logwriter.h38
-rw-r--r--src/util/lowercase.c43
-rw-r--r--src/util/lstat_as.c73
-rw-r--r--src/util/lstat_as.h35
-rw-r--r--src/util/mac_expand.c848
-rw-r--r--src/util/mac_expand.h81
-rw-r--r--src/util/mac_expand.in105
-rw-r--r--src/util/mac_expand.ref222
-rw-r--r--src/util/mac_parse.c199
-rw-r--r--src/util/mac_parse.h51
-rw-r--r--src/util/make_dirs.c173
-rw-r--r--src/util/make_dirs.h30
-rw-r--r--src/util/mask_addr.c68
-rw-r--r--src/util/mask_addr.h30
-rw-r--r--src/util/match_list.c276
-rw-r--r--src/util/match_list.h66
-rw-r--r--src/util/match_ops.c312
-rw-r--r--src/util/midna_domain.c419
-rw-r--r--src/util/midna_domain.h43
-rw-r--r--src/util/midna_domain_test.in21
-rw-r--r--src/util/midna_domain_test.ref89
-rw-r--r--src/util/miss_endif_cidr.map1
-rw-r--r--src/util/miss_endif_cidr.ref4
-rw-r--r--src/util/miss_endif_pcre.ref4
-rw-r--r--src/util/miss_endif_re.map1
-rw-r--r--src/util/miss_endif_regexp.ref4
-rw-r--r--src/util/mkmap.h50
-rw-r--r--src/util/mkmap_cdb.c65
-rw-r--r--src/util/mkmap_db.c172
-rw-r--r--src/util/mkmap_dbm.c116
-rw-r--r--src/util/mkmap_fail.c53
-rw-r--r--src/util/mkmap_lmdb.c76
-rw-r--r--src/util/mkmap_open.c185
-rw-r--r--src/util/mkmap_sdbm.c113
-rw-r--r--src/util/msg.c340
-rw-r--r--src/util/msg.h60
-rw-r--r--src/util/msg_logger.c371
-rw-r--r--src/util/msg_logger.h62
-rw-r--r--src/util/msg_output.c174
-rw-r--r--src/util/msg_output.h51
-rw-r--r--src/util/msg_rate_delay.c138
-rw-r--r--src/util/msg_syslog.c266
-rw-r--r--src/util/msg_syslog.h41
-rw-r--r--src/util/msg_vstream.c87
-rw-r--r--src/util/msg_vstream.h34
-rw-r--r--src/util/mvect.c117
-rw-r--r--src/util/mvect.h42
-rw-r--r--src/util/myaddrinfo.c905
-rw-r--r--src/util/myaddrinfo.h229
-rw-r--r--src/util/myaddrinfo.ref8
-rw-r--r--src/util/myaddrinfo.ref25
-rw-r--r--src/util/myaddrinfo4.ref6
-rw-r--r--src/util/myaddrinfo4.ref25
-rw-r--r--src/util/myflock.c152
-rw-r--r--src/util/myflock.h52
-rw-r--r--src/util/mymalloc.c269
-rw-r--r--src/util/mymalloc.h40
-rw-r--r--src/util/myrand.c63
-rw-r--r--src/util/myrand.h35
-rw-r--r--src/util/mystrtok.c347
-rw-r--r--src/util/mystrtok.ref48
-rw-r--r--src/util/name_code.c91
-rw-r--r--src/util/name_code.h39
-rw-r--r--src/util/name_mask.c511
-rw-r--r--src/util/name_mask.h86
-rw-r--r--src/util/name_mask.in9
-rw-r--r--src/util/name_mask.ref09
-rw-r--r--src/util/name_mask.ref112
-rw-r--r--src/util/name_mask.ref212
-rw-r--r--src/util/name_mask.ref314
-rw-r--r--src/util/name_mask.ref414
-rw-r--r--src/util/name_mask.ref514
-rw-r--r--src/util/name_mask.ref614
-rw-r--r--src/util/name_mask.ref712
-rw-r--r--src/util/name_mask.ref812
-rw-r--r--src/util/name_mask.ref912
-rw-r--r--src/util/nbbio.c382
-rw-r--r--src/util/nbbio.h85
-rw-r--r--src/util/netstring.c498
-rw-r--r--src/util/netstring.h55
-rw-r--r--src/util/neuter.c56
-rw-r--r--src/util/non_blocking.c66
-rw-r--r--src/util/nvtable.c122
-rw-r--r--src/util/nvtable.h44
-rw-r--r--src/util/open_as.c70
-rw-r--r--src/util/open_as.h30
-rw-r--r--src/util/open_limit.c100
-rw-r--r--src/util/open_lock.c76
-rw-r--r--src/util/open_lock.h41
-rw-r--r--src/util/pass_accept.c106
-rw-r--r--src/util/pass_trigger.c151
-rw-r--r--src/util/peekfd.c89
-rw-r--r--src/util/poll_fd.c269
-rw-r--r--src/util/posix_signals.c126
-rw-r--r--src/util/posix_signals.h59
-rw-r--r--src/util/printable.c100
-rw-r--r--src/util/rand_sleep.c89
-rw-r--r--src/util/readlline.c138
-rw-r--r--src/util/readlline.h38
-rw-r--r--src/util/recv_pass_attr.c98
-rw-r--r--src/util/ring.c121
-rw-r--r--src/util/ring.h59
-rw-r--r--src/util/safe.h30
-rw-r--r--src/util/safe_getenv.c41
-rw-r--r--src/util/safe_open.c283
-rw-r--r--src/util/safe_open.h42
-rw-r--r--src/util/sane_accept.c125
-rw-r--r--src/util/sane_accept.h29
-rw-r--r--src/util/sane_basename.c181
-rw-r--r--src/util/sane_basename.in18
-rw-r--r--src/util/sane_basename.ref18
-rw-r--r--src/util/sane_connect.c65
-rw-r--r--src/util/sane_connect.h29
-rw-r--r--src/util/sane_fsops.h35
-rw-r--r--src/util/sane_link.c72
-rw-r--r--src/util/sane_rename.c69
-rw-r--r--src/util/sane_socketpair.c71
-rw-r--r--src/util/sane_socketpair.h34
-rw-r--r--src/util/sane_strtol.c59
-rw-r--r--src/util/sane_strtol.h26
-rw-r--r--src/util/sane_time.c127
-rw-r--r--src/util/sane_time.h34
-rw-r--r--src/util/scan_dir.c216
-rw-r--r--src/util/scan_dir.h37
-rw-r--r--src/util/select_bug.c95
-rw-r--r--src/util/set_eugid.c70
-rw-r--r--src/util/set_eugid.h43
-rw-r--r--src/util/set_ugid.c61
-rw-r--r--src/util/set_ugid.h29
-rw-r--r--src/util/sigdelay.c118
-rw-r--r--src/util/sigdelay.h31
-rw-r--r--src/util/skipblanks.c43
-rw-r--r--src/util/slmdb.c938
-rw-r--r--src/util/slmdb.h117
-rw-r--r--src/util/sock_addr.c175
-rw-r--r--src/util/sock_addr.h109
-rw-r--r--src/util/spawn_command.c313
-rw-r--r--src/util/spawn_command.h66
-rw-r--r--src/util/split_at.c71
-rw-r--r--src/util/split_at.h35
-rw-r--r--src/util/split_nameval.c97
-rw-r--r--src/util/split_qnameval.c168
-rw-r--r--src/util/stat_as.c73
-rw-r--r--src/util/stat_as.h35
-rw-r--r--src/util/strcasecmp.c66
-rw-r--r--src/util/strcasecmp_utf8.c216
-rw-r--r--src/util/strcasecmp_utf8_test.in10
-rw-r--r--src/util/strcasecmp_utf8_test.ref20
-rw-r--r--src/util/stream_connect.c109
-rw-r--r--src/util/stream_listen.c102
-rw-r--r--src/util/stream_recv_fd.c120
-rw-r--r--src/util/stream_send_fd.c115
-rw-r--r--src/util/stream_test.c111
-rw-r--r--src/util/stream_trigger.c130
-rw-r--r--src/util/stringops.h114
-rw-r--r--src/util/surrogate.ref55
-rw-r--r--src/util/sys_compat.c389
-rw-r--r--src/util/sys_defs.h1801
-rw-r--r--src/util/testdb2
-rw-r--r--src/util/timecmp.c93
-rw-r--r--src/util/timecmp.h37
-rw-r--r--src/util/timed_connect.c111
-rw-r--r--src/util/timed_connect.h31
-rw-r--r--src/util/timed_read.c86
-rw-r--r--src/util/timed_wait.c122
-rw-r--r--src/util/timed_wait.h35
-rw-r--r--src/util/timed_write.c92
-rw-r--r--src/util/translit.c86
-rw-r--r--src/util/trigger.h34
-rw-r--r--src/util/trimblanks.c50
-rw-r--r--src/util/unescape.c208
-rw-r--r--src/util/unescape.in4
-rw-r--r--src/util/unescape.ref11
-rw-r--r--src/util/unix_connect.c110
-rw-r--r--src/util/unix_dgram_connect.c91
-rw-r--r--src/util/unix_dgram_listen.c93
-rw-r--r--src/util/unix_listen.c113
-rw-r--r--src/util/unix_pass_fd_fix.c67
-rw-r--r--src/util/unix_recv_fd.c178
-rw-r--r--src/util/unix_send_fd.c199
-rw-r--r--src/util/unix_trigger.c131
-rw-r--r--src/util/unsafe.c77
-rw-r--r--src/util/uppercase.c43
-rw-r--r--src/util/username.c47
-rw-r--r--src/util/username.h29
-rw-r--r--src/util/valid_hostname.c412
-rw-r--r--src/util/valid_hostname.h41
-rw-r--r--src/util/valid_hostname.in55
-rw-r--r--src/util/valid_hostname.ref143
-rw-r--r--src/util/valid_utf8_hostname.c87
-rw-r--r--src/util/valid_utf8_hostname.h35
-rw-r--r--src/util/valid_utf8_string.c139
-rw-r--r--src/util/vbuf.c238
-rw-r--r--src/util/vbuf.h110
-rw-r--r--src/util/vbuf_print.c385
-rw-r--r--src/util/vbuf_print.h40
-rw-r--r--src/util/vbuf_print_test.in33
-rw-r--r--src/util/vbuf_print_test.ref27
-rw-r--r--src/util/vstream.c2046
-rw-r--r--src/util/vstream.h293
-rw-r--r--src/util/vstream_popen.c363
-rw-r--r--src/util/vstream_test.in3
-rw-r--r--src/util/vstream_test.ref41
-rw-r--r--src/util/vstream_tweak.c168
-rw-r--r--src/util/vstring.c719
-rw-r--r--src/util/vstring.h125
-rw-r--r--src/util/vstring_test.ref6
-rw-r--r--src/util/vstring_vstream.c267
-rw-r--r--src/util/vstring_vstream.h82
-rw-r--r--src/util/warn_stat.c101
-rw-r--r--src/util/warn_stat.h38
-rw-r--r--src/util/watchdog.c314
-rw-r--r--src/util/watchdog.h36
-rw-r--r--src/util/write_buf.c89
468 files changed, 65610 insertions, 0 deletions
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..f69dec5
--- /dev/null
+++ b/src/util/Makefile.in
@@ -0,0 +1,2926 @@
+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 mkmap_cdb.c mkmap_db.c mkmap_dbm.c \
+ mkmap_fail.c mkmap_lmdb.c mkmap_open.c mkmap_sdbm.c inet_prefix_top.c \
+ inet_addr_sizes.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 mkmap_db.o mkmap_dbm.o \
+ mkmap_fail.o mkmap_open.o inet_prefix_top.o inet_addr_sizes.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 dict_cdb.o dict_lmdb.o dict_sdbm.o slmdb.o \
+ mkmap_cdb.o mkmap_lmdb.o mkmap_sdbm.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 mkmap.h \
+ inet_prefix_top.h inet_addr_sizes.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 hash_fnv argv \
+ clean_env inet_prefix_top
+PLUGIN_MAP_SO = $(LIB_PREFIX)pcre$(LIB_SUFFIX) $(LIB_PREFIX)lmdb$(LIB_SUFFIX) \
+ $(LIB_PREFIX)cdb$(LIB_SUFFIX) $(LIB_PREFIX)sdbm$(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)
+
+$(LIB_PREFIX)cdb$(LIB_SUFFIX): mkmap_cdb.o dict_cdb.o
+ $(PLUGIN_LD) $(SHLIB_RPATH) -o $@ mkmap_cdb.o \
+ dict_cdb.o $(AUXLIBS_CDB)
+
+$(LIB_PREFIX)lmdb$(LIB_SUFFIX): mkmap_lmdb.o dict_lmdb.o slmdb.o
+ $(PLUGIN_LD) $(SHLIB_RPATH) -o $@ mkmap_lmdb.o dict_lmdb.o \
+ slmdb.o $(AUXLIBS_LMDB)
+
+$(LIB_PREFIX)sdbm$(LIB_SUFFIX): mkmap_sdbm.o dict_sdbm.o
+ $(PLUGIN_LD) $(SHLIB_RPATH) -o $@ mkmap_sdbm.o \
+ dict_sdbm.o $(AUXLIBS_SDBM)
+
+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
+
+hash_fnv: $(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
+
+clean_env: $(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
+
+argv: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
+inet_prefix_top: $(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 host_port_test dict_tests \
+ attr_scan_plain_test htable_test hex_code_test myaddrinfo_test \
+ format_tv_test ip_match_test name_mask_tests base32_code_test \
+ surrogate_test timecmp_test midna_domain_test casefold_test \
+ strcasecmp_utf8_test vbuf_print_test miss_endif_cidr_test \
+ miss_endif_regexp_test split_qnameval_test vstring_test \
+ vstream_test byte_mask_tests mystrtok_test known_tcp_ports_test \
+ binhash_test argv_test inet_prefix_top_test
+
+dict_tests: all dict_test \
+ dict_pcre_tests dict_cidr_test dict_thash_test dict_static_test \
+ dict_inline_test dict_utf8_test dict_regexp_test dict_union_test \
+ dict_pipe_test dict_regexp_file_test dict_cidr_file_test \
+ dict_static_file_test dict_random_test dict_random_file_test \
+ dict_inline_file_test dict_stream_test dict_inline_regexp_test \
+ dict_inline_cidr_test
+
+dict_pcre_tests: dict_pcre_test miss_endif_pcre_test dict_pcre_file_test \
+ dict_inline_pcre_test
+
+root_tests:
+
+valid_hostname_test: valid_hostname valid_hostname.in valid_hostname.ref
+ $(SHLIB_ENV) ${VALGRIND} ./valid_hostname <valid_hostname.in 2>valid_hostname.tmp
+ diff valid_hostname.ref valid_hostname.tmp
+ rm -f valid_hostname.tmp
+
+mac_expand_test: mac_expand mac_expand.in mac_expand.ref
+ $(SHLIB_ENV) ${VALGRIND} ./mac_expand <mac_expand.in >mac_expand.tmp 2>&1
+ diff mac_expand.ref mac_expand.tmp
+ rm -f mac_expand.tmp
+
+unescape_test: unescape unescape.in unescape.ref
+ $(SHLIB_ENV) ${VALGRIND} ./unescape <unescape.in | od -cb >unescape.tmp
+ diff -b unescape.ref unescape.tmp
+# $(SHLIB_ENV) ${VALGRIND} ./unescape <unescape.in | $(SHLIB_ENV) ${VALGRIND} ./unescape -e >unescape.tmp
+# diff unescape.in unescape.tmp
+ rm -f unescape.tmp
+
+hex_quote_test: hex_quote
+ $(SHLIB_ENV) ${VALGRIND} ./hex_quote <hex_quote.c | od -cb >hex_quote.tmp
+ od -cb <hex_quote.c >hex_quote.ref
+ cmp hex_quote.ref hex_quote.tmp
+ rm -f hex_quote.ref hex_quote.tmp
+
+ctable_test: ctable
+ $(SHLIB_ENV) ${VALGRIND} ./ctable <ctable.in >ctable.tmp 2>&1
+ diff ctable.ref ctable.tmp
+ rm -f ctable.tmp
+
+# On Linux, following test may require "modprobe ipv6" to enable IPv6.
+
+inet_addr_list_test: inet_addr_list
+ $(SHLIB_ENV) ${VALGRIND} ./inet_addr_list `cat inet_addr_list.in` >inet_addr_list.tmp 2>&1
+ diff inet_addr_list.ref inet_addr_list.tmp
+ rm -f inet_addr_list.tmp
+
+sane_basename_test: sane_basename
+ $(SHLIB_ENV) ${VALGRIND} ./sane_basename <sane_basename.in >sane_basename.tmp 2>&1
+ diff sane_basename.ref sane_basename.tmp
+ rm -f sane_basename.tmp
+
+base64_code_test: base64_code
+ $(SHLIB_ENV) ${VALGRIND} ./base64_code
+
+attr_scan64_test: attr_print64 attr_scan64 attr_scan64.ref
+ ($(HTABLE_FIX) $(SHLIB_ENV) ${VALGRIND} ./attr_print64 2>&3 | (sleep 1; $(HTABLE_FIX) $(SHLIB_ENV) ${VALGRIND} ./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) ${VALGRIND} ./attr_scan0)) >attr_scan0.tmp 2>&1 3>&1
+ diff attr_scan0.ref attr_scan0.tmp
+ rm -f attr_scan0.tmp
+
+dict_test: dict_open testdb dict_test.in dict_test.ref
+ rm -f testdb.db testdb.dir testdb.pag
+ $(SHLIB_ENV) ../postmap/postmap -N hash:testdb
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open hash:testdb write < dict_test.in 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_test.tmp
+ diff dict_test.ref dict_test.tmp
+ $(SHLIB_ENV) ../postmap/postmap -n hash:testdb
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open hash:testdb write < dict_test.in 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_test.tmp
+ diff dict_test.ref dict_test.tmp
+ rm -f testdb.db testdb.dir testdb.pag dict_test.tmp
+
+dict_pcre_test: dict_open dict_pcre.in dict_pcre.map dict_pcre.ref
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open pcre:dict_pcre.map read \
+ <dict_pcre.in 2>&1 | sed -e 's/uid=[0-9][0-9][0-9]*/uid=USER/' \
+ -e 's/missing )/missing closing parenthesis/' >dict_pcre.tmp
+ diff dict_pcre.ref dict_pcre.tmp
+ rm -f dict_pcre.tmp
+
+dict_pcre_file_test: dict_open dict_pcre_file.in dict_pcre_file.map dict_pcre_file.ref
+ echo this-is-file1 > dict_pcre_file1
+ echo this-is-file2 > dict_pcre_file2
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open pcre:dict_pcre_file.map \
+ read src_rhs_is_file <dict_pcre_file.in 2>&1 | \
+ sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_pcre_file.tmp
+ diff dict_pcre_file.ref dict_pcre_file.tmp
+ rm -f dict_pcre_file.tmp dict_pcre_file1 dict_pcre_file2
+
+dict_regexp_test: dict_open dict_regexp.in dict_regexp.map dict_regexp.ref
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open regexp:dict_regexp.map read <dict_regexp.in 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_regexp.tmp
+ diff dict_regexp.ref dict_regexp.tmp
+ rm -f dict_regexp.tmp
+
+dict_regexp_file_test: dict_open dict_regexp_file.in dict_regexp_file.map dict_regexp_file.ref
+ echo this-is-file1 > dict_regexp_file1
+ echo this-is-file2 > dict_regexp_file2
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open regexp:dict_regexp_file.map \
+ read src_rhs_is_file <dict_regexp_file.in 2>&1 | \
+ sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_regexp_file.tmp
+ diff dict_regexp_file.ref dict_regexp_file.tmp
+ rm -f dict_regexp_file.tmp dict_regexp_file1 dict_regexp_file2
+
+dict_cidr_test: dict_open dict_cidr.in dict_cidr.map dict_cidr.ref
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open cidr:dict_cidr.map read <dict_cidr.in 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_cidr.tmp
+ diff dict_cidr.ref dict_cidr.tmp
+ rm -f dict_cidr.tmp
+
+dict_cidr_file_test: dict_open dict_cidr_file.in dict_cidr_file.map dict_cidr_file.ref
+ echo this-is-file1 > dict_cidr_file1
+ echo this-is-file2 > dict_cidr_file2
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open cidr:dict_cidr_file.map \
+ read src_rhs_is_file <dict_cidr_file.in 2>&1 | \
+ sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_cidr_file.tmp
+ diff dict_cidr_file.ref dict_cidr_file.tmp
+ rm -f dict_cidr_file.tmp dict_cidr_file1 dict_cidr_file2
+
+miss_endif_cidr_test: dict_open miss_endif_cidr.map miss_endif_cidr.ref
+ echo get 1.2.3.5 | $(SHLIB_ENV) ${VALGRIND} ./dict_open cidr:miss_endif_cidr.map read 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_cidr.tmp
+ diff miss_endif_cidr.ref dict_cidr.tmp
+ rm -f dict_cidr.tmp
+
+miss_endif_pcre_test: dict_open miss_endif_re.map miss_endif_pcre.ref
+ echo get 1.2.3.5 | $(SHLIB_ENV) ${VALGRIND} ./dict_open pcre:miss_endif_re.map read 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_pcre.tmp
+ diff miss_endif_pcre.ref dict_pcre.tmp
+ rm -f dict_pcre.tmp
+
+miss_endif_regexp_test: dict_open miss_endif_re.map miss_endif_regexp.ref
+ echo get 1.2.3.5 | $(SHLIB_ENV) ${VALGRIND} ./dict_open regexp:miss_endif_re.map read 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_regexp.tmp
+ diff miss_endif_regexp.ref dict_regexp.tmp
+ rm -f dict_regexp.tmp
+
+split_qnameval_test: split_qnameval update
+ $(SHLIB_ENV) ${VALGRIND} ./split_qnameval
+
+dict_seq_test: dict_open testdb dict_seq.in dict_seq.ref
+ rm -f testdb.db testdb.dir testdb.pag
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open hash:testdb create sync < dict_seq.in 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' > dict_seq.tmp
+ diff dict_seq.ref dict_seq.tmp
+ rm -f testdb.db testdb.dir testdb.pag dict_seq.tmp
+
+host_port_test: host_port host_port.in host_port.ref
+ $(SHLIB_ENV) ${VALGRIND} ./host_port <host_port.in >host_port.tmp 2>&1
+ diff host_port.ref host_port.tmp
+ rm -f host_port.tmp
+
+attr_scan_plain_test: attr_print_plain attr_scan_plain attr_scan_plain.ref
+ ($(HTABLE_FIX) $(SHLIB_ENV) ${VALGRIND} ./attr_print_plain 2>&3 | (sleep 1; $(HTABLE_FIX) $(SHLIB_ENV) ${VALGRIND} ./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
+
+hash_fnv_test: hash_fnv
+ $(SHLIB_ENV) ${VALGRIND} ./hash_fnv
+
+hex_code_test: hex_code
+ $(SHLIB_ENV) ${VALGRIND} ./hex_code
+
+timecmp_test: timecmp
+ $(SHLIB_ENV) ${VALGRIND} ./timecmp
+
+myaddrinfo_test: myaddrinfo myaddrinfo.ref myaddrinfo.ref2
+ $(SHLIB_ENV) ${VALGRIND} ./myaddrinfo all belly.porcupine.org 168.100.3.2 >myaddrinfo.tmp 2>&1
+ diff myaddrinfo.ref myaddrinfo.tmp
+ rm -f myaddrinfo.tmp
+ $(SHLIB_ENV) ${VALGRIND} ./myaddrinfo all null.porcupine.org 10.0.0.0 >myaddrinfo.tmp 2>&1
+ diff myaddrinfo.ref2 myaddrinfo.tmp
+ rm -f myaddrinfo.tmp
+
+myaddrinfo4_test: myaddrinfo4 myaddrinfo4.ref myaddrinfo4.ref2
+ $(SHLIB_ENV) ${VALGRIND} ./myaddrinfo4 all belly.porcupine.org 168.100.3.2 >myaddrinfo4.tmp 2>&1
+ diff myaddrinfo4.ref myaddrinfo4.tmp
+ $(SHLIB_ENV) ${VALGRIND} ./myaddrinfo4 all null.porcupine.org 10.0.0.0 >myaddrinfo4.tmp 2>&1
+ diff myaddrinfo4.ref2 myaddrinfo4.tmp
+ rm -f myaddrinfo4.tmp
+
+format_tv_test: format_tv format_tv.in format_tv.ref
+ $(SHLIB_ENV) ${VALGRIND} ./format_tv <format_tv.in >format_tv.tmp
+ diff format_tv.ref format_tv.tmp
+ rm -f format_tv.tmp
+
+ip_match_test: ip_match ip_match.in ip_match.ref
+ $(SHLIB_ENV) ${VALGRIND} ./ip_match <ip_match.in >ip_match.tmp
+ diff ip_match.ref ip_match.tmp
+ rm -f ip_match.tmp
+
+name_mask_tests: name_mask_test0 name_mask_test1 name_mask_test2 \
+ name_mask_test3 name_mask_test4 name_mask_test5 name_mask_test6 \
+ name_mask_test7 name_mask_test8 name_mask_test9
+
+name_mask_test0: name_mask name_mask.in name_mask.ref0
+ $(SHLIB_ENV) ${VALGRIND} ./name_mask IGNORE IGNORE < name_mask.in > name_mask.tmp 2>&1
+ diff name_mask.ref0 name_mask.tmp
+ rm -f name_mask.tmp
+
+name_mask_test1: name_mask name_mask.in name_mask.ref1
+ $(SHLIB_ENV) ${VALGRIND} ./name_mask NUMBER,WARN NUMBER < name_mask.in > name_mask.tmp 2>&1
+ diff name_mask.ref1 name_mask.tmp
+ rm -f name_mask.tmp
+
+name_mask_test2: name_mask name_mask.in name_mask.ref2
+ $(SHLIB_ENV) ${VALGRIND} ./name_mask NUMBER,RETURN NUMBER < name_mask.in > name_mask.tmp 2>&1
+ diff name_mask.ref2 name_mask.tmp
+ rm -f name_mask.tmp
+
+name_mask_test3: name_mask name_mask.in name_mask.ref3
+ $(SHLIB_ENV) ${VALGRIND} ./name_mask WARN NUMBER < name_mask.in > name_mask.tmp 2>&1
+ diff name_mask.ref3 name_mask.tmp
+ rm -f name_mask.tmp
+
+name_mask_test4: name_mask name_mask.in name_mask.ref4
+ $(SHLIB_ENV) ${VALGRIND} ./name_mask RETURN NUMBER < name_mask.in > name_mask.tmp 2>&1
+ diff name_mask.ref4 name_mask.tmp
+ rm -f name_mask.tmp
+
+name_mask_test5: name_mask name_mask.in name_mask.ref5
+ $(SHLIB_ENV) ${VALGRIND} ./name_mask NUMBER,WARN RETURN < name_mask.in > name_mask.tmp 2>&1
+ diff name_mask.ref5 name_mask.tmp
+ rm -f name_mask.tmp
+
+name_mask_test6: name_mask name_mask.in name_mask.ref6
+ $(SHLIB_ENV) ${VALGRIND} ./name_mask NUMBER,WARN WARN < name_mask.in > name_mask.tmp 2>&1
+ diff name_mask.ref6 name_mask.tmp
+ rm -f name_mask.tmp
+
+name_mask_test7: name_mask name_mask.in name_mask.ref7
+ $(SHLIB_ENV) ${VALGRIND} ./name_mask NUMBER,WARN IGNORE < name_mask.in > name_mask.tmp 2>&1
+ diff name_mask.ref7 name_mask.tmp
+ rm -f name_mask.tmp
+
+name_mask_test8: name_mask name_mask.in name_mask.ref8
+ $(SHLIB_ENV) ${VALGRIND} ./name_mask NUMBER,WARN NUMBER,COMMA < name_mask.in > name_mask.tmp 2>&1
+ diff name_mask.ref8 name_mask.tmp
+ rm -f name_mask.tmp
+
+name_mask_test9: name_mask name_mask.in name_mask.ref9
+ $(SHLIB_ENV) ${VALGRIND} ./name_mask NUMBER,WARN NUMBER,PIPE < name_mask.in > name_mask.tmp 2>&1
+ diff name_mask.ref9 name_mask.tmp
+ rm -f name_mask.tmp
+
+byte_mask_tests: byte_mask_test0 byte_mask_test1 byte_mask_test2
+
+byte_mask_test0: byte_mask byte_mask.in byte_mask.ref0
+ while read line; do \
+ echo "$$line" | $(SHLIB_ENV) ${VALGRIND} ./byte_mask IGNORE IGNORE; \
+ done < byte_mask.in > byte_mask.tmp 2>&1
+ diff byte_mask.ref0 byte_mask.tmp
+ rm -f byte_mask.tmp
+
+byte_mask_test1: byte_mask byte_mask.in byte_mask.ref1
+ while read line; do \
+ echo "$$line" | $(SHLIB_ENV) ${VALGRIND} ./byte_mask WARN WARN; \
+ done < byte_mask.in > byte_mask.tmp 2>&1
+ diff byte_mask.ref1 byte_mask.tmp
+ rm -f byte_mask.tmp
+
+byte_mask_test2: byte_mask byte_mask.in byte_mask.ref2
+ -while read line; do \
+ echo "$$line" | $(SHLIB_ENV) ${VALGRIND} ./byte_mask FATAL FATAL; \
+ done < byte_mask.in > byte_mask.tmp 2>&1
+ diff byte_mask.ref2 byte_mask.tmp
+ rm -f byte_mask.tmp
+
+base32_code_test: base32_code
+ $(SHLIB_ENV) ${VALGRIND} ./base32_code
+
+dict_thash_test: ../postmap/postmap dict_thash.map dict_thash.in dict_thash.ref
+ $(SHLIB_ENV) ${VALGRIND} ../postmap/postmap -fs texthash:dict_thash.map 2>&1 | \
+ LANG=C sort | diff dict_thash.map -
+ NORANDOMIZE=1 $(SHLIB_ENV) ${VALGRIND} ../postmap/postmap -fs texthash:dict_thash.in >dict_thash.tmp 2>&1
+ diff dict_thash.ref dict_thash.tmp
+ rm -f dict_thash.tmp
+
+surrogate_test: dict_open surrogate.ref
+ cp /dev/null surrogate.tmp
+ echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open cidr:/xx write >>surrogate.tmp 2>&1
+ echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open cidr:/xx read >>surrogate.tmp 2>&1
+ echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open pcre:/xx write >>surrogate.tmp 2>&1
+ echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open pcre:/xx read >>surrogate.tmp 2>&1
+ echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open regexp:/xx write >>surrogate.tmp 2>&1
+ echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open regexp:/xx read >>surrogate.tmp 2>&1
+ echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open unix:xx write >>surrogate.tmp 2>&1
+ echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open unix:xx read >>surrogate.tmp 2>&1
+ echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open texthash:/xx write >>surrogate.tmp 2>&1
+ echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open texthash:/xx read >>surrogate.tmp 2>&1
+ echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open hash:/xx read >>surrogate.tmp 2>&1
+ diff surrogate.ref surrogate.tmp
+ rm -f surrogate.tmp
+
+dict_static_test: dict_open dict_static.ref
+ (set -e; \
+ (echo get foo; echo get bar) | $(SHLIB_ENV) \
+ ${VALGRIND} ./dict_open static:fooxx read; \
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open static:'{ foo xx ' read </dev/null; \
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open static:'{ foo xx }x' read </dev/null; \
+ (echo get foo; echo get bar) | $(SHLIB_ENV) \
+ ${VALGRIND} ./dict_open static:'{ foo xx }' read; \
+ ) >dict_static.tmp 2>&1
+ diff dict_static.ref dict_static.tmp
+ rm -f dict_static.tmp
+
+dict_static_file_test: dict_open dict_static_file.ref
+ echo this-is-file1 > dict_static_file1
+ (set -e; \
+ echo get foo | $(SHLIB_ENV) \
+ ${VALGRIND} ./dict_open static:fooxx read src_rhs_is_file; \
+ (echo get file1; echo get file2) | $(SHLIB_ENV) \
+ ${VALGRIND} ./dict_open static:'{ dict_static_file1 }' read src_rhs_is_file; \
+ ) >dict_static_file.tmp 2>&1
+ diff dict_static_file.ref dict_static_file.tmp
+ rm -f dict_static_file.tmp dict_static_file1
+
+dict_random_test: dict_open dict_random.ref
+ (set -e; \
+ (echo get foo; echo get bar) | $(SHLIB_ENV) \
+ ${VALGRIND} ./dict_open randmap:'{123 123}' read; \
+ echo get foo | $(SHLIB_ENV) ${VALGRIND} ./dict_open randmap:123 read; \
+ echo get foo | $(SHLIB_ENV) ${VALGRIND} ./dict_open randmap:'{ 123 ' read; \
+ echo get foo | $(SHLIB_ENV) ${VALGRIND} ./dict_open randmap:'{ 123 }x' read; \
+ ) >dict_random.tmp 2>&1
+ diff dict_random.ref dict_random.tmp
+ rm -f dict_random.tmp
+
+dict_random_file_test: dict_open dict_random_file.ref
+ echo this-is-file1 > dict_random_file1
+ (set -e; \
+ echo get foo | $(SHLIB_ENV) \
+ ${VALGRIND} ./dict_open randmap:'{fooxx}' read src_rhs_is_file; \
+ (echo get foo; echo get bar) | $(SHLIB_ENV) \
+ ${VALGRIND} ./dict_open randmap:'{ dict_random_file1 }' read src_rhs_is_file; \
+ ) >dict_random_file.tmp 2>&1
+ diff dict_random_file.ref dict_random_file.tmp
+ rm -f dict_random_file.tmp dict_random_file1
+
+dict_inline_test: dict_open dict_inline.ref
+ (set -e; \
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open inline:'{ }' read </dev/null; \
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open inline:'{ foo = xx }' read </dev/null; \
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open inline:'{ foo=xx }x' read </dev/null; \
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open inline:'{ foo=xx x' read </dev/null; \
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open inline:'{ foo=xx {x=y}x}' read </dev/null; \
+ (echo get foo; echo get bar; echo get baz) | $(SHLIB_ENV) \
+ ${VALGRIND} ./dict_open inline:'{ foo=XX, { bAr = lotsa stuff }}' read fold_fix; \
+ (echo get foo; echo get bar; echo get baz) | $(SHLIB_ENV) \
+ ${VALGRIND} ./dict_open inline:'{ foo=XX, { bAr = lotsa stuff }}' read 'fold_fix,utf8_request'; \
+ ) >dict_inline.tmp 2>&1
+ diff dict_inline.ref dict_inline.tmp
+ rm -f dict_inline.tmp
+
+dict_inline_file_test: dict_open dict_inline_file.ref
+ (set -e; \
+ echo get foo | $(SHLIB_ENV) ${VALGRIND} \
+ ./dict_open inline:'{ foo=xx, bar=yy }' read src_rhs_is_file; \
+ echo this-is-file1 > dict_inline_file1; \
+ echo this-is-file2 > dict_inline_file2; \
+ (echo get file1; echo get file2; echo get file3) | $(SHLIB_ENV) \
+ ${VALGRIND} ./dict_open inline:'{ file1=dict_inline_file1, file2=dict_inline_file2}' read src_rhs_is_file; \
+ ) >dict_inline_file.tmp 2>&1
+ diff dict_inline_file.ref dict_inline_file.tmp
+ rm -f dict_inline_file.tmp dict_inline_file1 dict_inline_file2
+
+midna_domain_test: midna_domain midna_domain_test.in midna_domain_test.ref
+ $(SHLIB_ENV) ${VALGRIND} ./midna_domain <midna_domain_test.in >midna_domain_test.tmp 2>&1
+ diff midna_domain_test.ref midna_domain_test.tmp
+ rm -f midna_domain_test.tmp
+
+casefold_test: casefold casefold_test.in casefold_test.ref
+ $(SHLIB_ENV) ${VALGRIND} ./casefold <casefold_test.in >casefold_test.tmp 2>&1
+ diff casefold_test.ref casefold_test.tmp
+ rm -f casefold_test.tmp
+
+dict_utf8_test: dict_open dict_utf8_test.in dict_utf8_test.ref
+ $(SHLIB_ENV) sh dict_utf8_test.in >dict_utf8_test.tmp 2>&1
+ diff dict_utf8_test.ref dict_utf8_test.tmp
+ rm -f dict_utf8_test.tmp
+
+strcasecmp_utf8_test: strcasecmp_utf8 strcasecmp_utf8_test.in \
+ strcasecmp_utf8_test.ref
+ $(SHLIB_ENV) ${VALGRIND} ./strcasecmp_utf8 <strcasecmp_utf8_test.in >strcasecmp_utf8_test.tmp 2>&1
+ diff strcasecmp_utf8_test.ref strcasecmp_utf8_test.tmp
+ rm -f strcasecmp_utf8_test.tmp
+
+vbuf_print_test: vbuf_print vbuf_print_test.in vbuf_print_test.ref
+ $(SHLIB_ENV) ${VALGRIND} ./vbuf_print <vbuf_print_test.in >vbuf_print_test.tmp 2>&1
+ diff vbuf_print_test.ref vbuf_print_test.tmp
+ rm -f vbuf_print_test.tmp
+
+dict_union_test: dict_open dict_union_test.in dict_union_test.ref
+ $(SHLIB_ENV) ${VALGRIND} sh -x dict_union_test.in >dict_union_test.tmp 2>&1
+ diff dict_union_test.ref dict_union_test.tmp
+ rm -f dict_union_test.tmp
+
+dict_pipe_test: dict_open dict_pipe_test.in dict_pipe_test.ref
+ $(SHLIB_ENV) ${VALGRIND} sh -x dict_pipe_test.in >dict_pipe_test.tmp 2>&1
+ diff dict_pipe_test.ref dict_pipe_test.tmp
+ rm -f dict_pipe_test.tmp
+
+vstring_test: dict_open vstring vstring_test.ref
+ $(SHLIB_ENV) ${VALGRIND} ./vstring one two three >vstring_test.tmp 2>&1
+ diff vstring_test.ref vstring_test.tmp
+ rm -f vstring_test.tmp
+
+vstream_test: vstream vstream_test.in vstream_test.ref
+ $(SHLIB_ENV) ${VALGRIND} ./vstream <vstream_test.in >vstream_test.tmp 2>&1
+ diff vstream_test.ref vstream_test.tmp
+ rm -f vstream_test.tmp
+
+mystrtok_test: mystrtok mystrtok.ref
+ $(SHLIB_ENV) ${VALGRIND} ./mystrtok >mystrtok.tmp 2>&1
+ diff mystrtok.ref mystrtok.tmp
+ rm -f mystrtok.tmp
+
+known_tcp_ports_test: known_tcp_ports known_tcp_ports.ref
+ $(SHLIB_ENV) ${VALGRIND} ./known_tcp_ports >known_tcp_ports.tmp 2>&1
+ diff known_tcp_ports.ref known_tcp_ports.tmp
+ rm -f known_tcp_ports.tmp
+
+dict_stream_test: dict_stream dict_stream.ref
+ $(SHLIB_ENV) ${VALGRIND} ./dict_stream >dict_stream.tmp 2>&1
+ diff dict_stream.ref dict_stream.tmp
+ rm -f dict_stream.tmp
+
+dict_inline_pcre_test: dict_open dict_inline_pcre.ref
+ (echo get foo; echo get bar) | \
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open 'pcre:{{/foo/ got foo}}' \
+ read 2>&1 | grep -v uid= >dict_inline_pcre.tmp
+ diff dict_inline_pcre.ref dict_inline_pcre.tmp
+ rm -f dict_inline_pcre.tmp
+
+dict_inline_regexp_test: dict_open dict_inline_regexp.ref
+ (echo get foo; echo get bar) | \
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open 'regexp:{{/foo/ got foo}}' \
+ read 2>&1 | grep -v uid= >dict_inline_regexp.tmp
+ diff dict_inline_regexp.ref dict_inline_regexp.tmp
+ rm -f dict_inline_regexp.tmp
+
+dict_inline_cidr_test: dict_open dict_inline_cidr.ref
+ (echo get 1.2.3.4; echo get 4.3.2.1) | \
+ $(SHLIB_ENV) ${VALGRIND} ./dict_open \
+ 'cidr:{{1.2.3.4 got 1.2.3.4}}' \
+ read 2>&1 | grep -v uid= >dict_inline_cidr.tmp
+ diff dict_inline_cidr.ref dict_inline_cidr.tmp
+ rm -f dict_inline_cidr.tmp
+
+find_inet_test: find_inet find_inet.ref
+ $(SHLIB_ENV) ${VALGRIND} ./find_inet >find_inet.tmp 2>&1
+ diff find_inet.ref find_inet.tmp
+ rm -f find_inet.tmp
+
+argv_test: argv
+ $(SHLIB_ENV) ${VALGRIND} ./argv
+
+inet_prefix_top_test: inet_prefix_top
+ $(SHLIB_ENV) ${VALGRIND} ./inet_prefix_top
+
+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: check_arg.h
+clean_env.o: clean_env.c
+clean_env.o: clean_env.h
+clean_env.o: msg.h
+clean_env.o: mymalloc.h
+clean_env.o: safe.h
+clean_env.o: stringops.h
+clean_env.o: sys_defs.h
+clean_env.o: vbuf.h
+clean_env.o: vstring.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: mkmap.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: mkmap.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: mkmap.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: mkmap.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: mkmap.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: mkmap.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: mkmap.h
+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_addr_sizes.o: inet_addr_sizes.c
+inet_addr_sizes.o: inet_addr_sizes.h
+inet_addr_sizes.o: msg.h
+inet_addr_sizes.o: myaddrinfo.h
+inet_addr_sizes.o: sys_defs.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_prefix_top.o: check_arg.h
+inet_prefix_top.o: inet_addr_sizes.h
+inet_prefix_top.o: inet_prefix_top.c
+inet_prefix_top.o: inet_prefix_top.h
+inet_prefix_top.o: mask_addr.h
+inet_prefix_top.o: msg.h
+inet_prefix_top.o: sys_defs.h
+inet_prefix_top.o: vbuf.h
+inet_prefix_top.o: vstring.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: readlline.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_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
+mkmap_cdb.o: argv.h
+mkmap_cdb.o: check_arg.h
+mkmap_cdb.o: dict.h
+mkmap_cdb.o: dict_cdb.h
+mkmap_cdb.o: mkmap.h
+mkmap_cdb.o: mkmap_cdb.c
+mkmap_cdb.o: myflock.h
+mkmap_cdb.o: mymalloc.h
+mkmap_cdb.o: sys_defs.h
+mkmap_cdb.o: vbuf.h
+mkmap_cdb.o: vstream.h
+mkmap_cdb.o: vstring.h
+mkmap_db.o: argv.h
+mkmap_db.o: check_arg.h
+mkmap_db.o: dict.h
+mkmap_db.o: dict_db.h
+mkmap_db.o: mkmap.h
+mkmap_db.o: mkmap_db.c
+mkmap_db.o: msg.h
+mkmap_db.o: myflock.h
+mkmap_db.o: mymalloc.h
+mkmap_db.o: stringops.h
+mkmap_db.o: sys_defs.h
+mkmap_db.o: vbuf.h
+mkmap_db.o: vstream.h
+mkmap_db.o: vstring.h
+mkmap_db.o: warn_stat.h
+mkmap_dbm.o: argv.h
+mkmap_dbm.o: check_arg.h
+mkmap_dbm.o: dict.h
+mkmap_dbm.o: dict_dbm.h
+mkmap_dbm.o: mkmap.h
+mkmap_dbm.o: mkmap_dbm.c
+mkmap_dbm.o: msg.h
+mkmap_dbm.o: myflock.h
+mkmap_dbm.o: mymalloc.h
+mkmap_dbm.o: stringops.h
+mkmap_dbm.o: sys_defs.h
+mkmap_dbm.o: vbuf.h
+mkmap_dbm.o: vstream.h
+mkmap_dbm.o: vstring.h
+mkmap_fail.o: argv.h
+mkmap_fail.o: check_arg.h
+mkmap_fail.o: dict.h
+mkmap_fail.o: dict_fail.h
+mkmap_fail.o: mkmap.h
+mkmap_fail.o: mkmap_fail.c
+mkmap_fail.o: myflock.h
+mkmap_fail.o: mymalloc.h
+mkmap_fail.o: sys_defs.h
+mkmap_fail.o: vbuf.h
+mkmap_fail.o: vstream.h
+mkmap_fail.o: vstring.h
+mkmap_lmdb.o: argv.h
+mkmap_lmdb.o: check_arg.h
+mkmap_lmdb.o: dict.h
+mkmap_lmdb.o: dict_lmdb.h
+mkmap_lmdb.o: mkmap.h
+mkmap_lmdb.o: mkmap_lmdb.c
+mkmap_lmdb.o: myflock.h
+mkmap_lmdb.o: mymalloc.h
+mkmap_lmdb.o: sys_defs.h
+mkmap_lmdb.o: vbuf.h
+mkmap_lmdb.o: vstream.h
+mkmap_lmdb.o: vstring.h
+mkmap_open.o: argv.h
+mkmap_open.o: check_arg.h
+mkmap_open.o: dict.h
+mkmap_open.o: mkmap.h
+mkmap_open.o: mkmap_open.c
+mkmap_open.o: msg.h
+mkmap_open.o: myflock.h
+mkmap_open.o: mymalloc.h
+mkmap_open.o: sigdelay.h
+mkmap_open.o: stringops.h
+mkmap_open.o: sys_defs.h
+mkmap_open.o: vbuf.h
+mkmap_open.o: vstream.h
+mkmap_open.o: vstring.h
+mkmap_sdbm.o: argv.h
+mkmap_sdbm.o: check_arg.h
+mkmap_sdbm.o: dict.h
+mkmap_sdbm.o: dict_sdbm.h
+mkmap_sdbm.o: mkmap.h
+mkmap_sdbm.o: mkmap_sdbm.c
+mkmap_sdbm.o: msg.h
+mkmap_sdbm.o: myflock.h
+mkmap_sdbm.o: mymalloc.h
+mkmap_sdbm.o: stringops.h
+mkmap_sdbm.o: sys_defs.h
+mkmap_sdbm.o: vbuf.h
+mkmap_sdbm.o: vstream.h
+mkmap_sdbm.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: msg.h
+mystrtok.o: mystrtok.c
+mystrtok.o: stringops.h
+mystrtok.o: sys_defs.h
+mystrtok.o: vbuf.h
+mystrtok.o: vstring.h
+name_code.o: name_code.c
+name_code.o: name_code.h
+name_code.o: sys_defs.h
+name_mask.o: check_arg.h
+name_mask.o: msg.h
+name_mask.o: mymalloc.h
+name_mask.o: name_mask.c
+name_mask.o: name_mask.h
+name_mask.o: stringops.h
+name_mask.o: sys_defs.h
+name_mask.o: vbuf.h
+name_mask.o: vstring.h
+nbbio.o: events.h
+nbbio.o: msg.h
+nbbio.o: mymalloc.h
+nbbio.o: nbbio.c
+nbbio.o: nbbio.h
+nbbio.o: sys_defs.h
+netstring.o: check_arg.h
+netstring.o: compat_va_copy.h
+netstring.o: msg.h
+netstring.o: netstring.c
+netstring.o: netstring.h
+netstring.o: sys_defs.h
+netstring.o: vbuf.h
+netstring.o: vstream.h
+netstring.o: vstring.h
+neuter.o: check_arg.h
+neuter.o: neuter.c
+neuter.o: stringops.h
+neuter.o: sys_defs.h
+neuter.o: vbuf.h
+neuter.o: vstring.h
+non_blocking.o: iostuff.h
+non_blocking.o: msg.h
+non_blocking.o: non_blocking.c
+non_blocking.o: sys_defs.h
+nvtable.o: htable.h
+nvtable.o: mymalloc.h
+nvtable.o: nvtable.c
+nvtable.o: nvtable.h
+nvtable.o: sys_defs.h
+open_as.o: msg.h
+open_as.o: open_as.c
+open_as.o: open_as.h
+open_as.o: set_eugid.h
+open_as.o: sys_defs.h
+open_limit.o: iostuff.h
+open_limit.o: open_limit.c
+open_limit.o: sys_defs.h
+open_lock.o: check_arg.h
+open_lock.o: msg.h
+open_lock.o: myflock.h
+open_lock.o: open_lock.c
+open_lock.o: open_lock.h
+open_lock.o: safe_open.h
+open_lock.o: sys_defs.h
+open_lock.o: vbuf.h
+open_lock.o: vstream.h
+open_lock.o: vstring.h
+pass_accept.o: attr.h
+pass_accept.o: check_arg.h
+pass_accept.o: htable.h
+pass_accept.o: iostuff.h
+pass_accept.o: listen.h
+pass_accept.o: msg.h
+pass_accept.o: mymalloc.h
+pass_accept.o: nvtable.h
+pass_accept.o: pass_accept.c
+pass_accept.o: sys_defs.h
+pass_accept.o: vbuf.h
+pass_accept.o: vstream.h
+pass_accept.o: vstring.h
+pass_trigger.o: connect.h
+pass_trigger.o: events.h
+pass_trigger.o: iostuff.h
+pass_trigger.o: msg.h
+pass_trigger.o: mymalloc.h
+pass_trigger.o: pass_trigger.c
+pass_trigger.o: sys_defs.h
+pass_trigger.o: trigger.h
+peekfd.o: iostuff.h
+peekfd.o: peekfd.c
+peekfd.o: sys_defs.h
+poll_fd.o: iostuff.h
+poll_fd.o: msg.h
+poll_fd.o: poll_fd.c
+poll_fd.o: sys_defs.h
+posix_signals.o: posix_signals.c
+posix_signals.o: posix_signals.h
+posix_signals.o: sys_defs.h
+printable.o: check_arg.h
+printable.o: printable.c
+printable.o: stringops.h
+printable.o: sys_defs.h
+printable.o: vbuf.h
+printable.o: vstring.h
+rand_sleep.o: iostuff.h
+rand_sleep.o: msg.h
+rand_sleep.o: myrand.h
+rand_sleep.o: rand_sleep.c
+rand_sleep.o: sys_defs.h
+readlline.o: check_arg.h
+readlline.o: msg.h
+readlline.o: readlline.c
+readlline.o: readlline.h
+readlline.o: sys_defs.h
+readlline.o: vbuf.h
+readlline.o: vstream.h
+readlline.o: vstring.h
+recv_pass_attr.o: attr.h
+recv_pass_attr.o: check_arg.h
+recv_pass_attr.o: htable.h
+recv_pass_attr.o: iostuff.h
+recv_pass_attr.o: listen.h
+recv_pass_attr.o: mymalloc.h
+recv_pass_attr.o: nvtable.h
+recv_pass_attr.o: recv_pass_attr.c
+recv_pass_attr.o: sys_defs.h
+recv_pass_attr.o: vbuf.h
+recv_pass_attr.o: vstream.h
+recv_pass_attr.o: vstring.h
+ring.o: ring.c
+ring.o: ring.h
+safe_getenv.o: safe.h
+safe_getenv.o: safe_getenv.c
+safe_getenv.o: sys_defs.h
+safe_open.o: check_arg.h
+safe_open.o: msg.h
+safe_open.o: safe_open.c
+safe_open.o: safe_open.h
+safe_open.o: stringops.h
+safe_open.o: sys_defs.h
+safe_open.o: vbuf.h
+safe_open.o: vstream.h
+safe_open.o: vstring.h
+safe_open.o: warn_stat.h
+sane_accept.o: msg.h
+sane_accept.o: sane_accept.c
+sane_accept.o: sane_accept.h
+sane_accept.o: sys_defs.h
+sane_basename.o: check_arg.h
+sane_basename.o: sane_basename.c
+sane_basename.o: stringops.h
+sane_basename.o: sys_defs.h
+sane_basename.o: vbuf.h
+sane_basename.o: vstring.h
+sane_connect.o: msg.h
+sane_connect.o: sane_connect.c
+sane_connect.o: sane_connect.h
+sane_connect.o: sys_defs.h
+sane_link.o: msg.h
+sane_link.o: sane_fsops.h
+sane_link.o: sane_link.c
+sane_link.o: sys_defs.h
+sane_link.o: warn_stat.h
+sane_rename.o: msg.h
+sane_rename.o: sane_fsops.h
+sane_rename.o: sane_rename.c
+sane_rename.o: sys_defs.h
+sane_rename.o: warn_stat.h
+sane_socketpair.o: msg.h
+sane_socketpair.o: sane_socketpair.c
+sane_socketpair.o: sane_socketpair.h
+sane_socketpair.o: sys_defs.h
+sane_strtol.o: sane_strtol.c
+sane_strtol.o: sane_strtol.h
+sane_strtol.o: sys_defs.h
+sane_time.o: msg.h
+sane_time.o: sane_time.c
+sane_time.o: sane_time.h
+sane_time.o: sys_defs.h
+scan_dir.o: check_arg.h
+scan_dir.o: msg.h
+scan_dir.o: mymalloc.h
+scan_dir.o: scan_dir.c
+scan_dir.o: scan_dir.h
+scan_dir.o: stringops.h
+scan_dir.o: sys_defs.h
+scan_dir.o: vbuf.h
+scan_dir.o: vstring.h
+select_bug.o: check_arg.h
+select_bug.o: msg.h
+select_bug.o: msg_vstream.h
+select_bug.o: select_bug.c
+select_bug.o: sys_defs.h
+select_bug.o: vbuf.h
+select_bug.o: vstream.h
+set_eugid.o: msg.h
+set_eugid.o: set_eugid.c
+set_eugid.o: set_eugid.h
+set_eugid.o: sys_defs.h
+set_ugid.o: msg.h
+set_ugid.o: set_ugid.c
+set_ugid.o: set_ugid.h
+set_ugid.o: sys_defs.h
+sigdelay.o: msg.h
+sigdelay.o: posix_signals.h
+sigdelay.o: sigdelay.c
+sigdelay.o: sigdelay.h
+sigdelay.o: sys_defs.h
+skipblanks.o: check_arg.h
+skipblanks.o: skipblanks.c
+skipblanks.o: stringops.h
+skipblanks.o: sys_defs.h
+skipblanks.o: vbuf.h
+skipblanks.o: vstring.h
+slmdb.o: check_arg.h
+slmdb.o: slmdb.c
+slmdb.o: slmdb.h
+sock_addr.o: msg.h
+sock_addr.o: sock_addr.c
+sock_addr.o: sock_addr.h
+sock_addr.o: sys_defs.h
+spawn_command.o: argv.h
+spawn_command.o: check_arg.h
+spawn_command.o: clean_env.h
+spawn_command.o: exec_command.h
+spawn_command.o: msg.h
+spawn_command.o: set_ugid.h
+spawn_command.o: spawn_command.c
+spawn_command.o: spawn_command.h
+spawn_command.o: sys_defs.h
+spawn_command.o: timed_wait.h
+split_at.o: split_at.c
+split_at.o: split_at.h
+split_at.o: sys_defs.h
+split_nameval.o: check_arg.h
+split_nameval.o: msg.h
+split_nameval.o: split_nameval.c
+split_nameval.o: stringops.h
+split_nameval.o: sys_defs.h
+split_nameval.o: vbuf.h
+split_nameval.o: vstring.h
+split_qnameval.o: check_arg.h
+split_qnameval.o: msg.h
+split_qnameval.o: split_qnameval.c
+split_qnameval.o: stringops.h
+split_qnameval.o: sys_defs.h
+split_qnameval.o: vbuf.h
+split_qnameval.o: vstring.h
+stat_as.o: msg.h
+stat_as.o: set_eugid.h
+stat_as.o: stat_as.c
+stat_as.o: stat_as.h
+stat_as.o: sys_defs.h
+stat_as.o: warn_stat.h
+strcasecmp.o: strcasecmp.c
+strcasecmp.o: sys_defs.h
+strcasecmp_utf8.o: check_arg.h
+strcasecmp_utf8.o: strcasecmp_utf8.c
+strcasecmp_utf8.o: stringops.h
+strcasecmp_utf8.o: sys_defs.h
+strcasecmp_utf8.o: vbuf.h
+strcasecmp_utf8.o: vstring.h
+stream_connect.o: connect.h
+stream_connect.o: iostuff.h
+stream_connect.o: msg.h
+stream_connect.o: stream_connect.c
+stream_connect.o: sys_defs.h
+stream_listen.o: htable.h
+stream_listen.o: iostuff.h
+stream_listen.o: listen.h
+stream_listen.o: msg.h
+stream_listen.o: stream_listen.c
+stream_listen.o: sys_defs.h
+stream_recv_fd.o: iostuff.h
+stream_recv_fd.o: msg.h
+stream_recv_fd.o: stream_recv_fd.c
+stream_recv_fd.o: sys_defs.h
+stream_send_fd.o: iostuff.h
+stream_send_fd.o: msg.h
+stream_send_fd.o: stream_send_fd.c
+stream_send_fd.o: sys_defs.h
+stream_test.o: check_arg.h
+stream_test.o: connect.h
+stream_test.o: htable.h
+stream_test.o: iostuff.h
+stream_test.o: listen.h
+stream_test.o: msg.h
+stream_test.o: msg_vstream.h
+stream_test.o: stream_test.c
+stream_test.o: sys_defs.h
+stream_test.o: vbuf.h
+stream_test.o: vstream.h
+stream_trigger.o: connect.h
+stream_trigger.o: events.h
+stream_trigger.o: iostuff.h
+stream_trigger.o: msg.h
+stream_trigger.o: mymalloc.h
+stream_trigger.o: stream_trigger.c
+stream_trigger.o: sys_defs.h
+stream_trigger.o: trigger.h
+sys_compat.o: sys_compat.c
+sys_compat.o: sys_defs.h
+timecmp.o: timecmp.c
+timecmp.o: timecmp.h
+timed_connect.o: iostuff.h
+timed_connect.o: msg.h
+timed_connect.o: sane_connect.h
+timed_connect.o: sys_defs.h
+timed_connect.o: timed_connect.c
+timed_connect.o: timed_connect.h
+timed_read.o: iostuff.h
+timed_read.o: msg.h
+timed_read.o: sys_defs.h
+timed_read.o: timed_read.c
+timed_wait.o: msg.h
+timed_wait.o: posix_signals.h
+timed_wait.o: sys_defs.h
+timed_wait.o: timed_wait.c
+timed_wait.o: timed_wait.h
+timed_write.o: iostuff.h
+timed_write.o: msg.h
+timed_write.o: sys_defs.h
+timed_write.o: timed_write.c
+translit.o: check_arg.h
+translit.o: stringops.h
+translit.o: sys_defs.h
+translit.o: translit.c
+translit.o: vbuf.h
+translit.o: vstring.h
+trimblanks.o: check_arg.h
+trimblanks.o: stringops.h
+trimblanks.o: sys_defs.h
+trimblanks.o: trimblanks.c
+trimblanks.o: vbuf.h
+trimblanks.o: vstring.h
+unescape.o: check_arg.h
+unescape.o: stringops.h
+unescape.o: sys_defs.h
+unescape.o: unescape.c
+unescape.o: vbuf.h
+unescape.o: vstring.h
+unix_connect.o: connect.h
+unix_connect.o: iostuff.h
+unix_connect.o: msg.h
+unix_connect.o: sane_connect.h
+unix_connect.o: sys_defs.h
+unix_connect.o: timed_connect.h
+unix_connect.o: unix_connect.c
+unix_dgram_connect.o: connect.h
+unix_dgram_connect.o: iostuff.h
+unix_dgram_connect.o: msg.h
+unix_dgram_connect.o: sys_defs.h
+unix_dgram_connect.o: unix_dgram_connect.c
+unix_dgram_listen.o: htable.h
+unix_dgram_listen.o: iostuff.h
+unix_dgram_listen.o: listen.h
+unix_dgram_listen.o: msg.h
+unix_dgram_listen.o: sys_defs.h
+unix_dgram_listen.o: unix_dgram_listen.c
+unix_listen.o: htable.h
+unix_listen.o: iostuff.h
+unix_listen.o: listen.h
+unix_listen.o: msg.h
+unix_listen.o: sane_accept.h
+unix_listen.o: sys_defs.h
+unix_listen.o: unix_listen.c
+unix_pass_fd_fix.o: check_arg.h
+unix_pass_fd_fix.o: iostuff.h
+unix_pass_fd_fix.o: name_mask.h
+unix_pass_fd_fix.o: sys_defs.h
+unix_pass_fd_fix.o: unix_pass_fd_fix.c
+unix_pass_fd_fix.o: vbuf.h
+unix_pass_fd_fix.o: vstring.h
+unix_recv_fd.o: iostuff.h
+unix_recv_fd.o: msg.h
+unix_recv_fd.o: sys_defs.h
+unix_recv_fd.o: unix_recv_fd.c
+unix_send_fd.o: iostuff.h
+unix_send_fd.o: msg.h
+unix_send_fd.o: sys_defs.h
+unix_send_fd.o: unix_send_fd.c
+unix_trigger.o: connect.h
+unix_trigger.o: events.h
+unix_trigger.o: iostuff.h
+unix_trigger.o: msg.h
+unix_trigger.o: mymalloc.h
+unix_trigger.o: sys_defs.h
+unix_trigger.o: trigger.h
+unix_trigger.o: unix_trigger.c
+unsafe.o: safe.h
+unsafe.o: sys_defs.h
+unsafe.o: unsafe.c
+uppercase.o: check_arg.h
+uppercase.o: stringops.h
+uppercase.o: sys_defs.h
+uppercase.o: uppercase.c
+uppercase.o: vbuf.h
+uppercase.o: vstring.h
+username.o: sys_defs.h
+username.o: username.c
+username.o: username.h
+valid_hostname.o: check_arg.h
+valid_hostname.o: msg.h
+valid_hostname.o: mymalloc.h
+valid_hostname.o: stringops.h
+valid_hostname.o: sys_defs.h
+valid_hostname.o: valid_hostname.c
+valid_hostname.o: valid_hostname.h
+valid_hostname.o: vbuf.h
+valid_hostname.o: vstring.h
+valid_utf8_hostname.o: check_arg.h
+valid_utf8_hostname.o: midna_domain.h
+valid_utf8_hostname.o: msg.h
+valid_utf8_hostname.o: mymalloc.h
+valid_utf8_hostname.o: stringops.h
+valid_utf8_hostname.o: sys_defs.h
+valid_utf8_hostname.o: valid_hostname.h
+valid_utf8_hostname.o: valid_utf8_hostname.c
+valid_utf8_hostname.o: valid_utf8_hostname.h
+valid_utf8_hostname.o: vbuf.h
+valid_utf8_hostname.o: vstring.h
+valid_utf8_string.o: check_arg.h
+valid_utf8_string.o: stringops.h
+valid_utf8_string.o: sys_defs.h
+valid_utf8_string.o: valid_utf8_string.c
+valid_utf8_string.o: vbuf.h
+valid_utf8_string.o: vstring.h
+vbuf.o: sys_defs.h
+vbuf.o: vbuf.c
+vbuf.o: vbuf.h
+vbuf_print.o: check_arg.h
+vbuf_print.o: msg.h
+vbuf_print.o: mymalloc.h
+vbuf_print.o: sys_defs.h
+vbuf_print.o: vbuf.h
+vbuf_print.o: vbuf_print.c
+vbuf_print.o: vbuf_print.h
+vbuf_print.o: vstring.h
+vstream.o: check_arg.h
+vstream.o: iostuff.h
+vstream.o: msg.h
+vstream.o: mymalloc.h
+vstream.o: sys_defs.h
+vstream.o: vbuf.h
+vstream.o: vbuf_print.h
+vstream.o: vstream.c
+vstream.o: vstream.h
+vstream.o: vstring.h
+vstream_popen.o: argv.h
+vstream_popen.o: check_arg.h
+vstream_popen.o: clean_env.h
+vstream_popen.o: exec_command.h
+vstream_popen.o: iostuff.h
+vstream_popen.o: msg.h
+vstream_popen.o: set_ugid.h
+vstream_popen.o: sys_defs.h
+vstream_popen.o: vbuf.h
+vstream_popen.o: vstream.h
+vstream_popen.o: vstream_popen.c
+vstream_tweak.o: check_arg.h
+vstream_tweak.o: msg.h
+vstream_tweak.o: sys_defs.h
+vstream_tweak.o: vbuf.h
+vstream_tweak.o: vstream.h
+vstream_tweak.o: vstream_tweak.c
+vstring.o: check_arg.h
+vstring.o: msg.h
+vstring.o: mymalloc.h
+vstring.o: sys_defs.h
+vstring.o: vbuf.h
+vstring.o: vbuf_print.h
+vstring.o: vstring.c
+vstring.o: vstring.h
+vstring_vstream.o: check_arg.h
+vstring_vstream.o: msg.h
+vstring_vstream.o: sys_defs.h
+vstring_vstream.o: vbuf.h
+vstring_vstream.o: vstream.h
+vstring_vstream.o: vstring.h
+vstring_vstream.o: vstring_vstream.c
+vstring_vstream.o: vstring_vstream.h
+warn_stat.o: msg.h
+warn_stat.o: sys_defs.h
+warn_stat.o: warn_stat.c
+warn_stat.o: warn_stat.h
+watchdog.o: events.h
+watchdog.o: iostuff.h
+watchdog.o: killme_after.h
+watchdog.o: msg.h
+watchdog.o: mymalloc.h
+watchdog.o: posix_signals.h
+watchdog.o: sys_defs.h
+watchdog.o: watchdog.c
+watchdog.o: watchdog.h
+write_buf.o: iostuff.h
+write_buf.o: msg.h
+write_buf.o: sys_defs.h
+write_buf.o: write_buf.c
diff --git a/src/util/allascii.c b/src/util/allascii.c
new file mode 100644
index 0000000..4ac820e
--- /dev/null
+++ b/src/util/allascii.c
@@ -0,0 +1,65 @@
+/*++
+/* NAME
+/* allascii 3
+/* SUMMARY
+/* predicate if string is all ASCII
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* int allascii(buffer)
+/* const char *buffer;
+/*
+/* int allascii_len(buffer, len)
+/* const char *buffer;
+/* ssize_t len;
+/* DESCRIPTION
+/* allascii() determines if its argument is an all-ASCII string.
+/*
+/* Arguments:
+/* .IP buffer
+/* The null-terminated input string.
+/* .IP len
+/* The string length, -1 to determine the length dynamically.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <ctype.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include "stringops.h"
+
+/* allascii_len - return true if string is all ASCII */
+
+int allascii_len(const char *string, ssize_t len)
+{
+ const char *cp;
+ int ch;
+
+ if (len < 0)
+ len = strlen(string);
+ if (len == 0)
+ return (0);
+ for (cp = string; cp < string + len
+ && (ch = *(unsigned char *) cp) != 0; cp++)
+ if (!ISASCII(ch))
+ return (0);
+ return (1);
+}
diff --git a/src/util/alldig.c b/src/util/alldig.c
new file mode 100644
index 0000000..cabe5c3
--- /dev/null
+++ b/src/util/alldig.c
@@ -0,0 +1,73 @@
+/*++
+/* NAME
+/* alldig 3
+/* SUMMARY
+/* predicate if string is all numerical
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* int alldig(string)
+/* const char *string;
+/*
+/* int allalnum(string)
+/* const char *string;
+/* DESCRIPTION
+/* alldig() determines if its argument is an all-numerical string.
+/*
+/* allalnum() determines if its argument is an all-alphanumerical
+/* string.
+/* SEE ALSO
+/* An alldig() routine appears in Brian W. Kernighan, P.J. Plauger:
+/* "Software Tools", Addison-Wesley 1976.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <stringops.h>
+
+/* alldig - return true if string is all digits */
+
+int alldig(const char *string)
+{
+ const char *cp;
+
+ if (*string == 0)
+ return (0);
+ for (cp = string; *cp != 0; cp++)
+ if (!ISDIGIT(*cp))
+ return (0);
+ return (1);
+}
+
+/* allalnum - return true if string is all alphanum */
+
+int allalnum(const char *string)
+{
+ const char *cp;
+
+ if (*string == 0)
+ return (0);
+ for (cp = string; *cp != 0; cp++)
+ if (!ISALNUM(*cp))
+ return (0);
+ return (1);
+}
diff --git a/src/util/allprint.c b/src/util/allprint.c
new file mode 100644
index 0000000..6e9c519
--- /dev/null
+++ b/src/util/allprint.c
@@ -0,0 +1,50 @@
+/*++
+/* NAME
+/* allprint 3
+/* SUMMARY
+/* predicate if string is all printable
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* int allprint(buffer)
+/* const char *buffer;
+/* DESCRIPTION
+/* allprint() determines if its argument is an all-printable string.
+/*
+/* Arguments:
+/* .IP buffer
+/* The null-terminated input string.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include "stringops.h"
+
+/* allprint - return true if string is all printable */
+
+int allprint(const char *string)
+{
+ const char *cp;
+ int ch;
+
+ if (*string == 0)
+ return (0);
+ for (cp = string; (ch = *(unsigned char *) cp) != 0; cp++)
+ if (!ISASCII(ch) || !ISPRINT(ch))
+ return (0);
+ return (1);
+}
diff --git a/src/util/allspace.c b/src/util/allspace.c
new file mode 100644
index 0000000..9a05347
--- /dev/null
+++ b/src/util/allspace.c
@@ -0,0 +1,50 @@
+/*++
+/* NAME
+/* allspace 3
+/* SUMMARY
+/* predicate if string is all space
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* int allspace(buffer)
+/* const char *buffer;
+/* DESCRIPTION
+/* allspace() determines if its argument is an all-space string.
+/*
+/* Arguments:
+/* .IP buffer
+/* The null-terminated input string.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include "stringops.h"
+
+/* allspace - return true if string is all space */
+
+int allspace(const char *string)
+{
+ const char *cp;
+ int ch;
+
+ if (*string == 0)
+ return (0);
+ for (cp = string; (ch = *(unsigned char *) cp) != 0; cp++)
+ if (!ISASCII(ch) || !ISSPACE(ch))
+ return (0);
+ return (1);
+}
diff --git a/src/util/argv.c b/src/util/argv.c
new file mode 100644
index 0000000..4e05fd0
--- /dev/null
+++ b/src/util/argv.c
@@ -0,0 +1,721 @@
+/*++
+/* NAME
+/* argv 3
+/* SUMMARY
+/* string array utilities
+/* SYNOPSIS
+/* #include <argv.h>
+/*
+/* typedef int (*ARGV_COMPAR_FN)(const void *, const void *);
+/*
+/* ARGV *argv_alloc(len)
+/* ssize_t len;
+/*
+/* ARGV *argv_qsort(argvp, compar)
+/* ARGV *argvp;
+/* ARGV_COMPAR_FN compar;
+/*
+/* void argv_uniq(argvp, compar)
+/* ARGV *argvp;
+/* ARGV_COMPAR_FN compar;
+/*
+/* 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_qsort() sorts the elements of argvp in place, and
+/* returns its first argument. If the compar argument specifies
+/* a null pointer, then argv_qsort() will use byte-by-byte
+/* comparison.
+/*
+/* argv_uniq() reduces adjacent same-value elements to one
+/* element, and returns its first argument. If the compar
+/* argument specifies a null pointer, then argv_uniq() will
+/* use byte-by-byte comparison.
+/*
+/* 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
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System libraries. */
+
+#include <sys_defs.h>
+#include <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+#include <string.h>
+
+/* Application-specific. */
+
+#include "mymalloc.h"
+#include "msg.h"
+#include "argv.h"
+
+#ifdef TEST
+extern NORETURN PRINTFLIKE(1, 2) test_msg_panic(const char *,...);
+
+#define msg_panic test_msg_panic
+#endif
+
+/* 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_qsort - sort array in place */
+
+ARGV *argv_qsort(ARGV *argvp, ARGV_COMPAR_FN compar)
+{
+ qsort(argvp->argv, argvp->argc, sizeof(argvp->argv[0]),
+ compar ? compar : argv_cmp);
+ return (argvp);
+}
+
+/* argv_sort - binary compatibility */
+
+ARGV *argv_sort(ARGV *argvp)
+{
+ qsort(argvp->argv, argvp->argc, sizeof(argvp->argv[0]), argv_cmp);
+ return (argvp);
+}
+
+/* argv_uniq - deduplicate adjacent array elements */
+
+ARGV *argv_uniq(ARGV *argvp, ARGV_COMPAR_FN compar)
+{
+ char **cpp;
+ char **prev;
+
+ if (compar == 0)
+ compar = argv_cmp;
+ for (prev = 0, cpp = argvp->argv; cpp < argvp->argv + argvp->argc; cpp++) {
+ if (prev != 0 && compar(prev, cpp) == 0) {
+ argv_delete(argvp, cpp - argvp->argv, 1);
+ cpp = prev;
+ } else {
+ prev = cpp;
+ }
+ }
+ 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;
+}
+
+#ifdef TEST
+
+ /*
+ * System library.
+ */
+#include <setjmp.h>
+
+ /*
+ * Utility library.
+ */
+#include <msg_vstream.h>
+#include <stringops.h>
+
+#define ARRAY_LEN (10)
+
+typedef struct TEST_CASE {
+ const char *label; /* identifies test case */
+ const char *inputs[ARRAY_LEN]; /* input strings */
+ int terminate; /* terminate result */
+ ARGV *(*populate_fn) (const struct TEST_CASE *, ARGV *);
+ const char *exp_panic_msg; /* expected panic */
+ int exp_argc; /* expected array length */
+ const char *exp_argv[ARRAY_LEN]; /* expected array content */
+} TEST_CASE;
+
+#define TERMINATE_ARRAY (1)
+
+#define PASS (0)
+#define FAIL (1)
+
+VSTRING *test_panic_str;
+jmp_buf test_panic_jbuf;
+
+/* test_msg_panic - does not return, and does not terminate */
+
+void test_msg_panic(const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ test_panic_str = vstring_alloc(100);
+ vstring_vsprintf(test_panic_str, fmt, ap);
+ va_end(ap);
+ longjmp(test_panic_jbuf, 1);
+}
+
+/* test_argv_populate - populate result, optionally terminate */
+
+static ARGV *test_argv_populate(const TEST_CASE *tp, ARGV *argvp)
+{
+ const char *const * cpp;
+
+ for (cpp = tp->inputs; *cpp; cpp++)
+ argv_add(argvp, *cpp, (char *) 0);
+ if (tp->terminate)
+ argv_terminate(argvp);
+ return (argvp);
+}
+
+/* test_argv_sort - populate and sort result */
+
+static ARGV *test_argv_sort(const TEST_CASE *tp, ARGV *argvp)
+{
+ test_argv_populate(tp, argvp);
+ argv_qsort(argvp, (ARGV_COMPAR_FN) 0);
+ return (argvp);
+}
+
+/* test_argv_sort_uniq - populate, sort, uniq result */
+
+static ARGV *test_argv_sort_uniq(const TEST_CASE *tp, ARGV *argvp)
+{
+
+ /*
+ * This also tests argv_delete().
+ */
+ test_argv_sort(tp, argvp);
+ argv_uniq(argvp, (ARGV_COMPAR_FN) 0);
+ return (argvp);
+}
+
+/* test_argv_good_truncate - populate and truncate to good size */
+
+static ARGV *test_argv_good_truncate(const TEST_CASE *tp, ARGV *argvp)
+{
+ test_argv_populate(tp, argvp);
+ argv_truncate(argvp, tp->exp_argc);
+ return (argvp);
+}
+
+/* test_argv_bad_truncate - populate and truncate to bad size */
+
+static ARGV *test_argv_bad_truncate(const TEST_CASE *tp, ARGV *argvp)
+{
+ test_argv_populate(tp, argvp);
+ argv_truncate(argvp, -1);
+ return (argvp);
+}
+
+/* test_argv_good_insert - populate and insert at good position */
+
+static ARGV *test_argv_good_insert(const TEST_CASE *tp, ARGV *argvp)
+{
+ test_argv_populate(tp, argvp);
+ argv_insert_one(argvp, 1, "new");
+ return (argvp);
+}
+
+/* test_argv_bad_insert1 - populate and insert at bad position */
+
+static ARGV *test_argv_bad_insert1(const TEST_CASE *tp, ARGV *argvp)
+{
+ test_argv_populate(tp, argvp);
+ argv_insert_one(argvp, -1, "new");
+ return (argvp);
+}
+
+/* test_argv_bad_insert2 - populate and insert at bad position */
+
+static ARGV *test_argv_bad_insert2(const TEST_CASE *tp, ARGV *argvp)
+{
+ test_argv_populate(tp, argvp);
+ argv_insert_one(argvp, 100, "new");
+ return (argvp);
+}
+
+/* test_argv_good_replace - populate and replace at good position */
+
+static ARGV *test_argv_good_replace(const TEST_CASE *tp, ARGV *argvp)
+{
+ test_argv_populate(tp, argvp);
+ argv_replace_one(argvp, 1, "new");
+ return (argvp);
+}
+
+/* test_argv_bad_replace1 - populate and replace at bad position */
+
+static ARGV *test_argv_bad_replace1(const TEST_CASE *tp, ARGV *argvp)
+{
+ test_argv_populate(tp, argvp);
+ argv_replace_one(argvp, -1, "new");
+ return (argvp);
+}
+
+/* test_argv_bad_replace2 - populate and replace at bad position */
+
+static ARGV *test_argv_bad_replace2(const TEST_CASE *tp, ARGV *argvp)
+{
+ test_argv_populate(tp, argvp);
+ argv_replace_one(argvp, 100, "new");
+ return (argvp);
+}
+
+/* test_argv_bad_delete1 - populate and delete at bad position */
+
+static ARGV *test_argv_bad_delete1(const TEST_CASE *tp, ARGV *argvp)
+{
+ test_argv_populate(tp, argvp);
+ argv_delete(argvp, -1, 1);
+ return (argvp);
+}
+
+/* test_argv_bad_delete2 - populate and delete at bad position */
+
+static ARGV *test_argv_bad_delete2(const TEST_CASE *tp, ARGV *argvp)
+{
+ test_argv_populate(tp, argvp);
+ argv_delete(argvp, 0, -1);
+ return (argvp);
+}
+
+/* test_argv_bad_delete3 - populate and delete at bad position */
+
+static ARGV *test_argv_bad_delete3(const TEST_CASE *tp, ARGV *argvp)
+{
+ test_argv_populate(tp, argvp);
+ argv_delete(argvp, 100, 1);
+ return (argvp);
+}
+
+/* test_argv_verify - verify result */
+
+static int test_argv_verify(const TEST_CASE *tp, ARGV *argvp)
+{
+ int idx;
+
+ if (tp->exp_panic_msg != 0) {
+ if (test_panic_str == 0) {
+ msg_warn("test case '%s': got no panic, want: '%s'",
+ tp->label, tp->exp_panic_msg);
+ return (FAIL);
+ }
+ if (strcmp(vstring_str(test_panic_str), tp->exp_panic_msg) != 0) {
+ msg_warn("test case '%s': got '%s', want: '%s'",
+ tp->label, vstring_str(test_panic_str), tp->exp_panic_msg);
+ return (FAIL);
+ }
+ return (PASS);
+ }
+ if (test_panic_str != 0) {
+ msg_warn("test case '%s': got '%s', want: no panic",
+ tp->label, vstring_str(test_panic_str));
+ return (FAIL);
+ }
+ if (argvp->argc != tp->exp_argc) {
+ msg_warn("test case '%s': got argc: %ld, want: %d",
+ tp->label, (long) argvp->argc, tp->exp_argc);
+ return (FAIL);
+ }
+ if (argvp->argv[argvp->argc] != 0 && tp->terminate) {
+ msg_warn("test case '%s': got unterminated, want: terminated", tp->label);
+ return (FAIL);
+ }
+ for (idx = 0; idx < argvp->argc; idx++) {
+ if (strcmp(argvp->argv[idx], tp->exp_argv[idx]) != 0) {
+ msg_warn("test case '%s': index %d: got '%s', want: '%s'",
+ tp->label, idx, argvp->argv[idx], tp->exp_argv[idx]);
+ return (FAIL);
+ }
+ }
+ return (PASS);
+}
+
+ /*
+ * The test cases. TODO: argv_addn with good and bad string length.
+ */
+static const TEST_CASE test_cases[] = {
+ {"multiple strings, unterminated array",
+ {"foo", "baz", "bar", 0}, 0, test_argv_populate,
+ 0, 3, {"foo", "baz", "bar", 0}
+ },
+ {"multiple strings, terminated array",
+ {"foo", "baz", "bar", 0}, TERMINATE_ARRAY, test_argv_populate,
+ 0, 3, {"foo", "baz", "bar", 0}
+ },
+ {"distinct strings, sorted array",
+ {"foo", "baz", "bar", 0}, 0, test_argv_sort,
+ 0, 3, {"bar", "baz", "foo", 0}
+ },
+ {"duplicate strings, sorted array",
+ {"foo", "baz", "baz", "bar", 0}, 0, test_argv_sort,
+ 0, 4, {"bar", "baz", "baz", "foo", 0}
+ },
+ {"duplicate strings, sorted, uniqued-middle elements",
+ {"foo", "baz", "baz", "bar", 0}, 0, test_argv_sort_uniq,
+ 0, 3, {"bar", "baz", "foo", 0}
+ },
+ {"duplicate strings, sorted, uniqued-first elements",
+ {"foo", "bar", "baz", "bar", 0}, 0, test_argv_sort_uniq,
+ 0, 3, {"bar", "baz", "foo", 0}
+ },
+ {"duplicate strings, sorted, uniqued-last elements",
+ {"foo", "foo", "baz", "bar", 0}, 0, test_argv_sort_uniq,
+ 0, 3, {"bar", "baz", "foo", 0}
+ },
+ {"multiple strings, truncate array by one",
+ {"foo", "baz", "bar", 0}, 0, test_argv_good_truncate,
+ 0, 2, {"foo", "baz", 0}
+ },
+ {"multiple strings, truncate whole array",
+ {"foo", "baz", "bar", 0}, 0, test_argv_good_truncate,
+ 0, 0, {"foo", "baz", 0}
+ },
+ {"multiple strings, bad truncate",
+ {"foo", "baz", "bar", 0}, 0, test_argv_bad_truncate,
+ "argv_truncate: bad length -1"
+ },
+ {"multiple strings, insert one at good position",
+ {"foo", "baz", "bar", 0}, 0, test_argv_good_insert,
+ 0, 4, {"foo", "new", "baz", "bar", 0}
+ },
+ {"multiple strings, insert one at bad position",
+ {"foo", "baz", "bar", 0}, 0, test_argv_bad_insert1,
+ "argv_insert_one bad position: -1"
+ },
+ {"multiple strings, insert one at bad position",
+ {"foo", "baz", "bar", 0}, 0, test_argv_bad_insert2,
+ "argv_insert_one bad position: 100"
+ },
+ {"multiple strings, replace one at good position",
+ {"foo", "baz", "bar", 0}, 0, test_argv_good_replace,
+ 0, 3, {"foo", "new", "bar", 0}
+ },
+ {"multiple strings, replace one at bad position",
+ {"foo", "baz", "bar", 0}, 0, test_argv_bad_replace1,
+ "argv_replace_one bad position: -1"
+ },
+ {"multiple strings, replace one at bad position",
+ {"foo", "baz", "bar", 0}, 0, test_argv_bad_replace2,
+ "argv_replace_one bad position: 100"
+ },
+ {"multiple strings, delete one at negative position",
+ {"foo", "baz", "bar", 0}, 0, test_argv_bad_delete1,
+ "argv_delete bad range: (start=-1 count=1)"
+ },
+ {"multiple strings, delete with bad range end",
+ {"foo", "baz", "bar", 0}, 0, test_argv_bad_delete2,
+ "argv_delete bad range: (start=0 count=-1)"
+ },
+ {"multiple strings, delete at too large position",
+ {"foo", "baz", "bar", 0}, 0, test_argv_bad_delete3,
+ "argv_delete bad range: (start=100 count=1)"
+ },
+ 0,
+};
+
+int main(int argc, char **argv)
+{
+ const TEST_CASE *tp;
+ int pass = 0;
+ int fail = 0;
+
+ msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR);
+
+ for (tp = test_cases; tp->label != 0; tp++) {
+ int test_failed;
+ ARGV *argvp;
+
+ argvp = argv_alloc(1);
+ if (setjmp(test_panic_jbuf) == 0)
+ tp->populate_fn(tp, argvp);
+ test_failed = test_argv_verify(tp, argvp);
+ if (test_failed) {
+ msg_info("%s: FAIL", tp->label);
+ fail++;
+ } else {
+ msg_info("%s: PASS", tp->label);
+ pass++;
+ }
+ argv_free(argvp);
+ if (test_panic_str) {
+ vstring_free(test_panic_str);
+ test_panic_str = 0;
+ }
+ }
+ msg_info("PASS=%d FAIL=%d", pass, fail);
+ exit(fail != 0);
+}
+
+#endif
diff --git a/src/util/argv.h b/src/util/argv.h
new file mode 100644
index 0000000..b0098ce
--- /dev/null
+++ b/src/util/argv.h
@@ -0,0 +1,78 @@
+#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;
+
+typedef int (*ARGV_COMPAR_FN)(const void *, const void *);
+
+extern ARGV *argv_alloc(ssize_t);
+extern ARGV *argv_sort(ARGV *); /* backwards compatibility */
+extern ARGV *argv_qsort(ARGV *, ARGV_COMPAR_FN);
+extern ARGV *argv_uniq(ARGV *, ARGV_COMPAR_FN);
+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
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/argv_attr.h b/src/util/argv_attr.h
new file mode 100644
index 0000000..53c587f
--- /dev/null
+++ b/src/util/argv_attr.h
@@ -0,0 +1,43 @@
+#ifndef _ARGV_ATTR_H_INCLUDED_
+#define _ARGV_ATTR_H_INCLUDED_
+
+/*++
+/* NAME
+/* argv_attr 3h
+/* SUMMARY
+/* argv serialization/deserialization
+/* SYNOPSIS
+/* #include <argv_attr.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <argv.h>
+#include <attr.h>
+#include <check_arg.h>
+#include <vstream.h>
+
+ /*
+ * External API.
+ */
+#define ARGV_ATTR_SIZE "argv_size"
+#define ARGV_ATTR_VALUE "argv_value"
+#define ARGV_ATTR_MAX 1024
+
+extern int argv_attr_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *);
+extern int argv_attr_scan(ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/argv_attr_print.c b/src/util/argv_attr_print.c
new file mode 100644
index 0000000..78e3315
--- /dev/null
+++ b/src/util/argv_attr_print.c
@@ -0,0 +1,73 @@
+/*++
+/* NAME
+/* argv_attr_print
+/* SUMMARY
+/* write ARGV to stream
+/* SYNOPSIS
+/* #include <argv_attr.h>
+/*
+/* int argv_attr_print(print_fn, stream, flags, ptr)
+/* ATTR_PRINT_COMMON_FN print_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/* DESCRIPTION
+/* argv_attr_print() writes an ARGV to the named stream using
+/* the specified attribute print routine. argv_attr_print() is meant
+/* to be passed as a call-back to attr_print(), thusly:
+/*
+/* ... SEND_ATTR_FUNC(argv_attr_print, (const void *) argv), ...
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/*
+/* The result value is zero in case of success, non-zero
+/* otherwise.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+
+ /*
+ * Utility library.
+ */
+#include <argv.h>
+#include <argv_attr.h>
+#include <attr.h>
+#include <vstream.h>
+#include <msg.h>
+
+/* argv_attr_print - write ARGV to stream */
+
+int argv_attr_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp,
+ int flags, const void *ptr)
+{
+ ARGV *argv = (ARGV *) ptr;
+ int n;
+ int ret;
+ int argc = argv ? argv->argc : 0;
+
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_INT(ARGV_ATTR_SIZE, argc),
+ ATTR_TYPE_END);
+ if (msg_verbose)
+ msg_info("argv_attr_print count=%d", argc);
+ for (n = 0; ret == 0 && n < argc; n++)
+ ret = print_fn(fp, flags | ATTR_FLAG_MORE,
+ SEND_ATTR_STR(ARGV_ATTR_VALUE, argv->argv[n]),
+ ATTR_TYPE_END);
+ if (msg_verbose)
+ msg_info("argv_attr_print ret=%d", ret);
+ /* Do not flush the stream. */
+ return (ret);
+}
diff --git a/src/util/argv_attr_scan.c b/src/util/argv_attr_scan.c
new file mode 100644
index 0000000..0987bf8
--- /dev/null
+++ b/src/util/argv_attr_scan.c
@@ -0,0 +1,93 @@
+/*++
+/* NAME
+/* argv_attr_scan
+/* SUMMARY
+/* read ARGV from stream
+/* SYNOPSIS
+/* #include <argv_attr.h>
+/*
+/* int argv_attr_scan(scan_fn, stream, flags, ptr)
+/* ATTR_SCAN_COMMON_FN scan_fn;
+/* VSTREAM *stream;
+/* int flags;
+/* void *ptr;
+/* DESCRIPTION
+/* argv_attr_scan() creates an ARGV and reads its contents
+/* from the named stream using the specified attribute scan
+/* routine. argv_attr_scan() is meant to be passed as a call-back
+/* to attr_scan(), thusly:
+/*
+/* ARGV *argv = 0;
+/* ...
+/* ... RECV_ATTR_FUNC(argv_attr_scan, (void *) &argv), ...
+/* ...
+/* if (argv)
+/* argv_free(argv);
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/*
+/* In case of error, this function returns non-zero and creates
+/* an ARGV null pointer.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+
+ /*
+ * Utility library.
+ */
+#include <argv.h>
+#include <argv_attr.h>
+#include <attr.h>
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+
+/* argv_attr_scan - write ARGV to stream */
+
+int argv_attr_scan(ATTR_PRINT_COMMON_FN scan_fn, VSTREAM *fp,
+ int flags, void *ptr)
+{
+ ARGV *argv = 0;
+ int size;
+ int ret;
+
+ if ((ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_INT(ARGV_ATTR_SIZE, &size),
+ ATTR_TYPE_END)) == 1) {
+ if (msg_verbose)
+ msg_info("argv_attr_scan count=%d", size);
+ if (size < 0 || size > ARGV_ATTR_MAX) {
+ msg_warn("invalid size %d from %s while reading ARGV",
+ size, VSTREAM_PATH(fp));
+ ret = -1;
+ } else if (size > 0) {
+ VSTRING *buffer = vstring_alloc(100);
+
+ argv = argv_alloc(size);
+ while (ret == 1 && size-- > 0) {
+ if ((ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
+ RECV_ATTR_STR(ARGV_ATTR_VALUE, buffer),
+ ATTR_TYPE_END)) == 1)
+ argv_add(argv, vstring_str(buffer), ARGV_END);
+ }
+ argv_terminate(argv);
+ vstring_free(buffer);
+ }
+ }
+ *(ARGV **) ptr = argv;
+ if (msg_verbose)
+ msg_info("argv_attr_scan ret=%d", ret);
+ return (ret);
+}
diff --git a/src/util/argv_split.c b/src/util/argv_split.c
new file mode 100644
index 0000000..a9f3afb
--- /dev/null
+++ b/src/util/argv_split.c
@@ -0,0 +1,112 @@
+/*++
+/* NAME
+/* argv_split 3
+/* SUMMARY
+/* string array utilities
+/* SYNOPSIS
+/* #include <argv.h>
+/*
+/* ARGV *argv_split(string, delim)
+/* const char *string;
+/* const char *delim;
+/*
+/* ARGV *argv_split_count(string, delim, count)
+/* const char *string;
+/* const char *delim;
+/* ssize_t count;
+/*
+/* ARGV *argv_split_append(argv, string, delim)
+/* ARGV *argv;
+/* const char *string;
+/* const char *delim;
+/* DESCRIPTION
+/* argv_split() breaks up \fIstring\fR into tokens according
+/* to the delimiters specified in \fIdelim\fR. The result is
+/* a null-terminated string array.
+/*
+/* argv_split_count() is like argv_split() but stops splitting
+/* input after at most \fIcount\fR -1 times and leaves the
+/* remainder, if any, in the last array element. It is an error
+/* to specify a count < 1.
+/*
+/* argv_split_append() performs the same operation as argv_split(),
+/* but appends the result to an existing string array.
+/* SEE ALSO
+/* mystrtok(), safe string splitter.
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation problem.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System libraries. */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Application-specific. */
+
+#include "mymalloc.h"
+#include "stringops.h"
+#include "argv.h"
+#include "msg.h"
+
+/* argv_split - split string into token array */
+
+ARGV *argv_split(const char *string, const char *delim)
+{
+ ARGV *argvp = argv_alloc(1);
+ char *saved_string = mystrdup(string);
+ char *bp = saved_string;
+ char *arg;
+
+ while ((arg = mystrtok(&bp, delim)) != 0)
+ argv_add(argvp, arg, (char *) 0);
+ argv_terminate(argvp);
+ myfree(saved_string);
+ return (argvp);
+}
+
+/* argv_split_count - split string into token array */
+
+ARGV *argv_split_count(const char *string, const char *delim, ssize_t count)
+{
+ ARGV *argvp = argv_alloc(1);
+ char *saved_string = mystrdup(string);
+ char *bp = saved_string;
+ char *arg;
+
+ if (count < 1)
+ msg_panic("argv_split_count: bad count: %ld", (long) count);
+ while (count-- > 1 && (arg = mystrtok(&bp, delim)) != 0)
+ argv_add(argvp, arg, (char *) 0);
+ if (*bp)
+ bp += strspn(bp, delim);
+ if (*bp)
+ argv_add(argvp, bp, (char *) 0);
+ argv_terminate(argvp);
+ myfree(saved_string);
+ return (argvp);
+}
+
+/* argv_split_append - split string into token array, append to array */
+
+ARGV *argv_split_append(ARGV *argvp, const char *string, const char *delim)
+{
+ char *saved_string = mystrdup(string);
+ char *bp = saved_string;
+ char *arg;
+
+ while ((arg = mystrtok(&bp, delim)) != 0)
+ argv_add(argvp, arg, (char *) 0);
+ argv_terminate(argvp);
+ myfree(saved_string);
+ return (argvp);
+}
diff --git a/src/util/argv_split_at.c b/src/util/argv_split_at.c
new file mode 100644
index 0000000..fcdb4ff
--- /dev/null
+++ b/src/util/argv_split_at.c
@@ -0,0 +1,124 @@
+/*++
+/* NAME
+/* argv_split_at 3
+/* SUMMARY
+/* string array utilities
+/* SYNOPSIS
+/* #include <argv.h>
+/*
+/* ARGV *argv_split_at(string, sep)
+/* const char *string;
+/* int sep;
+/*
+/* ARGV *argv_split_at_count(string, sep, count)
+/* const char *string;
+/* int sep;
+/* ssize_t count;
+/*
+/* ARGV *argv_split_at_append(argv, string, sep)
+/* ARGV *argv;
+/* const char *string;
+/* int sep;
+/* DESCRIPTION
+/* argv_split_at() splits \fIstring\fR into fields using a
+/* single separator specified in \fIsep\fR. The result is a
+/* null-terminated string array.
+/*
+/* argv_split_at_count() is like argv_split_at() but stops
+/* splitting input after at most \fIcount\fR -1 times and
+/* leaves the remainder, if any, in the last array element.
+/* It is an error to specify a count < 1.
+/*
+/* argv_split_at_append() performs the same operation as
+/* argv_split_at(), but appends the result to an existing
+/* string array.
+/* SEE ALSO
+/* split_at(), trivial string splitter.
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation problem.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System libraries. */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Application-specific. */
+
+#include <mymalloc.h>
+#include <stringops.h>
+#include <argv.h>
+#include <msg.h>
+#include <split_at.h>
+
+/* argv_split_at - split string into field array */
+
+ARGV *argv_split_at(const char *string, int sep)
+{
+ ARGV *argvp = argv_alloc(1);
+ char *saved_string = mystrdup(string);
+ char *bp = saved_string;
+ char *arg;
+
+ while ((arg = split_at(bp, sep)) != 0) {
+ argv_add(argvp, bp, (char *) 0);
+ bp = arg;
+ }
+ argv_add(argvp, bp, (char *) 0);
+ argv_terminate(argvp);
+ myfree(saved_string);
+ return (argvp);
+}
+
+/* argv_split_at_count - split string into field array */
+
+ARGV *argv_split_at_count(const char *string, int sep, ssize_t count)
+{
+ ARGV *argvp = argv_alloc(1);
+ char *saved_string = mystrdup(string);
+ char *bp = saved_string;
+ char *arg;
+
+ if (count < 1)
+ msg_panic("argv_split_at_count: bad count: %ld", (long) count);
+ while (count-- > 1 && (arg = split_at(bp, sep)) != 0) {
+ argv_add(argvp, bp, (char *) 0);
+ bp = arg;
+ }
+ argv_add(argvp, bp, (char *) 0);
+ argv_terminate(argvp);
+ myfree(saved_string);
+ return (argvp);
+}
+
+/* argv_split_at_append - split string into field array, append to array */
+
+ARGV *argv_split_at_append(ARGV *argvp, const char *string, int sep)
+{
+ char *saved_string = mystrdup(string);
+ char *bp = saved_string;
+ char *arg;
+
+ while ((arg = split_at(bp, sep)) != 0) {
+ argv_add(argvp, bp, (char *) 0);
+ bp = arg;
+ }
+ argv_add(argvp, bp, (char *) 0);
+ argv_terminate(argvp);
+ myfree(saved_string);
+ return (argvp);
+}
diff --git a/src/util/argv_splitq.c b/src/util/argv_splitq.c
new file mode 100644
index 0000000..3900ee1
--- /dev/null
+++ b/src/util/argv_splitq.c
@@ -0,0 +1,118 @@
+/*++
+/* NAME
+/* argv_splitq 3
+/* SUMMARY
+/* string array utilities
+/* SYNOPSIS
+/* #include <argv.h>
+/*
+/* ARGV *argv_splitq(string, delim, parens)
+/* const char *string;
+/* const char *delim;
+/* const char *parens;
+/*
+/* ARGV *argv_splitq_count(string, delim, parens, count)
+/* const char *string;
+/* const char *delim;
+/* const char *parens;
+/* ssize_t count;
+/*
+/* ARGV *argv_splitq_append(argv, string, delim, parens)
+/* ARGV *argv;
+/* const char *string;
+/* const char *delim;
+/* const char *parens;
+/* DESCRIPTION
+/* argv_splitq() breaks up \fIstring\fR into tokens according
+/* to the delimiters specified in \fIdelim\fR, while avoiding
+/* splitting text between matching parentheses. The result is
+/* a null-terminated string array.
+/*
+/* argv_splitq_count() is like argv_splitq() but stops splitting
+/* input after at most \fIcount\fR -1 times and leaves the
+/* remainder, if any, in the last array element. It is an error
+/* to specify a count < 1.
+/*
+/* argv_splitq_append() performs the same operation as argv_splitq(),
+/* but appends the result to an existing string array.
+/* SEE ALSO
+/* mystrtokq(), safe string splitter.
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation problem.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System libraries. */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Application-specific. */
+
+#include "mymalloc.h"
+#include "stringops.h"
+#include "argv.h"
+#include "msg.h"
+
+/* argv_splitq - split string into token array */
+
+ARGV *argv_splitq(const char *string, const char *delim, const char *parens)
+{
+ ARGV *argvp = argv_alloc(1);
+ char *saved_string = mystrdup(string);
+ char *bp = saved_string;
+ char *arg;
+
+ while ((arg = mystrtokq(&bp, delim, parens)) != 0)
+ argv_add(argvp, arg, (char *) 0);
+ argv_terminate(argvp);
+ myfree(saved_string);
+ return (argvp);
+}
+
+/* argv_splitq_count - split string into token array */
+
+ARGV *argv_splitq_count(const char *string, const char *delim,
+ const char *parens, ssize_t count)
+{
+ ARGV *argvp = argv_alloc(1);
+ char *saved_string = mystrdup(string);
+ char *bp = saved_string;
+ char *arg;
+
+ if (count < 1)
+ msg_panic("argv_splitq_count: bad count: %ld", (long) count);
+ while (count-- > 1 && (arg = mystrtokq(&bp, delim, parens)) != 0)
+ argv_add(argvp, arg, (char *) 0);
+ if (*bp)
+ bp += strspn(bp, delim);
+ if (*bp)
+ argv_add(argvp, bp, (char *) 0);
+ argv_terminate(argvp);
+ myfree(saved_string);
+ return (argvp);
+}
+
+/* argv_splitq_append - split string into token array, append to array */
+
+ARGV *argv_splitq_append(ARGV *argvp, const char *string, const char *delim,
+ const char *parens)
+{
+ char *saved_string = mystrdup(string);
+ char *bp = saved_string;
+ char *arg;
+
+ while ((arg = mystrtokq(&bp, delim, parens)) != 0)
+ argv_add(argvp, arg, (char *) 0);
+ argv_terminate(argvp);
+ myfree(saved_string);
+ return (argvp);
+}
diff --git a/src/util/attr.h b/src/util/attr.h
new file mode 100644
index 0000000..7cd0cf2
--- /dev/null
+++ b/src/util/attr.h
@@ -0,0 +1,188 @@
+#ifndef _ATTR_H_INCLUDED_
+#define _ATTR_H_INCLUDED_
+
+/*++
+/* NAME
+/* attr 3h
+/* SUMMARY
+/* attribute list manipulations
+/* SYNOPSIS
+/* #include "attr.h"
+ DESCRIPTION
+ .nf
+
+ /*
+ * System library.
+ */
+#include <stdarg.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <vstring.h>
+#include <htable.h>
+#include <nvtable.h>
+#include <check_arg.h>
+
+ /*
+ * Delegation for better data abstraction.
+ */
+typedef int (*ATTR_SCAN_COMMON_FN) (VSTREAM *, int,...);
+typedef int (*ATTR_SCAN_CUSTOM_FN) (ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *);
+typedef int (*ATTR_PRINT_COMMON_FN) (VSTREAM *, int,...);
+typedef int (*ATTR_PRINT_CUSTOM_FN) (ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *);
+
+ /*
+ * Attribute types. See attr_scan(3) for documentation.
+ */
+#define ATTR_TYPE_END 0 /* end of data */
+#define ATTR_TYPE_INT 1 /* Unsigned integer */
+#define ATTR_TYPE_NUM ATTR_TYPE_INT
+#define ATTR_TYPE_STR 2 /* Character string */
+#define ATTR_TYPE_HASH 3 /* Hash table */
+#define ATTR_TYPE_NV 3 /* Name-value table */
+#define ATTR_TYPE_LONG 4 /* Unsigned long */
+#define ATTR_TYPE_DATA 5 /* Binary data */
+#define ATTR_TYPE_FUNC 6 /* Function pointer */
+#define ATTR_TYPE_STREQ 7 /* Requires (name, value) match */
+
+ /*
+ * Optional sender-specified grouping for hash or nameval tables.
+ */
+#define ATTR_TYPE_OPEN '{'
+#define ATTR_TYPE_CLOSE '}'
+#define ATTR_NAME_OPEN "{"
+#define ATTR_NAME_CLOSE "}"
+
+#define ATTR_HASH_LIMIT 1024 /* Size of hash table */
+
+ /*
+ * Typechecking support for variadic function arguments. See check_arg(3h)
+ * for documentation.
+ */
+#define SEND_ATTR_INT(name, val) ATTR_TYPE_INT, CHECK_CPTR(ATTR, char, (name)), CHECK_VAL(ATTR, int, (val))
+#define SEND_ATTR_UINT(name, val) ATTR_TYPE_INT, CHECK_CPTR(ATTR, char, (name)), CHECK_VAL(ATTR, unsigned, (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_UINT(name, val) ATTR_TYPE_INT, CHECK_CPTR(ATTR, char, (name)), CHECK_PTR(ATTR, unsigned, (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_VAL_HELPER_DCL(ATTR, unsigned);
+CHECK_PTR_HELPER_DCL(ATTR, void);
+CHECK_PTR_HELPER_DCL(ATTR, long);
+CHECK_PTR_HELPER_DCL(ATTR, int);
+CHECK_PTR_HELPER_DCL(ATTR, unsigned);
+CHECK_PTR_HELPER_DCL(ATTR, VSTRING);
+CHECK_PTR_HELPER_DCL(ATTR, NVTABLE);
+CHECK_PTR_HELPER_DCL(ATTR, HTABLE);
+CHECK_CPTR_HELPER_DCL(ATTR, void);
+CHECK_CPTR_HELPER_DCL(ATTR, char);
+CHECK_CPTR_HELPER_DCL(ATTR, NVTABLE);
+CHECK_CPTR_HELPER_DCL(ATTR, HTABLE);
+CHECK_VAL_HELPER_DCL(ATTR, ATTR_PRINT_CUSTOM_FN);
+CHECK_VAL_HELPER_DCL(ATTR, ATTR_SCAN_CUSTOM_FN);
+
+ /*
+ * Flags that control processing. See attr_scan(3) for documentation.
+ */
+#define ATTR_FLAG_NONE 0
+#define ATTR_FLAG_MISSING (1<<0) /* Flag missing attribute */
+#define ATTR_FLAG_EXTRA (1<<1) /* Flag spurious attribute */
+#define ATTR_FLAG_MORE (1<<2) /* Don't skip or terminate */
+#define ATTR_FLAG_PRINTABLE (1<<3) /* Sanitize received strings */
+
+#define ATTR_FLAG_STRICT (ATTR_FLAG_MISSING | ATTR_FLAG_EXTRA)
+#define ATTR_FLAG_ALL (017)
+
+ /*
+ * Default to null-terminated, as opposed to base64-encoded.
+ */
+#define attr_print attr_print0
+#define attr_vprint attr_vprint0
+#define attr_scan attr_scan0
+#define attr_vscan attr_vscan0
+#define attr_scan_more attr_scan_more0
+
+ /*
+ * attr_print64.c.
+ */
+extern int attr_print64(VSTREAM *, int,...);
+extern int attr_vprint64(VSTREAM *, int, va_list);
+
+ /*
+ * attr_scan64.c.
+ */
+extern int WARN_UNUSED_RESULT attr_scan64(VSTREAM *, int,...);
+extern int WARN_UNUSED_RESULT attr_vscan64(VSTREAM *, int, va_list);
+extern int WARN_UNUSED_RESULT attr_scan_more64(VSTREAM *);
+
+ /*
+ * attr_print0.c.
+ */
+extern int attr_print0(VSTREAM *, int,...);
+extern int attr_vprint0(VSTREAM *, int, va_list);
+
+ /*
+ * attr_scan0.c.
+ */
+extern int WARN_UNUSED_RESULT attr_scan0(VSTREAM *, int,...);
+extern int WARN_UNUSED_RESULT attr_vscan0(VSTREAM *, int, va_list);
+extern int WARN_UNUSED_RESULT attr_scan_more0(VSTREAM *);
+
+ /*
+ * attr_scan_plain.c.
+ */
+extern int attr_print_plain(VSTREAM *, int,...);
+extern int attr_vprint_plain(VSTREAM *, int, va_list);
+extern int attr_scan_more_plain(VSTREAM *);
+
+ /*
+ * attr_print_plain.c.
+ */
+extern int WARN_UNUSED_RESULT attr_scan_plain(VSTREAM *, int,...);
+extern int WARN_UNUSED_RESULT attr_vscan_plain(VSTREAM *, int, va_list);
+
+ /*
+ * Attribute names for testing the compatibility of the read and write
+ * routines.
+ */
+#ifdef TEST
+#define ATTR_NAME_INT "number"
+#define ATTR_NAME_STR "string"
+#define ATTR_NAME_LONG "long_number"
+#define ATTR_NAME_DATA "data"
+#endif
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/attr_clnt.c b/src/util/attr_clnt.c
new file mode 100644
index 0000000..d944be5
--- /dev/null
+++ b/src/util/attr_clnt.c
@@ -0,0 +1,300 @@
+/*++
+/* NAME
+/* attr_clnt 3
+/* SUMMARY
+/* attribute query-reply client
+/* SYNOPSIS
+/* #include <attr_clnt.h>
+/*
+/* typedef int (*ATTR_CLNT_PRINT_FN) (VSTREAM *, int, va_list);
+/* typedef int (*ATTR_CLNT_SCAN_FN) (VSTREAM *, int, va_list);
+/* typedef int (*ATTR_CLNT_HANDSHAKE_FN) (VSTREAM *);
+/*
+/* ATTR_CLNT *attr_clnt_create(server, timeout, max_idle, max_ttl)
+/* const char *server;
+/* int timeout;
+/* int max_idle;
+/* int max_ttl;
+/*
+/* int attr_clnt_request(client,
+/* send_flags, send_type, send_name, ..., ATTR_TYPE_END,
+/* recv_flags, recv_type, recv_name, ..., ATTR_TYPE_END)
+/* ATTR_CLNT *client;
+/* int send_flags;
+/* int send_type;
+/* const char *send_name;
+/* int recv_flags;
+/* int recv_type;
+/* const char *recv_name;
+/*
+/* void attr_clnt_free(client)
+/* ATTR_CLNT *client;
+/*
+/* void attr_clnt_control(client, name, value, ... ATTR_CLNT_CTL_END)
+/* ATTR_CLNT *client;
+/* int name;
+/* DESCRIPTION
+/* This module implements a client for a simple attribute-based
+/* protocol. The default protocol is described in attr_scan_plain(3).
+/*
+/* attr_clnt_create() creates a client handle. See auto_clnt(3) for
+/* a description of the arguments.
+/*
+/* attr_clnt_request() sends the specified request attributes and
+/* receives a reply. The reply argument specifies a name-value table.
+/* The other arguments are as described in attr_print_plain(3). The
+/* result is the number of attributes received or -1 in case of trouble.
+/*
+/* attr_clnt_free() destroys a client handle and closes its connection.
+/*
+/* attr_clnt_control() allows the user to fine tune the behavior of
+/* the specified client. The arguments are a list of (name, value)
+/* terminated with ATTR_CLNT_CTL_END.
+/* The following lists the names and the types of the corresponding
+/* value arguments.
+/* .IP "ATTR_CLNT_CTL_PROTO(ATTR_CLNT_PRINT_FN, ATTR_CLNT_SCAN_FN)"
+/* Specifies alternatives for the attr_plain_print() and
+/* attr_plain_scan() functions.
+/* .IP "ATTR_CLNT_CTL_REQ_LIMIT(int)"
+/* The maximal number of requests per connection (default: 0,
+/* i.e. no limit). To enable the limit, specify a value greater
+/* than zero.
+/* .IP "ATTR_CLNT_CTL_TRY_LIMIT(int)"
+/* The maximal number of attempts to send a request before
+/* giving up (default: 2). To disable the limit, specify a
+/* value equal to zero.
+/* .IP "ATTR_CLNT_CTL_TRY_DELAY(int)"
+/* The time in seconds between attempts to send a request
+/* (default: 1). Specify a value greater than zero.
+/* .IP "ATTR_CLNT_CTL_HANDSHAKE(VSTREAM *)"
+/* A pointer to function that will be called at the start of a
+/* new connection, and that returns 0 in case of success.
+/* DIAGNOSTICS
+/* Warnings: communication failure.
+/* SEE ALSO
+/* auto_clnt(3), client endpoint management
+/* attr_scan_plain(3), attribute protocol
+/* attr_print_plain(3), attribute protocol
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <unistd.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <htable.h>
+#include <attr.h>
+#include <iostuff.h>
+#include <compat_va_copy.h>
+#include <auto_clnt.h>
+#include <attr_clnt.h>
+
+/* Application-specific. */
+
+struct ATTR_CLNT {
+ AUTO_CLNT *auto_clnt;
+ /* Remaining properties are set with attr_clnt_control(). */
+ ATTR_CLNT_PRINT_FN print;
+ ATTR_CLNT_SCAN_FN scan;
+ int req_limit;
+ int req_count;
+ int try_limit;
+ int try_delay;
+};
+
+#define ATTR_CLNT_DEF_REQ_LIMIT (0) /* default per-session request limit */
+#define ATTR_CLNT_DEF_TRY_LIMIT (2) /* default request (re)try limit */
+#define ATTR_CLNT_DEF_TRY_DELAY (1) /* default request (re)try delay */
+
+/* attr_clnt_free - destroy attribute client */
+
+void attr_clnt_free(ATTR_CLNT *client)
+{
+ auto_clnt_free(client->auto_clnt);
+ myfree((void *) client);
+}
+
+/* attr_clnt_create - create attribute client */
+
+ATTR_CLNT *attr_clnt_create(const char *service, int timeout,
+ int max_idle, int max_ttl)
+{
+ ATTR_CLNT *client;
+
+ client = (ATTR_CLNT *) mymalloc(sizeof(*client));
+ client->auto_clnt = auto_clnt_create(service, timeout, max_idle, max_ttl);
+ client->scan = attr_vscan_plain;
+ client->print = attr_vprint_plain;
+ client->req_limit = ATTR_CLNT_DEF_REQ_LIMIT;
+ client->req_count = 0;
+ client->try_limit = ATTR_CLNT_DEF_TRY_LIMIT;
+ client->try_delay = ATTR_CLNT_DEF_TRY_DELAY;
+ return (client);
+}
+
+/* attr_clnt_request - send query, receive reply */
+
+int attr_clnt_request(ATTR_CLNT *client, int send_flags,...)
+{
+ const char *myname = "attr_clnt_request";
+ VSTREAM *stream;
+ int count = 0;
+ va_list saved_ap;
+ va_list ap;
+ int type;
+ int recv_flags;
+ int err;
+ int ret;
+
+ /*
+ * XXX If the stream is readable before we send anything, then assume the
+ * remote end disconnected.
+ *
+ * XXX For some reason we can't simply call the scan routine after the print
+ * routine, that messes up the argument list.
+ */
+#define SKIP_ARG(ap, type) { \
+ (void) va_arg(ap, char *); \
+ (void) va_arg(ap, type); \
+ }
+#define SKIP_ARG2(ap, t1, t2) { \
+ SKIP_ARG(ap, t1); \
+ (void) va_arg(ap, t2); \
+ }
+
+ /* Finalize argument lists before returning. */
+ va_start(saved_ap, send_flags);
+ for (;;) {
+ errno = 0;
+ if ((stream = auto_clnt_access(client->auto_clnt)) != 0
+ && readable(vstream_fileno(stream)) == 0) {
+ errno = 0;
+ VA_COPY(ap, saved_ap);
+ err = (client->print(stream, send_flags, ap) != 0
+ || vstream_fflush(stream) != 0);
+ va_end(ap);
+ if (err == 0) {
+ VA_COPY(ap, saved_ap);
+ while ((type = va_arg(ap, int)) != ATTR_TYPE_END) {
+ switch (type) {
+ case ATTR_TYPE_STR:
+ SKIP_ARG(ap, char *);
+ break;
+ case ATTR_TYPE_DATA:
+ SKIP_ARG2(ap, ssize_t, char *);
+ break;
+ case ATTR_TYPE_INT:
+ SKIP_ARG(ap, int);
+ break;
+ case ATTR_TYPE_LONG:
+ SKIP_ARG(ap, long);
+ break;
+ case ATTR_TYPE_HASH:
+ (void) va_arg(ap, HTABLE *);
+ break;
+ default:
+ msg_panic("%s: unexpected attribute type %d",
+ myname, type);
+ }
+ }
+ recv_flags = va_arg(ap, int);
+ ret = client->scan(stream, recv_flags, ap);
+ va_end(ap);
+ /* Finalize argument lists before returning. */
+ if (ret > 0) {
+ if (client->req_limit > 0
+ && (client->req_count += 1) >= client->req_limit) {
+ auto_clnt_recover(client->auto_clnt);
+ client->req_count = 0;
+ }
+ break;
+ }
+ }
+ }
+ if ((++count >= client->try_limit && client->try_limit > 0)
+ || msg_verbose
+ || (errno && errno != EPIPE && errno != ENOENT && errno != ECONNRESET))
+ msg_warn("problem talking to server %s: %m",
+ auto_clnt_name(client->auto_clnt));
+ /* Finalize argument lists before returning. */
+ if (count >= client->try_limit && client->try_limit > 0) {
+ ret = -1;
+ break;
+ }
+ sleep(client->try_delay);
+ auto_clnt_recover(client->auto_clnt);
+ client->req_count = 0;
+ }
+ /* Finalize argument lists before returning. */
+ va_end(saved_ap);
+ return (ret);
+}
+
+/* attr_clnt_control - fine control */
+
+void attr_clnt_control(ATTR_CLNT *client, int name,...)
+{
+ const char *myname = "attr_clnt_control";
+ va_list ap;
+
+ for (va_start(ap, name); name != ATTR_CLNT_CTL_END; name = va_arg(ap, int)) {
+ switch (name) {
+ case ATTR_CLNT_CTL_PROTO:
+ client->print = va_arg(ap, ATTR_CLNT_PRINT_FN);
+ client->scan = va_arg(ap, ATTR_CLNT_SCAN_FN);
+ break;
+ case ATTR_CLNT_CTL_HANDSHAKE:
+ auto_clnt_control(client->auto_clnt,
+ AUTO_CLNT_CTL_HANDSHAKE,
+ va_arg(ap, ATTR_CLNT_HANDSHAKE_FN),
+ AUTO_CLNT_CTL_END);
+ break;
+ case ATTR_CLNT_CTL_REQ_LIMIT:
+ client->req_limit = va_arg(ap, int);
+ if (client->req_limit < 0)
+ msg_panic("%s: bad request limit: %d",
+ myname, client->req_limit);
+ if (msg_verbose)
+ msg_info("%s: new request limit %d",
+ myname, client->req_limit);
+ break;
+ case ATTR_CLNT_CTL_TRY_LIMIT:
+ client->try_limit = va_arg(ap, int);
+ if (client->try_limit < 0)
+ msg_panic("%s: bad retry limit: %d", myname, client->try_limit);
+ if (msg_verbose)
+ msg_info("%s: new retry limit %d", myname, client->try_limit);
+ break;
+ case ATTR_CLNT_CTL_TRY_DELAY:
+ client->try_delay = va_arg(ap, int);
+ if (client->try_delay <= 0)
+ msg_panic("%s: bad retry delay: %d", myname, client->try_delay);
+ if (msg_verbose)
+ msg_info("%s: new retry delay %d", myname, client->try_delay);
+ break;
+ default:
+ msg_panic("%s: bad name %d", myname, name);
+ }
+ }
+ va_end(ap);
+}
diff --git a/src/util/attr_clnt.h b/src/util/attr_clnt.h
new file mode 100644
index 0000000..ca630cd
--- /dev/null
+++ b/src/util/attr_clnt.h
@@ -0,0 +1,60 @@
+#ifndef _ATTR_CLNT_H_INCLUDED_
+#define _ATTR_CLNT_H_INCLUDED_
+
+/*++
+/* NAME
+/* attr_clnt 3h
+/* SUMMARY
+/* attribute query-reply client
+/* SYNOPSIS
+/* #include <attr_clnt.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <stdarg.h>
+
+ /*
+ * Utility library.
+ */
+#include <attr.h>
+
+ /*
+ * External interface.
+ */
+typedef struct ATTR_CLNT ATTR_CLNT;
+typedef int (*ATTR_CLNT_PRINT_FN) (VSTREAM *, int, va_list);
+typedef int (*ATTR_CLNT_SCAN_FN) (VSTREAM *, int, va_list);
+typedef int (*ATTR_CLNT_HANDSHAKE_FN) (VSTREAM *);
+
+extern ATTR_CLNT *attr_clnt_create(const char *, int, int, int);
+extern int attr_clnt_request(ATTR_CLNT *, int,...);
+extern void attr_clnt_free(ATTR_CLNT *);
+extern void attr_clnt_control(ATTR_CLNT *, int,...);
+
+#define ATTR_CLNT_CTL_END 0
+#define ATTR_CLNT_CTL_PROTO 1 /* print/scan functions */
+#define ATTR_CLNT_CTL_REQ_LIMIT 2 /* requests per connection */
+#define ATTR_CLNT_CTL_TRY_LIMIT 3 /* attempts per request */
+#define ATTR_CLNT_CTL_TRY_DELAY 4 /* pause between requests */
+#define ATTR_CLNT_CTL_HANDSHAKE 5 /* handshake before first request */
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/attr_print0.c b/src/util/attr_print0.c
new file mode 100644
index 0000000..98c5118
--- /dev/null
+++ b/src/util/attr_print0.c
@@ -0,0 +1,256 @@
+/*++
+/* NAME
+/* attr_print0 3
+/* SUMMARY
+/* send attributes over byte stream
+/* SYNOPSIS
+/* #include <attr.h>
+/*
+/* int attr_print0(fp, flags, type, name, ..., ATTR_TYPE_END)
+/* VSTREAM fp;
+/* int flags;
+/* int type;
+/* char *name;
+/*
+/* int attr_vprint0(fp, flags, ap)
+/* VSTREAM fp;
+/* int flags;
+/* va_list ap;
+/* DESCRIPTION
+/* attr_print0() takes zero or more (name, value) simple attributes
+/* and converts its input to a byte stream that can be recovered with
+/* attr_scan0(). The stream is not flushed.
+/*
+/* attr_vprint0() provides an alternate interface that is convenient
+/* for calling from within variadic functions.
+/*
+/* Attributes are sent in the requested order as specified with the
+/* attr_print0() argument list. This routine satisfies the formatting
+/* rules as outlined in attr_scan0(3).
+/*
+/* Arguments:
+/* .IP fp
+/* Stream to write the result to.
+/* .IP flags
+/* The bit-wise OR of zero or more of the following.
+/* .RS
+/* .IP ATTR_FLAG_MORE
+/* After sending the requested attributes, leave the output stream in
+/* a state that is usable for more attribute sending operations on
+/* the same output attribute list.
+/* By default, attr_print0() automatically appends an attribute list
+/* terminator when it has sent the last requested attribute.
+/* .RE
+/* .IP List of attributes followed by terminator:
+/* .RS
+/* .IP "SEND_ATTR_INT(const char *name, int value)"
+/* The arguments are an attribute name and an integer.
+/* .IP "SEND_ATTR_LONG(const char *name, long value)"
+/* The arguments are an attribute name and a long integer.
+/* .IP "SEND_ATTR_STR(const char *name, const char *value)"
+/* The arguments are an attribute name and a null-terminated
+/* string.
+/* .IP "SEND_ATTR_DATA(const char *name, ssize_t len, const void *value)"
+/* The arguments are an attribute name, an attribute value
+/* length, and an attribute value pointer.
+/* .IP "SEND_ATTR_FUNC(ATTR_PRINT_CUSTOM_FN, const void *value)"
+/* The arguments are a function pointer and generic data
+/* pointer. The caller-specified function returns whatever the
+/* specified attribute printing function returns.
+/* .IP "SEND_ATTR_HASH(const HTABLE *table)"
+/* .IP "SEND_ATTR_NAMEVAL(const NVTABLE *table)"
+/* The content of the table is sent as a sequence of string-valued
+/* attributes with names equal to the table lookup keys.
+/* .IP ATTR_TYPE_END
+/* This terminates the attribute list.
+/* .RE
+/* DIAGNOSTICS
+/* The result value is 0 in case of success, VSTREAM_EOF in case
+/* of trouble.
+/*
+/* Panic: interface violation. All system call errors are fatal.
+/* SEE ALSO
+/* attr_scan0(3) recover attributes from byte stream
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdarg.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <htable.h>
+#include <attr.h>
+#include <base64_code.h>
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* attr_vprint0 - send attribute list to stream */
+
+int attr_vprint0(VSTREAM *fp, int flags, va_list ap)
+{
+ const char *myname = "attr_print0";
+ int attr_type;
+ char *attr_name;
+ unsigned int_val;
+ unsigned long long_val;
+ char *str_val;
+ HTABLE_INFO **ht_info_list;
+ HTABLE_INFO **ht;
+ ssize_t len_val;
+ static VSTRING *base64_buf;
+ ATTR_PRINT_CUSTOM_FN print_fn;
+ void *print_arg;
+
+ /*
+ * Sanity check.
+ */
+ if (flags & ~ATTR_FLAG_ALL)
+ msg_panic("%s: bad flags: 0x%x", myname, flags);
+
+ /*
+ * Iterate over all (type, name, value) triples, and produce output on
+ * the fly.
+ */
+ while ((attr_type = va_arg(ap, int)) != ATTR_TYPE_END) {
+ switch (attr_type) {
+ case ATTR_TYPE_INT:
+ attr_name = va_arg(ap, char *);
+ vstream_fwrite(fp, attr_name, strlen(attr_name) + 1);
+ int_val = va_arg(ap, int);
+ vstream_fprintf(fp, "%u", (unsigned) int_val);
+ VSTREAM_PUTC('\0', fp);
+ if (msg_verbose)
+ msg_info("send attr %s = %u", attr_name, int_val);
+ break;
+ case ATTR_TYPE_LONG:
+ attr_name = va_arg(ap, char *);
+ vstream_fwrite(fp, attr_name, strlen(attr_name) + 1);
+ long_val = va_arg(ap, unsigned long);
+ vstream_fprintf(fp, "%lu", (unsigned long) long_val);
+ VSTREAM_PUTC('\0', fp);
+ if (msg_verbose)
+ msg_info("send attr %s = %lu", attr_name, long_val);
+ break;
+ case ATTR_TYPE_STR:
+ attr_name = va_arg(ap, char *);
+ vstream_fwrite(fp, attr_name, strlen(attr_name) + 1);
+ str_val = va_arg(ap, char *);
+ vstream_fwrite(fp, str_val, strlen(str_val) + 1);
+ if (msg_verbose)
+ msg_info("send attr %s = %s", attr_name, str_val);
+ break;
+ case ATTR_TYPE_DATA:
+ attr_name = va_arg(ap, char *);
+ vstream_fwrite(fp, attr_name, strlen(attr_name) + 1);
+ len_val = va_arg(ap, ssize_t);
+ str_val = va_arg(ap, char *);
+ if (base64_buf == 0)
+ base64_buf = vstring_alloc(10);
+ base64_encode(base64_buf, str_val, len_val);
+ vstream_fwrite(fp, STR(base64_buf), LEN(base64_buf) + 1);
+ if (msg_verbose)
+ msg_info("send attr %s = [data %ld bytes]",
+ attr_name, (long) len_val);
+ break;
+ case ATTR_TYPE_FUNC:
+ print_fn = va_arg(ap, ATTR_PRINT_CUSTOM_FN);
+ print_arg = va_arg(ap, void *);
+ print_fn(attr_print0, fp, flags | ATTR_FLAG_MORE, print_arg);
+ break;
+ case ATTR_TYPE_HASH:
+ vstream_fwrite(fp, ATTR_NAME_OPEN, sizeof(ATTR_NAME_OPEN));
+ ht_info_list = htable_list(va_arg(ap, HTABLE *));
+ for (ht = ht_info_list; *ht; ht++) {
+ vstream_fwrite(fp, ht[0]->key, strlen(ht[0]->key) + 1);
+ vstream_fwrite(fp, ht[0]->value, strlen(ht[0]->value) + 1);
+ if (msg_verbose)
+ msg_info("send attr name %s value %s",
+ ht[0]->key, (char *) ht[0]->value);
+ }
+ myfree((void *) ht_info_list);
+ vstream_fwrite(fp, ATTR_NAME_CLOSE, sizeof(ATTR_NAME_CLOSE));
+ break;
+ default:
+ msg_panic("%s: unknown type code: %d", myname, attr_type);
+ }
+ }
+ if ((flags & ATTR_FLAG_MORE) == 0)
+ VSTREAM_PUTC('\0', fp);
+ return (vstream_ferror(fp));
+}
+
+int attr_print0(VSTREAM *fp, int flags,...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, flags);
+ ret = attr_vprint0(fp, flags, ap);
+ va_end(ap);
+ return (ret);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof of concept test program. Mirror image of the attr_scan0 test
+ * program.
+ */
+#include <msg_vstream.h>
+
+int main(int unused_argc, char **argv)
+{
+ HTABLE *table = htable_create(1);
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ msg_verbose = 1;
+ htable_enter(table, "foo-name", mystrdup("foo-value"));
+ htable_enter(table, "bar-name", mystrdup("bar-value"));
+ attr_print0(VSTREAM_OUT, ATTR_FLAG_NONE,
+ SEND_ATTR_STR("protocol", "test"),
+ SEND_ATTR_INT(ATTR_NAME_INT, 4711),
+ SEND_ATTR_LONG(ATTR_NAME_LONG, 1234L),
+ SEND_ATTR_STR(ATTR_NAME_STR, "whoopee"),
+ SEND_ATTR_DATA(ATTR_NAME_DATA, strlen("whoopee"), "whoopee"),
+ SEND_ATTR_HASH(table),
+ SEND_ATTR_LONG(ATTR_NAME_LONG, 4321L),
+ ATTR_TYPE_END);
+ attr_print0(VSTREAM_OUT, ATTR_FLAG_NONE,
+ SEND_ATTR_STR("protocol", "test"),
+ SEND_ATTR_INT(ATTR_NAME_INT, 4711),
+ SEND_ATTR_LONG(ATTR_NAME_LONG, 1234L),
+ SEND_ATTR_STR(ATTR_NAME_STR, "whoopee"),
+ SEND_ATTR_DATA(ATTR_NAME_DATA, strlen("whoopee"), "whoopee"),
+ ATTR_TYPE_END);
+ attr_print0(VSTREAM_OUT, ATTR_FLAG_NONE,
+ SEND_ATTR_STR("protocol", "not-test"),
+ ATTR_TYPE_END);
+ if (vstream_fflush(VSTREAM_OUT) != 0)
+ msg_fatal("write error: %m");
+
+ htable_free(table, myfree);
+ return (0);
+}
+
+#endif
diff --git a/src/util/attr_print64.c b/src/util/attr_print64.c
new file mode 100644
index 0000000..085ba33
--- /dev/null
+++ b/src/util/attr_print64.c
@@ -0,0 +1,297 @@
+/*++
+/* NAME
+/* attr_print64 3
+/* SUMMARY
+/* send attributes over byte stream
+/* SYNOPSIS
+/* #include <attr.h>
+/*
+/* int attr_print64(fp, flags, type, name, ..., ATTR_TYPE_END)
+/* VSTREAM fp;
+/* int flags;
+/* int type;
+/* char *name;
+/*
+/* int attr_vprint64(fp, flags, ap)
+/* VSTREAM fp;
+/* int flags;
+/* va_list ap;
+/* DESCRIPTION
+/* attr_print64() takes zero or more (name, value) simple attributes
+/* and converts its input to a byte stream that can be recovered with
+/* attr_scan64(). The stream is not flushed.
+/*
+/* attr_vprint64() provides an alternate interface that is convenient
+/* for calling from within variadic functions.
+/*
+/* Attributes are sent in the requested order as specified with the
+/* attr_print64() argument list. This routine satisfies the formatting
+/* rules as outlined in attr_scan64(3).
+/*
+/* Arguments:
+/* .IP fp
+/* Stream to write the result to.
+/* .IP flags
+/* The bit-wise OR of zero or more of the following.
+/* .RS
+/* .IP ATTR_FLAG_MORE
+/* After sending the requested attributes, leave the output stream in
+/* a state that is usable for more attribute sending operations on
+/* the same output attribute list.
+/* By default, attr_print64() automatically appends an attribute list
+/* terminator when it has sent the last requested attribute.
+/* .RE
+/* .IP List of attributes followed by terminator:
+/* .RS
+/* .IP "SEND_ATTR_INT(const char *name, int value)"
+/* The arguments are an attribute name and an integer.
+/* .IP "SEND_ATTR_LONG(const char *name, long value)"
+/* The arguments are an attribute name and a long integer.
+/* .IP "SEND_ATTR_STR(const char *name, const char *value)"
+/* The arguments are an attribute name and a null-terminated
+/* string.
+/* .IP "SEND_ATTR_DATA(const char *name, ssize_t len, const void *value)"
+/* The arguments are an attribute name, an attribute value
+/* length, and an attribute value pointer.
+/* .IP "SEND_ATTR_FUNC(ATTR_PRINT_CUSTOM_FN, const void *value)"
+/* The arguments are a function pointer and generic data
+/* pointer. The caller-specified function returns whatever the
+/* specified attribute printing function returns.
+/* .IP "SEND_ATTR_HASH(const HTABLE *table)"
+/* .IP "SEND_ATTR_NAMEVAL(const NVTABLE *table)"
+/* The content of the table is sent as a sequence of string-valued
+/* attributes with names equal to the table lookup keys.
+/* .IP ATTR_TYPE_END
+/* This terminates the attribute list.
+/* .RE
+/* DIAGNOSTICS
+/* The result value is 0 in case of success, VSTREAM_EOF in case
+/* of trouble.
+/*
+/* Panic: interface violation. All system call errors are fatal.
+/* SEE ALSO
+/* attr_scan64(3) recover attributes from byte stream
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdarg.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <htable.h>
+#include <base64_code.h>
+#include <attr.h>
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* attr_print64_str - encode and send attribute information */
+
+static void attr_print64_str(VSTREAM *fp, const char *str, ssize_t len)
+{
+ static VSTRING *base64_buf;
+
+ if (base64_buf == 0)
+ base64_buf = vstring_alloc(10);
+
+ base64_encode(base64_buf, str, len);
+ vstream_fputs(STR(base64_buf), fp);
+}
+
+static void attr_print64_num(VSTREAM *fp, unsigned num)
+{
+ static VSTRING *plain;
+
+ if (plain == 0)
+ plain = vstring_alloc(10);
+
+ vstring_sprintf(plain, "%u", num);
+ attr_print64_str(fp, STR(plain), LEN(plain));
+}
+
+static void attr_print64_long_num(VSTREAM *fp, unsigned long long_num)
+{
+ static VSTRING *plain;
+
+ if (plain == 0)
+ plain = vstring_alloc(10);
+
+ vstring_sprintf(plain, "%lu", long_num);
+ attr_print64_str(fp, STR(plain), LEN(plain));
+}
+
+/* attr_vprint64 - send attribute list to stream */
+
+int attr_vprint64(VSTREAM *fp, int flags, va_list ap)
+{
+ const char *myname = "attr_print64";
+ int attr_type;
+ char *attr_name;
+ unsigned int_val;
+ unsigned long long_val;
+ char *str_val;
+ HTABLE_INFO **ht_info_list;
+ HTABLE_INFO **ht;
+ ssize_t len_val;
+ ATTR_PRINT_CUSTOM_FN print_fn;
+ void *print_arg;
+
+ /*
+ * Sanity check.
+ */
+ if (flags & ~ATTR_FLAG_ALL)
+ msg_panic("%s: bad flags: 0x%x", myname, flags);
+
+ /*
+ * Iterate over all (type, name, value) triples, and produce output on
+ * the fly.
+ */
+ while ((attr_type = va_arg(ap, int)) != ATTR_TYPE_END) {
+ switch (attr_type) {
+ case ATTR_TYPE_INT:
+ attr_name = va_arg(ap, char *);
+ attr_print64_str(fp, attr_name, strlen(attr_name));
+ int_val = va_arg(ap, int);
+ VSTREAM_PUTC(':', fp);
+ attr_print64_num(fp, (unsigned) int_val);
+ VSTREAM_PUTC('\n', fp);
+ if (msg_verbose)
+ msg_info("send attr %s = %u", attr_name, int_val);
+ break;
+ case ATTR_TYPE_LONG:
+ attr_name = va_arg(ap, char *);
+ attr_print64_str(fp, attr_name, strlen(attr_name));
+ long_val = va_arg(ap, long);
+ VSTREAM_PUTC(':', fp);
+ attr_print64_long_num(fp, (unsigned long) long_val);
+ VSTREAM_PUTC('\n', fp);
+ if (msg_verbose)
+ msg_info("send attr %s = %lu", attr_name, long_val);
+ break;
+ case ATTR_TYPE_STR:
+ attr_name = va_arg(ap, char *);
+ attr_print64_str(fp, attr_name, strlen(attr_name));
+ str_val = va_arg(ap, char *);
+ VSTREAM_PUTC(':', fp);
+ attr_print64_str(fp, str_val, strlen(str_val));
+ VSTREAM_PUTC('\n', fp);
+ if (msg_verbose)
+ msg_info("send attr %s = %s", attr_name, str_val);
+ break;
+ case ATTR_TYPE_DATA:
+ attr_name = va_arg(ap, char *);
+ attr_print64_str(fp, attr_name, strlen(attr_name));
+ len_val = va_arg(ap, ssize_t);
+ str_val = va_arg(ap, char *);
+ VSTREAM_PUTC(':', fp);
+ attr_print64_str(fp, str_val, len_val);
+ VSTREAM_PUTC('\n', fp);
+ if (msg_verbose)
+ msg_info("send attr %s = [data %ld bytes]",
+ attr_name, (long) len_val);
+ break;
+ case ATTR_TYPE_FUNC:
+ print_fn = va_arg(ap, ATTR_PRINT_CUSTOM_FN);
+ print_arg = va_arg(ap, void *);
+ print_fn(attr_print64, fp, flags | ATTR_FLAG_MORE, print_arg);
+ break;
+ case ATTR_TYPE_HASH:
+ attr_print64_str(fp, ATTR_NAME_OPEN, sizeof(ATTR_NAME_OPEN) - 1);
+ VSTREAM_PUTC('\n', fp);
+ ht_info_list = htable_list(va_arg(ap, HTABLE *));
+ for (ht = ht_info_list; *ht; ht++) {
+ attr_print64_str(fp, ht[0]->key, strlen(ht[0]->key));
+ VSTREAM_PUTC(':', fp);
+ attr_print64_str(fp, ht[0]->value, strlen(ht[0]->value));
+ VSTREAM_PUTC('\n', fp);
+ if (msg_verbose)
+ msg_info("send attr name %s value %s",
+ ht[0]->key, (char *) ht[0]->value);
+ }
+ myfree((void *) ht_info_list);
+ attr_print64_str(fp, ATTR_NAME_CLOSE, sizeof(ATTR_NAME_CLOSE) - 1);
+ VSTREAM_PUTC('\n', fp);
+ break;
+ default:
+ msg_panic("%s: unknown type code: %d", myname, attr_type);
+ }
+ }
+ if ((flags & ATTR_FLAG_MORE) == 0)
+ VSTREAM_PUTC('\n', fp);
+ return (vstream_ferror(fp));
+}
+
+int attr_print64(VSTREAM *fp, int flags,...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, flags);
+ ret = attr_vprint64(fp, flags, ap);
+ va_end(ap);
+ return (ret);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof of concept test program. Mirror image of the attr_scan64 test
+ * program.
+ */
+#include <msg_vstream.h>
+
+int main(int unused_argc, char **argv)
+{
+ HTABLE *table = htable_create(1);
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ msg_verbose = 1;
+ htable_enter(table, "foo-name", mystrdup("foo-value"));
+ htable_enter(table, "bar-name", mystrdup("bar-value"));
+ attr_print64(VSTREAM_OUT, ATTR_FLAG_NONE,
+ SEND_ATTR_STR("protocol", "test"),
+ SEND_ATTR_INT(ATTR_NAME_INT, 4711),
+ SEND_ATTR_LONG(ATTR_NAME_LONG, 1234L),
+ SEND_ATTR_STR(ATTR_NAME_STR, "whoopee"),
+ SEND_ATTR_DATA(ATTR_NAME_DATA, strlen("whoopee"), "whoopee"),
+ SEND_ATTR_HASH(table),
+ SEND_ATTR_LONG(ATTR_NAME_LONG, 4321L),
+ ATTR_TYPE_END);
+ attr_print64(VSTREAM_OUT, ATTR_FLAG_NONE,
+ SEND_ATTR_STR("protocol", "test"),
+ SEND_ATTR_INT(ATTR_NAME_INT, 4711),
+ SEND_ATTR_LONG(ATTR_NAME_LONG, 1234L),
+ SEND_ATTR_STR(ATTR_NAME_STR, "whoopee"),
+ SEND_ATTR_DATA(ATTR_NAME_DATA, strlen("whoopee"), "whoopee"),
+ ATTR_TYPE_END);
+ attr_print64(VSTREAM_OUT, ATTR_FLAG_NONE,
+ SEND_ATTR_STR("protocol", "not-test"),
+ ATTR_TYPE_END);
+ if (vstream_fflush(VSTREAM_OUT) != 0)
+ msg_fatal("write error: %m");
+
+ htable_free(table, myfree);
+ return (0);
+}
+
+#endif
diff --git a/src/util/attr_print_plain.c b/src/util/attr_print_plain.c
new file mode 100644
index 0000000..7d2d02f
--- /dev/null
+++ b/src/util/attr_print_plain.c
@@ -0,0 +1,252 @@
+/*++
+/* NAME
+/* attr_print_plain 3
+/* SUMMARY
+/* send attributes over byte stream
+/* SYNOPSIS
+/* #include <attr.h>
+/*
+/* int attr_print_plain(fp, flags, type, name, ..., ATTR_TYPE_END)
+/* VSTREAM fp;
+/* int flags;
+/* int type;
+/* char *name;
+/*
+/* int attr_vprint_plain(fp, flags, ap)
+/* VSTREAM fp;
+/* int flags;
+/* va_list ap;
+/* DESCRIPTION
+/* attr_print_plain() takes zero or more (name, value) simple attributes
+/* and converts its input to a byte stream that can be recovered with
+/* attr_scan_plain(). The stream is not flushed.
+/*
+/* attr_vprint_plain() provides an alternate interface that is convenient
+/* for calling from within variadic functions.
+/*
+/* Attributes are sent in the requested order as specified with the
+/* attr_print_plain() argument list. This routine satisfies the formatting
+/* rules as outlined in attr_scan_plain(3).
+/*
+/* Arguments:
+/* .IP fp
+/* Stream to write the result to.
+/* .IP flags
+/* The bit-wise OR of zero or more of the following.
+/* .RS
+/* .IP ATTR_FLAG_MORE
+/* After sending the requested attributes, leave the output stream in
+/* a state that is usable for more attribute sending operations on
+/* the same output attribute list.
+/* By default, attr_print_plain() automatically appends an attribute list
+/* terminator when it has sent the last requested attribute.
+/* .RE
+/* .IP List of attributes followed by terminator:
+/* .RS
+/* .IP "SEND_ATTR_INT(const char *name, int value)"
+/* The arguments are an attribute name and an integer.
+/* .IP "SEND_ATTR_LONG(const char *name, long value)"
+/* The arguments are an attribute name and a long integer.
+/* .IP "SEND_ATTR_STR(const char *name, const char *value)"
+/* The arguments are an attribute name and a null-terminated
+/* string.
+/* .IP "SEND_ATTR_DATA(const char *name, ssize_t len, const void *value)"
+/* The arguments are an attribute name, an attribute value
+/* length, and an attribute value pointer.
+/* .IP "SEND_ATTR_FUNC(ATTR_PRINT_CUSTOM_FN, const void *value)"
+/* The arguments are a function pointer and generic data
+/* pointer. The caller-specified function returns whatever the
+/* specified attribute printing function returns.
+/* .IP "SEND_ATTR_HASH(const HTABLE *table)"
+/* .IP "SEND_ATTR_NAMEVAL(const NVTABLE *table)"
+/* The content of the table is sent as a sequence of string-valued
+/* attributes with names equal to the table lookup keys.
+/* .IP ATTR_TYPE_END
+/* This terminates the attribute list.
+/* .RE
+/* DIAGNOSTICS
+/* The result value is 0 in case of success, VSTREAM_EOF in case
+/* of trouble.
+/*
+/* Panic: interface violation. All system call errors are fatal.
+/* SEE ALSO
+/* attr_scan_plain(3) recover attributes from byte stream
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdarg.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <htable.h>
+#include <base64_code.h>
+#include <vstring.h>
+#include <attr.h>
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* attr_vprint_plain - send attribute list to stream */
+
+int attr_vprint_plain(VSTREAM *fp, int flags, va_list ap)
+{
+ const char *myname = "attr_print_plain";
+ int attr_type;
+ char *attr_name;
+ unsigned int_val;
+ unsigned long long_val;
+ char *str_val;
+ HTABLE_INFO **ht_info_list;
+ HTABLE_INFO **ht;
+ static VSTRING *base64_buf;
+ ssize_t len_val;
+ ATTR_PRINT_CUSTOM_FN print_fn;
+ void *print_arg;
+
+ /*
+ * Sanity check.
+ */
+ if (flags & ~ATTR_FLAG_ALL)
+ msg_panic("%s: bad flags: 0x%x", myname, flags);
+
+ /*
+ * Iterate over all (type, name, value) triples, and produce output on
+ * the fly.
+ */
+ while ((attr_type = va_arg(ap, int)) != ATTR_TYPE_END) {
+ switch (attr_type) {
+ case ATTR_TYPE_INT:
+ attr_name = va_arg(ap, char *);
+ int_val = va_arg(ap, int);
+ vstream_fprintf(fp, "%s=%u\n", attr_name, (unsigned) int_val);
+ if (msg_verbose)
+ msg_info("send attr %s = %u", attr_name, (unsigned) int_val);
+ break;
+ case ATTR_TYPE_LONG:
+ attr_name = va_arg(ap, char *);
+ long_val = va_arg(ap, long);
+ vstream_fprintf(fp, "%s=%lu\n", attr_name, long_val);
+ if (msg_verbose)
+ msg_info("send attr %s = %lu", attr_name, long_val);
+ break;
+ case ATTR_TYPE_STR:
+ attr_name = va_arg(ap, char *);
+ str_val = va_arg(ap, char *);
+ vstream_fprintf(fp, "%s=%s\n", attr_name, str_val);
+ if (msg_verbose)
+ msg_info("send attr %s = %s", attr_name, str_val);
+ break;
+ case ATTR_TYPE_DATA:
+ attr_name = va_arg(ap, char *);
+ len_val = va_arg(ap, ssize_t);
+ str_val = va_arg(ap, char *);
+ if (base64_buf == 0)
+ base64_buf = vstring_alloc(10);
+ base64_encode(base64_buf, str_val, len_val);
+ vstream_fprintf(fp, "%s=%s\n", attr_name, STR(base64_buf));
+ if (msg_verbose)
+ msg_info("send attr %s = [data %ld bytes]",
+ attr_name, (long) len_val);
+ break;
+ case ATTR_TYPE_FUNC:
+ print_fn = va_arg(ap, ATTR_PRINT_CUSTOM_FN);
+ print_arg = va_arg(ap, void *);
+ print_fn(attr_print_plain, fp, flags | ATTR_FLAG_MORE, print_arg);
+ break;
+ case ATTR_TYPE_HASH:
+ vstream_fwrite(fp, ATTR_NAME_OPEN, sizeof(ATTR_NAME_OPEN));
+ VSTREAM_PUTC('\n', fp);
+ ht_info_list = htable_list(va_arg(ap, HTABLE *));
+ for (ht = ht_info_list; *ht; ht++) {
+ vstream_fprintf(fp, "%s=%s\n", ht[0]->key, (char *) ht[0]->value);
+ if (msg_verbose)
+ msg_info("send attr name %s value %s",
+ ht[0]->key, (char *) ht[0]->value);
+ }
+ myfree((void *) ht_info_list);
+ vstream_fwrite(fp, ATTR_NAME_CLOSE, sizeof(ATTR_NAME_CLOSE));
+ VSTREAM_PUTC('\n', fp);
+ break;
+ default:
+ msg_panic("%s: unknown type code: %d", myname, attr_type);
+ }
+ }
+ if ((flags & ATTR_FLAG_MORE) == 0)
+ VSTREAM_PUTC('\n', fp);
+ return (vstream_ferror(fp));
+}
+
+int attr_print_plain(VSTREAM *fp, int flags,...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, flags);
+ ret = attr_vprint_plain(fp, flags, ap);
+ va_end(ap);
+ return (ret);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof of concept test program. Mirror image of the attr_scan_plain test
+ * program.
+ */
+#include <msg_vstream.h>
+
+int main(int unused_argc, char **argv)
+{
+ HTABLE *table = htable_create(1);
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ msg_verbose = 1;
+ htable_enter(table, "foo-name", mystrdup("foo-value"));
+ htable_enter(table, "bar-name", mystrdup("bar-value"));
+ attr_print_plain(VSTREAM_OUT, ATTR_FLAG_NONE,
+ SEND_ATTR_STR("protocol", "test"),
+ SEND_ATTR_INT(ATTR_NAME_INT, 4711),
+ SEND_ATTR_LONG(ATTR_NAME_LONG, 1234L),
+ SEND_ATTR_STR(ATTR_NAME_STR, "whoopee"),
+ SEND_ATTR_DATA(ATTR_NAME_DATA, strlen("whoopee"), "whoopee"),
+ SEND_ATTR_HASH(table),
+ SEND_ATTR_LONG(ATTR_NAME_LONG, 4321L),
+ ATTR_TYPE_END);
+ attr_print_plain(VSTREAM_OUT, ATTR_FLAG_NONE,
+ SEND_ATTR_STR("protocol", "test"),
+ SEND_ATTR_INT(ATTR_NAME_INT, 4711),
+ SEND_ATTR_LONG(ATTR_NAME_LONG, 1234L),
+ SEND_ATTR_STR(ATTR_NAME_STR, "whoopee"),
+ SEND_ATTR_DATA(ATTR_NAME_DATA, strlen("whoopee"), "whoopee"),
+ ATTR_TYPE_END);
+ attr_print_plain(VSTREAM_OUT, ATTR_FLAG_NONE,
+ SEND_ATTR_STR("protocol", "not-test"),
+ ATTR_TYPE_END);
+ if (vstream_fflush(VSTREAM_OUT) != 0)
+ msg_fatal("write error: %m");
+
+ htable_free(table, myfree);
+ return (0);
+}
+
+#endif
diff --git a/src/util/attr_scan.ref b/src/util/attr_scan.ref
new file mode 100644
index 0000000..cd06a27
--- /dev/null
+++ b/src/util/attr_scan.ref
@@ -0,0 +1,36 @@
+./attr_print: send attr number = 4711
+./attr_print: send attr string = whoopee
+./attr_print: send attr name foo-name value foo-value
+./attr_print: send attr name bar-name value bar-value
+./attr_print: send attr number = 4711
+./attr_print: send attr string = whoopee
+./attr_scan: unknown_stream: wanted attribute: number
+./attr_scan: input attribute name: number
+./attr_scan: input attribute value: 4711
+./attr_scan: unknown_stream: wanted attribute: string
+./attr_scan: input attribute name: string
+./attr_scan: input attribute value: whoopee
+./attr_scan: unknown_stream: wanted attribute: (any attribute name or list terminator)
+./attr_scan: input attribute name: foo-name
+./attr_scan: input attribute value: foo-value
+./attr_scan: unknown_stream: wanted attribute: (any attribute name or list terminator)
+./attr_scan: input attribute name: bar-name
+./attr_scan: input attribute value: bar-value
+./attr_scan: unknown_stream: wanted attribute: (any attribute name or list terminator)
+./attr_scan: input attribute name: (end)
+./attr_scan: unknown_stream: wanted attribute: number
+./attr_scan: input attribute name: number
+./attr_scan: input attribute value: 4711
+./attr_scan: unknown_stream: wanted attribute: string
+./attr_scan: input attribute name: string
+./attr_scan: input attribute value: whoopee
+./attr_scan: unknown_stream: wanted attribute: (list terminator)
+./attr_scan: input attribute name: (end)
+number 4711
+string whoopee
+(hash) foo-name foo-value
+(hash) bar-name bar-value
+number 4711
+string whoopee
+(hash) foo-name foo-value
+(hash) bar-name bar-value
diff --git a/src/util/attr_scan0.c b/src/util/attr_scan0.c
new file mode 100644
index 0000000..13aa125
--- /dev/null
+++ b/src/util/attr_scan0.c
@@ -0,0 +1,596 @@
+/*++
+/* NAME
+/* attr_scan0 3
+/* SUMMARY
+/* recover attributes from byte stream
+/* SYNOPSIS
+/* #include <attr.h>
+/*
+/* int attr_scan0(fp, flags, type, name, ..., ATTR_TYPE_END)
+/* VSTREAM *fp;
+/* int flags;
+/* int type;
+/* char *name;
+/*
+/* int attr_vscan0(fp, flags, ap)
+/* VSTREAM *fp;
+/* int flags;
+/* va_list ap;
+/*
+/* int attr_scan_more0(fp)
+/* VSTREAM *fp;
+/* DESCRIPTION
+/* attr_scan0() takes zero or more (name, value) request attributes
+/* and recovers the attribute values from the byte stream that was
+/* possibly generated by attr_print0().
+/*
+/* attr_vscan0() provides an alternative interface that is convenient
+/* for calling from within a variadic function.
+/*
+/* attr_scan_more0() returns 0 when a terminator is found (and
+/* consumes that terminator), returns 1 when more input is
+/* expected (without consuming input), and returns -1 otherwise
+/* (error).
+/*
+/* The input stream is formatted as follows, where (item)* stands
+/* for zero or more instances of the specified item, and where
+/* (item1 | item2) stands for choice:
+/*
+/* .in +5
+/* attr-list :== (simple-attr | multi-attr)* null
+/* .br
+/* multi-attr :== "{" null simple-attr* "}" null
+/* .br
+/* simple-attr :== attr-name null attr-value null
+/* .br
+/* attr-name :== any string not containing null
+/* .br
+/* attr-value :== any string not containing null
+/* .br
+/* null :== the ASCII null character
+/* .in
+/*
+/* All attribute names and attribute values are sent as null terminated
+/* strings. Each string must be no longer than 4*var_line_limit
+/* characters including the terminator.
+/* These formatting rules favor implementations in C.
+/*
+/* Normally, attributes must be received in the sequence as specified with
+/* the attr_scan0() argument list. The input stream may contain additional
+/* attributes at any point in the input stream, including additional
+/* instances of requested attributes.
+/*
+/* Additional input attributes or input attribute instances are silently
+/* skipped over, unless the ATTR_FLAG_EXTRA processing flag is specified
+/* (see below). This allows for some flexibility in the evolution of
+/* protocols while still providing the option of being strict where
+/* this is desirable.
+/*
+/* Arguments:
+/* .IP fp
+/* Stream to recover the input attributes from.
+/* .IP flags
+/* The bit-wise OR of zero or more of the following.
+/* .RS
+/* .IP ATTR_FLAG_MISSING
+/* Log a warning when the input attribute list terminates before all
+/* requested attributes are recovered. It is always an error when the
+/* input stream ends without the newline attribute list terminator.
+/* .IP ATTR_FLAG_EXTRA
+/* Log a warning and stop attribute recovery when the input stream
+/* contains an attribute that was not requested. This includes the
+/* case of additional instances of a requested attribute.
+/* .IP ATTR_FLAG_MORE
+/* After recovering the requested attributes, leave the input stream
+/* in a state that is usable for more attr_scan0() operations from the
+/* same input attribute list.
+/* By default, attr_scan0() skips forward past the input attribute list
+/* terminator.
+/* .IP ATTR_FLAG_PRINTABLE
+/* Santize received string values with printable(_, '?').
+/* .IP ATTR_FLAG_STRICT
+/* For convenience, this value combines both ATTR_FLAG_MISSING and
+/* ATTR_FLAG_EXTRA.
+/* .IP ATTR_FLAG_NONE
+/* For convenience, this value requests none of the above.
+/* .RE
+/* .IP List of attributes followed by terminator:
+/* .RS
+/* .IP "RECV_ATTR_INT(const char *name, int *ptr)"
+/* This argument is followed by an attribute name and an integer pointer.
+/* .IP "RECV_ATTR_LONG(const char *name, long *ptr)"
+/* This argument is followed by an attribute name and a long pointer.
+/* .IP "RECV_ATTR_STR(const char *name, VSTRING *vp)"
+/* This argument is followed by an attribute name and a VSTRING pointer.
+/* .IP "RECV_ATTR_STREQ(const char *name, const char *value)"
+/* The name and value must match what the client sends.
+/* This attribute does not increment the result value.
+/* .IP "RECV_ATTR_DATA(const char *name, VSTRING *vp)"
+/* This argument is followed by an attribute name and a VSTRING pointer.
+/* .IP "RECV_ATTR_FUNC(ATTR_SCAN_CUSTOM_FN, void *data)"
+/* This argument is followed by a function pointer and a generic data
+/* pointer. The caller-specified function returns < 0 in case of
+/* error.
+/* .IP "RECV_ATTR_HASH(HTABLE *table)"
+/* .IP "RECV_ATTR_NAMEVAL(NVTABLE *table)"
+/* Receive a sequence of attribute names and string values.
+/* There can be no more than 1024 attributes in a hash table.
+/* .sp
+/* The attribute string values are stored in the hash table under
+/* keys equal to the attribute name (obtained from the input stream).
+/* Values from the input stream are added to the hash table. Existing
+/* hash table entries are not replaced.
+/* .sp
+/* Note: the SEND_ATTR_HASH or SEND_ATTR_NAMEVAL requests
+/* format their payload as a multi-attr sequence (see syntax
+/* above). When the receiver's input does not start with a
+/* multi-attr delimiter (i.e. the sender did not request
+/* SEND_ATTR_HASH or SEND_ATTR_NAMEVAL), the receiver will
+/* store all attribute names and values up to the attribute
+/* list terminator. In terms of code, this means that the
+/* RECV_ATTR_HASH or RECV_ATTR_NAMEVAL request must be followed
+/* by ATTR_TYPE_END.
+/* .IP ATTR_TYPE_END
+/* This argument terminates the requested attribute list.
+/* .RE
+/* BUGS
+/* RECV_ATTR_HASH (RECV_ATTR_NAMEVAL) accepts attributes with arbitrary
+/* names from possibly untrusted sources.
+/* This is unsafe, unless the resulting table is queried only with
+/* known to be good attribute names.
+/* DIAGNOSTICS
+/* attr_scan0() and attr_vscan0() return -1 when malformed input is
+/* detected (string too long, incomplete line, missing end marker).
+/* Otherwise, the result value is the number of attributes that were
+/* successfully recovered from the input stream (a hash table counts
+/* as the number of entries stored into the table).
+/*
+/* Panic: interface violation. All system call errors are fatal.
+/* SEE ALSO
+/* attr_print0(3) send attributes over byte stream.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdio.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <vstring_vstream.h>
+#include <htable.h>
+#include <base64_code.h>
+#include <stringops.h>
+#include <attr.h>
+
+/* Application specific. */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* attr_scan0_string - pull a string from the input stream */
+
+static int attr_scan0_string(VSTREAM *fp, VSTRING *plain_buf, const char *context)
+{
+ int ch;
+
+ if ((ch = vstring_get_null(plain_buf, fp)) == VSTREAM_EOF) {
+ msg_warn("%s on %s while reading %s",
+ vstream_ftimeout(fp) ? "timeout" : "premature end-of-input",
+ VSTREAM_PATH(fp), context);
+ return (-1);
+ }
+ if (ch != 0) {
+ msg_warn("unexpected end-of-input from %s while reading %s",
+ VSTREAM_PATH(fp), context);
+ return (-1);
+ }
+ if (msg_verbose)
+ msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)");
+ return (ch);
+}
+
+/* attr_scan0_data - pull a data blob from the input stream */
+
+static int attr_scan0_data(VSTREAM *fp, VSTRING *str_buf,
+ const char *context)
+{
+ static VSTRING *base64_buf = 0;
+ int ch;
+
+ if (base64_buf == 0)
+ base64_buf = vstring_alloc(10);
+ if ((ch = attr_scan0_string(fp, base64_buf, context)) < 0)
+ return (-1);
+ if (base64_decode(str_buf, STR(base64_buf), LEN(base64_buf)) == 0) {
+ msg_warn("malformed base64 data from %s while reading %s: %.100s",
+ VSTREAM_PATH(fp), context, STR(base64_buf));
+ return (-1);
+ }
+ return (ch);
+}
+
+/* attr_scan0_number - pull a number from the input stream */
+
+static int attr_scan0_number(VSTREAM *fp, unsigned *ptr, VSTRING *str_buf,
+ const char *context)
+{
+ char junk = 0;
+ int ch;
+
+ if ((ch = attr_scan0_string(fp, str_buf, context)) < 0)
+ return (-1);
+ if (sscanf(STR(str_buf), "%u%c", ptr, &junk) != 1 || junk != 0) {
+ msg_warn("malformed numerical data from %s while reading %s: %.100s",
+ VSTREAM_PATH(fp), context, STR(str_buf));
+ return (-1);
+ }
+ return (ch);
+}
+
+/* attr_scan0_long_number - pull a number from the input stream */
+
+static int attr_scan0_long_number(VSTREAM *fp, unsigned long *ptr,
+ VSTRING *str_buf,
+ const char *context)
+{
+ char junk = 0;
+ int ch;
+
+ if ((ch = attr_scan0_string(fp, str_buf, context)) < 0)
+ return (-1);
+ if (sscanf(STR(str_buf), "%lu%c", ptr, &junk) != 1 || junk != 0) {
+ msg_warn("malformed numerical data from %s while reading %s: %.100s",
+ VSTREAM_PATH(fp), context, STR(str_buf));
+ return (-1);
+ }
+ return (ch);
+}
+
+/* attr_vscan0 - receive attribute list from stream */
+
+int attr_vscan0(VSTREAM *fp, int flags, va_list ap)
+{
+ const char *myname = "attr_scan0";
+ static VSTRING *str_buf = 0;
+ static VSTRING *name_buf = 0;
+ int wanted_type = -1;
+ char *wanted_name;
+ unsigned int *number;
+ unsigned long *long_number;
+ VSTRING *string;
+ HTABLE *hash_table;
+ int ch;
+ int conversions;
+ ATTR_SCAN_CUSTOM_FN scan_fn;
+ void *scan_arg;
+ const char *expect_val;
+
+ /*
+ * Sanity check.
+ */
+ if (flags & ~ATTR_FLAG_ALL)
+ msg_panic("%s: bad flags: 0x%x", myname, flags);
+
+ /*
+ * EOF check.
+ */
+ if ((ch = VSTREAM_GETC(fp)) == VSTREAM_EOF)
+ return (0);
+ vstream_ungetc(fp, ch);
+
+ /*
+ * Initialize.
+ */
+ if (str_buf == 0) {
+ str_buf = vstring_alloc(10);
+ name_buf = vstring_alloc(10);
+ }
+
+ /*
+ * Iterate over all (type, name, value) triples.
+ */
+ for (conversions = 0; /* void */ ; conversions++) {
+
+ /*
+ * Determine the next attribute type and attribute name on the
+ * caller's wish list.
+ *
+ * If we're reading into a hash table, we already know that the
+ * attribute value is string-valued, and we get the attribute name
+ * from the input stream instead. This is secure only when the
+ * resulting table is queried with known to be good attribute names.
+ */
+ if (wanted_type != ATTR_TYPE_HASH
+ && wanted_type != ATTR_TYPE_CLOSE) {
+ wanted_type = va_arg(ap, int);
+ if (wanted_type == ATTR_TYPE_END) {
+ if ((flags & ATTR_FLAG_MORE) != 0)
+ return (conversions);
+ wanted_name = "(list terminator)";
+ } else if (wanted_type == ATTR_TYPE_HASH) {
+ wanted_name = "(any attribute name or list terminator)";
+ hash_table = va_arg(ap, HTABLE *);
+ } else if (wanted_type != ATTR_TYPE_FUNC) {
+ wanted_name = va_arg(ap, char *);
+ }
+ }
+
+ /*
+ * Locate the next attribute of interest in the input stream.
+ */
+ while (wanted_type != ATTR_TYPE_FUNC) {
+
+ /*
+ * Get the name of the next attribute. Hitting EOF is always bad.
+ * Hitting the end-of-input early is OK if the caller is prepared
+ * to deal with missing inputs.
+ */
+ if (msg_verbose)
+ msg_info("%s: wanted attribute: %s",
+ VSTREAM_PATH(fp), wanted_name);
+ if ((ch = attr_scan0_string(fp, name_buf,
+ "input attribute name")) == VSTREAM_EOF)
+ return (-1);
+ if (LEN(name_buf) == 0) {
+ if (wanted_type == ATTR_TYPE_END
+ || wanted_type == ATTR_TYPE_HASH)
+ return (conversions);
+ if ((flags & ATTR_FLAG_MISSING) != 0)
+ msg_warn("missing attribute %s in input from %s",
+ wanted_name, VSTREAM_PATH(fp));
+ return (conversions);
+ }
+
+ /*
+ * See if the caller asks for this attribute.
+ */
+ if (wanted_type == ATTR_TYPE_HASH
+ && strcmp(ATTR_NAME_OPEN, STR(name_buf)) == 0) {
+ wanted_type = ATTR_TYPE_CLOSE;
+ wanted_name = "(any attribute name or '}')";
+ /* Advance in the input stream. */
+ continue;
+ } else if (wanted_type == ATTR_TYPE_CLOSE
+ && strcmp(ATTR_NAME_CLOSE, STR(name_buf)) == 0) {
+ /* Advance in the argument list. */
+ wanted_type = -1;
+ break;
+ }
+ if (wanted_type == ATTR_TYPE_HASH
+ || wanted_type == ATTR_TYPE_CLOSE
+ || (wanted_type != ATTR_TYPE_END
+ && strcmp(wanted_name, STR(name_buf)) == 0))
+ break;
+ if ((flags & ATTR_FLAG_EXTRA) != 0) {
+ msg_warn("unexpected attribute %s from %s (expecting: %s)",
+ STR(name_buf), VSTREAM_PATH(fp), wanted_name);
+ return (conversions);
+ }
+
+ /*
+ * Skip over this attribute. The caller does not ask for it.
+ */
+ (void) attr_scan0_string(fp, str_buf, "input attribute value");
+ }
+
+ /*
+ * Do the requested conversion.
+ */
+ switch (wanted_type) {
+ case ATTR_TYPE_INT:
+ number = va_arg(ap, unsigned int *);
+ if ((ch = attr_scan0_number(fp, number, str_buf,
+ "input attribute value")) < 0)
+ return (-1);
+ break;
+ case ATTR_TYPE_LONG:
+ long_number = va_arg(ap, unsigned long *);
+ if ((ch = attr_scan0_long_number(fp, long_number, str_buf,
+ "input attribute value")) < 0)
+ return (-1);
+ break;
+ case ATTR_TYPE_STR:
+ string = va_arg(ap, VSTRING *);
+ if ((ch = attr_scan0_string(fp, string,
+ "input attribute value")) < 0)
+ return (-1);
+ if (flags & ATTR_FLAG_PRINTABLE)
+ (void) printable(STR(string), '?');
+ break;
+ case ATTR_TYPE_DATA:
+ string = va_arg(ap, VSTRING *);
+ if ((ch = attr_scan0_data(fp, string,
+ "input attribute value")) < 0)
+ return (-1);
+ break;
+ case ATTR_TYPE_FUNC:
+ scan_fn = va_arg(ap, ATTR_SCAN_CUSTOM_FN);
+ scan_arg = va_arg(ap, void *);
+ if (scan_fn(attr_scan0, fp, flags | ATTR_FLAG_MORE, scan_arg) < 0)
+ return (-1);
+ break;
+ case ATTR_TYPE_STREQ:
+ expect_val = va_arg(ap, const char *);
+ if ((ch = attr_scan0_string(fp, str_buf,
+ "input attribute value")) < 0)
+ return (-1);
+ if (strcmp(expect_val, STR(str_buf)) != 0) {
+ msg_warn("unexpected %s %s from %s (expected: %s)",
+ STR(name_buf), STR(str_buf), VSTREAM_PATH(fp),
+ expect_val);
+ return (-1);
+ }
+ conversions -= 1;
+ break;
+ case ATTR_TYPE_HASH:
+ case ATTR_TYPE_CLOSE:
+ if ((ch = attr_scan0_string(fp, str_buf,
+ "input attribute value")) < 0)
+ return (-1);
+ if (flags & ATTR_FLAG_PRINTABLE) {
+ (void) printable(STR(name_buf), '?');
+ (void) printable(STR(str_buf), '?');
+ }
+ if (htable_locate(hash_table, STR(name_buf)) != 0) {
+ if ((flags & ATTR_FLAG_EXTRA) != 0) {
+ msg_warn("duplicate attribute %s in input from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (conversions);
+ }
+ } else if (hash_table->used >= ATTR_HASH_LIMIT) {
+ msg_warn("attribute count exceeds limit %d in input from %s",
+ ATTR_HASH_LIMIT, VSTREAM_PATH(fp));
+ return (conversions);
+ } else {
+ htable_enter(hash_table, STR(name_buf),
+ mystrdup(STR(str_buf)));
+ }
+ break;
+ case -1:
+ conversions -= 1;
+ break;
+ default:
+ msg_panic("%s: unknown type code: %d", myname, wanted_type);
+ }
+ }
+}
+
+/* attr_scan0 - read attribute list from stream */
+
+int attr_scan0(VSTREAM *fp, int flags,...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, flags);
+ ret = attr_vscan0(fp, flags, ap);
+ va_end(ap);
+ return (ret);
+}
+
+/* attr_scan_more0 - look ahead for more */
+
+int attr_scan_more0(VSTREAM *fp)
+{
+ int ch;
+
+ switch (ch = VSTREAM_GETC(fp)) {
+ case 0:
+ if (msg_verbose)
+ msg_info("%s: terminator (consumed)", VSTREAM_PATH(fp));
+ return (0);
+ case VSTREAM_EOF:
+ if (msg_verbose)
+ msg_info("%s: EOF", VSTREAM_PATH(fp));
+ return (-1);
+ default:
+ if (msg_verbose)
+ msg_info("%s: non-terminator '%c' (lookahead)",
+ VSTREAM_PATH(fp), ch);
+ (void) vstream_ungetc(fp, ch);
+ return (1);
+ }
+}
+
+#ifdef TEST
+
+ /*
+ * Proof of concept test program. Mirror image of the attr_scan0 test
+ * program.
+ */
+#include <msg_vstream.h>
+
+int var_line_limit = 2048;
+
+int main(int unused_argc, char **used_argv)
+{
+ VSTRING *data_val = vstring_alloc(1);
+ VSTRING *str_val = vstring_alloc(1);
+ HTABLE *table = htable_create(1);
+ HTABLE_INFO **ht_info_list;
+ HTABLE_INFO **ht;
+ int int_val;
+ long long_val;
+ long long_val2;
+ int ret;
+
+ msg_verbose = 1;
+ msg_vstream_init(used_argv[0], VSTREAM_ERR);
+ if ((ret = attr_scan0(VSTREAM_IN,
+ ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ("protocol", "test"),
+ RECV_ATTR_INT(ATTR_NAME_INT, &int_val),
+ RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val),
+ RECV_ATTR_STR(ATTR_NAME_STR, str_val),
+ RECV_ATTR_DATA(ATTR_NAME_DATA, data_val),
+ RECV_ATTR_HASH(table),
+ RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val2),
+ ATTR_TYPE_END)) > 4) {
+ vstream_printf("%s %d\n", ATTR_NAME_INT, int_val);
+ vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
+ vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val));
+ vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(str_val));
+ ht_info_list = htable_list(table);
+ for (ht = ht_info_list; *ht; ht++)
+ vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value);
+ myfree((void *) ht_info_list);
+ vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val2);
+ } else {
+ vstream_printf("return: %d\n", ret);
+ }
+ if ((ret = attr_scan0(VSTREAM_IN,
+ ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ("protocol", "test"),
+ RECV_ATTR_INT(ATTR_NAME_INT, &int_val),
+ RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val),
+ RECV_ATTR_STR(ATTR_NAME_STR, str_val),
+ RECV_ATTR_DATA(ATTR_NAME_DATA, data_val),
+ ATTR_TYPE_END)) == 4) {
+ vstream_printf("%s %d\n", ATTR_NAME_INT, int_val);
+ vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
+ vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val));
+ vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val));
+ ht_info_list = htable_list(table);
+ for (ht = ht_info_list; *ht; ht++)
+ vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value);
+ myfree((void *) ht_info_list);
+ } else {
+ vstream_printf("return: %d\n", ret);
+ }
+ if ((ret = attr_scan0(VSTREAM_IN,
+ ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ("protocol", "test"),
+ ATTR_TYPE_END)) != 0)
+ vstream_printf("return: %d\n", ret);
+ if (vstream_fflush(VSTREAM_OUT) != 0)
+ msg_fatal("write error: %m");
+
+ vstring_free(data_val);
+ vstring_free(str_val);
+ htable_free(table, myfree);
+
+ return (0);
+}
+
+#endif
diff --git a/src/util/attr_scan0.ref b/src/util/attr_scan0.ref
new file mode 100644
index 0000000..9055d79
--- /dev/null
+++ b/src/util/attr_scan0.ref
@@ -0,0 +1,79 @@
+./attr_print0: send attr protocol = test
+./attr_print0: send attr number = 4711
+./attr_print0: send attr long_number = 1234
+./attr_print0: send attr string = whoopee
+./attr_print0: send attr data = [data 7 bytes]
+./attr_print0: send attr name bar-name value bar-value
+./attr_print0: send attr name foo-name value foo-value
+./attr_print0: send attr long_number = 4321
+./attr_print0: send attr protocol = test
+./attr_print0: send attr number = 4711
+./attr_print0: send attr long_number = 1234
+./attr_print0: send attr string = whoopee
+./attr_print0: send attr data = [data 7 bytes]
+./attr_print0: send attr protocol = not-test
+./attr_scan0: unknown_stream: wanted attribute: protocol
+./attr_scan0: input attribute name: protocol
+./attr_scan0: input attribute value: test
+./attr_scan0: unknown_stream: wanted attribute: number
+./attr_scan0: input attribute name: number
+./attr_scan0: input attribute value: 4711
+./attr_scan0: unknown_stream: wanted attribute: long_number
+./attr_scan0: input attribute name: long_number
+./attr_scan0: input attribute value: 1234
+./attr_scan0: unknown_stream: wanted attribute: string
+./attr_scan0: input attribute name: string
+./attr_scan0: input attribute value: whoopee
+./attr_scan0: unknown_stream: wanted attribute: data
+./attr_scan0: input attribute name: data
+./attr_scan0: input attribute value: d2hvb3BlZQ==
+./attr_scan0: unknown_stream: wanted attribute: (any attribute name or list terminator)
+./attr_scan0: input attribute name: {
+./attr_scan0: unknown_stream: wanted attribute: (any attribute name or '}')
+./attr_scan0: input attribute name: bar-name
+./attr_scan0: input attribute value: bar-value
+./attr_scan0: unknown_stream: wanted attribute: (any attribute name or '}')
+./attr_scan0: input attribute name: foo-name
+./attr_scan0: input attribute value: foo-value
+./attr_scan0: unknown_stream: wanted attribute: (any attribute name or '}')
+./attr_scan0: input attribute name: }
+./attr_scan0: unknown_stream: wanted attribute: long_number
+./attr_scan0: input attribute name: long_number
+./attr_scan0: input attribute value: 4321
+./attr_scan0: unknown_stream: wanted attribute: (list terminator)
+./attr_scan0: input attribute name: (end)
+./attr_scan0: unknown_stream: wanted attribute: protocol
+./attr_scan0: input attribute name: protocol
+./attr_scan0: input attribute value: test
+./attr_scan0: unknown_stream: wanted attribute: number
+./attr_scan0: input attribute name: number
+./attr_scan0: input attribute value: 4711
+./attr_scan0: unknown_stream: wanted attribute: long_number
+./attr_scan0: input attribute name: long_number
+./attr_scan0: input attribute value: 1234
+./attr_scan0: unknown_stream: wanted attribute: string
+./attr_scan0: input attribute name: string
+./attr_scan0: input attribute value: whoopee
+./attr_scan0: unknown_stream: wanted attribute: data
+./attr_scan0: input attribute name: data
+./attr_scan0: input attribute value: d2hvb3BlZQ==
+./attr_scan0: unknown_stream: wanted attribute: (list terminator)
+./attr_scan0: input attribute name: (end)
+./attr_scan0: unknown_stream: wanted attribute: protocol
+./attr_scan0: input attribute name: protocol
+./attr_scan0: input attribute value: not-test
+./attr_scan0: warning: unexpected protocol not-test from unknown_stream (expected: test)
+number 4711
+long_number 1234
+string whoopee
+data whoopee
+(hash) bar-name bar-value
+(hash) foo-name foo-value
+long_number 4321
+number 4711
+long_number 1234
+string whoopee
+data whoopee
+(hash) bar-name bar-value
+(hash) foo-name foo-value
+return: -1
diff --git a/src/util/attr_scan64.c b/src/util/attr_scan64.c
new file mode 100644
index 0000000..0d9b114
--- /dev/null
+++ b/src/util/attr_scan64.c
@@ -0,0 +1,665 @@
+/*++
+/* NAME
+/* attr_scan64 3
+/* SUMMARY
+/* recover attributes from byte stream
+/* SYNOPSIS
+/* #include <attr.h>
+/*
+/* int attr_scan64(fp, flags, type, name, ..., ATTR_TYPE_END)
+/* VSTREAM *fp;
+/* int flags;
+/* int type;
+/* char *name;
+/*
+/* int attr_vscan64(fp, flags, ap)
+/* VSTREAM *fp;
+/* int flags;
+/* va_list ap;
+/*
+/* int attr_scan_more64(fp)
+/* VSTREAM *fp;
+/* DESCRIPTION
+/* attr_scan64() takes zero or more (name, value) request attributes
+/* and recovers the attribute values from the byte stream that was
+/* possibly generated by attr_print64().
+/*
+/* attr_vscan64() provides an alternative interface that is convenient
+/* for calling from within a variadic function.
+/*
+/* attr_scan_more64() returns 0 when a terminator is found
+/* (and consumes that terminator), returns 1 when more input
+/* is expected (without consuming input), and returns -1
+/* otherwise (error).
+/*
+/* The input stream is formatted as follows, where (item)* stands
+/* for zero or more instances of the specified item, and where
+/* (item1 | item2) stands for choice:
+/*
+/* .in +5
+/* attr-list :== (simple-attr | multi-attr)* newline
+/* .br
+/* multi-attr :== "{" newline simple-attr* "}" newline
+/* .br
+/* simple-attr :== attr-name colon attr-value newline
+/* .br
+/* attr-name :== any base64 encoded string
+/* .br
+/* attr-value :== any base64 encoded string
+/* .br
+/* colon :== the ASCII colon character
+/* .br
+/* newline :== the ASCII newline character
+/* .in
+/*
+/* All attribute names and attribute values are sent as base64-encoded
+/* strings. Each base64 encoding must be no longer than 4*var_line_limit
+/* characters. The formatting rules aim to make implementations in PERL
+/* and other languages easy.
+/*
+/* Normally, attributes must be received in the sequence as specified with
+/* the attr_scan64() argument list. The input stream may contain additional
+/* attributes at any point in the input stream, including additional
+/* instances of requested attributes.
+/*
+/* Additional input attributes or input attribute instances are silently
+/* skipped over, unless the ATTR_FLAG_EXTRA processing flag is specified
+/* (see below). This allows for some flexibility in the evolution of
+/* protocols while still providing the option of being strict where
+/* this is desirable.
+/*
+/* Arguments:
+/* .IP fp
+/* Stream to recover the input attributes from.
+/* .IP flags
+/* The bit-wise OR of zero or more of the following.
+/* .RS
+/* .IP ATTR_FLAG_MISSING
+/* Log a warning when the input attribute list terminates before all
+/* requested attributes are recovered. It is always an error when the
+/* input stream ends without the newline attribute list terminator.
+/* .IP ATTR_FLAG_EXTRA
+/* Log a warning and stop attribute recovery when the input stream
+/* contains an attribute that was not requested. This includes the
+/* case of additional instances of a requested attribute.
+/* .IP ATTR_FLAG_MORE
+/* After recovering the requested attributes, leave the input stream
+/* in a state that is usable for more attr_scan64() operations from the
+/* same input attribute list.
+/* By default, attr_scan64() skips forward past the input attribute list
+/* terminator.
+/* .IP ATTR_FLAG_PRINTABLE
+/* Santize received string values with printable(_, '?').
+/* .IP ATTR_FLAG_STRICT
+/* For convenience, this value combines both ATTR_FLAG_MISSING and
+/* ATTR_FLAG_EXTRA.
+/* .IP ATTR_FLAG_NONE
+/* For convenience, this value requests none of the above.
+/* .RE
+/* .IP List of attributes followed by terminator:
+/* .RS
+/* .IP "RECV_ATTR_INT(const char *name, int *ptr)"
+/* This argument is followed by an attribute name and an integer pointer.
+/* .IP "RECV_ATTR_LONG(const char *name, long *ptr)"
+/* This argument is followed by an attribute name and a long pointer.
+/* .IP "RECV_ATTR_STR(const char *name, VSTRING *vp)"
+/* This argument is followed by an attribute name and a VSTRING pointer.
+/* .IP "RECV_ATTR_STREQ(const char *name, const char *value)"
+/* The name and value must match what the client sends.
+/* This attribute does not increment the result value.
+/* .IP "RECV_ATTR_DATA(const char *name, VSTRING *vp)"
+/* This argument is followed by an attribute name and a VSTRING pointer.
+/* .IP "RECV_ATTR_FUNC(ATTR_SCAN_CUSTOM_FN, void *data)"
+/* This argument is followed by a function pointer and a generic data
+/* pointer. The caller-specified function returns < 0 in case of
+/* error.
+/* .IP "RECV_ATTR_HASH(HTABLE *table)"
+/* .IP "RECV_ATTR_NAMEVAL(NVTABLE *table)"
+/* Receive a sequence of attribute names and string values.
+/* There can be no more than 1024 attributes in a hash table.
+/* .sp
+/* The attribute string values are stored in the hash table under
+/* keys equal to the attribute name (obtained from the input stream).
+/* Values from the input stream are added to the hash table. Existing
+/* hash table entries are not replaced.
+/* .sp
+/* Note: the SEND_ATTR_HASH or SEND_ATTR_NAMEVAL requests
+/* format their payload as a multi-attr sequence (see syntax
+/* above). When the receiver's input does not start with a
+/* multi-attr delimiter (i.e. the sender did not request
+/* SEND_ATTR_HASH or SEND_ATTR_NAMEVAL), the receiver will
+/* store all attribute names and values up to the attribute
+/* list terminator. In terms of code, this means that the
+/* RECV_ATTR_HASH or RECV_ATTR_NAMEVAL request must be followed
+/* by ATTR_TYPE_END.
+/* .IP ATTR_TYPE_END
+/* This argument terminates the requested attribute list.
+/* .RE
+/* BUGS
+/* RECV_ATTR_HASH (RECV_ATTR_NAMEVAL) accepts attributes with arbitrary
+/* names from possibly untrusted sources.
+/* This is unsafe, unless the resulting table is queried only with
+/* known to be good attribute names.
+/* DIAGNOSTICS
+/* attr_scan64() and attr_vscan64() return -1 when malformed input is
+/* detected (string too long, incomplete line, missing end marker).
+/* Otherwise, the result value is the number of attributes that were
+/* successfully recovered from the input stream (a hash table counts
+/* as the number of entries stored into the table).
+/*
+/* Panic: interface violation. All system call errors are fatal.
+/* SEE ALSO
+/* attr_print64(3) send attributes over byte stream.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdio.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <htable.h>
+#include <base64_code.h>
+#include <stringops.h>
+#include <attr.h>
+
+/* Application specific. */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* attr_scan64_string - pull a string from the input stream */
+
+static int attr_scan64_string(VSTREAM *fp, VSTRING *plain_buf, const char *context)
+{
+ static VSTRING *base64_buf = 0;
+
+#if 0
+ extern int var_line_limit; /* XXX */
+ int limit = var_line_limit * 4;
+
+#endif
+ int ch;
+
+ if (base64_buf == 0)
+ base64_buf = vstring_alloc(10);
+
+ VSTRING_RESET(base64_buf);
+ while ((ch = VSTREAM_GETC(fp)) != ':' && ch != '\n') {
+ if (ch == VSTREAM_EOF) {
+ msg_warn("%s on %s while reading %s",
+ vstream_ftimeout(fp) ? "timeout" : "premature end-of-input",
+ VSTREAM_PATH(fp), context);
+ return (-1);
+ }
+ VSTRING_ADDCH(base64_buf, ch);
+#if 0
+ if (LEN(base64_buf) > limit) {
+ msg_warn("string length > %d characters from %s while reading %s",
+ limit, VSTREAM_PATH(fp), context);
+ return (-1);
+ }
+#endif
+ }
+ VSTRING_TERMINATE(base64_buf);
+ if (base64_decode(plain_buf, STR(base64_buf), LEN(base64_buf)) == 0) {
+ msg_warn("malformed base64 data from %s: %.100s",
+ VSTREAM_PATH(fp), STR(base64_buf));
+ return (-1);
+ }
+ if (msg_verbose)
+ msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)");
+ return (ch);
+}
+
+/* attr_scan64_number - pull a number from the input stream */
+
+static int attr_scan64_number(VSTREAM *fp, unsigned *ptr, VSTRING *str_buf,
+ const char *context)
+{
+ char junk = 0;
+ int ch;
+
+ if ((ch = attr_scan64_string(fp, str_buf, context)) < 0)
+ return (-1);
+ if (sscanf(STR(str_buf), "%u%c", ptr, &junk) != 1 || junk != 0) {
+ msg_warn("malformed numerical data from %s while reading %s: %.100s",
+ VSTREAM_PATH(fp), context, STR(str_buf));
+ return (-1);
+ }
+ return (ch);
+}
+
+/* attr_scan64_long_number - pull a number from the input stream */
+
+static int attr_scan64_long_number(VSTREAM *fp, unsigned long *ptr,
+ VSTRING *str_buf,
+ const char *context)
+{
+ char junk = 0;
+ int ch;
+
+ if ((ch = attr_scan64_string(fp, str_buf, context)) < 0)
+ return (-1);
+ if (sscanf(STR(str_buf), "%lu%c", ptr, &junk) != 1 || junk != 0) {
+ msg_warn("malformed numerical data from %s while reading %s: %.100s",
+ VSTREAM_PATH(fp), context, STR(str_buf));
+ return (-1);
+ }
+ return (ch);
+}
+
+/* attr_vscan64 - receive attribute list from stream */
+
+int attr_vscan64(VSTREAM *fp, int flags, va_list ap)
+{
+ const char *myname = "attr_scan64";
+ static VSTRING *str_buf = 0;
+ static VSTRING *name_buf = 0;
+ int wanted_type = -1;
+ char *wanted_name;
+ unsigned int *number;
+ unsigned long *long_number;
+ VSTRING *string;
+ HTABLE *hash_table;
+ int ch;
+ int conversions;
+ ATTR_SCAN_CUSTOM_FN scan_fn;
+ void *scan_arg;
+ const char *expect_val;
+
+ /*
+ * Sanity check.
+ */
+ if (flags & ~ATTR_FLAG_ALL)
+ msg_panic("%s: bad flags: 0x%x", myname, flags);
+
+ /*
+ * EOF check.
+ */
+ if ((ch = VSTREAM_GETC(fp)) == VSTREAM_EOF)
+ return (0);
+ vstream_ungetc(fp, ch);
+
+ /*
+ * Initialize.
+ */
+ if (str_buf == 0) {
+ str_buf = vstring_alloc(10);
+ name_buf = vstring_alloc(10);
+ }
+
+ /*
+ * Iterate over all (type, name, value) triples.
+ */
+ for (conversions = 0; /* void */ ; conversions++) {
+
+ /*
+ * Determine the next attribute type and attribute name on the
+ * caller's wish list.
+ *
+ * If we're reading into a hash table, we already know that the
+ * attribute value is string-valued, and we get the attribute name
+ * from the input stream instead. This is secure only when the
+ * resulting table is queried with known to be good attribute names.
+ */
+ if (wanted_type != ATTR_TYPE_HASH
+ && wanted_type != ATTR_TYPE_CLOSE) {
+ wanted_type = va_arg(ap, int);
+ if (wanted_type == ATTR_TYPE_END) {
+ if ((flags & ATTR_FLAG_MORE) != 0)
+ return (conversions);
+ wanted_name = "(list terminator)";
+ } else if (wanted_type == ATTR_TYPE_HASH) {
+ wanted_name = "(any attribute name or list terminator)";
+ hash_table = va_arg(ap, HTABLE *);
+ } else if (wanted_type != ATTR_TYPE_FUNC) {
+ wanted_name = va_arg(ap, char *);
+ }
+ }
+
+ /*
+ * Locate the next attribute of interest in the input stream.
+ */
+ while (wanted_type != ATTR_TYPE_FUNC) {
+
+ /*
+ * Get the name of the next attribute. Hitting EOF is always bad.
+ * Hitting the end-of-input early is OK if the caller is prepared
+ * to deal with missing inputs.
+ */
+ if (msg_verbose)
+ msg_info("%s: wanted attribute: %s",
+ VSTREAM_PATH(fp), wanted_name);
+ if ((ch = attr_scan64_string(fp, name_buf,
+ "input attribute name")) == VSTREAM_EOF)
+ return (-1);
+ if (ch == '\n' && LEN(name_buf) == 0) {
+ if (wanted_type == ATTR_TYPE_END
+ || wanted_type == ATTR_TYPE_HASH)
+ return (conversions);
+ if ((flags & ATTR_FLAG_MISSING) != 0)
+ msg_warn("missing attribute %s in input from %s",
+ wanted_name, VSTREAM_PATH(fp));
+ return (conversions);
+ }
+
+ /*
+ * See if the caller asks for this attribute.
+ */
+ if (wanted_type == ATTR_TYPE_HASH
+ && ch == '\n' && strcmp(ATTR_NAME_OPEN, STR(name_buf)) == 0) {
+ wanted_type = ATTR_TYPE_CLOSE;
+ wanted_name = "(any attribute name or '}')";
+ /* Advance in the input stream. */
+ continue;
+ } else if (wanted_type == ATTR_TYPE_CLOSE
+ && ch == '\n' && strcmp(ATTR_NAME_CLOSE, STR(name_buf)) == 0) {
+ /* Advance in the argument list. */
+ wanted_type = -1;
+ break;
+ }
+ if (wanted_type == ATTR_TYPE_HASH
+ || wanted_type == ATTR_TYPE_CLOSE
+ || (wanted_type != ATTR_TYPE_END
+ && strcmp(wanted_name, STR(name_buf)) == 0))
+ break;
+ if ((flags & ATTR_FLAG_EXTRA) != 0) {
+ msg_warn("unexpected attribute %s from %s (expecting: %s)",
+ STR(name_buf), VSTREAM_PATH(fp), wanted_name);
+ return (conversions);
+ }
+
+ /*
+ * Skip over this attribute. The caller does not ask for it.
+ */
+ while (ch != '\n' && (ch = VSTREAM_GETC(fp)) != VSTREAM_EOF)
+ /* void */ ;
+ }
+
+ /*
+ * Do the requested conversion. If the target attribute is a
+ * non-array type, disallow sending a multi-valued attribute, and
+ * disallow sending no value. If the target attribute is an array
+ * type, allow the sender to send a zero-element array (i.e. no value
+ * at all). XXX Need to impose a bound on the number of array
+ * elements.
+ */
+ switch (wanted_type) {
+ case ATTR_TYPE_INT:
+ if (ch != ':') {
+ msg_warn("missing value for number attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ number = va_arg(ap, unsigned int *);
+ if ((ch = attr_scan64_number(fp, number, str_buf,
+ "input attribute value")) < 0)
+ return (-1);
+ if (ch != '\n') {
+ msg_warn("multiple values for attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ break;
+ case ATTR_TYPE_LONG:
+ if (ch != ':') {
+ msg_warn("missing value for number attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ long_number = va_arg(ap, unsigned long *);
+ if ((ch = attr_scan64_long_number(fp, long_number, str_buf,
+ "input attribute value")) < 0)
+ return (-1);
+ if (ch != '\n') {
+ msg_warn("multiple values for attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ break;
+ case ATTR_TYPE_STR:
+ if (ch != ':') {
+ msg_warn("missing value for string attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ string = va_arg(ap, VSTRING *);
+ if ((ch = attr_scan64_string(fp, string,
+ "input attribute value")) < 0)
+ return (-1);
+ if (ch != '\n') {
+ msg_warn("multiple values for attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ if (flags & ATTR_FLAG_PRINTABLE)
+ (void) printable(STR(string), '?');
+ break;
+ case ATTR_TYPE_DATA:
+ if (ch != ':') {
+ msg_warn("missing value for data attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ string = va_arg(ap, VSTRING *);
+ if ((ch = attr_scan64_string(fp, string,
+ "input attribute value")) < 0)
+ return (-1);
+ if (ch != '\n') {
+ msg_warn("multiple values for attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ break;
+ case ATTR_TYPE_FUNC:
+ scan_fn = va_arg(ap, ATTR_SCAN_CUSTOM_FN);
+ scan_arg = va_arg(ap, void *);
+ if (scan_fn(attr_scan64, fp, flags | ATTR_FLAG_MORE, scan_arg) < 0)
+ return (-1);
+ break;
+ case ATTR_TYPE_STREQ:
+ if (ch != ':') {
+ msg_warn("missing value for string attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ expect_val = va_arg(ap, const char *);
+ if ((ch = attr_scan64_string(fp, str_buf,
+ "input attribute value")) < 0)
+ return (-1);
+ if (ch != '\n') {
+ msg_warn("multiple values for attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ if (strcmp(expect_val, STR(str_buf)) != 0) {
+ msg_warn("unexpected %s %s from %s (expected: %s)",
+ STR(name_buf), STR(str_buf), VSTREAM_PATH(fp),
+ expect_val);
+ return (-1);
+ }
+ conversions -= 1;
+ break;
+ case ATTR_TYPE_HASH:
+ case ATTR_TYPE_CLOSE:
+ if (ch != ':') {
+ msg_warn("missing value for string attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ if ((ch = attr_scan64_string(fp, str_buf,
+ "input attribute value")) < 0)
+ return (-1);
+ if (ch != '\n') {
+ msg_warn("multiple values for attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ if (flags & ATTR_FLAG_PRINTABLE) {
+ (void) printable(STR(name_buf), '?');
+ (void) printable(STR(str_buf), '?');
+ }
+ if (htable_locate(hash_table, STR(name_buf)) != 0) {
+ if ((flags & ATTR_FLAG_EXTRA) != 0) {
+ msg_warn("duplicate attribute %s in input from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (conversions);
+ }
+ } else if (hash_table->used >= ATTR_HASH_LIMIT) {
+ msg_warn("attribute count exceeds limit %d in input from %s",
+ ATTR_HASH_LIMIT, VSTREAM_PATH(fp));
+ return (conversions);
+ } else {
+ htable_enter(hash_table, STR(name_buf),
+ mystrdup(STR(str_buf)));
+ }
+ break;
+ case -1:
+ conversions -= 1;
+ break;
+ default:
+ msg_panic("%s: unknown type code: %d", myname, wanted_type);
+ }
+ }
+}
+
+/* attr_scan64 - read attribute list from stream */
+
+int attr_scan64(VSTREAM *fp, int flags,...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, flags);
+ ret = attr_vscan64(fp, flags, ap);
+ va_end(ap);
+ return (ret);
+}
+
+/* attr_scan_more64 - look ahead for more */
+
+int attr_scan_more64(VSTREAM *fp)
+{
+ int ch;
+
+ switch (ch = VSTREAM_GETC(fp)) {
+ case '\n':
+ if (msg_verbose)
+ msg_info("%s: terminator (consumed)", VSTREAM_PATH(fp));
+ return (0);
+ case VSTREAM_EOF:
+ if (msg_verbose)
+ msg_info("%s: EOF", VSTREAM_PATH(fp));
+ return (-1);
+ default:
+ if (msg_verbose)
+ msg_info("%s: non-terminator '%c' (lookahead)",
+ VSTREAM_PATH(fp), ch);
+ (void) vstream_ungetc(fp, ch);
+ return (1);
+ }
+}
+
+#ifdef TEST
+
+ /*
+ * Proof of concept test program. Mirror image of the attr_scan64 test
+ * program.
+ */
+#include <msg_vstream.h>
+
+int var_line_limit = 2048;
+
+int main(int unused_argc, char **used_argv)
+{
+ VSTRING *data_val = vstring_alloc(1);
+ VSTRING *str_val = vstring_alloc(1);
+ HTABLE *table = htable_create(1);
+ HTABLE_INFO **ht_info_list;
+ HTABLE_INFO **ht;
+ int int_val;
+ long long_val;
+ long long_val2;
+ int ret;
+
+ msg_verbose = 1;
+ msg_vstream_init(used_argv[0], VSTREAM_ERR);
+ if ((ret = attr_scan64(VSTREAM_IN,
+ ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ("protocol", "test"),
+ RECV_ATTR_INT(ATTR_NAME_INT, &int_val),
+ RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val),
+ RECV_ATTR_STR(ATTR_NAME_STR, str_val),
+ RECV_ATTR_DATA(ATTR_NAME_DATA, data_val),
+ RECV_ATTR_HASH(table),
+ RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val2),
+ ATTR_TYPE_END)) > 4) {
+ vstream_printf("%s %d\n", ATTR_NAME_INT, int_val);
+ vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
+ vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val));
+ vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val));
+ ht_info_list = htable_list(table);
+ for (ht = ht_info_list; *ht; ht++)
+ vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value);
+ myfree((void *) ht_info_list);
+ vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val2);
+ } else {
+ vstream_printf("return: %d\n", ret);
+ }
+ if ((ret = attr_scan64(VSTREAM_IN,
+ ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ("protocol", "test"),
+ RECV_ATTR_INT(ATTR_NAME_INT, &int_val),
+ RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val),
+ RECV_ATTR_STR(ATTR_NAME_STR, str_val),
+ RECV_ATTR_DATA(ATTR_NAME_DATA, data_val),
+ ATTR_TYPE_END)) == 4) {
+ vstream_printf("%s %d\n", ATTR_NAME_INT, int_val);
+ vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
+ vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val));
+ vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val));
+ ht_info_list = htable_list(table);
+ for (ht = ht_info_list; *ht; ht++)
+ vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value);
+ myfree((void *) ht_info_list);
+ } else {
+ vstream_printf("return: %d\n", ret);
+ }
+ if ((ret = attr_scan64(VSTREAM_IN,
+ ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ("protocol", "test"),
+ ATTR_TYPE_END)) != 0)
+ vstream_printf("return: %d\n", ret);
+ if (vstream_fflush(VSTREAM_OUT) != 0)
+ msg_fatal("write error: %m");
+
+ vstring_free(data_val);
+ vstring_free(str_val);
+ htable_free(table, myfree);
+
+ return (0);
+}
+
+#endif
diff --git a/src/util/attr_scan64.ref b/src/util/attr_scan64.ref
new file mode 100644
index 0000000..ccf27f1
--- /dev/null
+++ b/src/util/attr_scan64.ref
@@ -0,0 +1,79 @@
+./attr_print64: send attr protocol = test
+./attr_print64: send attr number = 4711
+./attr_print64: send attr long_number = 1234
+./attr_print64: send attr string = whoopee
+./attr_print64: send attr data = [data 7 bytes]
+./attr_print64: send attr name bar-name value bar-value
+./attr_print64: send attr name foo-name value foo-value
+./attr_print64: send attr long_number = 4321
+./attr_print64: send attr protocol = test
+./attr_print64: send attr number = 4711
+./attr_print64: send attr long_number = 1234
+./attr_print64: send attr string = whoopee
+./attr_print64: send attr data = [data 7 bytes]
+./attr_print64: send attr protocol = not-test
+./attr_scan64: unknown_stream: wanted attribute: protocol
+./attr_scan64: input attribute name: protocol
+./attr_scan64: input attribute value: test
+./attr_scan64: unknown_stream: wanted attribute: number
+./attr_scan64: input attribute name: number
+./attr_scan64: input attribute value: 4711
+./attr_scan64: unknown_stream: wanted attribute: long_number
+./attr_scan64: input attribute name: long_number
+./attr_scan64: input attribute value: 1234
+./attr_scan64: unknown_stream: wanted attribute: string
+./attr_scan64: input attribute name: string
+./attr_scan64: input attribute value: whoopee
+./attr_scan64: unknown_stream: wanted attribute: data
+./attr_scan64: input attribute name: data
+./attr_scan64: input attribute value: whoopee
+./attr_scan64: unknown_stream: wanted attribute: (any attribute name or list terminator)
+./attr_scan64: input attribute name: {
+./attr_scan64: unknown_stream: wanted attribute: (any attribute name or '}')
+./attr_scan64: input attribute name: bar-name
+./attr_scan64: input attribute value: bar-value
+./attr_scan64: unknown_stream: wanted attribute: (any attribute name or '}')
+./attr_scan64: input attribute name: foo-name
+./attr_scan64: input attribute value: foo-value
+./attr_scan64: unknown_stream: wanted attribute: (any attribute name or '}')
+./attr_scan64: input attribute name: }
+./attr_scan64: unknown_stream: wanted attribute: long_number
+./attr_scan64: input attribute name: long_number
+./attr_scan64: input attribute value: 4321
+./attr_scan64: unknown_stream: wanted attribute: (list terminator)
+./attr_scan64: input attribute name: (end)
+./attr_scan64: unknown_stream: wanted attribute: protocol
+./attr_scan64: input attribute name: protocol
+./attr_scan64: input attribute value: test
+./attr_scan64: unknown_stream: wanted attribute: number
+./attr_scan64: input attribute name: number
+./attr_scan64: input attribute value: 4711
+./attr_scan64: unknown_stream: wanted attribute: long_number
+./attr_scan64: input attribute name: long_number
+./attr_scan64: input attribute value: 1234
+./attr_scan64: unknown_stream: wanted attribute: string
+./attr_scan64: input attribute name: string
+./attr_scan64: input attribute value: whoopee
+./attr_scan64: unknown_stream: wanted attribute: data
+./attr_scan64: input attribute name: data
+./attr_scan64: input attribute value: whoopee
+./attr_scan64: unknown_stream: wanted attribute: (list terminator)
+./attr_scan64: input attribute name: (end)
+./attr_scan64: unknown_stream: wanted attribute: protocol
+./attr_scan64: input attribute name: protocol
+./attr_scan64: input attribute value: not-test
+./attr_scan64: warning: unexpected protocol not-test from unknown_stream (expected: test)
+number 4711
+long_number 1234
+string whoopee
+data whoopee
+(hash) bar-name bar-value
+(hash) foo-name foo-value
+long_number 4321
+number 4711
+long_number 1234
+string whoopee
+data whoopee
+(hash) bar-name bar-value
+(hash) foo-name foo-value
+return: -1
diff --git a/src/util/attr_scan_plain.c b/src/util/attr_scan_plain.c
new file mode 100644
index 0000000..d7e2f66
--- /dev/null
+++ b/src/util/attr_scan_plain.c
@@ -0,0 +1,643 @@
+/*++
+/* NAME
+/* attr_scan_plain 3
+/* SUMMARY
+/* recover attributes from byte stream
+/* SYNOPSIS
+/* #include <attr.h>
+/*
+/* int attr_scan_plain(fp, flags, type, name, ..., ATTR_TYPE_END)
+/* VSTREAM *fp;
+/* int flags;
+/* int type;
+/* char *name;
+/*
+/* int attr_vscan_plain(fp, flags, ap)
+/* VSTREAM *fp;
+/* int flags;
+/* va_list ap;
+/*
+/* int attr_scan_more_plain(fp)
+/* VSTREAM *fp;
+/* DESCRIPTION
+/* attr_scan_plain() takes zero or more (name, value) request attributes
+/* and recovers the attribute values from the byte stream that was
+/* possibly generated by attr_print_plain().
+/*
+/* attr_vscan_plain() provides an alternative interface that is convenient
+/* for calling from within a variadic function.
+/*
+/* attr_scan_more_plain() returns 0 when a terminator is found
+/* (and consumes that terminator), returns 1 when more input
+/* is expected (without consuming input), and returns -1
+/* otherwise (error).
+/*
+/* The input stream is formatted as follows, where (item)* stands
+/* for zero or more instances of the specified item, and where
+/* (item1 | item2) stands for choice:
+/*
+/* .in +5
+/* attr-list :== (simple-attr | multi-attr)* newline
+/* .br
+/* multi-attr :== "{" newline simple-attr* "}" newline
+/* .br
+/* simple-attr :== attr-name "=" attr-value newline
+/* .br
+/* attr-name :== any string without null or "=" or newline.
+/* .br
+/* attr-value :== any string without null or newline.
+/* .br
+/* newline :== the ASCII newline character
+/* .in
+/*
+/* All attribute names and attribute values are sent as plain
+/* strings. Each string must be no longer than 4*var_line_limit
+/* characters. The formatting rules aim to make implementations in PERL
+/* and other languages easy.
+/*
+/* Normally, attributes must be received in the sequence as specified
+/* with the attr_scan_plain() argument list. The input stream may
+/* contain additional attributes at any point in the input stream,
+/* including additional instances of requested attributes.
+/*
+/* Additional input attributes or input attribute instances are silently
+/* skipped over, unless the ATTR_FLAG_EXTRA processing flag is specified
+/* (see below). This allows for some flexibility in the evolution of
+/* protocols while still providing the option of being strict where
+/* this is desirable.
+/*
+/* Arguments:
+/* .IP fp
+/* Stream to recover the input attributes from.
+/* .IP flags
+/* The bit-wise OR of zero or more of the following.
+/* .RS
+/* .IP ATTR_FLAG_MISSING
+/* Log a warning when the input attribute list terminates before all
+/* requested attributes are recovered. It is always an error when the
+/* input stream ends without the newline attribute list terminator.
+/* .IP ATTR_FLAG_EXTRA
+/* Log a warning and stop attribute recovery when the input stream
+/* contains an attribute that was not requested. This includes the
+/* case of additional instances of a requested attribute.
+/* .IP ATTR_FLAG_MORE
+/* After recovering the requested attributes, leave the input stream
+/* in a state that is usable for more attr_scan_plain() operations
+/* from the same input attribute list.
+/* By default, attr_scan_plain() skips forward past the input attribute
+/* list terminator.
+/* .IP ATTR_FLAG_PRINTABLE
+/* Santize received string values with printable(_, '?').
+/* .IP ATTR_FLAG_STRICT
+/* For convenience, this value combines both ATTR_FLAG_MISSING and
+/* ATTR_FLAG_EXTRA.
+/* .IP ATTR_FLAG_NONE
+/* For convenience, this value requests none of the above.
+/* .RE
+/* .IP List of attributes followed by terminator:
+/* .RS
+/* .IP "RECV_ATTR_INT(const char *name, int *ptr)"
+/* This argument is followed by an attribute name and an integer pointer.
+/* .IP "RECV_ATTR_LONG(const char *name, long *ptr)"
+/* This argument is followed by an attribute name and a long pointer.
+/* .IP "RECV_ATTR_STR(const char *name, VSTRING *vp)"
+/* This argument is followed by an attribute name and a VSTRING pointer.
+/* .IP "RECV_ATTR_STREQ(const char *name, const char *value)"
+/* The name and value must match what the client sends.
+/* This attribute does not increment the result value.
+/* .IP "RECV_ATTR_DATA(const char *name, VSTRING *vp)"
+/* This argument is followed by an attribute name and a VSTRING pointer.
+/* .IP "RECV_ATTR_FUNC(ATTR_SCAN_CUSTOM_FN, void *data)"
+/* This argument is followed by a function pointer and a generic data
+/* pointer. The caller-specified function returns < 0 in case of
+/* error.
+/* .IP "RECV_ATTR_HASH(HTABLE *table)"
+/* .IP "RECV_ATTR_NAMEVAL(NVTABLE *table)"
+/* Receive a sequence of attribute names and string values.
+/* There can be no more than 1024 attributes in a hash table.
+/* .sp
+/* The attribute string values are stored in the hash table under
+/* keys equal to the attribute name (obtained from the input stream).
+/* Values from the input stream are added to the hash table. Existing
+/* hash table entries are not replaced.
+/* .sp
+/* Note: the SEND_ATTR_HASH or SEND_ATTR_NAMEVAL requests
+/* format their payload as a multi-attr sequence (see syntax
+/* above). When the receiver's input does not start with a
+/* multi-attr delimiter (i.e. the sender did not request
+/* SEND_ATTR_HASH or SEND_ATTR_NAMEVAL), the receiver will
+/* store all attribute names and values up to the attribute
+/* list terminator. In terms of code, this means that the
+/* RECV_ATTR_HASH or RECV_ATTR_NAMEVAL request must be followed
+/* by ATTR_TYPE_END.
+/* .IP ATTR_TYPE_END
+/* This argument terminates the requested attribute list.
+/* .RE
+/* BUGS
+/* RECV_ATTR_HASH (RECV_ATTR_NAMEVAL) accepts attributes with arbitrary
+/* names from possibly untrusted sources.
+/* This is unsafe, unless the resulting table is queried only with
+/* known to be good attribute names.
+/* DIAGNOSTICS
+/* attr_scan_plain() and attr_vscan_plain() return -1 when malformed input
+/* is detected (string too long, incomplete line, missing end marker).
+/* Otherwise, the result value is the number of attributes that were
+/* successfully recovered from the input stream (a hash table counts
+/* as the number of entries stored into the table).
+/*
+/* Panic: interface violation. All system call errors are fatal.
+/* SEE ALSO
+/* attr_print_plain(3) send attributes over byte stream.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdio.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <htable.h>
+#include <base64_code.h>
+#include <stringops.h>
+#include <attr.h>
+
+/* Application specific. */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* attr_scan_plain_string - pull a string from the input stream */
+
+static int attr_scan_plain_string(VSTREAM *fp, VSTRING *plain_buf,
+ int terminator, const char *context)
+{
+#if 0
+ extern int var_line_limit; /* XXX */
+ int limit = var_line_limit * 4;
+
+#endif
+ int ch;
+
+ VSTRING_RESET(plain_buf);
+ while ((ch = VSTREAM_GETC(fp)) != '\n'
+ && (terminator == 0 || ch != terminator)) {
+ if (ch == VSTREAM_EOF) {
+ msg_warn("%s on %s while reading %s",
+ vstream_ftimeout(fp) ? "timeout" : "premature end-of-input",
+ VSTREAM_PATH(fp), context);
+ return (-1);
+ }
+ VSTRING_ADDCH(plain_buf, ch);
+#if 0
+ if (LEN(plain_buf) > limit) {
+ msg_warn("string length > %d characters from %s while reading %s",
+ limit, VSTREAM_PATH(fp), context);
+ return (-1);
+ }
+#endif
+ }
+ VSTRING_TERMINATE(plain_buf);
+
+ if (msg_verbose)
+ msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)");
+ return (ch);
+}
+
+/* attr_scan_plain_data - pull a data blob from the input stream */
+
+static int attr_scan_plain_data(VSTREAM *fp, VSTRING *str_buf,
+ int terminator,
+ const char *context)
+{
+ static VSTRING *base64_buf = 0;
+ int ch;
+
+ if (base64_buf == 0)
+ base64_buf = vstring_alloc(10);
+ if ((ch = attr_scan_plain_string(fp, base64_buf, terminator, context)) < 0)
+ return (-1);
+ if (base64_decode(str_buf, STR(base64_buf), LEN(base64_buf)) == 0) {
+ msg_warn("malformed base64 data from %s while reading %s: %.100s",
+ VSTREAM_PATH(fp), context, STR(base64_buf));
+ return (-1);
+ }
+ return (ch);
+}
+
+/* attr_scan_plain_number - pull a number from the input stream */
+
+static int attr_scan_plain_number(VSTREAM *fp, unsigned *ptr, VSTRING *str_buf,
+ int terminator, const char *context)
+{
+ char junk = 0;
+ int ch;
+
+ if ((ch = attr_scan_plain_string(fp, str_buf, terminator, context)) < 0)
+ return (-1);
+ if (sscanf(STR(str_buf), "%u%c", ptr, &junk) != 1 || junk != 0) {
+ msg_warn("malformed numerical data from %s while reading %s: %.100s",
+ VSTREAM_PATH(fp), context, STR(str_buf));
+ return (-1);
+ }
+ return (ch);
+}
+
+/* attr_scan_plain_long_number - pull a number from the input stream */
+
+static int attr_scan_plain_long_number(VSTREAM *fp, unsigned long *ptr,
+ VSTRING *str_buf,
+ int terminator,
+ const char *context)
+{
+ char junk = 0;
+ int ch;
+
+ if ((ch = attr_scan_plain_string(fp, str_buf, terminator, context)) < 0)
+ return (-1);
+ if (sscanf(STR(str_buf), "%lu%c", ptr, &junk) != 1 || junk != 0) {
+ msg_warn("malformed numerical data from %s while reading %s: %.100s",
+ VSTREAM_PATH(fp), context, STR(str_buf));
+ return (-1);
+ }
+ return (ch);
+}
+
+/* attr_vscan_plain - receive attribute list from stream */
+
+int attr_vscan_plain(VSTREAM *fp, int flags, va_list ap)
+{
+ const char *myname = "attr_scan_plain";
+ static VSTRING *str_buf = 0;
+ static VSTRING *name_buf = 0;
+ int wanted_type = -1;
+ char *wanted_name;
+ unsigned int *number;
+ unsigned long *long_number;
+ VSTRING *string;
+ HTABLE *hash_table;
+ int ch;
+ int conversions;
+ ATTR_SCAN_CUSTOM_FN scan_fn;
+ void *scan_arg;
+ const char *expect_val;
+
+ /*
+ * Sanity check.
+ */
+ if (flags & ~ATTR_FLAG_ALL)
+ msg_panic("%s: bad flags: 0x%x", myname, flags);
+
+ /*
+ * EOF check.
+ */
+ if ((ch = VSTREAM_GETC(fp)) == VSTREAM_EOF)
+ return (0);
+ vstream_ungetc(fp, ch);
+
+ /*
+ * Initialize.
+ */
+ if (str_buf == 0) {
+ str_buf = vstring_alloc(10);
+ name_buf = vstring_alloc(10);
+ }
+
+ /*
+ * Iterate over all (type, name, value) triples.
+ */
+ for (conversions = 0; /* void */ ; conversions++) {
+
+ /*
+ * Determine the next attribute type and attribute name on the
+ * caller's wish list.
+ *
+ * If we're reading into a hash table, we already know that the
+ * attribute value is string-valued, and we get the attribute name
+ * from the input stream instead. This is secure only when the
+ * resulting table is queried with known to be good attribute names.
+ */
+ if (wanted_type != ATTR_TYPE_HASH
+ && wanted_type != ATTR_TYPE_CLOSE) {
+ wanted_type = va_arg(ap, int);
+ if (wanted_type == ATTR_TYPE_END) {
+ if ((flags & ATTR_FLAG_MORE) != 0)
+ return (conversions);
+ wanted_name = "(list terminator)";
+ } else if (wanted_type == ATTR_TYPE_HASH) {
+ wanted_name = "(any attribute name or list terminator)";
+ hash_table = va_arg(ap, HTABLE *);
+ } else if (wanted_type != ATTR_TYPE_FUNC) {
+ wanted_name = va_arg(ap, char *);
+ }
+ }
+
+ /*
+ * Locate the next attribute of interest in the input stream.
+ */
+ while (wanted_type != ATTR_TYPE_FUNC) {
+
+ /*
+ * Get the name of the next attribute. Hitting EOF is always bad.
+ * Hitting the end-of-input early is OK if the caller is prepared
+ * to deal with missing inputs.
+ */
+ if (msg_verbose)
+ msg_info("%s: wanted attribute: %s",
+ VSTREAM_PATH(fp), wanted_name);
+ if ((ch = attr_scan_plain_string(fp, name_buf, '=',
+ "input attribute name")) == VSTREAM_EOF)
+ return (-1);
+ if (ch == '\n' && LEN(name_buf) == 0) {
+ if (wanted_type == ATTR_TYPE_END
+ || wanted_type == ATTR_TYPE_HASH)
+ return (conversions);
+ if ((flags & ATTR_FLAG_MISSING) != 0)
+ msg_warn("missing attribute %s in input from %s",
+ wanted_name, VSTREAM_PATH(fp));
+ return (conversions);
+ }
+
+ /*
+ * See if the caller asks for this attribute.
+ */
+ if (wanted_type == ATTR_TYPE_HASH
+ && ch == '\n' && strcmp(ATTR_NAME_OPEN, STR(name_buf)) == 0) {
+ wanted_type = ATTR_TYPE_CLOSE;
+ wanted_name = "(any attribute name or '}')";
+ /* Advance in the input stream. */
+ continue;
+ } else if (wanted_type == ATTR_TYPE_CLOSE
+ && ch == '\n' && strcmp(ATTR_NAME_CLOSE, STR(name_buf)) == 0) {
+ /* Advance in the argument list. */
+ wanted_type = -1;
+ break;
+ }
+ if (wanted_type == ATTR_TYPE_HASH
+ || wanted_type == ATTR_TYPE_CLOSE
+ || (wanted_type != ATTR_TYPE_END
+ && strcmp(wanted_name, STR(name_buf)) == 0))
+ break;
+ if ((flags & ATTR_FLAG_EXTRA) != 0) {
+ msg_warn("unexpected attribute %s from %s (expecting: %s)",
+ STR(name_buf), VSTREAM_PATH(fp), wanted_name);
+ return (conversions);
+ }
+
+ /*
+ * Skip over this attribute. The caller does not ask for it.
+ */
+ while (ch != '\n' && (ch = VSTREAM_GETC(fp)) != VSTREAM_EOF)
+ /* void */ ;
+ }
+
+ /*
+ * Do the requested conversion.
+ */
+ switch (wanted_type) {
+ case ATTR_TYPE_INT:
+ if (ch != '=') {
+ msg_warn("missing value for number attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ number = va_arg(ap, unsigned int *);
+ if ((ch = attr_scan_plain_number(fp, number, str_buf, 0,
+ "input attribute value")) < 0)
+ return (-1);
+ break;
+ case ATTR_TYPE_LONG:
+ if (ch != '=') {
+ msg_warn("missing value for number attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ long_number = va_arg(ap, unsigned long *);
+ if ((ch = attr_scan_plain_long_number(fp, long_number, str_buf,
+ 0, "input attribute value")) < 0)
+ return (-1);
+ break;
+ case ATTR_TYPE_STR:
+ if (ch != '=') {
+ msg_warn("missing value for string attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ string = va_arg(ap, VSTRING *);
+ if ((ch = attr_scan_plain_string(fp, string, 0,
+ "input attribute value")) < 0)
+ return (-1);
+ if (flags & ATTR_FLAG_PRINTABLE)
+ (void) printable(STR(string), '?');
+ break;
+ case ATTR_TYPE_DATA:
+ if (ch != '=') {
+ msg_warn("missing value for data attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ string = va_arg(ap, VSTRING *);
+ if ((ch = attr_scan_plain_data(fp, string, 0,
+ "input attribute value")) < 0)
+ return (-1);
+ break;
+ case ATTR_TYPE_FUNC:
+ scan_fn = va_arg(ap, ATTR_SCAN_CUSTOM_FN);
+ scan_arg = va_arg(ap, void *);
+ if (scan_fn(attr_scan_plain, fp, flags | ATTR_FLAG_MORE, scan_arg) < 0)
+ return (-1);
+ break;
+ case ATTR_TYPE_STREQ:
+ if (ch != '=') {
+ msg_warn("missing value for string attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ expect_val = va_arg(ap, const char *);
+ if ((ch = attr_scan_plain_string(fp, str_buf, 0,
+ "input attribute value")) < 0)
+ return (-1);
+ if (strcmp(expect_val, STR(str_buf)) != 0) {
+ msg_warn("unexpected %s %s from %s (expected: %s)",
+ STR(name_buf), STR(str_buf), VSTREAM_PATH(fp),
+ expect_val);
+ return (-1);
+ }
+ conversions -= 1;
+ break;
+ case ATTR_TYPE_HASH:
+ case ATTR_TYPE_CLOSE:
+ if (ch != '=') {
+ msg_warn("missing value for string attribute %s from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (-1);
+ }
+ if ((ch = attr_scan_plain_string(fp, str_buf, 0,
+ "input attribute value")) < 0)
+ return (-1);
+ if (flags & ATTR_FLAG_PRINTABLE) {
+ (void) printable(STR(name_buf), '?');
+ (void) printable(STR(str_buf), '?');
+ }
+ if (htable_locate(hash_table, STR(name_buf)) != 0) {
+ if ((flags & ATTR_FLAG_EXTRA) != 0) {
+ msg_warn("duplicate attribute %s in input from %s",
+ STR(name_buf), VSTREAM_PATH(fp));
+ return (conversions);
+ }
+ } else if (hash_table->used >= ATTR_HASH_LIMIT) {
+ msg_warn("attribute count exceeds limit %d in input from %s",
+ ATTR_HASH_LIMIT, VSTREAM_PATH(fp));
+ return (conversions);
+ } else {
+ htable_enter(hash_table, STR(name_buf),
+ mystrdup(STR(str_buf)));
+ }
+ break;
+ case -1:
+ conversions -= 1;
+ break;
+ default:
+ msg_panic("%s: unknown type code: %d", myname, wanted_type);
+ }
+ }
+}
+
+/* attr_scan_plain - read attribute list from stream */
+
+int attr_scan_plain(VSTREAM *fp, int flags,...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, flags);
+ ret = attr_vscan_plain(fp, flags, ap);
+ va_end(ap);
+ return (ret);
+}
+
+/* attr_scan_more_plain - look ahead for more */
+
+int attr_scan_more_plain(VSTREAM *fp)
+{
+ int ch;
+
+ switch (ch = VSTREAM_GETC(fp)) {
+ case '\n':
+ if (msg_verbose)
+ msg_info("%s: terminator (consumed)", VSTREAM_PATH(fp));
+ return (0);
+ case VSTREAM_EOF:
+ if (msg_verbose)
+ msg_info("%s: EOF", VSTREAM_PATH(fp));
+ return (-1);
+ default:
+ if (msg_verbose)
+ msg_info("%s: non-terminator '%c' (lookahead)",
+ VSTREAM_PATH(fp), ch);
+ (void) vstream_ungetc(fp, ch);
+ return (1);
+ }
+}
+
+#ifdef TEST
+
+ /*
+ * Proof of concept test program. Mirror image of the attr_scan_plain test
+ * program.
+ */
+#include <msg_vstream.h>
+
+int var_line_limit = 2048;
+
+int main(int unused_argc, char **used_argv)
+{
+ VSTRING *data_val = vstring_alloc(1);
+ VSTRING *str_val = vstring_alloc(1);
+ HTABLE *table = htable_create(1);
+ HTABLE_INFO **ht_info_list;
+ HTABLE_INFO **ht;
+ int int_val;
+ long long_val;
+ long long_val2;
+ int ret;
+
+ msg_verbose = 1;
+ msg_vstream_init(used_argv[0], VSTREAM_ERR);
+ if ((ret = attr_scan_plain(VSTREAM_IN,
+ ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ("protocol", "test"),
+ RECV_ATTR_INT(ATTR_NAME_INT, &int_val),
+ RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val),
+ RECV_ATTR_STR(ATTR_NAME_STR, str_val),
+ RECV_ATTR_DATA(ATTR_NAME_DATA, data_val),
+ RECV_ATTR_HASH(table),
+ RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val2),
+ ATTR_TYPE_END)) > 4) {
+ vstream_printf("%s %d\n", ATTR_NAME_INT, int_val);
+ vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
+ vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val));
+ vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val));
+ ht_info_list = htable_list(table);
+ for (ht = ht_info_list; *ht; ht++)
+ vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value);
+ myfree((void *) ht_info_list);
+ vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val2);
+ } else {
+ vstream_printf("return: %d\n", ret);
+ }
+ if ((ret = attr_scan_plain(VSTREAM_IN,
+ ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ("protocol", "test"),
+ RECV_ATTR_INT(ATTR_NAME_INT, &int_val),
+ RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val),
+ RECV_ATTR_STR(ATTR_NAME_STR, str_val),
+ RECV_ATTR_DATA(ATTR_NAME_DATA, data_val),
+ ATTR_TYPE_END)) == 4) {
+ vstream_printf("%s %d\n", ATTR_NAME_INT, int_val);
+ vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val);
+ vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val));
+ vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val));
+ ht_info_list = htable_list(table);
+ for (ht = ht_info_list; *ht; ht++)
+ vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value);
+ myfree((void *) ht_info_list);
+ } else {
+ vstream_printf("return: %d\n", ret);
+ }
+ if ((ret = attr_scan_plain(VSTREAM_IN,
+ ATTR_FLAG_STRICT,
+ RECV_ATTR_STREQ("protocol", "test"),
+ ATTR_TYPE_END)) != 0)
+ vstream_printf("return: %d\n", ret);
+ if (vstream_fflush(VSTREAM_OUT) != 0)
+ msg_fatal("write error: %m");
+
+ vstring_free(data_val);
+ vstring_free(str_val);
+ htable_free(table, myfree);
+
+ return (0);
+}
+
+#endif
diff --git a/src/util/attr_scan_plain.ref b/src/util/attr_scan_plain.ref
new file mode 100644
index 0000000..1c0f358
--- /dev/null
+++ b/src/util/attr_scan_plain.ref
@@ -0,0 +1,79 @@
+./attr_print_plain: send attr protocol = test
+./attr_print_plain: send attr number = 4711
+./attr_print_plain: send attr long_number = 1234
+./attr_print_plain: send attr string = whoopee
+./attr_print_plain: send attr data = [data 7 bytes]
+./attr_print_plain: send attr name bar-name value bar-value
+./attr_print_plain: send attr name foo-name value foo-value
+./attr_print_plain: send attr long_number = 4321
+./attr_print_plain: send attr protocol = test
+./attr_print_plain: send attr number = 4711
+./attr_print_plain: send attr long_number = 1234
+./attr_print_plain: send attr string = whoopee
+./attr_print_plain: send attr data = [data 7 bytes]
+./attr_print_plain: send attr protocol = not-test
+./attr_scan_plain: unknown_stream: wanted attribute: protocol
+./attr_scan_plain: input attribute name: protocol
+./attr_scan_plain: input attribute value: test
+./attr_scan_plain: unknown_stream: wanted attribute: number
+./attr_scan_plain: input attribute name: number
+./attr_scan_plain: input attribute value: 4711
+./attr_scan_plain: unknown_stream: wanted attribute: long_number
+./attr_scan_plain: input attribute name: long_number
+./attr_scan_plain: input attribute value: 1234
+./attr_scan_plain: unknown_stream: wanted attribute: string
+./attr_scan_plain: input attribute name: string
+./attr_scan_plain: input attribute value: whoopee
+./attr_scan_plain: unknown_stream: wanted attribute: data
+./attr_scan_plain: input attribute name: data
+./attr_scan_plain: input attribute value: d2hvb3BlZQ==
+./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or list terminator)
+./attr_scan_plain: input attribute name: {
+./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or '}')
+./attr_scan_plain: input attribute name: bar-name
+./attr_scan_plain: input attribute value: bar-value
+./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or '}')
+./attr_scan_plain: input attribute name: foo-name
+./attr_scan_plain: input attribute value: foo-value
+./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or '}')
+./attr_scan_plain: input attribute name: }
+./attr_scan_plain: unknown_stream: wanted attribute: long_number
+./attr_scan_plain: input attribute name: long_number
+./attr_scan_plain: input attribute value: 4321
+./attr_scan_plain: unknown_stream: wanted attribute: (list terminator)
+./attr_scan_plain: input attribute name: (end)
+./attr_scan_plain: unknown_stream: wanted attribute: protocol
+./attr_scan_plain: input attribute name: protocol
+./attr_scan_plain: input attribute value: test
+./attr_scan_plain: unknown_stream: wanted attribute: number
+./attr_scan_plain: input attribute name: number
+./attr_scan_plain: input attribute value: 4711
+./attr_scan_plain: unknown_stream: wanted attribute: long_number
+./attr_scan_plain: input attribute name: long_number
+./attr_scan_plain: input attribute value: 1234
+./attr_scan_plain: unknown_stream: wanted attribute: string
+./attr_scan_plain: input attribute name: string
+./attr_scan_plain: input attribute value: whoopee
+./attr_scan_plain: unknown_stream: wanted attribute: data
+./attr_scan_plain: input attribute name: data
+./attr_scan_plain: input attribute value: d2hvb3BlZQ==
+./attr_scan_plain: unknown_stream: wanted attribute: (list terminator)
+./attr_scan_plain: input attribute name: (end)
+./attr_scan_plain: unknown_stream: wanted attribute: protocol
+./attr_scan_plain: input attribute name: protocol
+./attr_scan_plain: input attribute value: not-test
+./attr_scan_plain: warning: unexpected protocol not-test from unknown_stream (expected: test)
+number 4711
+long_number 1234
+string whoopee
+data whoopee
+(hash) bar-name bar-value
+(hash) foo-name foo-value
+long_number 4321
+number 4711
+long_number 1234
+string whoopee
+data whoopee
+(hash) bar-name bar-value
+(hash) foo-name foo-value
+return: -1
diff --git a/src/util/auto_clnt.c b/src/util/auto_clnt.c
new file mode 100644
index 0000000..cdbbe22
--- /dev/null
+++ b/src/util/auto_clnt.c
@@ -0,0 +1,372 @@
+/*++
+/* NAME
+/* auto_clnt 3
+/* SUMMARY
+/* client endpoint maintenance
+/* SYNOPSIS
+/* #include <auto_clnt.h>
+/*
+/* typedef void (*AUTO_CLNT_HANDSHAKE_FN)(VSTREAM *);
+/*
+/* AUTO_CLNT *auto_clnt_create(service, timeout, max_idle, max_ttl)
+/* const char *service;
+/* int timeout;
+/* int max_idle;
+/* int max_ttl;
+/*
+/* VSTREAM *auto_clnt_access(auto_clnt)
+/* AUTO_CLNT *auto_clnt;
+/*
+/* void auto_clnt_recover(auto_clnt)
+/* AUTO_CLNT *auto_clnt;
+/*
+/* const char *auto_clnt_name(auto_clnt)
+/* AUTO_CLNT *auto_clnt;
+/*
+/* void auto_clnt_free(auto_clnt)
+/* AUTO_CLNT *auto_clnt;
+/*
+/* void auto_clnt_control(auto_clnt, name, value, ... AUTO_CLNT_CTL_END)
+/* AUTO_CLNT *auto_clnt;
+/* int name;
+/* DESCRIPTION
+/* This module maintains IPC client endpoints that automatically
+/* disconnect after a being idle for a configurable amount of time,
+/* that disconnect after a configurable time to live,
+/* and that transparently handle most server-initiated disconnects.
+/*
+/* This module tries each operation only a limited number of
+/* times and then reports an error. This is unlike the
+/* clnt_stream(3) module which will retry forever, so that
+/* the application never experiences an error.
+/*
+/* auto_clnt_create() instantiates a client endpoint.
+/*
+/* auto_clnt_access() returns an open stream to the service specified
+/* to auto_clnt_create(). The stream instance may change between calls.
+/* The result is a null pointer in case of failure.
+/*
+/* auto_clnt_recover() recovers from a server-initiated disconnect
+/* that happened in the middle of an I/O operation.
+/*
+/* auto_clnt_name() returns the name of the specified client endpoint.
+/*
+/* auto_clnt_free() destroys of the specified client endpoint.
+/*
+/* auto_clnt_control() allows the user to fine tune the behavior of
+/* the specified client. The arguments are a list of (name, value)
+/* terminated with AUTO_CLNT_CTL_END.
+/* The following lists the names and the types of the corresponding
+/* value arguments.
+/* .IP "AUTO_CLNT_CTL_HANDSHAKE(VSTREAM *)"
+/* A pointer to function that will be called at the start of a
+/* new connection, and that returns 0 in case of success.
+/* .PP
+/* Arguments:
+/* .IP service
+/* The service argument specifies "transport:servername" where
+/* transport is currently limited to one of the following:
+/* .RS
+/* .IP inet
+/* servername has the form "inet:host:port".
+/* .IP local
+/* servername has the form "local:private/servicename" or
+/* "local:public/servicename". This is the preferred way to
+/* specify Postfix daemons that are configured as "unix" in
+/* master.cf.
+/* .IP unix
+/* servername has the form "unix:private/servicename" or
+/* "unix:public/servicename". This does not work on Solaris,
+/* where Postfix uses STREAMS instead of UNIX-domain sockets.
+/* .RE
+/* .IP timeout
+/* The time limit for sending, receiving, or for connecting
+/* to a server. Specify a value <=0 to disable the time limit.
+/* .IP max_idle
+/* Idle time after which the client disconnects. Specify 0 to
+/* disable the limit.
+/* .IP max_ttl
+/* Upper bound on the time that a connection is allowed to persist.
+/* Specify 0 to disable the limit.
+/* .IP open_action
+/* Application call-back routine that opens a stream or returns a
+/* null pointer upon failure. In case of success, the call-back routine
+/* is expected to set the stream pathname to the server endpoint name.
+/* .IP context
+/* Application context that is passed to the open_action routine.
+/* .IP handshake
+/* A null pointer, or a pointer to function that will be called
+/* at the start of a new connection and that returns 0 in case
+/* of success.
+/* DIAGNOSTICS
+/* Warnings: communication failure. Fatal error: out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <events.h>
+#include <iostuff.h>
+#include <connect.h>
+#include <split_at.h>
+#include <auto_clnt.h>
+
+/* Application-specific. */
+
+ /*
+ * AUTO_CLNT is an opaque structure. None of the access methods can easily
+ * be implemented as a macro, and access is not performance critical anyway.
+ */
+struct AUTO_CLNT {
+ VSTREAM *vstream; /* buffered I/O */
+ char *endpoint; /* host:port or pathname */
+ int timeout; /* I/O time limit */
+ int max_idle; /* time before client disconnect */
+ int max_ttl; /* time before client disconnect */
+ AUTO_CLNT_HANDSHAKE_FN handshake; /* new connection only */
+ int (*connect) (const char *, int, int); /* unix, local, inet */
+};
+
+static void auto_clnt_close(AUTO_CLNT *);
+
+/* auto_clnt_event - server-initiated disconnect or client-side max_idle */
+
+static void auto_clnt_event(int unused_event, void *context)
+{
+ AUTO_CLNT *auto_clnt = (AUTO_CLNT *) context;
+
+ /*
+ * Sanity check. This routine causes the stream to be closed, so it
+ * cannot be called when the stream is already closed.
+ */
+ if (auto_clnt->vstream == 0)
+ msg_panic("auto_clnt_event: stream is closed");
+
+ auto_clnt_close(auto_clnt);
+}
+
+/* auto_clnt_ttl_event - client-side expiration */
+
+static void auto_clnt_ttl_event(int event, void *context)
+{
+
+ /*
+ * XXX This function is needed only because event_request_timer() cannot
+ * distinguish between requests that specify the same call-back routine
+ * and call-back context. The fix is obvious: specify a request ID along
+ * with the call-back routine, but there is too much code that would have
+ * to be changed.
+ *
+ * XXX Should we be concerned that an overly aggressive optimizer will
+ * eliminate this function and replace calls to auto_clnt_ttl_event() by
+ * direct calls to auto_clnt_event()? It should not, because there exists
+ * code that takes the address of both functions.
+ */
+ auto_clnt_event(event, context);
+}
+
+/* auto_clnt_open - connect to service */
+
+static void auto_clnt_open(AUTO_CLNT *auto_clnt)
+{
+ const char *myname = "auto_clnt_open";
+ int fd;
+
+ /*
+ * Sanity check.
+ */
+ if (auto_clnt->vstream)
+ msg_panic("auto_clnt_open: stream is open");
+
+ /*
+ * Schedule a read event so that we can clean up when the remote side
+ * disconnects, and schedule a timer event so that we can cleanup an idle
+ * connection. Note that both events are handled by the same routine.
+ *
+ * Finally, schedule an event to force disconnection even when the
+ * connection is not idle. This is to prevent one client from clinging on
+ * to a server forever.
+ */
+ fd = auto_clnt->connect(auto_clnt->endpoint, BLOCKING, auto_clnt->timeout);
+ if (fd < 0) {
+ msg_warn("connect to %s: %m", auto_clnt->endpoint);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: connected to %s", myname, auto_clnt->endpoint);
+ auto_clnt->vstream = vstream_fdopen(fd, O_RDWR);
+ vstream_control(auto_clnt->vstream,
+ CA_VSTREAM_CTL_PATH(auto_clnt->endpoint),
+ CA_VSTREAM_CTL_TIMEOUT(auto_clnt->timeout),
+ CA_VSTREAM_CTL_END);
+ }
+
+ if (auto_clnt->vstream != 0) {
+ close_on_exec(vstream_fileno(auto_clnt->vstream), CLOSE_ON_EXEC);
+ event_enable_read(vstream_fileno(auto_clnt->vstream), auto_clnt_event,
+ (void *) auto_clnt);
+ if (auto_clnt->max_idle > 0)
+ event_request_timer(auto_clnt_event, (void *) auto_clnt,
+ auto_clnt->max_idle);
+ if (auto_clnt->max_ttl > 0)
+ event_request_timer(auto_clnt_ttl_event, (void *) auto_clnt,
+ auto_clnt->max_ttl);
+ }
+}
+
+/* auto_clnt_close - disconnect from service */
+
+static void auto_clnt_close(AUTO_CLNT *auto_clnt)
+{
+ const char *myname = "auto_clnt_close";
+
+ /*
+ * Sanity check.
+ */
+ if (auto_clnt->vstream == 0)
+ msg_panic("%s: stream is closed", myname);
+
+ /*
+ * Be sure to disable read and timer events.
+ */
+ if (msg_verbose)
+ msg_info("%s: disconnect %s stream",
+ myname, VSTREAM_PATH(auto_clnt->vstream));
+ event_disable_readwrite(vstream_fileno(auto_clnt->vstream));
+ event_cancel_timer(auto_clnt_event, (void *) auto_clnt);
+ event_cancel_timer(auto_clnt_ttl_event, (void *) auto_clnt);
+ (void) vstream_fclose(auto_clnt->vstream);
+ auto_clnt->vstream = 0;
+}
+
+/* auto_clnt_recover - recover from server-initiated disconnect */
+
+void auto_clnt_recover(AUTO_CLNT *auto_clnt)
+{
+
+ /*
+ * Clean up. Don't re-connect until the caller needs it.
+ */
+ if (auto_clnt->vstream)
+ auto_clnt_close(auto_clnt);
+}
+
+/* auto_clnt_access - access a client stream */
+
+VSTREAM *auto_clnt_access(AUTO_CLNT *auto_clnt)
+{
+ AUTO_CLNT_HANDSHAKE_FN handshake;
+
+ /*
+ * Open a stream or restart the idle timer.
+ *
+ * Important! Do not restart the TTL timer!
+ */
+ if (auto_clnt->vstream == 0) {
+ auto_clnt_open(auto_clnt);
+ handshake = (auto_clnt->vstream ? auto_clnt->handshake : 0);
+ } else {
+ if (auto_clnt->max_idle > 0)
+ event_request_timer(auto_clnt_event, (void *) auto_clnt,
+ auto_clnt->max_idle);
+ handshake = 0;
+ }
+ if (handshake != 0 && handshake(auto_clnt->vstream) != 0)
+ return (0);
+ return (auto_clnt->vstream);
+}
+
+/* auto_clnt_create - create client stream object */
+
+AUTO_CLNT *auto_clnt_create(const char *service, int timeout,
+ int max_idle, int max_ttl)
+{
+ const char *myname = "auto_clnt_create";
+ char *transport = mystrdup(service);
+ char *endpoint;
+ AUTO_CLNT *auto_clnt;
+
+ /*
+ * Don't open the stream until the caller needs it.
+ */
+ if ((endpoint = split_at(transport, ':')) == 0
+ || *endpoint == 0 || *transport == 0)
+ msg_fatal("need service transport:endpoint instead of \"%s\"", service);
+ if (msg_verbose)
+ msg_info("%s: transport=%s endpoint=%s", myname, transport, endpoint);
+ auto_clnt = (AUTO_CLNT *) mymalloc(sizeof(*auto_clnt));
+ auto_clnt->vstream = 0;
+ auto_clnt->endpoint = mystrdup(endpoint);
+ auto_clnt->timeout = timeout;
+ auto_clnt->max_idle = max_idle;
+ auto_clnt->max_ttl = max_ttl;
+ auto_clnt->handshake = 0;
+ if (strcmp(transport, "inet") == 0) {
+ auto_clnt->connect = inet_connect;
+ } else if (strcmp(transport, "local") == 0) {
+ auto_clnt->connect = LOCAL_CONNECT;
+ } else if (strcmp(transport, "unix") == 0) {
+ auto_clnt->connect = unix_connect;
+ } else {
+ msg_fatal("invalid transport name: %s in service: %s",
+ transport, service);
+ }
+ myfree(transport);
+ return (auto_clnt);
+}
+
+/* auto_clnt_name - return client stream name */
+
+const char *auto_clnt_name(AUTO_CLNT *auto_clnt)
+{
+ return (auto_clnt->endpoint);
+}
+
+/* auto_clnt_free - destroy client stream instance */
+
+void auto_clnt_free(AUTO_CLNT *auto_clnt)
+{
+ if (auto_clnt->vstream)
+ auto_clnt_close(auto_clnt);
+ myfree(auto_clnt->endpoint);
+ myfree((void *) auto_clnt);
+}
+
+/* auto_clnt_control - fine control */
+
+void auto_clnt_control(AUTO_CLNT *client, int name,...)
+{
+ const char *myname = "auto_clnt_control";
+ va_list ap;
+
+ for (va_start(ap, name); name != AUTO_CLNT_CTL_END; name = va_arg(ap, int)) {
+ switch (name) {
+ case AUTO_CLNT_CTL_HANDSHAKE:
+ client->handshake = va_arg(ap, AUTO_CLNT_HANDSHAKE_FN);
+ break;
+ default:
+ msg_panic("%s: bad name %d", myname, name);
+ }
+ }
+ va_end(ap);
+}
diff --git a/src/util/auto_clnt.h b/src/util/auto_clnt.h
new file mode 100644
index 0000000..61c6680
--- /dev/null
+++ b/src/util/auto_clnt.h
@@ -0,0 +1,51 @@
+#ifndef _AUTO_CLNT_H_INCLUDED_
+#define _AUTO_CLNT_H_INCLUDED_
+
+/*++
+/* NAME
+/* auto_clnt 3h
+/* SUMMARY
+/* client endpoint maintenance
+/* SYNOPSIS
+/* #include <auto_clnt.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+
+ /*
+ * External interface.
+ */
+typedef struct AUTO_CLNT AUTO_CLNT;
+typedef int (*AUTO_CLNT_HANDSHAKE_FN) (VSTREAM *);
+
+extern AUTO_CLNT *auto_clnt_create(const char *, int, int, int);
+extern VSTREAM *auto_clnt_access(AUTO_CLNT *);
+extern void auto_clnt_recover(AUTO_CLNT *);
+extern const char *auto_clnt_name(AUTO_CLNT *);
+extern void auto_clnt_free(AUTO_CLNT *);
+extern void auto_clnt_control(AUTO_CLNT *, int,...);
+
+#define AUTO_CLNT_CTL_END 0
+#define AUTO_CLNT_CTL_HANDSHAKE 1 /* handshake before first request */
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/balpar.c b/src/util/balpar.c
new file mode 100644
index 0000000..6ff97eb
--- /dev/null
+++ b/src/util/balpar.c
@@ -0,0 +1,56 @@
+/*++
+/* NAME
+/* balpar 3
+/* SUMMARY
+/* determine length of string in parentheses
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* size_t balpar(string, parens)
+/* const char *string;
+/* const char *parens;
+/* DESCRIPTION
+/* balpar() determines the length of a string enclosed in
+/* the specified parentheses, zero in case of error.
+/* SEE ALSO
+/* A balpar() routine appears in Brian W. Kernighan, P.J. Plauger:
+/* "Software Tools", Addison-Wesley 1976. This function is different.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <stringops.h>
+
+/* balpar - return length of {text} */
+
+size_t balpar(const char *string, const char *parens)
+{
+ const char *cp;
+ int level;
+ int ch;
+
+ if (*string != parens[0])
+ return (0);
+ for (level = 1, cp = string + 1; (ch = *cp) != 0; cp++) {
+ if (ch == parens[1]) {
+ if (--level == 0)
+ return (cp - string + 1);
+ } else if (ch == parens[0]) {
+ level++;
+ }
+ }
+ return (0);
+}
diff --git a/src/util/base32_code.c b/src/util/base32_code.c
new file mode 100644
index 0000000..31566ea
--- /dev/null
+++ b/src/util/base32_code.c
@@ -0,0 +1,266 @@
+/*++
+/* NAME
+/* base32_code 3
+/* SUMMARY
+/* encode/decode data, base 32 style
+/* SYNOPSIS
+/* #include <base32_code.h>
+/*
+/* VSTRING *base32_encode(result, in, len)
+/* VSTRING *result;
+/* const char *in;
+/* ssize_t len;
+/*
+/* VSTRING *base32_decode(result, in, len)
+/* VSTRING *result;
+/* const char *in;
+/* ssize_t len;
+/* DESCRIPTION
+/* base32_encode() takes a block of len bytes and encodes it as one
+/* null-terminated string. The result value is the result argument.
+/*
+/* base32_decode() performs the opposite transformation. The result
+/* value is the result argument. The result is null terminated, whether
+/* or not that makes sense.
+/* DIAGNOSTICS
+/* base32_decode() returns a null pointer when the input contains
+/* characters not in the base 32 alphabet.
+/* SEE ALSO
+/* RFC 4648; padding is strictly enforced
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+
+#ifndef UCHAR_MAX
+#define UCHAR_MAX 0xff
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <base32_code.h>
+
+/* Application-specific. */
+
+static unsigned char to_b32[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+
+#define UNSIG_CHAR_PTR(x) ((unsigned char *)(x))
+
+/* base32_encode - raw data to encoded */
+
+VSTRING *base32_encode(VSTRING *result, const char *in, ssize_t len)
+{
+ const unsigned char *cp;
+ ssize_t count;
+ static int pad_count[] = {0, 6, 4, 3, 1};
+
+ /*
+ * Encode 5 -> 8.
+ */
+ VSTRING_RESET(result);
+ for (cp = UNSIG_CHAR_PTR(in), count = len; count > 0; count -= 5, cp += 5) {
+ VSTRING_ADDCH(result, to_b32[cp[0] >> 3]);
+ if (count < 2) {
+ VSTRING_ADDCH(result, to_b32[(cp[0] & 0x7) << 2]);
+ break;
+ }
+ VSTRING_ADDCH(result, to_b32[(cp[0] & 0x7) << 2 | cp[1] >> 6]);
+ VSTRING_ADDCH(result, to_b32[(cp[1] & 0x3f) >> 1]);
+ if (count < 3) {
+ VSTRING_ADDCH(result, to_b32[(cp[1] & 0x1) << 4]);
+ break;
+ }
+ VSTRING_ADDCH(result, to_b32[(cp[1] & 0x1) << 4 | cp[2] >> 4]);
+ if (count < 4) {
+ VSTRING_ADDCH(result, to_b32[(cp[2] & 0xf) << 1]);
+ break;
+ }
+ VSTRING_ADDCH(result, to_b32[(cp[2] & 0xf) << 1 | cp[3] >> 7]);
+ VSTRING_ADDCH(result, to_b32[(cp[3] & 0x7f) >> 2]);
+ if (count < 5) {
+ VSTRING_ADDCH(result, to_b32[(cp[3] & 0x3) << 3]);
+ break;
+ }
+ VSTRING_ADDCH(result, to_b32[(cp[3] & 0x3) << 3 | cp[4] >> 5]);
+ VSTRING_ADDCH(result, to_b32[cp[4] & 0x1f]);
+ }
+ if (count > 0)
+ vstring_strncat(result, "======", pad_count[count]);
+ VSTRING_TERMINATE(result);
+ return (result);
+}
+
+/* base32_decode - encoded data to raw */
+
+VSTRING *base32_decode(VSTRING *result, const char *in, ssize_t len)
+{
+ static unsigned char *un_b32 = 0;
+ const unsigned char *cp;
+ ssize_t count;
+ unsigned int ch0;
+ unsigned int ch1;
+ unsigned int ch2;
+ unsigned int ch3;
+ unsigned int ch4;
+ unsigned int ch5;
+ unsigned int ch6;
+ unsigned int ch7;
+
+#define CHARS_PER_BYTE (UCHAR_MAX + 1)
+#define INVALID 0xff
+#if 1
+#define ENFORCE_LENGTH(x) (x)
+#define ENFORCE_PADDING(x) (x)
+#define ENFORCE_NULL_BITS(x) (x)
+#else
+#define ENFORCE_LENGTH(x) (1)
+#define ENFORCE_PADDING(x) (1)
+#define ENFORCE_NULL_BITS(x) (1)
+#endif
+
+ /*
+ * Sanity check.
+ */
+ if (ENFORCE_LENGTH(len % 8))
+ return (0);
+
+ /*
+ * Once: initialize the decoding lookup table on the fly.
+ */
+ if (un_b32 == 0) {
+ un_b32 = (unsigned char *) mymalloc(CHARS_PER_BYTE);
+ memset(un_b32, INVALID, CHARS_PER_BYTE);
+ for (cp = to_b32; cp < to_b32 + sizeof(to_b32) - 1; cp++)
+ un_b32[*cp] = cp - to_b32;
+ }
+
+ /*
+ * Decode 8 -> 5.
+ */
+ VSTRING_RESET(result);
+ for (cp = UNSIG_CHAR_PTR(in), count = 0; count < len; count += 8) {
+ if ((ch0 = un_b32[*cp++]) == INVALID
+ || (ch1 = un_b32[*cp++]) == INVALID)
+ return (0);
+ VSTRING_ADDCH(result, ch0 << 3 | ch1 >> 2);
+ if ((ch2 = *cp++) == '='
+ && ENFORCE_PADDING(strcmp((char *) cp, "=====") == 0)
+ && ENFORCE_NULL_BITS((ch1 & 0x3) == 0))
+ break;
+ if ((ch2 = un_b32[ch2]) == INVALID)
+ return (0);
+ if ((ch3 = un_b32[*cp++]) == INVALID)
+ return (0);
+ VSTRING_ADDCH(result, ch1 << 6 | ch2 << 1 | ch3 >> 4);
+ if ((ch4 = *cp++) == '='
+ && ENFORCE_PADDING(strcmp((char *) cp, "===") == 0)
+ && ENFORCE_NULL_BITS((ch3 & 0xf) == 0))
+ break;
+ if ((ch4 = un_b32[ch4]) == INVALID)
+ return (0);
+ VSTRING_ADDCH(result, ch3 << 4 | ch4 >> 1);
+ if ((ch5 = *cp++) == '='
+ && ENFORCE_PADDING(strcmp((char *) cp, "==") == 0)
+ && ENFORCE_NULL_BITS((ch4 & 0x1) == 0))
+ break;
+ if ((ch5 = un_b32[ch5]) == INVALID)
+ return (0);
+ if ((ch6 = un_b32[*cp++]) == INVALID)
+ return (0);
+ VSTRING_ADDCH(result, ch4 << 7 | ch5 << 2 | ch6 >> 3);
+ if ((ch7 = *cp++) == '='
+ && ENFORCE_NULL_BITS((ch6 & 0x7) == 0))
+ break;
+ if ((ch7 = un_b32[ch7]) == INVALID)
+ return (0);
+ VSTRING_ADDCH(result, ch6 << 5 | ch7);
+ }
+ VSTRING_TERMINATE(result);
+ return (result);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program: convert to base 32 and back.
+ */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *b1 = vstring_alloc(1);
+ VSTRING *b2 = vstring_alloc(1);
+ VSTRING *test = vstring_alloc(1);
+ int i, j;
+
+ /*
+ * Test all byte values (except null) on all byte positions.
+ */
+ for (j = 0; j < 256; j++)
+ for (i = 1; i < 256; i++)
+ VSTRING_ADDCH(test, i);
+ VSTRING_TERMINATE(test);
+
+#define DECODE(b,x,l) { \
+ if (base32_decode((b),(x),(l)) == 0) \
+ msg_panic("bad base32: %s", (x)); \
+ }
+#define VERIFY(b,t,l) { \
+ if (memcmp((b), (t), (l)) != 0) \
+ msg_panic("bad test: %s", (b)); \
+ }
+
+ /*
+ * Test all padding variants.
+ */
+ for (i = 1; i <= 8; i++) {
+ base32_encode(b1, STR(test), LEN(test));
+ DECODE(b2, STR(b1), LEN(b1));
+ VERIFY(STR(b2), STR(test), LEN(test));
+
+ base32_encode(b1, STR(test), LEN(test));
+ base32_encode(b2, STR(b1), LEN(b1));
+ base32_encode(b1, STR(b2), LEN(b2));
+ DECODE(b2, STR(b1), LEN(b1));
+ DECODE(b1, STR(b2), LEN(b2));
+ DECODE(b2, STR(b1), LEN(b1));
+ VERIFY(STR(b2), STR(test), LEN(test));
+
+ base32_encode(b1, STR(test), LEN(test));
+ base32_encode(b2, STR(b1), LEN(b1));
+ base32_encode(b1, STR(b2), LEN(b2));
+ base32_encode(b2, STR(b1), LEN(b1));
+ base32_encode(b1, STR(b2), LEN(b2));
+ DECODE(b2, STR(b1), LEN(b1));
+ DECODE(b1, STR(b2), LEN(b2));
+ DECODE(b2, STR(b1), LEN(b1));
+ DECODE(b1, STR(b2), LEN(b2));
+ DECODE(b2, STR(b1), LEN(b1));
+ VERIFY(STR(b2), STR(test), LEN(test));
+ vstring_truncate(test, LEN(test) - 1);
+ }
+ vstring_free(test);
+ vstring_free(b1);
+ vstring_free(b2);
+ return (0);
+}
+
+#endif
diff --git a/src/util/base32_code.h b/src/util/base32_code.h
new file mode 100644
index 0000000..aa258fe
--- /dev/null
+++ b/src/util/base32_code.h
@@ -0,0 +1,41 @@
+#ifndef _BASE32_CODE_H_INCLUDED_
+#define _BASE32_CODE_H_INCLUDED_
+
+/*++
+/* NAME
+/* base32_code 3h
+/* SUMMARY
+/* encode/decode data, base 32 style
+/* SYNOPSIS
+/* #include <base32_code.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern VSTRING *base32_encode(VSTRING *, const char *, ssize_t);
+extern VSTRING *WARN_UNUSED_RESULT base32_decode(VSTRING *, const char *, ssize_t);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/base64_code.c b/src/util/base64_code.c
new file mode 100644
index 0000000..e607218
--- /dev/null
+++ b/src/util/base64_code.c
@@ -0,0 +1,228 @@
+/*++
+/* NAME
+/* base64_code 3
+/* SUMMARY
+/* encode/decode data, base 64 style
+/* SYNOPSIS
+/* #include <base64_code.h>
+/*
+/* VSTRING *base64_encode(result, in, len)
+/* VSTRING *result;
+/* const char *in;
+/* ssize_t len;
+/*
+/* VSTRING *base64_decode(result, in, len)
+/* VSTRING *result;
+/* const char *in;
+/* ssize_t len;
+/*
+/* VSTRING *base64_encode_opt(result, in, len, flags)
+/* VSTRING *result;
+/* const char *in;
+/* ssize_t len;
+/* int flags;
+/*
+/* VSTRING *base64_decode_opt(result, in, len, flags)
+/* VSTRING *result;
+/* const char *in;
+/* ssize_t len;
+/* int flags;
+/* DESCRIPTION
+/* base64_encode() takes a block of len bytes and encodes it as one
+/* null-terminated string. The result value is the result argument.
+/*
+/* base64_decode() performs the opposite transformation. The result
+/* value is the result argument. The result is null terminated, whether
+/* or not that makes sense.
+/*
+/* base64_encode_opt() and base64_decode_opt() provide extended
+/* interfaces. In both cases the flags arguments is the bit-wise
+/* OR of zero or more the following:
+/* .IP BASE64_FLAG_APPEND
+/* Append the result, instead of overwriting the result buffer.
+/* .PP
+/* For convenience, BASE64_FLAG_NONE specifies none of the above.
+/* DIAGNOSTICS
+/* base64_decode () returns a null pointer when the input contains
+/* characters not in the base 64 alphabet.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+
+#ifndef UCHAR_MAX
+#define UCHAR_MAX 0xff
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <base64_code.h>
+
+/* Application-specific. */
+
+static unsigned char to_b64[] =
+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+#define UNSIG_CHAR_PTR(x) ((unsigned char *)(x))
+
+/* base64_encode - raw data to encoded */
+
+#undef base64_encode
+
+extern VSTRING *base64_encode(VSTRING *, const char *, ssize_t);
+
+VSTRING *base64_encode(VSTRING *result, const char *in, ssize_t len)
+{
+ return (base64_encode_opt(result, in, len, BASE64_FLAG_NONE));
+}
+
+VSTRING *base64_encode_opt(VSTRING *result, const char *in, ssize_t len,
+ int flags)
+{
+ const unsigned char *cp;
+ ssize_t count;
+
+ /*
+ * Encode 3 -> 4.
+ */
+ if ((flags & BASE64_FLAG_APPEND) == 0)
+ VSTRING_RESET(result);
+ for (cp = UNSIG_CHAR_PTR(in), count = len; count > 0; count -= 3, cp += 3) {
+ VSTRING_ADDCH(result, to_b64[cp[0] >> 2]);
+ if (count > 1) {
+ VSTRING_ADDCH(result, to_b64[(cp[0] & 0x3) << 4 | cp[1] >> 4]);
+ if (count > 2) {
+ VSTRING_ADDCH(result, to_b64[(cp[1] & 0xf) << 2 | cp[2] >> 6]);
+ VSTRING_ADDCH(result, to_b64[cp[2] & 0x3f]);
+ } else {
+ VSTRING_ADDCH(result, to_b64[(cp[1] & 0xf) << 2]);
+ VSTRING_ADDCH(result, '=');
+ break;
+ }
+ } else {
+ VSTRING_ADDCH(result, to_b64[(cp[0] & 0x3) << 4]);
+ VSTRING_ADDCH(result, '=');
+ VSTRING_ADDCH(result, '=');
+ break;
+ }
+ }
+ VSTRING_TERMINATE(result);
+ return (result);
+}
+
+/* base64_decode - encoded data to raw */
+
+#undef base64_decode
+
+extern VSTRING *base64_decode(VSTRING *, const char *, ssize_t);
+
+VSTRING *base64_decode(VSTRING *result, const char *in, ssize_t len)
+{
+ return (base64_decode_opt(result, in, len, BASE64_FLAG_NONE));
+}
+
+VSTRING *base64_decode_opt(VSTRING *result, const char *in, ssize_t len,
+ int flags)
+{
+ static unsigned char *un_b64 = 0;
+ const unsigned char *cp;
+ ssize_t count;
+ unsigned int ch0;
+ unsigned int ch1;
+ unsigned int ch2;
+ unsigned int ch3;
+
+#define CHARS_PER_BYTE (UCHAR_MAX + 1)
+#define INVALID 0xff
+
+ /*
+ * Sanity check.
+ */
+ if (len % 4)
+ return (0);
+
+ /*
+ * Once: initialize the decoding lookup table on the fly.
+ */
+ if (un_b64 == 0) {
+ un_b64 = (unsigned char *) mymalloc(CHARS_PER_BYTE);
+ memset(un_b64, INVALID, CHARS_PER_BYTE);
+ for (cp = to_b64; cp < to_b64 + sizeof(to_b64) - 1; cp++)
+ un_b64[*cp] = cp - to_b64;
+ }
+
+ /*
+ * Decode 4 -> 3.
+ */
+ if ((flags & BASE64_FLAG_APPEND) == 0)
+ VSTRING_RESET(result);
+ for (cp = UNSIG_CHAR_PTR(in), count = 0; count < len; count += 4) {
+ if ((ch0 = un_b64[*cp++]) == INVALID
+ || (ch1 = un_b64[*cp++]) == INVALID)
+ return (0);
+ VSTRING_ADDCH(result, ch0 << 2 | ch1 >> 4);
+ if ((ch2 = *cp++) == '=')
+ break;
+ if ((ch2 = un_b64[ch2]) == INVALID)
+ return (0);
+ VSTRING_ADDCH(result, ch1 << 4 | ch2 >> 2);
+ if ((ch3 = *cp++) == '=')
+ break;
+ if ((ch3 = un_b64[ch3]) == INVALID)
+ return (0);
+ VSTRING_ADDCH(result, ch2 << 6 | ch3);
+ }
+ VSTRING_TERMINATE(result);
+ return (result);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program: convert to base 64 and back.
+ */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *b1 = vstring_alloc(1);
+ VSTRING *b2 = vstring_alloc(1);
+ char test[256];
+ int n;
+
+ for (n = 0; n < sizeof(test); n++)
+ test[n] = n;
+ base64_encode(b1, test, sizeof(test));
+ if (base64_decode(b2, STR(b1), LEN(b1)) == 0)
+ msg_panic("bad base64: %s", STR(b1));
+ if (LEN(b2) != sizeof(test))
+ msg_panic("bad decode length: %ld != %ld",
+ (long) LEN(b2), (long) sizeof(test));
+ for (n = 0; n < sizeof(test); n++)
+ if (STR(b2)[n] != test[n])
+ msg_panic("bad decode value %d != %d",
+ (unsigned char) STR(b2)[n], (unsigned char) test[n]);
+ vstring_free(b1);
+ vstring_free(b2);
+ return (0);
+}
+
+#endif
diff --git a/src/util/base64_code.h b/src/util/base64_code.h
new file mode 100644
index 0000000..fefdf4e
--- /dev/null
+++ b/src/util/base64_code.h
@@ -0,0 +1,49 @@
+#ifndef _BASE64_CODE_H_INCLUDED_
+#define _BASE64_CODE_H_INCLUDED_
+
+/*++
+/* NAME
+/* base64_code 3h
+/* SUMMARY
+/* encode/decode data, base 64 style
+/* SYNOPSIS
+/* #include <base64_code.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern VSTRING *base64_encode_opt(VSTRING *, const char *, ssize_t, int);
+extern VSTRING *WARN_UNUSED_RESULT base64_decode_opt(VSTRING *, const char *, ssize_t, int);
+
+#define BASE64_FLAG_NONE 0
+#define BASE64_FLAG_APPEND (1<<0)
+
+#define base64_encode(bp, cp, ln) \
+ base64_encode_opt((bp), (cp), (ln), BASE64_FLAG_NONE)
+#define base64_decode(bp, cp, ln) \
+ base64_decode_opt((bp), (cp), (ln), BASE64_FLAG_NONE)
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/basename.c b/src/util/basename.c
new file mode 100644
index 0000000..429e6cf
--- /dev/null
+++ b/src/util/basename.c
@@ -0,0 +1,49 @@
+/*++
+/* NAME
+/* basename 3
+/* SUMMARY
+/* extract file basename
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* char *basename(path)
+/* const char *path;
+/* DESCRIPTION
+/* The \fBbasename\fR routine skips over the last '/' in
+/* \fIpath\fR and returns a pointer to the result.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+
+#ifndef HAVE_BASENAME
+
+/* Utility library. */
+
+#include "stringops.h"
+
+/* basename - skip directory prefix */
+
+char *basename(const char *path)
+{
+ char *result;
+
+ if ((result = strrchr(path, '/')) == 0)
+ result = (char *) path;
+ else
+ result += 1;
+ return (result);
+}
+
+#endif
diff --git a/src/util/binhash.c b/src/util/binhash.c
new file mode 100644
index 0000000..fd9bc87
--- /dev/null
+++ b/src/util/binhash.c
@@ -0,0 +1,447 @@
+/*++
+/* NAME
+/* binhash 3
+/* SUMMARY
+/* hash table manager
+/* SYNOPSIS
+/* #include <binhash.h>
+/*
+/* typedef struct {
+/* .in +4
+/* void *key;
+/* ssize_t key_len;
+/* void *value;
+/* /* private fields... */
+/* .in -4
+/* } BINHASH_INFO;
+/*
+/* BINHASH *binhash_create(size)
+/* ssize_t size;
+/*
+/* BINHASH_INFO *binhash_enter(table, key, key_len, value)
+/* BINHASH *table;
+/* const void *key;
+/* ssize_t key_len;
+/* void *value;
+/*
+/* char *binhash_find(table, key, key_len)
+/* BINHASH *table;
+/* const void *key;
+/* ssize_t key_len;
+/*
+/* BINHASH_INFO *binhash_locate(table, key, key_len)
+/* BINHASH *table;
+/* const void *key;
+/* ssize_t key_len;
+/*
+/* void binhash_delete(table, key, key_len, free_fn)
+/* BINHASH *table;
+/* const void *key;
+/* ssize_t key_len;
+/* void (*free_fn)(void *);
+/*
+/* void binhash_free(table, free_fn)
+/* BINHASH *table;
+/* void (*free_fn)(void *);
+/*
+/* void binhash_walk(table, action, ptr)
+/* BINHASH *table;
+/* void (*action)(BINHASH_INFO *info, void *ptr);
+/* void *ptr;
+/*
+/* BINHASH_INFO **binhash_list(table)
+/* BINHASH *table;
+/*
+/* BINHASH_INFO *binhash_sequence(table, how)
+/* BINHASH *table;
+/* int how;
+/* DESCRIPTION
+/* This module maintains one or more hash tables. Each table entry
+/* consists of a unique binary-valued lookup key and a generic
+/* character-pointer value.
+/* The tables are automatically resized when they fill up. When the
+/* values to be remembered are not character pointers, proper casts
+/* should be used or the code will not be portable.
+/*
+/* binhash_create() creates a table of the specified size and returns a
+/* pointer to the result. The lookup keys are saved with mymemdup().
+/*
+/* binhash_enter() stores a (key, value) pair into the specified table
+/* and returns a pointer to the resulting entry. The code does not
+/* check if an entry with that key already exists: use binhash_locate()
+/* for updating an existing entry. The key is copied; the value is not.
+/*
+/* binhash_find() returns the value that was stored under the given key,
+/* or a null pointer if it was not found. In order to distinguish
+/* a null value from a non-existent value, use binhash_locate().
+/*
+/* binhash_locate() returns a pointer to the entry that was stored
+/* for the given key, or a null pointer if it was not found.
+/*
+/* binhash_delete() removes one entry that was stored under the given key.
+/* If the free_fn argument is not a null pointer, the corresponding
+/* function is called with as argument the value that was stored under
+/* the key.
+/*
+/* binhash_free() destroys a hash table, including contents. If the free_fn
+/* argument is not a null pointer, the corresponding function is called
+/* for each table entry, with as argument the value that was stored
+/* with the entry.
+/*
+/* binhash_walk() invokes the action function for each table entry, with
+/* a pointer to the entry as its argument. The ptr argument is passed
+/* on to the action function.
+/*
+/* binhash_list() returns a null-terminated list of pointers to
+/* all elements in the named table. The list should be passed to
+/* myfree().
+/*
+/* binhash_sequence() returns the first or next element
+/* depending on the value of the "how" argument. Specify
+/* BINHASH_SEQ_FIRST to start a new sequence, BINHASH_SEQ_NEXT
+/* to continue, and BINHASH_SEQ_STOP to terminate a sequence
+/* early. The caller must not delete an element before it is
+/* visited.
+/* RESTRICTIONS
+/* A callback function should not modify the hash table that is
+/* specified to its caller.
+/* DIAGNOSTICS
+/* The following conditions are reported and cause the program to
+/* terminate immediately: memory allocation failure; an attempt
+/* to delete a non-existent entry.
+/* SEE ALSO
+/* mymalloc(3) memory management wrapper
+/* hash_fnv(3) Fowler/Noll/Vo hash function
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* C library */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Local stuff */
+
+#include "mymalloc.h"
+#include "msg.h"
+#include "binhash.h"
+
+/* binhash_hash - hash a string */
+
+#ifndef NO_HASH_FNV
+#include "hash_fnv.h"
+
+#define binhash_hash(key, len, size) (hash_fnv((key), (len)) % (size))
+
+#else
+
+static size_t binhash_hash(const void *key, ssize_t len, size_t size)
+{
+ size_t h = 0;
+ size_t g;
+
+ /*
+ * From the "Dragon" book by Aho, Sethi and Ullman.
+ */
+
+ while (len-- > 0) {
+ h = (h << 4U) + *(unsigned const char *) key++;
+ if ((g = (h & 0xf0000000)) != 0) {
+ h ^= (g >> 24U);
+ h ^= g;
+ }
+ }
+ return (h % size);
+}
+
+#endif
+
+/* binhash_link - insert element into table */
+
+#define binhash_link(table, elm) { \
+ BINHASH_INFO **_h = table->data + binhash_hash(elm->key, elm->key_len, table->size);\
+ elm->prev = 0; \
+ if ((elm->next = *_h) != 0) \
+ (*_h)->prev = elm; \
+ *_h = elm; \
+ table->used++; \
+}
+
+/* binhash_size - allocate and initialize hash table */
+
+static void binhash_size(BINHASH *table, size_t size)
+{
+ BINHASH_INFO **h;
+
+ size |= 1;
+
+ table->data = h = (BINHASH_INFO **) mymalloc(size * sizeof(BINHASH_INFO *));
+ table->size = size;
+ table->used = 0;
+
+ while (size-- > 0)
+ *h++ = 0;
+}
+
+/* binhash_create - create initial hash table */
+
+BINHASH *binhash_create(ssize_t size)
+{
+ BINHASH *table;
+
+ table = (BINHASH *) mymalloc(sizeof(BINHASH));
+ binhash_size(table, size < 13 ? 13 : size);
+ table->seq_bucket = table->seq_element = 0;
+ return (table);
+}
+
+/* binhash_grow - extend existing table */
+
+static void binhash_grow(BINHASH *table)
+{
+ BINHASH_INFO *ht;
+ BINHASH_INFO *next;
+ ssize_t old_size = table->size;
+ BINHASH_INFO **h = table->data;
+ BINHASH_INFO **old_entries = h;
+
+ binhash_size(table, 2 * old_size);
+
+ while (old_size-- > 0) {
+ for (ht = *h++; ht; ht = next) {
+ next = ht->next;
+ binhash_link(table, ht);
+ }
+ }
+ myfree((void *) old_entries);
+}
+
+/* binhash_enter - enter (key, value) pair */
+
+BINHASH_INFO *binhash_enter(BINHASH *table, const void *key, ssize_t key_len, void *value)
+{
+ BINHASH_INFO *ht;
+
+ if (table->used >= table->size)
+ binhash_grow(table);
+ ht = (BINHASH_INFO *) mymalloc(sizeof(BINHASH_INFO));
+ ht->key = mymemdup(key, key_len);
+ ht->key_len = key_len;
+ ht->value = value;
+ binhash_link(table, ht);
+ return (ht);
+}
+
+/* binhash_find - lookup value */
+
+void *binhash_find(BINHASH *table, const void *key, ssize_t key_len)
+{
+ BINHASH_INFO *ht;
+
+#define KEY_EQ(x,y,l) (((unsigned char *) x)[0] == ((unsigned char *) y)[0] && memcmp(x,y,l) == 0)
+
+ if (table != 0)
+ for (ht = table->data[binhash_hash(key, key_len, table->size)]; ht; ht = ht->next)
+ if (key_len == ht->key_len && KEY_EQ(key, ht->key, key_len))
+ return (ht->value);
+ return (0);
+}
+
+/* binhash_locate - lookup entry */
+
+BINHASH_INFO *binhash_locate(BINHASH *table, const void *key, ssize_t key_len)
+{
+ BINHASH_INFO *ht;
+
+ if (table != 0)
+ for (ht = table->data[binhash_hash(key, key_len, table->size)]; ht; ht = ht->next)
+ if (key_len == ht->key_len && KEY_EQ(key, ht->key, key_len))
+ return (ht);
+ return (0);
+}
+
+/* binhash_delete - delete one entry */
+
+void binhash_delete(BINHASH *table, const void *key, ssize_t key_len, void (*free_fn) (void *))
+{
+ if (table != 0) {
+ BINHASH_INFO *ht;
+ BINHASH_INFO **h = table->data + binhash_hash(key, key_len, table->size);
+
+ for (ht = *h; ht; ht = ht->next) {
+ if (key_len == ht->key_len && KEY_EQ(key, ht->key, key_len)) {
+ if (ht->next)
+ ht->next->prev = ht->prev;
+ if (ht->prev)
+ ht->prev->next = ht->next;
+ else
+ *h = ht->next;
+ table->used--;
+ myfree(ht->key);
+ if (free_fn)
+ (*free_fn) (ht->value);
+ myfree((void *) ht);
+ return;
+ }
+ }
+ msg_panic("binhash_delete: unknown_key: \"%s\"", (char *) key);
+ }
+}
+
+/* binhash_free - destroy hash table */
+
+void binhash_free(BINHASH *table, void (*free_fn) (void *))
+{
+ if (table != 0) {
+ ssize_t i = table->size;
+ BINHASH_INFO *ht;
+ BINHASH_INFO *next;
+ BINHASH_INFO **h = table->data;
+
+ while (i-- > 0) {
+ for (ht = *h++; ht; ht = next) {
+ next = ht->next;
+ myfree(ht->key);
+ if (free_fn)
+ (*free_fn) (ht->value);
+ myfree((void *) ht);
+ }
+ }
+ myfree((void *) table->data);
+ table->data = 0;
+ if (table->seq_bucket)
+ myfree((void *) table->seq_bucket);
+ table->seq_bucket = 0;
+ myfree((void *) table);
+ }
+}
+
+/* binhash_walk - iterate over hash table */
+
+void binhash_walk(BINHASH *table, void (*action) (BINHASH_INFO *, void *),
+ void *ptr) {
+ if (table != 0) {
+ ssize_t i = table->size;
+ BINHASH_INFO **h = table->data;
+ BINHASH_INFO *ht;
+
+ while (i-- > 0)
+ for (ht = *h++; ht; ht = ht->next)
+ (*action) (ht, ptr);
+ }
+}
+
+/* binhash_list - list all table members */
+
+BINHASH_INFO **binhash_list(table)
+BINHASH *table;
+{
+ BINHASH_INFO **list;
+ BINHASH_INFO *member;
+ ssize_t count = 0;
+ ssize_t i;
+
+ if (table != 0) {
+ list = (BINHASH_INFO **) mymalloc(sizeof(*list) * (table->used + 1));
+ for (i = 0; i < table->size; i++)
+ for (member = table->data[i]; member != 0; member = member->next)
+ list[count++] = member;
+ } else {
+ list = (BINHASH_INFO **) mymalloc(sizeof(*list));
+ }
+ list[count] = 0;
+ return (list);
+}
+
+/* binhash_sequence - dict(3) compatibility iterator */
+
+BINHASH_INFO *binhash_sequence(BINHASH *table, int how)
+{
+ if (table == 0)
+ return (0);
+
+ switch (how) {
+ case BINHASH_SEQ_FIRST: /* start new sequence */
+ if (table->seq_bucket)
+ myfree((void *) table->seq_bucket);
+ table->seq_bucket = binhash_list(table);
+ table->seq_element = table->seq_bucket;
+ return (*(table->seq_element)++);
+ case BINHASH_SEQ_NEXT: /* next element */
+ if (table->seq_element && *table->seq_element)
+ return (*(table->seq_element)++);
+ /* FALLTHROUGH */
+ default: /* terminate sequence */
+ if (table->seq_bucket) {
+ myfree((void *) table->seq_bucket);
+ table->seq_bucket = table->seq_element = 0;
+ }
+ return (0);
+ }
+}
+
+#ifdef TEST
+#include <vstring_vstream.h>
+#include <myrand.h>
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *buf = vstring_alloc(10);
+ ssize_t count = 0;
+ BINHASH *hash;
+ BINHASH_INFO **ht_info;
+ BINHASH_INFO **ht;
+ BINHASH_INFO *info;
+ ssize_t i;
+ ssize_t r;
+ int op;
+
+ /*
+ * Load a large number of strings including terminator, and delete them
+ * in a random order.
+ */
+ hash = binhash_create(10);
+ while (vstring_get(buf, VSTREAM_IN) != VSTREAM_EOF)
+ binhash_enter(hash, vstring_str(buf), VSTRING_LEN(buf) + 1,
+ CAST_INT_TO_VOID_PTR(count++));
+ if (count != hash->used)
+ msg_panic("%ld entries stored, but %lu entries exist",
+ (long) count, (unsigned long) hash->used);
+ for (i = 0, op = BINHASH_SEQ_FIRST; (info = binhash_sequence(hash, op)) != 0;
+ i++, op = BINHASH_SEQ_NEXT)
+ if (memchr(info->key, 0, info->key_len) == 0)
+ msg_panic("no null byte in lookup key");
+ if (i != hash->used)
+ msg_panic("%ld entries found, but %lu entries exist",
+ (long) i, (unsigned long) hash->used);
+ ht_info = binhash_list(hash);
+ for (i = 0; i < hash->used; i++) {
+ r = myrand() % hash->used;
+ info = ht_info[i];
+ ht_info[i] = ht_info[r];
+ ht_info[r] = info;
+ }
+ for (ht = ht_info; *ht; ht++)
+ binhash_delete(hash, ht[0]->key, ht[0]->key_len, (void (*) (void *)) 0);
+ if (hash->used > 0)
+ msg_panic("%ld entries not deleted", (long) hash->used);
+ myfree((void *) ht_info);
+ binhash_free(hash, (void (*) (void *)) 0);
+ vstring_free(buf);
+ return (0);
+}
+
+#endif
diff --git a/src/util/binhash.h b/src/util/binhash.h
new file mode 100644
index 0000000..3b4e1a4
--- /dev/null
+++ b/src/util/binhash.h
@@ -0,0 +1,65 @@
+#ifndef _BINHASH_H_INCLUDED_
+#define _BINHASH_H_INCLUDED_
+
+/*++
+/* NAME
+/* binhash 3h
+/* SUMMARY
+/* hash table manager
+/* SYNOPSIS
+/* #include <binhash.h>
+/* DESCRIPTION
+/* .nf
+
+ /* Structure of one hash table entry. */
+
+typedef struct BINHASH_INFO {
+ void *key; /* lookup key */
+ ssize_t key_len; /* key length */
+ void *value; /* associated value */
+ struct BINHASH_INFO *next; /* colliding entry */
+ struct BINHASH_INFO *prev; /* colliding entry */
+} BINHASH_INFO;
+
+ /* Structure of one hash table. */
+
+typedef struct BINHASH {
+ ssize_t size; /* length of entries array */
+ ssize_t used; /* number of entries in table */
+ BINHASH_INFO **data; /* entries array, auto-resized */
+ BINHASH_INFO **seq_bucket; /* current sequence hash bucket */
+ BINHASH_INFO **seq_element; /* current sequence element */
+} BINHASH;
+
+extern BINHASH *binhash_create(ssize_t);
+extern BINHASH_INFO *binhash_enter(BINHASH *, const void *, ssize_t, void *);
+extern BINHASH_INFO *binhash_locate(BINHASH *, const void *, ssize_t);
+extern void *binhash_find(BINHASH *, const void *, ssize_t);
+extern void binhash_delete(BINHASH *, const void *, ssize_t, void (*) (void *));
+extern void binhash_free(BINHASH *, void (*) (void *));
+extern void binhash_walk(BINHASH *, void (*) (BINHASH_INFO *, void *), void *);
+extern BINHASH_INFO **binhash_list(BINHASH *);
+extern BINHASH_INFO *binhash_sequence(BINHASH *, int);
+
+#define BINHASH_SEQ_FIRST 0
+#define BINHASH_SEQ_NEXT 1
+#define BINHASH_SEQ_STOP (-1)
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/* CREATION DATE
+/* Thu Feb 20 16:54:29 EST 1997
+/* LAST MODIFICATION
+/* %E% %U%
+/* VERSION/RELEASE
+/* %I%
+/*--*/
+
+#endif
diff --git a/src/util/byte_mask.c b/src/util/byte_mask.c
new file mode 100644
index 0000000..e818476
--- /dev/null
+++ b/src/util/byte_mask.c
@@ -0,0 +1,306 @@
+/*++
+/* NAME
+/* byte_mask 3
+/* SUMMARY
+/* map byte sequence to bit mask
+/* SYNOPSIS
+/* #include <byte_mask.h>
+/*
+/* typedef struct {
+/* .in +4
+/* int byte_val;
+/* int mask;
+/* .in -4
+/* } BYTE_MASK;
+/*
+/* int byte_mask(
+/* const char *context,
+/* const BYTE_MASK *table,
+/* const char *bytes);
+/*
+/* const char *str_byte_mask(
+/* const char *context,
+/* const BYTE_MASK *table,
+/* int mask);
+/*
+/* int byte_mask_opt(
+/* const char *context;
+/* const BYTE_MASK *table,
+/* const char *bytes,
+/* int flags);
+/*
+/* const char *str_byte_mask_opt(
+/* VSTRING *buf,
+/* const char *context,
+/* const BYTE_MASK *table,
+/* int mask,
+/* int flags);
+/* DESCRIPTION
+/* byte_mask() takes a null-terminated \fItable\fR with (byte
+/* value, single-bit mask) pairs and computes the bit-wise OR
+/* of the masks that correspond to the byte values pointed to
+/* by the \fIbytes\fR argument.
+/*
+/* str_byte_mask() translates a bit mask into its equivalent
+/* bytes. The result is written to a static buffer that is
+/* overwritten upon each call.
+/*
+/* byte_mask_opt() and str_byte_mask_opt() are extended versions
+/* with additional fine control.
+/*
+/* Arguments:
+/* .IP buf
+/* Null pointer or pointer to buffer storage.
+/* .IP context
+/* What kind of byte values and bit masks are being manipulated,
+/* reported in error messages. Typically, this would be the
+/* name of a user-configurable parameter or command-line
+/* attribute.
+/* .IP table
+/* Table with (byte value, single-bit mask) pairs.
+/* .IP bytes
+/* A null-terminated string that is to be converted into a bit
+/* mask.
+/* .IP mask
+/* A bit mask that is to be converted into null-terminated
+/* string.
+/* .IP flags
+/* Bit-wise OR of one or more of the following. Where features
+/* would have conflicting results (e.g., FATAL versus IGNORE),
+/* the feature that takes precedence is described first.
+/*
+/* When converting from string to mask, at least one of the
+/* following must be specified: BYTE_MASK_FATAL, BYTE_MASK_RETURN,
+/* BYTE_MASK_WARN or BYTE_MASK_IGNORE.
+/*
+/* When converting from mask to string, at least one of the
+/* following must be specified: BYTE_MASK_FATAL, BYTE_MASK_RETURN,
+/* BYTE_MASK_WARN or BYTE_MASK_IGNORE.
+/* .RS
+/* .IP BYTE_MASK_FATAL
+/* Require that all values in \fIbytes\fR exist in \fItable\fR,
+/* and require that all bits listed in \fImask\fR exist in
+/* \fItable\fR. Terminate with a fatal run-time error if this
+/* condition is not met. This feature is enabled by default
+/* when calling byte_mask() or str_name_mask().
+/* .IP BYTE_MASK_RETURN
+/* Require that all values in \fIbytes\fR exist in \fItable\fR,
+/* and require that all bits listed in \fImask\fR exist in
+/* \fItable\fR. Log a warning, and return 0 (byte_mask()) or
+/* a null pointer (str_byte_mask()) if this condition is not
+/* met. This feature is not enabled by default when calling
+/* byte_mask() or str_name_mask().
+/* .IP BYTE_MASK_WARN
+/* Require that all values in \fIbytes\fR exist in \fItable\fR,
+/* and require that all bits listed in \fImask\fR exist in
+/* \fItable\fR. Log a warning if this condition is not met,
+/* continue processing, and return all valid bits or bytes.
+/* This feature is not enabled by default when calling byte_mask()
+/* or str_byte_mask().
+/* .IP BYTE_MASK_IGNORE
+/* Silently ignore values in \fIbytes\fR that don't exist in
+/* \fItable\fR, and silently ignore bits listed in \fImask\fR
+/* that don't exist in \fItable\fR. This feature is not enabled
+/* by default when calling byte_mask() or str_byte_mask().
+/* .IP BYTE_MASK_ANY_CASE
+/* Enable case-insensitive matching. This feature is not
+/* enabled by default when calling byte_mask(); it has no
+/* effect with str_byte_mask().
+/* .RE
+/* The value BYTE_MASK_NONE explicitly requests no features,
+/* and BYTE_MASK_DEFAULT enables the default options.
+/* DIAGNOSTICS
+/* Fatal: the \fIbytes\fR argument specifies a name not found in
+/* \fItable\fR, or the \fImask\fR specifies a bit not found in
+/* \fItable\fR.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* HISTORY
+/* This code is a clone of Postfix name_mask(3).
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <byte_mask.h>
+#include <vstring.h>
+
+#define STR(x) vstring_str(x)
+
+/* byte_mask_opt - compute mask corresponding to byte string */
+
+int byte_mask_opt(const char *context, const BYTE_MASK *table,
+ const char *bytes, int flags)
+{
+ const char myname[] = "byte_mask";
+ const char *bp;
+ int result = 0;
+ const BYTE_MASK *np;
+
+ if ((flags & BYTE_MASK_REQUIRED) == 0)
+ msg_panic("%s: missing BYTE_MASK_FATAL/RETURN/WARN/IGNORE flag",
+ myname);
+
+ /*
+ * Iterate over bytes string, and look up each byte in the table. If the
+ * byte is found, merge its mask with the result.
+ */
+ for (bp = bytes; *bp; bp++) {
+ int byte_val = *(const unsigned char *) bp;
+
+ for (np = table; /* void */ ; np++) {
+ if (np->byte_val == 0) {
+ if (flags & BYTE_MASK_FATAL) {
+ msg_fatal("unknown %s value \"%c\" in \"%s\"",
+ context, byte_val, bytes);
+ } else if (flags & BYTE_MASK_RETURN) {
+ msg_warn("unknown %s value \"%c\" in \"%s\"",
+ context, byte_val, bytes);
+ return (0);
+ } else if (flags & BYTE_MASK_WARN) {
+ msg_warn("unknown %s value \"%c\" in \"%s\"",
+ context, byte_val, bytes);
+ }
+ break;
+ }
+ if ((flags & BYTE_MASK_ANY_CASE) ?
+ (TOLOWER(byte_val) == TOLOWER(np->byte_val)) :
+ (byte_val == np->byte_val)) {
+ if (msg_verbose)
+ msg_info("%s: %c", myname, byte_val);
+ result |= np->mask;
+ break;
+ }
+ }
+ }
+ return (result);
+}
+
+/* str_byte_mask_opt - mask to string */
+
+const char *str_byte_mask_opt(VSTRING *buf, const char *context,
+ const BYTE_MASK *table,
+ int mask, int flags)
+{
+ const char myname[] = "byte_mask";
+ const BYTE_MASK *np;
+ static VSTRING *my_buf = 0;
+
+ if ((flags & STR_BYTE_MASK_REQUIRED) == 0)
+ msg_panic("%s: missing BYTE_MASK_FATAL/RETURN/WARN/IGNORE flag",
+ myname);
+
+ if (buf == 0) {
+ if (my_buf == 0)
+ my_buf = vstring_alloc(1);
+ buf = my_buf;
+ }
+ VSTRING_RESET(buf);
+
+ for (np = table; mask != 0; np++) {
+ if (np->byte_val == 0) {
+ if (flags & BYTE_MASK_FATAL) {
+ msg_fatal("%s: unknown %s bit in mask: 0x%x",
+ myname, context, mask);
+ } else if (flags & BYTE_MASK_RETURN) {
+ msg_warn("%s: unknown %s bit in mask: 0x%x",
+ myname, context, mask);
+ return (0);
+ } else if (flags & BYTE_MASK_WARN) {
+ msg_warn("%s: unknown %s bit in mask: 0x%x",
+ myname, context, mask);
+ }
+ break;
+ }
+ if (mask & np->mask) {
+ mask &= ~np->mask;
+ vstring_sprintf_append(buf, "%c", np->byte_val);
+ }
+ }
+ VSTRING_TERMINATE(buf);
+
+ return (STR(buf));
+}
+
+#ifdef TEST
+
+ /*
+ * Stand-alone test program.
+ */
+#include <stdlib.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <name_mask.h>
+
+int main(int argc, char **argv)
+{
+ static const BYTE_MASK demo_table[] = {
+ '0', 1 << 0,
+ '1', 1 << 1,
+ '2', 1 << 2,
+ '3', 1 << 3,
+ 0, 0,
+ };
+ static const NAME_MASK feature_table[] = {
+ "DEFAULT", BYTE_MASK_DEFAULT,
+ "FATAL", BYTE_MASK_FATAL,
+ "ANY_CASE", BYTE_MASK_ANY_CASE,
+ "RETURN", BYTE_MASK_RETURN,
+ "WARN", BYTE_MASK_WARN,
+ "IGNORE", BYTE_MASK_IGNORE,
+ 0,
+ };
+ int in_feature_mask;
+ int out_feature_mask;
+ int demo_mask;
+ const char *demo_str;
+ VSTRING *out_buf = vstring_alloc(1);
+ VSTRING *in_buf = vstring_alloc(1);
+
+ if (argc != 3)
+ msg_fatal("usage: %s in-feature-mask out-feature-mask", argv[0]);
+ in_feature_mask = name_mask(argv[1], feature_table, argv[1]);
+ out_feature_mask = name_mask(argv[2], feature_table, argv[2]);
+ while (vstring_get_nonl(in_buf, VSTREAM_IN) != VSTREAM_EOF) {
+ demo_mask = byte_mask_opt("name", demo_table,
+ STR(in_buf), in_feature_mask);
+ demo_str = str_byte_mask_opt(out_buf, "mask", demo_table,
+ demo_mask, out_feature_mask);
+ vstream_printf("%s -> 0x%x -> %s\n",
+ STR(in_buf), demo_mask,
+ demo_str ? demo_str : "(null)");
+ demo_mask <<=1;
+ demo_str = str_byte_mask_opt(out_buf, "mask", demo_table,
+ demo_mask, out_feature_mask);
+ vstream_printf("0x%x -> %s\n",
+ demo_mask, demo_str ? demo_str : "(null)");
+ vstream_fflush(VSTREAM_OUT);
+ }
+ vstring_free(in_buf);
+ vstring_free(out_buf);
+ exit(0);
+}
+
+#endif
diff --git a/src/util/byte_mask.h b/src/util/byte_mask.h
new file mode 100644
index 0000000..6cdea1d
--- /dev/null
+++ b/src/util/byte_mask.h
@@ -0,0 +1,64 @@
+#ifndef _BYTE_MASK_H_INCLUDED_
+#define _BYTE_MASK_H_INCLUDED_
+
+/*++
+/* NAME
+/* byte_mask 3h
+/* SUMMARY
+/* map names to bit mask
+/* SYNOPSIS
+/* #include <byte_mask.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+typedef struct {
+ int byte_val;
+ int mask;
+} BYTE_MASK;
+
+#define BYTE_MASK_FATAL (1<<0)
+#define BYTE_MASK_ANY_CASE (1<<1)
+#define BYTE_MASK_RETURN (1<<2)
+#define BYTE_MASK_WARN (1<<6)
+#define BYTE_MASK_IGNORE (1<<7)
+
+#define BYTE_MASK_REQUIRED \
+ (BYTE_MASK_FATAL | BYTE_MASK_RETURN | BYTE_MASK_WARN | BYTE_MASK_IGNORE)
+#define STR_BYTE_MASK_REQUIRED (BYTE_MASK_REQUIRED)
+
+#define BYTE_MASK_NONE 0
+#define BYTE_MASK_DEFAULT (BYTE_MASK_FATAL)
+
+#define byte_mask(tag, table, str) \
+ byte_mask_opt((tag), (table), (str), BYTE_MASK_DEFAULT)
+#define str_byte_mask(tag, table, mask) \
+ str_byte_mask_opt(((VSTRING *) 0), (tag), (table), (mask), BYTE_MASK_DEFAULT)
+
+extern int byte_mask_opt(const char *, const BYTE_MASK *, const char *, int);
+extern const char *str_byte_mask_opt(VSTRING *, const char *, const BYTE_MASK *, int, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/byte_mask.in b/src/util/byte_mask.in
new file mode 100644
index 0000000..6d89c5a
--- /dev/null
+++ b/src/util/byte_mask.in
@@ -0,0 +1,7 @@
+0123
+0
+1
+2
+3
+1234
+abcd
diff --git a/src/util/byte_mask.ref0 b/src/util/byte_mask.ref0
new file mode 100644
index 0000000..a6ab11b
--- /dev/null
+++ b/src/util/byte_mask.ref0
@@ -0,0 +1,14 @@
+0123 -> 0xf -> 0123
+0x1e -> 123
+0 -> 0x1 -> 0
+0x2 -> 1
+1 -> 0x2 -> 1
+0x4 -> 2
+2 -> 0x4 -> 2
+0x8 -> 3
+3 -> 0x8 -> 3
+0x10 ->
+1234 -> 0xe -> 123
+0x1c -> 23
+abcd -> 0x0 ->
+0x0 ->
diff --git a/src/util/byte_mask.ref1 b/src/util/byte_mask.ref1
new file mode 100644
index 0000000..92e3f4f
--- /dev/null
+++ b/src/util/byte_mask.ref1
@@ -0,0 +1,22 @@
+unknown: warning: byte_mask: unknown mask bit in mask: 0x10
+0123 -> 0xf -> 0123
+0x1e -> 123
+0 -> 0x1 -> 0
+0x2 -> 1
+1 -> 0x2 -> 1
+0x4 -> 2
+2 -> 0x4 -> 2
+0x8 -> 3
+unknown: warning: byte_mask: unknown mask bit in mask: 0x10
+3 -> 0x8 -> 3
+0x10 ->
+unknown: warning: unknown name value "4" in "1234"
+unknown: warning: byte_mask: unknown mask bit in mask: 0x10
+1234 -> 0xe -> 123
+0x1c -> 23
+unknown: warning: unknown name value "a" in "abcd"
+unknown: warning: unknown name value "b" in "abcd"
+unknown: warning: unknown name value "c" in "abcd"
+unknown: warning: unknown name value "d" in "abcd"
+abcd -> 0x0 ->
+0x0 ->
diff --git a/src/util/byte_mask.ref2 b/src/util/byte_mask.ref2
new file mode 100644
index 0000000..631ec53
--- /dev/null
+++ b/src/util/byte_mask.ref2
@@ -0,0 +1,10 @@
+unknown: fatal: byte_mask: unknown mask bit in mask: 0x10
+0 -> 0x1 -> 0
+0x2 -> 1
+1 -> 0x2 -> 1
+0x4 -> 2
+2 -> 0x4 -> 2
+0x8 -> 3
+unknown: fatal: byte_mask: unknown mask bit in mask: 0x10
+unknown: fatal: unknown name value "4" in "1234"
+unknown: fatal: unknown name value "a" in "abcd"
diff --git a/src/util/cache.in b/src/util/cache.in
new file mode 100644
index 0000000..89974bd
--- /dev/null
+++ b/src/util/cache.in
@@ -0,0 +1,26 @@
+a
+1
+b
+2
+c
+3
+d
+4
+e
+5
+f
+6
+f
+e
+d
+c
+b
+a
+1
+b
+c
+d
+e
+f
+6
+f
diff --git a/src/util/casefold.c b/src/util/casefold.c
new file mode 100644
index 0000000..d3ebd4b
--- /dev/null
+++ b/src/util/casefold.c
@@ -0,0 +1,359 @@
+/*++
+/* NAME
+/* casefold 3
+/* SUMMARY
+/* casefold text for caseless comparison
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* char *casefold(
+/* VSTRING *dst,
+/* const char *src)
+/*
+/* char *casefold_append(
+/* VSTRING *dst,
+/* const char *src)
+/*
+/* char *casefold_len(
+/* VSTRING *dst,
+/* const char *src,
+/* ssize_t src_len)
+/* AUXILIARY FUNCTIONS
+/* char *casefoldx(
+/* int flags,
+/* VSTRING *dst,
+/* const char *src,
+/* ssize_t src_len)
+/* DESCRIPTION
+/* casefold() converts text to a form that is suitable for
+/* caseless comparison, rather than presentation to humans.
+/*
+/* When compiled without EAI support or util_utf8_enable is
+/* zero, casefold() implements ASCII case folding, leaving
+/* non-ASCII byte values unchanged.
+/*
+/* When compiled with EAI support and util_utf8_enable is
+/* non-zero, casefold() implements UTF-8 case folding using
+/* the en_US locale, as recommended when the conversion result
+/* is not meant to be presented to humans.
+/*
+/* casefold_len() implements casefold() with a source length
+/* argument.
+/*
+/* casefold_append() implements casefold() without overwriting
+/* the result.
+/*
+/* casefoldx() implements a more complex API that implements
+/* all of the above and more.
+/*
+/* Arguments:
+/* .IP src
+/* Null-terminated input string.
+/* .IP dest
+/* Output buffer, null-terminated. Specify a null pointer to
+/* use an internal buffer that is overwritten upon each call.
+/* .IP src_len
+/* The string length, -1 to determine the length dynamically.
+/* .IP flags
+/* Bitwise OR of zero or more of the following:
+/* .RS
+/* .IP CASEF_FLAG_UTF8
+/* Enable UTF-8 support. This flag has no effect when compiled
+/* without EAI support.
+/* .IP CASEF_FLAG_APPEND
+/* Append the result to the buffer, instead of overwriting it.
+/* DIAGNOSTICS
+/* All errors are fatal. There appear to be no input-dependent
+/* errors.
+/*
+/* With the ICU 4.8 library, there is no casefold error for
+/* UTF-8 code points U+0000..U+10FFFF (including surrogate
+/* range), not even when running inside an empty chroot jail.
+/* Nor does malformed UTF-8 trigger errors; non-UTF-8 bytes
+/* are copied verbatim. Based on ICU 4.8 source-code review
+/* and experimentation(!) we conclude that UTF-8 casefolding
+/* has no data-dependent error cases, and that it is safe to
+/* treat all casefolding errors as fatal runtime errors.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+#include <ctype.h>
+#ifndef NO_EAI
+#include <unicode/ucasemap.h>
+#include <unicode/ustring.h>
+#include <unicode/uchar.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <stringops.h>
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* casefoldx - casefold an UTF-8 string */
+
+char *casefoldx(int flags, VSTRING *dest, const char *src, ssize_t len)
+{
+ size_t old_len;
+
+#ifdef NO_EAI
+
+ /*
+ * ASCII mode only.
+ */
+ if (len < 0)
+ len = strlen(src);
+ if ((flags & CASEF_FLAG_APPEND) == 0)
+ VSTRING_RESET(dest);
+ old_len = VSTRING_LEN(dest);
+ vstring_strncat(dest, src, len);
+ lowercase(STR(dest) + old_len);
+ return (STR(dest));
+#else
+
+ /*
+ * Unicode mode.
+ */
+ const char myname[] = "casefold";
+ static VSTRING *fold_buf = 0;
+ static UCaseMap *csm = 0;
+ UErrorCode error;
+ ssize_t space_needed;
+ int n;
+
+ /*
+ * Handle special cases.
+ */
+ if (len < 0)
+ len = strlen(src);
+ if (dest == 0)
+ dest = (fold_buf != 0 ? fold_buf : (fold_buf = vstring_alloc(100)));
+ if ((flags & CASEF_FLAG_APPEND) == 0)
+ VSTRING_RESET(dest);
+ old_len = VSTRING_LEN(dest);
+
+ /*
+ * All-ASCII input, or ASCII mode only.
+ */
+ if ((flags & CASEF_FLAG_UTF8) == 0 || allascii(src)) {
+ vstring_strncat(dest, src, len);
+ lowercase(STR(dest) + old_len);
+ return (STR(dest));
+ }
+
+ /*
+ * ICU 4.8 ucasemap_utf8FoldCase() does not complain about UTF-8 syntax
+ * errors. XXX Based on source-code review we conclude that non-UTF-8
+ * bytes are copied verbatim, and experiments confirm this. Given that
+ * this behavior is intentional, we assume that it will stay that way.
+ */
+#if 0
+ if (valid_utf8_string(src, len) == 0) {
+ if (err)
+ *err = "malformed UTF-8 or invalid codepoint";
+ return (0);
+ }
+#endif
+
+ /*
+ * One-time initialization. With ICU 4.8 this works while chrooted.
+ */
+ if (csm == 0) {
+ error = U_ZERO_ERROR;
+ csm = ucasemap_open("en_US", U_FOLD_CASE_DEFAULT, &error);
+ if (U_SUCCESS(error) == 0)
+ msg_fatal("ucasemap_open error: %s", u_errorName(error));
+ }
+
+ /*
+ * Fold the input, adjusting the buffer size if needed. Safety: don't
+ * loop forever.
+ *
+ * Note: the requested amount of space for casemapped output (as reported
+ * with space_needed below) does not include storage for the null
+ * terminator. The terminator is written only when the output buffer is
+ * large enough. This is why we overallocate space when the output does
+ * not fit. But if the output fits exactly, then the output will be
+ * unterminated, and we have to terminate the output ourselves.
+ */
+ for (n = 0; n < 3; n++) {
+ error = U_ZERO_ERROR;
+ space_needed = ucasemap_utf8FoldCase(csm, STR(dest) + old_len,
+ vstring_avail(dest), src, len, &error);
+ if (U_SUCCESS(error)) {
+ vstring_set_payload_size(dest, old_len + space_needed);
+ if (vstring_avail(dest) == 0) /* exact fit, no terminator */
+ VSTRING_TERMINATE(dest); /* add terminator */
+ break;
+ } else if (error == U_BUFFER_OVERFLOW_ERROR) {
+ VSTRING_SPACE(dest, space_needed + 1); /* for terminator */
+ } else {
+ msg_fatal("%s: conversion error for \"%s\": %s",
+ myname, src, u_errorName(error));
+ }
+ }
+ return (STR(dest));
+#endif /* NO_EAI */
+}
+
+#ifdef TEST
+
+static void encode_utf8(VSTRING *buffer, int codepoint)
+{
+ const char myname[] = "encode_utf8";
+
+ VSTRING_RESET(buffer);
+ if (codepoint < 0x80) {
+ VSTRING_ADDCH(buffer, codepoint);
+ } else if (codepoint < 0x800) {
+ VSTRING_ADDCH(buffer, 0xc0 | (codepoint >> 6));
+ VSTRING_ADDCH(buffer, 0x80 | (codepoint & 0x3f));
+ } else if (codepoint < 0x10000) {
+ VSTRING_ADDCH(buffer, 0xe0 | (codepoint >> 12));
+ VSTRING_ADDCH(buffer, 0x80 | ((codepoint >> 6) & 0x3f));
+ VSTRING_ADDCH(buffer, 0x80 | (codepoint & 0x3f));
+ } else if (codepoint <= 0x10FFFF) {
+ VSTRING_ADDCH(buffer, 0xf0 | (codepoint >> 18));
+ VSTRING_ADDCH(buffer, 0x80 | ((codepoint >> 12) & 0x3f));
+ VSTRING_ADDCH(buffer, 0x80 | ((codepoint >> 6) & 0x3f));
+ VSTRING_ADDCH(buffer, 0x80 | (codepoint & 0x3f));
+ } else {
+ msg_panic("%s: out-of-range codepoint U+%X", myname, codepoint);
+ }
+ VSTRING_TERMINATE(buffer);
+}
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <locale.h>
+
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <msg_vstream.h>
+
+int main(int argc, char **argv)
+{
+ VSTRING *buffer = vstring_alloc(1);
+ VSTRING *dest = vstring_alloc(1);
+ char *bp;
+ char *conv_res;
+ char *cmd;
+ int codepoint, first, last;
+ VSTREAM *fp;
+
+ if (setlocale(LC_ALL, "C") == 0)
+ msg_fatal("setlocale(LC_ALL, C) failed: %m");
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ util_utf8_enable = 1;
+
+ VSTRING_SPACE(buffer, 256); /* chroot/file pathname */
+
+ while (vstring_fgets_nonl(buffer, VSTREAM_IN)) {
+ bp = STR(buffer);
+ vstream_printf("> %s\n", bp);
+ cmd = mystrtok(&bp, CHARS_SPACE);
+ if (cmd == 0 || *cmd == '#')
+ continue;
+ while (ISSPACE(*bp))
+ bp++;
+
+ /*
+ * Null-terminated string.
+ */
+ if (strcmp(cmd, "fold") == 0) {
+ conv_res = casefold(dest, bp);
+ vstream_printf("\"%s\" ->fold \"%s\"\n", bp, conv_res);
+ }
+
+ /*
+ * Codepoint range.
+ */
+ else if (strcmp(cmd, "range") == 0
+ && sscanf(bp, "%i %i", &first, &last) == 2
+ && first <= last) {
+ for (codepoint = first; codepoint <= last; codepoint++) {
+ if (codepoint >= 0xD800 && codepoint <= 0xDFFF) {
+ vstream_printf("skipping surrogate range\n");
+ codepoint = 0xDFFF;
+ } else {
+ encode_utf8(buffer, codepoint);
+ if (msg_verbose)
+ vstream_printf("U+%X -> %s\n", codepoint, STR(buffer));
+ if (valid_utf8_string(STR(buffer), LEN(buffer)) == 0)
+ msg_fatal("bad utf-8 encoding for U+%X\n", codepoint);
+ casefold(dest, STR(buffer));
+ }
+ }
+ vstream_printf("range completed: 0x%x..0x%x\n", first, last);
+ }
+
+ /*
+ * Chroot directory.
+ */
+ else if (strcmp(cmd, "chroot") == 0
+ && sscanf(bp, "%255s", STR(buffer)) == 1) {
+ if (geteuid() == 0) {
+ if (chdir(STR(buffer)) < 0)
+ msg_fatal("chdir(%s): %m", STR(buffer));
+ if (chroot(STR(buffer)) < 0)
+ msg_fatal("chroot(%s): %m", STR(buffer));
+ vstream_printf("chroot %s completed\n", STR(buffer));
+ }
+ }
+
+ /*
+ * File.
+ */
+ else if (strcmp(cmd, "file") == 0
+ && sscanf(bp, "%255s", STR(buffer)) == 1) {
+ if ((fp = vstream_fopen(STR(buffer), O_RDONLY, 0)) == 0)
+ msg_fatal("open(%s): %m", STR(buffer));
+ while (vstring_fgets_nonl(buffer, fp))
+ vstream_printf("%s\n", casefold(dest, STR(buffer)));
+ vstream_fclose(fp);
+ }
+
+ /*
+ * Verbose.
+ */
+ else if (strcmp(cmd, "verbose") == 0
+ && sscanf(bp, "%i", &msg_verbose) == 1) {
+ /* void */ ;
+ }
+
+ /*
+ * Usage
+ */
+ else {
+ vstream_printf("Usage: %s chroot <path> | file <path> | fold <text> | range <first> <last> | verbose <int>\n",
+ argv[0]);
+ }
+ vstream_fflush(VSTREAM_OUT);
+ }
+ vstring_free(buffer);
+ vstring_free(dest);
+ exit(0);
+}
+
+#endif /* TEST */
diff --git a/src/util/casefold_test.in b/src/util/casefold_test.in
new file mode 100644
index 0000000..c1a530e
--- /dev/null
+++ b/src/util/casefold_test.in
@@ -0,0 +1,24 @@
+# Ignored when not running as root.
+chroot /tmp
+# Casefold U+0000 .. U+10FFFF excluding surrogates.
+range 0x0 0xD7FF
+range 0xD800 0xD800
+range 0xDFFF 0xDFFF
+range 0xE000 0x10FFFF
+# Demonstrate that range is not a noop.
+verbose 1
+range 0xE000 0xE007
+verbose 0
+# Upper-case greek -> lower-case greek.
+fold Δημοσθένους.example.com
+# Exact-fit null termination test.
+fold Δημοσθένους.exxample.com
+# Upper-case ASCII -> lower-case ASCII.
+fold HeLlO.ExAmPlE.CoM
+# Folding does not change aliases for '.'.
+fold x。example.com
+fold x.example.com
+fold x。example.com
+# Bad UTF-8
+fold YYY€€€
+fold €€€XXX
diff --git a/src/util/casefold_test.ref b/src/util/casefold_test.ref
new file mode 100644
index 0000000..639fe06
--- /dev/null
+++ b/src/util/casefold_test.ref
@@ -0,0 +1,47 @@
+> # Ignored when not running as root.
+> chroot /tmp
+> # Casefold U+0000 .. U+10FFFF excluding surrogates.
+> range 0x0 0xD7FF
+range completed: 0x0..0xd7ff
+> range 0xD800 0xD800
+skipping surrogate range
+range completed: 0xd800..0xd800
+> range 0xDFFF 0xDFFF
+skipping surrogate range
+range completed: 0xdfff..0xdfff
+> range 0xE000 0x10FFFF
+range completed: 0xe000..0x10ffff
+> # Demonstrate that range is not a noop.
+> verbose 1
+> range 0xE000 0xE007
+U+E000 -> 
+U+E001 -> î€
+U+E002 -> 
+U+E003 -> 
+U+E004 -> 
+U+E005 -> 
+U+E006 -> 
+U+E007 -> 
+range completed: 0xe000..0xe007
+> verbose 0
+> # Upper-case greek -> lower-case greek.
+> fold Δημοσθένους.example.com
+"Δημοσθένους.example.com" ->fold "δημοσθένουσ.example.com"
+> # Exact-fit null termination test.
+> fold Δημοσθένους.exxample.com
+"Δημοσθένους.exxample.com" ->fold "δημοσθένουσ.exxample.com"
+> # Upper-case ASCII -> lower-case ASCII.
+> fold HeLlO.ExAmPlE.CoM
+"HeLlO.ExAmPlE.CoM" ->fold "hello.example.com"
+> # Folding does not change aliases for '.'.
+> fold x。example.com
+"x。example.com" ->fold "x。example.com"
+> fold x.example.com
+"x.example.com" ->fold "x.example.com"
+> fold x。example.com
+"x。example.com" ->fold "x。example.com"
+> # Bad UTF-8
+> fold YYY€€€
+"YYY€€€" ->fold "yyy€€€"
+> fold €€€XXX
+"€€€XXX" ->fold "€€€xxx"
diff --git a/src/util/check_arg.h b/src/util/check_arg.h
new file mode 100644
index 0000000..09f0932
--- /dev/null
+++ b/src/util/check_arg.h
@@ -0,0 +1,157 @@
+#ifndef _CHECK_ARG_INCLUDED_
+#define _CHECK_ARG_INCLUDED_
+
+/*++
+/* NAME
+/* check_arg 3h
+/* SUMMARY
+/* type checking/narrowing/widening for unprototyped arguments
+/* SYNOPSIS
+/* #include <check_arg.h>
+/*
+/* /* Example checking infrastructure for int, int *, const int *. */
+/* CHECK_VAL_HELPER_DCL(tag, int);
+/* CHECK_PTR_HELPER_DCL(tag, int);
+/* CHECK_CPTR_HELPER_DCL(tag, int);
+/*
+/* /* Example variables with type int, int *, const int *. */
+/* int int_val;
+/* int *int_ptr;
+/* const int *int_cptr;
+/*
+/* /* Example variadic function with type-flag arguments. */
+/* func(FLAG_INT_VAL, CHECK_VAL(tag, int, int_val),
+/* FLAG_INT_PTR, CHECK_PTR(tag, int, int_ptr),
+/* FLAG_INT_CPTR, CHECK_CPTR(tag, int, int_cptr)
+/* FLAG_END);
+/* DESCRIPTION
+/* This module implements wrappers for unprototyped function
+/* arguments, to enable the same type checking, type narrowing,
+/* and type widening as for prototyped function arguments. The
+/* wrappers may also be useful in other contexts.
+/*
+/* Typically, these wrappers are hidden away in a per-module
+/* header file that is read by the consumers of that module.
+/* To protect consumers against name collisions between wrappers
+/* in different header files, wrappers should be called with
+/* a distinct per-module tag value. The tag syntax is that
+/* of a C identifier.
+/*
+/* Use CHECK_VAL(tag, type, argument) for arguments with a
+/* basic type: int, long, etc., and types defined with "typedef"
+/* where indirection is built into the type itself (for example,
+/* the result of "typedef int *foo" or function pointer
+/* typedefs).
+/*
+/* Use CHECK_PTR(tag, type, argument) for non-const pointer
+/* arguments, CHECK_CPTR(tag, type, argument) for const pointer
+/* arguments, and CHECK_PPTR(tag, type, argument) for pointer-
+/* to-pointer arguments.
+/*
+/* Use CHECK_*_HELPER_DCL(tag, type) to provide the
+/* checking infrastructure for all CHECK_*(tag, type, ...)
+/* instances with the same *, tag and type. Depending on
+/* the compilation environment, the infrastructure consists
+/* of an inline function definition or a dummy assignment
+/* target declaration.
+/*
+/* The compiler should report the following problems:
+/* .IP \(bu
+/* Const pointer argument where a non-const pointer is expected.
+/* .IP \(bu
+/* Pointer argument where a non-pointer is expected and
+/* vice-versa.
+/* .IP \(bu
+/* Pointer/pointer type mismatches except void/non-void pointers.
+/* Just like function prototypes, all CHECK_*PTR() wrappers
+/* cast their result to the desired type.
+/* .IP \(bu
+/* Non-constant non-pointer argument where a pointer is expected.
+/*. PP
+/* Just like function prototypes, the CHECK_*PTR() wrappers
+/* handle "bare" numerical constants by casting their argument
+/* to the desired pointer type.
+/*
+/* Just like function prototypes, the CHECK_VAL() wrapper
+/* cannot force the caller to specify a particular non-pointer
+/* type and casts its argument value to the desired type which
+/* may wider or narrower than the argument value.
+/* IMPLEMENTATION
+
+ /*
+ * Choose between an implementation based on inline functions (standardized
+ * with C99) or conditional assignment (portable to older compilers, with
+ * some caveats as discussed below).
+ */
+#ifndef NO_INLINE
+
+ /*
+ * Parameter checks expand into inline helper function calls.
+ */
+#define CHECK_VAL(tag, type, v) check_val_##tag##type(v)
+#define CHECK_PTR(tag, type, v) check_ptr_##tag##type(v)
+#define CHECK_CPTR(tag, type, v) check_cptr_##tag##type(v)
+#define CHECK_PPTR(tag, type, v) check_pptr_##tag##type(v)
+
+ /*
+ * Macros to instantiate the inline helper functions.
+ */
+#define CHECK_VAL_HELPER_DCL(tag, type) \
+ static inline type check_val_##tag##type(type v) { return v; }
+#define CHECK_PTR_HELPER_DCL(tag, type) \
+ static inline type *check_ptr_##tag##type(type *v) { return v; }
+#define CHECK_CPTR_HELPER_DCL(tag, type) \
+ static inline const type *check_cptr_##tag##type(const type *v) \
+ { return v; }
+#define CHECK_PPTR_HELPER_DCL(tag, type) \
+ static inline type **check_pptr_##tag##type(type **v) { return v; }
+
+#else /* NO_INLINE */
+
+ /*
+ * Parameter checks expand into unreachable conditional assignments.
+ * Inspired by OpenSSL's verified pointer check, our implementation also
+ * detects const/non-const pointer conflicts, and it also supports
+ * non-pointer expressions.
+ */
+#define CHECK_VAL(tag, type, v) ((type) (1 ? (v) : (CHECK_VAL_DUMMY(type) = (v))))
+#define CHECK_PTR(tag, type, v) ((type *) (1 ? (v) : (CHECK_PTR_DUMMY(type) = (v))))
+#define CHECK_CPTR(tag, type, v) \
+ ((const type *) (1 ? (v) : (CHECK_CPTR_DUMMY(type) = (v))))
+#define CHECK_PPTR(tag, type, v) ((type **) (1 ? (v) : (CHECK_PPTR_DUMMY(type) = (v))))
+
+ /*
+ * These macros instantiate assignment target declarations. Since the
+ * assignment is made in unreachable code, the compiler "should" not emit
+ * any references to those assignment targets. We use the "extern" class so
+ * that gcc will not complain about unused variables. Using "extern" breaks
+ * when a compiler does emit references to unreachable assignment targets.
+ * Hopefully, those cases will be rare.
+ */
+#define CHECK_VAL_HELPER_DCL(tag, type) extern type CHECK_VAL_DUMMY(type)
+#define CHECK_PTR_HELPER_DCL(tag, type) extern type *CHECK_PTR_DUMMY(type)
+#define CHECK_CPTR_HELPER_DCL(tag, type) extern const type *CHECK_CPTR_DUMMY(type)
+#define CHECK_PPTR_HELPER_DCL(tag, type) extern type **CHECK_PPTR_DUMMY(type)
+
+ /*
+ * The actual dummy assignment target names.
+ */
+#define CHECK_VAL_DUMMY(type) check_val_dummy_##type
+#define CHECK_PTR_DUMMY(type) check_ptr_dummy_##type
+#define CHECK_CPTR_DUMMY(type) check_cptr_dummy_##type
+#define CHECK_PPTR_DUMMY(type) check_pptr_dummy_##type
+
+#endif /* NO_INLINE */
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/chroot_uid.c b/src/util/chroot_uid.c
new file mode 100644
index 0000000..4a7660f
--- /dev/null
+++ b/src/util/chroot_uid.c
@@ -0,0 +1,88 @@
+/*++
+/* NAME
+/* chroot_uid 3
+/* SUMMARY
+/* limit possible damage a process can do
+/* SYNOPSIS
+/* #include <chroot_uid.h>
+/*
+/* void chroot_uid(root_dir, user_name)
+/* const char *root_dir;
+/* const char *user_name;
+/* DESCRIPTION
+/* \fBchroot_uid\fR changes the process root to \fIroot_dir\fR and
+/* changes process privileges to those of \fIuser_name\fR.
+/* DIAGNOSTICS
+/* System call errors are reported via the msg(3) interface.
+/* All errors are fatal.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <grp.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "chroot_uid.h"
+
+/* chroot_uid - restrict the damage that this program can do */
+
+void chroot_uid(const char *root_dir, const char *user_name)
+{
+ struct passwd *pwd;
+ uid_t uid;
+ gid_t gid;
+
+ /*
+ * Look up the uid/gid before entering the jail, and save them so they
+ * can't be clobbered. Set up the primary and secondary groups.
+ */
+ if (user_name != 0) {
+ if ((pwd = getpwnam(user_name)) == 0)
+ msg_fatal("unknown user: %s", user_name);
+ uid = pwd->pw_uid;
+ gid = pwd->pw_gid;
+ if (setgid(gid) < 0)
+ msg_fatal("setgid(%ld): %m", (long) gid);
+ if (initgroups(user_name, gid) < 0)
+ msg_fatal("initgroups: %m");
+ }
+
+ /*
+ * Enter the jail.
+ */
+ if (root_dir) {
+ if (chroot(root_dir))
+ msg_fatal("chroot(%s): %m", root_dir);
+ if (chdir("/"))
+ msg_fatal("chdir(/): %m");
+ }
+
+ /*
+ * Drop the user privileges.
+ */
+ if (user_name != 0)
+ if (setuid(uid) < 0)
+ msg_fatal("setuid(%ld): %m", (long) uid);
+
+ /*
+ * Give the desperate developer a clue of what is happening.
+ */
+ if (msg_verbose > 1)
+ msg_info("chroot %s user %s",
+ root_dir ? root_dir : "(none)",
+ user_name ? user_name : "(none)");
+}
diff --git a/src/util/chroot_uid.h b/src/util/chroot_uid.h
new file mode 100644
index 0000000..f2a8399
--- /dev/null
+++ b/src/util/chroot_uid.h
@@ -0,0 +1,29 @@
+#ifndef _CHROOT_UID_H_INCLUDED_
+#define _CHROOT_UID_H_INCLUDED_
+
+/*++
+/* NAME
+/* chroot_uid 3h
+/* SUMMARY
+/* limit possible damage a process can do
+/* SYNOPSIS
+/* #include <chroot_uid.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern void chroot_uid(const char *, const char *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/cidr_match.c b/src/util/cidr_match.c
new file mode 100644
index 0000000..0ae7c56
--- /dev/null
+++ b/src/util/cidr_match.c
@@ -0,0 +1,316 @@
+/*++
+/* NAME
+/* cidr_match 3
+/* SUMMARY
+/* CIDR-style pattern matching
+/* SYNOPSIS
+/* #include <cidr_match.h>
+/*
+/* VSTRING *cidr_match_parse(info, pattern, match, why)
+/* CIDR_MATCH *info;
+/* char *pattern;
+/* VSTRING *why;
+/*
+/* int cidr_match_execute(info, address)
+/* CIDR_MATCH *info;
+/* const char *address;
+/* AUXILIARY FUNCTIONS
+/* VSTRING *cidr_match_parse_if(info, pattern, match, why)
+/* CIDR_MATCH *info;
+/* char *pattern;
+/* VSTRING *why;
+/*
+/* void cidr_match_endif(info)
+/* CIDR_MATCH *info;
+/* DESCRIPTION
+/* This module parses address or address/length patterns and
+/* provides simple address matching. The implementation is
+/* such that parsing and execution can be done without dynamic
+/* memory allocation. The purpose is to minimize overhead when
+/* called by functions that parse and execute on the fly, such
+/* as match_hostaddr().
+/*
+/* cidr_match_parse() parses an address or address/mask
+/* expression and stores the result into the info argument.
+/* A non-zero (or zero) match argument requests a positive (or
+/* negative) match. The symbolic constants CIDR_MATCH_TRUE and
+/* CIDR_MATCH_FALSE may help to improve code readability.
+/* The result is non-zero in case of problems: either the
+/* value of the why argument, or a newly allocated VSTRING
+/* (the caller should give the latter to vstring_free()).
+/* The pattern argument is destroyed.
+/*
+/* cidr_match_parse_if() parses the address that follows an IF
+/* token, and stores the result into the info argument.
+/* The arguments are the same as for cidr_match_parse().
+/*
+/* cidr_match_endif() handles the occurrence of an ENDIF token,
+/* and updates the info argument.
+/*
+/* cidr_match_execute() matches the specified address against
+/* a list of parsed expressions, and returns the matching
+/* expression's data structure.
+/* SEE ALSO
+/* dict_cidr(3) CIDR-style lookup table
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <split_at.h>
+#include <myaddrinfo.h>
+#include <mask_addr.h>
+#include <cidr_match.h>
+
+/* Application-specific. */
+
+ /*
+ * This is how we figure out the address family, address bit count and
+ * address byte count for a CIDR_MATCH entry.
+ */
+#ifdef HAS_IPV6
+#define CIDR_MATCH_ADDR_FAMILY(a) (strchr((a), ':') ? AF_INET6 : AF_INET)
+#define CIDR_MATCH_ADDR_BIT_COUNT(f) \
+ ((f) == AF_INET6 ? MAI_V6ADDR_BITS : \
+ (f) == AF_INET ? MAI_V4ADDR_BITS : \
+ (msg_panic("%s: bad address family %d", myname, (f)), 0))
+#define CIDR_MATCH_ADDR_BYTE_COUNT(f) \
+ ((f) == AF_INET6 ? MAI_V6ADDR_BYTES : \
+ (f) == AF_INET ? MAI_V4ADDR_BYTES : \
+ (msg_panic("%s: bad address family %d", myname, (f)), 0))
+#else
+#define CIDR_MATCH_ADDR_FAMILY(a) (AF_INET)
+#define CIDR_MATCH_ADDR_BIT_COUNT(f) \
+ ((f) == AF_INET ? MAI_V4ADDR_BITS : \
+ (msg_panic("%s: bad address family %d", myname, (f)), 0))
+#define CIDR_MATCH_ADDR_BYTE_COUNT(f) \
+ ((f) == AF_INET ? MAI_V4ADDR_BYTES : \
+ (msg_panic("%s: bad address family %d", myname, (f)), 0))
+#endif
+
+/* cidr_match_entry - match one entry */
+
+static inline int cidr_match_entry(CIDR_MATCH *entry,
+ unsigned char *addr_bytes)
+{
+ unsigned char *mp;
+ unsigned char *np;
+ unsigned char *ap;
+
+ /* Unoptimized case: netmask with some or all bits zero. */
+ if (entry->mask_shift < entry->addr_bit_count) {
+ for (np = entry->net_bytes, mp = entry->mask_bytes,
+ ap = addr_bytes; /* void */ ; np++, mp++, ap++) {
+ if (ap >= addr_bytes + entry->addr_byte_count)
+ return (entry->match);
+ if ((*ap & *mp) != *np)
+ break;
+ }
+ }
+ /* Optimized case: all 1 netmask (i.e. no netmask specified). */
+ else {
+ for (np = entry->net_bytes,
+ ap = addr_bytes; /* void */ ; np++, ap++) {
+ if (ap >= addr_bytes + entry->addr_byte_count)
+ return (entry->match);
+ if (*ap != *np)
+ break;
+ }
+ }
+ return (!entry->match);
+}
+
+/* cidr_match_execute - match address against compiled CIDR pattern list */
+
+CIDR_MATCH *cidr_match_execute(CIDR_MATCH *list, const char *addr)
+{
+ unsigned char addr_bytes[CIDR_MATCH_ABYTES];
+ unsigned addr_family;
+ CIDR_MATCH *entry;
+
+ addr_family = CIDR_MATCH_ADDR_FAMILY(addr);
+ if (inet_pton(addr_family, addr, addr_bytes) != 1)
+ return (0);
+
+ for (entry = list; entry; entry = entry->next) {
+
+ switch (entry->op) {
+
+ case CIDR_MATCH_OP_MATCH:
+ if (entry->addr_family == addr_family)
+ if (cidr_match_entry(entry, addr_bytes))
+ return (entry);
+ break;
+
+ case CIDR_MATCH_OP_IF:
+ if (entry->addr_family == addr_family)
+ if (cidr_match_entry(entry, addr_bytes))
+ continue;
+ /* An IF without matching ENDIF has no end-of block entry. */
+ if ((entry = entry->block_end) == 0)
+ return (0);
+ /* FALLTHROUGH */
+
+ case CIDR_MATCH_OP_ENDIF:
+ continue;
+ }
+ }
+ return (0);
+}
+
+/* cidr_match_parse - parse CIDR pattern */
+
+VSTRING *cidr_match_parse(CIDR_MATCH *ip, char *pattern, int match,
+ VSTRING *why)
+{
+ const char *myname = "cidr_match_parse";
+ char *mask_search;
+ char *mask;
+ MAI_HOSTADDR_STR hostaddr;
+ unsigned char *np;
+ unsigned char *mp;
+
+ /*
+ * Strip [] from [addr/len] or [addr]/len, destroying the pattern. CIDR
+ * maps don't need [] to eliminate syntax ambiguity, but matchlists need
+ * it. While stripping [], figure out where we should start looking for
+ * /mask information.
+ */
+ if (*pattern == '[') {
+ pattern++;
+ if ((mask_search = split_at(pattern, ']')) == 0) {
+ vstring_sprintf(why ? why : (why = vstring_alloc(20)),
+ "missing ']' character after \"[%s\"", pattern);
+ return (why);
+ } else if (*mask_search != '/') {
+ if (*mask_search != 0) {
+ vstring_sprintf(why ? why : (why = vstring_alloc(20)),
+ "garbage after \"[%s]\"", pattern);
+ return (why);
+ }
+ mask_search = pattern;
+ }
+ } else
+ mask_search = pattern;
+
+ /*
+ * Parse the pattern into network and mask, destroying the pattern.
+ */
+ if ((mask = split_at(mask_search, '/')) != 0) {
+ const char *parse_error;
+
+ ip->addr_family = CIDR_MATCH_ADDR_FAMILY(pattern);
+ ip->addr_bit_count = CIDR_MATCH_ADDR_BIT_COUNT(ip->addr_family);
+ ip->addr_byte_count = CIDR_MATCH_ADDR_BYTE_COUNT(ip->addr_family);
+ if (!alldig(mask)) {
+ parse_error = "bad mask value";
+ } else if ((ip->mask_shift = atoi(mask)) > ip->addr_bit_count) {
+ parse_error = "bad mask length";
+ } else if (inet_pton(ip->addr_family, pattern, ip->net_bytes) != 1) {
+ parse_error = "bad network value";
+ } else {
+ parse_error = 0;
+ }
+ if (parse_error != 0) {
+ vstring_sprintf(why ? why : (why = vstring_alloc(20)),
+ "%s in \"%s/%s\"", parse_error, pattern, mask);
+ return (why);
+ }
+ if (ip->mask_shift > 0) {
+ /* Allow for bytes > 8. */
+ memset(ip->mask_bytes, ~0U, ip->addr_byte_count);
+ mask_addr(ip->mask_bytes, ip->addr_byte_count, ip->mask_shift);
+ } else
+ memset(ip->mask_bytes, 0, ip->addr_byte_count);
+
+ /*
+ * Sanity check: all host address bits must be zero.
+ */
+ for (np = ip->net_bytes, mp = ip->mask_bytes;
+ np < ip->net_bytes + ip->addr_byte_count; np++, mp++) {
+ if (*np & ~(*mp)) {
+ mask_addr(ip->net_bytes, ip->addr_byte_count, ip->mask_shift);
+ if (inet_ntop(ip->addr_family, ip->net_bytes, hostaddr.buf,
+ sizeof(hostaddr.buf)) == 0)
+ msg_fatal("inet_ntop: %m");
+ vstring_sprintf(why ? why : (why = vstring_alloc(20)),
+ "non-null host address bits in \"%s/%s\", "
+ "perhaps you should use \"%s/%d\" instead",
+ pattern, mask, hostaddr.buf, ip->mask_shift);
+ return (why);
+ }
+ }
+ }
+
+ /*
+ * No /mask specified. Treat a bare network address as /allbits.
+ */
+ else {
+ ip->addr_family = CIDR_MATCH_ADDR_FAMILY(pattern);
+ ip->addr_bit_count = CIDR_MATCH_ADDR_BIT_COUNT(ip->addr_family);
+ ip->addr_byte_count = CIDR_MATCH_ADDR_BYTE_COUNT(ip->addr_family);
+ if (inet_pton(ip->addr_family, pattern, ip->net_bytes) != 1) {
+ vstring_sprintf(why ? why : (why = vstring_alloc(20)),
+ "bad address pattern: \"%s\"", pattern);
+ return (why);
+ }
+ ip->mask_shift = ip->addr_bit_count;
+ /* Allow for bytes > 8. */
+ memset(ip->mask_bytes, ~0U, ip->addr_byte_count);
+ }
+
+ /*
+ * Wrap up the result.
+ */
+ ip->op = CIDR_MATCH_OP_MATCH;
+ ip->match = match;
+ ip->next = 0;
+ ip->block_end = 0;
+
+ return (0);
+}
+
+/* cidr_match_parse_if - parse CIDR pattern after IF */
+
+VSTRING *cidr_match_parse_if(CIDR_MATCH *ip, char *pattern, int match,
+ VSTRING *why)
+{
+ VSTRING *ret;
+
+ if ((ret = cidr_match_parse(ip, pattern, match, why)) == 0)
+ ip->op = CIDR_MATCH_OP_IF;
+ return (ret);
+}
+
+/* cidr_match_endif - handle ENDIF pattern */
+
+void cidr_match_endif(CIDR_MATCH *ip)
+{
+ memset(ip, 0, sizeof(*ip));
+ ip->op = CIDR_MATCH_OP_ENDIF;
+ ip->next = 0; /* maybe not all bits 0 */
+ ip->block_end = 0;
+}
diff --git a/src/util/cidr_match.h b/src/util/cidr_match.h
new file mode 100644
index 0000000..22f16a0
--- /dev/null
+++ b/src/util/cidr_match.h
@@ -0,0 +1,82 @@
+#ifndef _CIDR_MATCH_H_INCLUDED_
+#define _CIDR_MATCH_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_cidr 3h
+/* SUMMARY
+/* CIDR-style pattern matching
+/* SYNOPSIS
+/* #include <cidr_match.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <limits.h> /* CHAR_BIT */
+
+ /*
+ * Utility library.
+ */
+#include <myaddrinfo.h> /* MAI_V6ADDR_BYTES etc. */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ *
+ * Address length is protocol dependent. Find out how large our address byte
+ * strings should be.
+ */
+#ifdef HAS_IPV6
+# define CIDR_MATCH_ABYTES MAI_V6ADDR_BYTES
+#else
+# define CIDR_MATCH_ABYTES MAI_V4ADDR_BYTES
+#endif
+
+ /*
+ * Each parsed CIDR pattern can be member of a linked list.
+ */
+typedef struct CIDR_MATCH {
+ int op; /* operation, match or control flow */
+ int match; /* positive or negative match */
+ unsigned char net_bytes[CIDR_MATCH_ABYTES]; /* network portion */
+ unsigned char mask_bytes[CIDR_MATCH_ABYTES]; /* network mask */
+ unsigned char addr_family; /* AF_XXX */
+ unsigned char addr_byte_count; /* typically, 4 or 16 */
+ unsigned char addr_bit_count; /* optimization */
+ unsigned char mask_shift; /* optimization */
+ struct CIDR_MATCH *next; /* next entry */
+ struct CIDR_MATCH *block_end; /* block terminator */
+} CIDR_MATCH;
+
+#define CIDR_MATCH_OP_MATCH 1 /* Match this pattern */
+#define CIDR_MATCH_OP_IF 2 /* Increase if/endif nesting on match */
+#define CIDR_MATCH_OP_ENDIF 3 /* Decrease if/endif nesting on match */
+
+#define CIDR_MATCH_TRUE 1 /* Request positive match */
+#define CIDR_MATCH_FALSE 0 /* Request negative match */
+
+extern VSTRING *cidr_match_parse(CIDR_MATCH *, char *, int, VSTRING *);
+extern VSTRING *cidr_match_parse_if(CIDR_MATCH *, char *, int, VSTRING *);
+extern void cidr_match_endif(CIDR_MATCH *);
+
+extern CIDR_MATCH *cidr_match_execute(CIDR_MATCH *, const char *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/clean_env.c b/src/util/clean_env.c
new file mode 100644
index 0000000..4dfc33a
--- /dev/null
+++ b/src/util/clean_env.c
@@ -0,0 +1,165 @@
+/*++
+/* NAME
+/* clean_env 3
+/* SUMMARY
+/* clean up the environment
+/* SYNOPSIS
+/* #include <clean_env.h>
+/*
+/* void clean_env(preserve_list)
+/* const char **preserve_list;
+/*
+/* void update_env(preserve_list)
+/* const char **preserve_list;
+/* DESCRIPTION
+/* clean_env() reduces the process environment to the bare minimum.
+/* The function takes a null-terminated list of arguments.
+/* Each argument specifies the name of an environment variable
+/* that should be preserved, or specifies a name=value that should
+/* be entered into the new environment.
+/*
+/* update_env() applies name=value settings, but otherwise does not
+/* change the process environment.
+/* DIAGNOSTICS
+/* Fatal error: out of memory.
+/* SEE ALSO
+/* safe_getenv(3), guarded getenv()
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <argv.h>
+#include <safe.h>
+#include <clean_env.h>
+#include <stringops.h>
+
+/* clean_env - clean up the environment */
+
+void clean_env(char **preserve_list)
+{
+ extern char **environ;
+ ARGV *save_list;
+ char *value;
+ char **cpp;
+ char *copy;
+ char *key;
+ char *val;
+ const char *err;
+
+ /*
+ * Preserve or specify selected environment variables.
+ */
+ save_list = argv_alloc(10);
+ for (cpp = preserve_list; *cpp; cpp++) {
+ if (strchr(*cpp, '=') != 0) {
+ copy = mystrdup(*cpp);
+ err = split_nameval(copy, &key, &val);
+ if (err != 0)
+ msg_fatal("clean_env: %s in: %s", err, *cpp);
+ argv_add(save_list, key, val, (char *) 0);
+ myfree(copy);
+ } else if ((value = safe_getenv(*cpp)) != 0) {
+ argv_add(save_list, *cpp, value, (char *) 0);
+ }
+ }
+
+ /*
+ * Truncate the process environment, if available. On some systems
+ * (Ultrix!), environ can be a null pointer.
+ */
+ if (environ)
+ environ[0] = 0;
+
+ /*
+ * Restore preserved environment variables.
+ */
+ for (cpp = save_list->argv; *cpp; cpp += 2)
+ if (setenv(cpp[0], cpp[1], 1))
+ msg_fatal("setenv(%s, %s): %m", cpp[0], cpp[1]);
+
+ /*
+ * Cleanup.
+ */
+ argv_free(save_list);
+}
+
+/* update_env - apply name=value settings only */
+
+void update_env(char **preserve_list)
+{
+ char **cpp;
+ ARGV *save_list;
+ char *copy;
+ char *key;
+ char *val;
+ const char *err;
+
+ /*
+ * Extract name=value settings.
+ */
+ save_list = argv_alloc(10);
+ for (cpp = preserve_list; *cpp; cpp++) {
+ if (strchr(*cpp, '=') != 0) {
+ copy = mystrdup(*cpp);
+ err = split_nameval(copy, &key, &val);
+ if (err != 0)
+ msg_fatal("update_env: %s in: %s", err, *cpp);
+ argv_add(save_list, key, val, (char *) 0);
+ myfree(copy);
+ }
+ }
+
+ /*
+ * Apply name=value settings.
+ */
+ for (cpp = save_list->argv; *cpp; cpp += 2)
+ if (setenv(cpp[0], cpp[1], 1))
+ msg_fatal("setenv(%s, %s): %m", cpp[0], cpp[1]);
+
+ /*
+ * Cleanup.
+ */
+ argv_free(save_list);
+}
+
+#ifdef TEST
+
+#include <stdlib.h>
+#include <vstream.h>
+
+int main(int argc, char **argv)
+{
+ extern char **environ;
+ char **cpp;
+
+ clean_env(argv + 1);
+ for (cpp = environ; *cpp; cpp++)
+ vstream_printf("%s\n", *cpp);
+ vstream_fflush(VSTREAM_OUT);
+ exit(0);
+}
+
+#endif
diff --git a/src/util/clean_env.h b/src/util/clean_env.h
new file mode 100644
index 0000000..5c89723
--- /dev/null
+++ b/src/util/clean_env.h
@@ -0,0 +1,36 @@
+#ifndef _CLEAN_ENV_H_INCLUDED_
+#define _CLEAN_ENV_H_INCLUDED_
+
+/*++
+/* NAME
+/* clean_env 3h
+/* SUMMARY
+/* clean up the environment
+/* SYNOPSIS
+/* #include <clean_env.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern void clean_env(char **);
+extern void update_env(char **);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/close_on_exec.c b/src/util/close_on_exec.c
new file mode 100644
index 0000000..efa3415
--- /dev/null
+++ b/src/util/close_on_exec.c
@@ -0,0 +1,60 @@
+/*++
+/* NAME
+/* close_on_exec 3
+/* SUMMARY
+/* set/clear close-on-exec flag
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* int close_on_exec(int fd, int on)
+/* DESCRIPTION
+/* the \fIclose_on_exec\fR() function manipulates the close-on-exec
+/* flag for the specified open file, and returns the old setting.
+/*
+/* Arguments:
+/* .IP fd
+/* A file descriptor.
+/* .IP on
+/* Use CLOSE_ON_EXEC or PASS_ON_EXEC.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System interfaces. */
+
+#include <sys_defs.h>
+#include <fcntl.h>
+
+/* Utility library. */
+
+#include "msg.h"
+
+/* Application-specific. */
+
+#include "iostuff.h"
+
+#define PATTERN FD_CLOEXEC
+
+/* close_on_exec - set/clear close-on-exec flag */
+
+int close_on_exec(fd, on)
+int fd;
+int on;
+{
+ int flags;
+
+ if ((flags = fcntl(fd, F_GETFD, 0)) < 0)
+ msg_fatal("fcntl: get flags: %m");
+ if (fcntl(fd, F_SETFD, on ? flags | PATTERN : flags & ~PATTERN) < 0)
+ msg_fatal("fcntl: set close-on-exec flag %s: %m", on ? "on" : "off");
+ return ((flags & PATTERN) != 0);
+}
diff --git a/src/util/compat_va_copy.h b/src/util/compat_va_copy.h
new file mode 100644
index 0000000..6a2042b
--- /dev/null
+++ b/src/util/compat_va_copy.h
@@ -0,0 +1,44 @@
+#ifndef _COMPAT_VA_COPY_H_INCLUDED_
+#define _COMPAT_VA_COPY_H_INCLUDED_
+
+/*++
+/* NAME
+/* compat_va_copy 3h
+/* SUMMARY
+/* compatibility
+/* SYNOPSIS
+/* #include <compat_va_copy.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * C99 defines va_start and va_copy as macros, so we can probe the
+ * compilation environment with #ifdef etc. Some environments define
+ * __va_copy so we probe for that, too.
+ */
+#if !defined(va_start)
+#error "include <stdarg.h> first"
+#endif
+
+#if !defined(VA_COPY)
+#if defined(va_copy)
+#define VA_COPY(dest, src) va_copy(dest, src)
+#elif defined(__va_copy)
+#define VA_COPY(dest, src) __va_copy(dest, src)
+#else
+#define VA_COPY(dest, src) (dest) = (src)
+#endif
+#endif /* VA_COPY */
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/concatenate.c b/src/util/concatenate.c
new file mode 100644
index 0000000..7b6a3eb
--- /dev/null
+++ b/src/util/concatenate.c
@@ -0,0 +1,73 @@
+/*++
+/* NAME
+/* concatenate 3
+/* SUMMARY
+/* concatenate strings
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* char *concatenate(str, ...)
+/* const char *str;
+/* DESCRIPTION
+/* The \fBconcatenate\fR routine concatenates a null-terminated
+/* list of pointers to null-terminated character strings.
+/* The result is dynamically allocated and should be passed to myfree()
+/* when no longer needed.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include "mymalloc.h"
+#include "stringops.h"
+#include "compat_va_copy.h"
+
+/* concatenate - concatenate null-terminated list of strings */
+
+char *concatenate(const char *arg0,...)
+{
+ char *result;
+ va_list ap;
+ va_list ap2;
+ ssize_t len;
+ char *arg;
+
+ /*
+ * Initialize argument lists.
+ */
+ va_start(ap, arg0);
+ VA_COPY(ap2, ap);
+
+ /*
+ * Compute the length of the resulting string.
+ */
+ len = strlen(arg0);
+ while ((arg = va_arg(ap, char *)) != 0)
+ len += strlen(arg);
+ va_end(ap);
+
+ /*
+ * Build the resulting string. Don't care about wasting a CPU cycle.
+ */
+ result = mymalloc(len + 1);
+ strcpy(result, arg0);
+ while ((arg = va_arg(ap2, char *)) != 0)
+ strcat(result, arg);
+ va_end(ap2);
+ return (result);
+}
diff --git a/src/util/connect.h b/src/util/connect.h
new file mode 100644
index 0000000..1e8de11
--- /dev/null
+++ b/src/util/connect.h
@@ -0,0 +1,43 @@
+#ifndef _CONNECT_H_INCLUDED_
+#define _CONNECT_H_INCLUDED_
+
+/*++
+/* NAME
+/* connect 3h
+/* SUMMARY
+/* client interface file
+/* SYNOPSIS
+/* #include <connect.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <iostuff.h>
+
+ /*
+ * Client external interface.
+ */
+extern int unix_connect(const char *, int, int);
+extern int inet_connect(const char *, int, int);
+extern int stream_connect(const char *, int, int);
+extern int unix_dgram_connect(const char *, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/ctable.c b/src/util/ctable.c
new file mode 100644
index 0000000..4993671
--- /dev/null
+++ b/src/util/ctable.c
@@ -0,0 +1,321 @@
+/*++
+/* NAME
+/* ctable 3
+/* SUMMARY
+/* cache manager
+/* SYNOPSIS
+/* #include <ctable.h>
+/*
+/* CTABLE *ctable_create(limit, create, delete, context)
+/* ssize_t limit;
+/* void *(*create)(const char *key, void *context);
+/* void (*delete)(void *value, void *context);
+/* void *context;
+/*
+/* const void *ctable_locate(cache, key)
+/* CTABLE *cache;
+/* const char *key;
+/*
+/* const void *ctable_refresh(cache, key)
+/* CTABLE *cache;
+/* const char *key;
+/*
+/* const void *ctable_newcontext(cache, context)
+/* CTABLE *cache;
+/* void *context;
+/*
+/* void ctable_free(cache)
+/* CTABLE *cache;
+/*
+/* void ctable_walk(cache, action)
+/* CTABLE *cache;
+/* void (*action)(const char *key, const void *value);
+/* DESCRIPTION
+/* This module maintains multiple caches. Cache items are purged
+/* automatically when the number of items exceeds a configurable
+/* limit. Caches never shrink. Each cache entry consists of a
+/* string-valued lookup key and a generic data pointer value.
+/*
+/* ctable_create() creates a cache with the specified size limit, and
+/* returns a pointer to the result. The create and delete arguments
+/* specify pointers to call-back functions that create a value, given
+/* a key, and delete a given value, respectively. The context argument
+/* is passed on to the call-back routines.
+/* The create() and delete() functions must not modify the cache.
+/*
+/* ctable_locate() looks up or generates the value that corresponds to
+/* the specified key, and returns that value.
+/*
+/* ctable_refresh() flushes the value (if any) associated with
+/* the specified key, and returns the same result as ctable_locate().
+/*
+/* ctable_newcontext() updates the context that is passed on
+/* to call-back routines.
+/*
+/* ctable_free() destroys the specified cache, including its contents.
+/*
+/* ctable_walk() iterates over all elements in the cache, and invokes
+/* the action function for each cache element with the corresponding
+/* key and value as arguments. This function is useful mainly for
+/* cache performance debugging.
+/* Note: the action() function must not modify the cache.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory. Panic: interface violation.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <stddef.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <ring.h>
+#include <htable.h>
+#include <ctable.h>
+
+ /*
+ * Cache entries are kept in most-recently used order. We use a hash table
+ * to quickly locate cache entries.
+ */
+#define CTABLE_ENTRY struct ctable_entry
+
+struct ctable_entry {
+ RING ring; /* MRU linkage */
+ const char *key; /* lookup key */
+ void *value; /* corresponding value */
+};
+
+#define RING_TO_CTABLE_ENTRY(ring_ptr) \
+ RING_TO_APPL(ring_ptr, CTABLE_ENTRY, ring)
+#define RING_PTR_OF(x) (&((x)->ring))
+
+struct ctable {
+ HTABLE *table; /* table with key, ctable_entry pairs */
+ size_t limit; /* max nr of entries */
+ size_t used; /* current nr of entries */
+ CTABLE_CREATE_FN create; /* constructor */
+ CTABLE_DELETE_FN delete; /* destructor */
+ RING ring; /* MRU linkage */
+ void *context; /* application context */
+};
+
+#define CTABLE_MIN_SIZE 5
+
+/* ctable_create - create empty cache */
+
+CTABLE *ctable_create(ssize_t limit, CTABLE_CREATE_FN create,
+ CTABLE_DELETE_FN delete, void *context)
+{
+ CTABLE *cache = (CTABLE *) mymalloc(sizeof(CTABLE));
+ const char *myname = "ctable_create";
+
+ if (limit < 1)
+ msg_panic("%s: bad cache limit: %ld", myname, (long) limit);
+
+ cache->table = htable_create(limit);
+ cache->limit = (limit < CTABLE_MIN_SIZE ? CTABLE_MIN_SIZE : limit);
+ cache->used = 0;
+ cache->create = create;
+ cache->delete = delete;
+ ring_init(RING_PTR_OF(cache));
+ cache->context = context;
+ return (cache);
+}
+
+/* ctable_locate - look up or create cache item */
+
+const void *ctable_locate(CTABLE *cache, const char *key)
+{
+ const char *myname = "ctable_locate";
+ CTABLE_ENTRY *entry;
+
+ /*
+ * If the entry is not in the cache, make sure there is room for a new
+ * entry and install it at the front of the MRU chain. Otherwise, move
+ * the entry to the front of the MRU chain if it is not already there.
+ * All this means that the cache never shrinks.
+ */
+ if ((entry = (CTABLE_ENTRY *) htable_find(cache->table, key)) == 0) {
+ if (cache->used >= cache->limit) {
+ entry = RING_TO_CTABLE_ENTRY(ring_pred(RING_PTR_OF(cache)));
+ if (msg_verbose)
+ msg_info("%s: purge entry key %s", myname, entry->key);
+ ring_detach(RING_PTR_OF(entry));
+ cache->delete(entry->value, cache->context);
+ htable_delete(cache->table, entry->key, (void (*) (void *)) 0);
+ } else {
+ entry = (CTABLE_ENTRY *) mymalloc(sizeof(CTABLE_ENTRY));
+ cache->used++;
+ }
+ entry->value = cache->create(key, cache->context);
+ entry->key = htable_enter(cache->table, key, (void *) entry)->key;
+ ring_append(RING_PTR_OF(cache), RING_PTR_OF(entry));
+ if (msg_verbose)
+ msg_info("%s: install entry key %s", myname, entry->key);
+ } else if (entry == RING_TO_CTABLE_ENTRY(ring_succ(RING_PTR_OF(cache)))) {
+ if (msg_verbose)
+ msg_info("%s: leave existing entry key %s", myname, entry->key);
+ } else {
+ ring_detach(RING_PTR_OF(entry));
+ ring_append(RING_PTR_OF(cache), RING_PTR_OF(entry));
+ if (msg_verbose)
+ msg_info("%s: move existing entry key %s", myname, entry->key);
+ }
+ return (entry->value);
+}
+
+/* ctable_refresh - page-in fresh data for given key */
+
+const void *ctable_refresh(CTABLE *cache, const char *key)
+{
+ const char *myname = "ctable_refresh";
+ CTABLE_ENTRY *entry;
+
+ /* Materialize entry if missing. */
+ if ((entry = (CTABLE_ENTRY *) htable_find(cache->table, key)) == 0)
+ return ctable_locate(cache, key);
+
+ /* Otherwise, refresh its content. */
+ cache->delete(entry->value, cache->context);
+ entry->value = cache->create(key, cache->context);
+
+ /* Update its MRU linkage. */
+ if (entry != RING_TO_CTABLE_ENTRY(ring_succ(RING_PTR_OF(cache)))) {
+ ring_detach(RING_PTR_OF(entry));
+ ring_append(RING_PTR_OF(cache), RING_PTR_OF(entry));
+ }
+ if (msg_verbose)
+ msg_info("%s: refresh entry key %s", myname, entry->key);
+ return (entry->value);
+}
+
+/* ctable_newcontext - update call-back context */
+
+void ctable_newcontext(CTABLE *cache, void *context)
+{
+ cache->context = context;
+}
+
+static CTABLE *ctable_free_cache;
+
+/* ctable_free_callback - callback function */
+
+static void ctable_free_callback(void *ptr)
+{
+ CTABLE_ENTRY *entry = (CTABLE_ENTRY *) ptr;
+
+ ctable_free_cache->delete(entry->value, ctable_free_cache->context);
+ myfree((void *) entry);
+}
+
+/* ctable_free - destroy cache and contents */
+
+void ctable_free(CTABLE *cache)
+{
+ CTABLE *saved_cache = ctable_free_cache;
+
+ /*
+ * XXX the hash table does not pass application context so we have to
+ * store it in a global variable.
+ */
+ ctable_free_cache = cache;
+ htable_free(cache->table, ctable_free_callback);
+ myfree((void *) cache);
+ ctable_free_cache = saved_cache;
+}
+
+/* ctable_walk - iterate over all cache entries */
+
+void ctable_walk(CTABLE *cache, void (*action) (const char *, const void *))
+{
+ RING *entry = RING_PTR_OF(cache);
+
+ /* Walking down the MRU chain is less work than using ht_walk(). */
+
+ while ((entry = ring_succ(entry)) != RING_PTR_OF(cache))
+ action((RING_TO_CTABLE_ENTRY(entry)->key),
+ (RING_TO_CTABLE_ENTRY(entry)->value));
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program. Read keys from stdin, ask for values not
+ * in cache.
+ */
+#include <unistd.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <vstring_vstream.h>
+#include <msg_vstream.h>
+
+#define STR(x) vstring_str(x)
+
+static void *ask(const char *key, void *context)
+{
+ VSTRING *data_buf = (VSTRING *) context;
+
+ vstream_printf("ask: %s = ", key);
+ vstream_fflush(VSTREAM_OUT);
+ if (vstring_get_nonl(data_buf, VSTREAM_IN) == VSTREAM_EOF)
+ vstream_longjmp(VSTREAM_IN, 1);
+ if (!isatty(0)) {
+ vstream_printf("%s\n", STR(data_buf));
+ vstream_fflush(VSTREAM_OUT);
+ }
+ return (mystrdup(STR(data_buf)));
+}
+
+static void drop(void *data, void *unused_context)
+{
+ myfree(data);
+}
+
+int main(int unused_argc, char **argv)
+{
+ VSTRING *key_buf;
+ VSTRING *data_buf;
+ CTABLE *cache;
+ const char *value;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ key_buf = vstring_alloc(100);
+ data_buf = vstring_alloc(100);
+ cache = ctable_create(1, ask, drop, (void *) data_buf);
+ msg_verbose = 1;
+ vstream_control(VSTREAM_IN, CA_VSTREAM_CTL_EXCEPT, CA_VSTREAM_CTL_END);
+
+ if (vstream_setjmp(VSTREAM_IN) == 0) {
+ for (;;) {
+ vstream_printf("key = ");
+ vstream_fflush(VSTREAM_OUT);
+ if (vstring_get_nonl(key_buf, VSTREAM_IN) == VSTREAM_EOF)
+ vstream_longjmp(VSTREAM_IN, 1);
+ if (!isatty(0)) {
+ vstream_printf("%s\n", STR(key_buf));
+ vstream_fflush(VSTREAM_OUT);
+ }
+ value = ctable_locate(cache, STR(key_buf));
+ vstream_printf("result: %s\n", value);
+ }
+ }
+ ctable_free(cache);
+ vstring_free(key_buf);
+ vstring_free(data_buf);
+ return (0);
+}
+
+#endif
diff --git a/src/util/ctable.h b/src/util/ctable.h
new file mode 100644
index 0000000..ea16914
--- /dev/null
+++ b/src/util/ctable.h
@@ -0,0 +1,41 @@
+#ifndef _CTABLE_H_INCLUDED_
+#define _CTABLE_H_INCLUDED_
+
+/*++
+/* NAME
+/* ctable 5
+/* SUMMARY
+/* cache manager
+/* SYNOPSIS
+/* #include <ctable.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Interface of the cache manager. The structure of a cache is not visible
+ * to the caller.
+ */
+
+#define CTABLE struct ctable
+typedef void *(*CTABLE_CREATE_FN) (const char *, void *);
+typedef void (*CTABLE_DELETE_FN) (void *, void *);
+
+extern CTABLE *ctable_create(ssize_t, CTABLE_CREATE_FN, CTABLE_DELETE_FN, void *);
+extern void ctable_free(CTABLE *);
+extern void ctable_walk(CTABLE *, void (*) (const char *, const void *));
+extern const void *ctable_locate(CTABLE *, const char *);
+extern const void *ctable_refresh(CTABLE *, const char *);
+extern void ctable_newcontext(CTABLE *, void *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/ctable.in b/src/util/ctable.in
new file mode 100644
index 0000000..78763cf
--- /dev/null
+++ b/src/util/ctable.in
@@ -0,0 +1,39 @@
+a
+1
+b
+2
+c
+3
+d
+4
+e
+5
+f
+6
+f
+a
+1
+b
+2
+c
+3
+d
+4
+e
+5
+f
+6
+f
+e
+d
+c
+b
+a
+1
+b
+c
+d
+e
+f
+6
+f
diff --git a/src/util/ctable.ref b/src/util/ctable.ref
new file mode 100644
index 0000000..34e8a95
--- /dev/null
+++ b/src/util/ctable.ref
@@ -0,0 +1,99 @@
+key = a
+ask: a = 1
+./ctable: ctable_locate: install entry key a
+result: 1
+key = b
+ask: b = 2
+./ctable: ctable_locate: install entry key b
+result: 2
+key = c
+ask: c = 3
+./ctable: ctable_locate: install entry key c
+result: 3
+key = d
+ask: d = 4
+./ctable: ctable_locate: install entry key d
+result: 4
+key = e
+ask: e = 5
+./ctable: ctable_locate: install entry key e
+result: 5
+key = f
+./ctable: ctable_locate: purge entry key a
+ask: f = 6
+./ctable: ctable_locate: install entry key f
+result: 6
+key = f
+./ctable: ctable_locate: leave existing entry key f
+result: 6
+key = a
+./ctable: ctable_locate: purge entry key b
+ask: a = 1
+./ctable: ctable_locate: install entry key a
+result: 1
+key = b
+./ctable: ctable_locate: purge entry key c
+ask: b = 2
+./ctable: ctable_locate: install entry key b
+result: 2
+key = c
+./ctable: ctable_locate: purge entry key d
+ask: c = 3
+./ctable: ctable_locate: install entry key c
+result: 3
+key = d
+./ctable: ctable_locate: purge entry key e
+ask: d = 4
+./ctable: ctable_locate: install entry key d
+result: 4
+key = e
+./ctable: ctable_locate: purge entry key f
+ask: e = 5
+./ctable: ctable_locate: install entry key e
+result: 5
+key = f
+./ctable: ctable_locate: purge entry key a
+ask: f = 6
+./ctable: ctable_locate: install entry key f
+result: 6
+key = f
+./ctable: ctable_locate: leave existing entry key f
+result: 6
+key = e
+./ctable: ctable_locate: move existing entry key e
+result: 5
+key = d
+./ctable: ctable_locate: move existing entry key d
+result: 4
+key = c
+./ctable: ctable_locate: move existing entry key c
+result: 3
+key = b
+./ctable: ctable_locate: move existing entry key b
+result: 2
+key = a
+./ctable: ctable_locate: purge entry key f
+ask: a = 1
+./ctable: ctable_locate: install entry key a
+result: 1
+key = b
+./ctable: ctable_locate: move existing entry key b
+result: 2
+key = c
+./ctable: ctable_locate: move existing entry key c
+result: 3
+key = d
+./ctable: ctable_locate: move existing entry key d
+result: 4
+key = e
+./ctable: ctable_locate: move existing entry key e
+result: 5
+key = f
+./ctable: ctable_locate: purge entry key a
+ask: f = 6
+./ctable: ctable_locate: install entry key f
+result: 6
+key = f
+./ctable: ctable_locate: leave existing entry key f
+result: 6
+key = \ No newline at end of file
diff --git a/src/util/dict.c b/src/util/dict.c
new file mode 100644
index 0000000..35c02db
--- /dev/null
+++ b/src/util/dict.c
@@ -0,0 +1,670 @@
+/*++
+/* NAME
+/* dict 3
+/* SUMMARY
+/* dictionary manager
+/* SYNOPSIS
+/* #include <dict.h>
+/*
+/* void dict_register(dict_name, dict_info)
+/* const char *dict_name;
+/* DICT *dict_info;
+/*
+/* DICT *dict_handle(dict_name)
+/* const char *dict_name;
+/*
+/* void dict_unregister(dict_name)
+/* const char *dict_name;
+/*
+/* int dict_update(dict_name, member, value)
+/* const char *dict_name;
+/* const char *member;
+/* const char *value;
+/*
+/* const char *dict_lookup(dict_name, member)
+/* const char *dict_name;
+/* const char *member;
+/*
+/* int dict_delete(dict_name, member)
+/* const char *dict_name;
+/* const char *member;
+/*
+/* int dict_sequence(dict_name, func, member, value)
+/* const char *dict_name;
+/* int func;
+/* const char **member;
+/* const char **value;
+/*
+/* const char *dict_eval(dict_name, string, int recursive)
+/* const char *dict_name;
+/* const char *string;
+/* int recursive;
+/*
+/* int dict_walk(action, context)
+/* void (*action)(dict_name, dict_handle, context)
+/* void *context;
+/*
+/* int dict_error(dict_name)
+/* const char *dict_name;
+/*
+/* const char *dict_changed_name()
+/*
+/* void DICT_OWNER_AGGREGATE_INIT(aggregate)
+/* DICT_OWNER aggregate;
+/*
+/* void DICT_OWNER_AGGREGATE_UPDATE(aggregate, source)
+/* DICT_OWNER aggregate;
+/* DICT_OWNER source;
+/* AUXILIARY FUNCTIONS
+/* int dict_load_file_xt(dict_name, path)
+/* const char *dict_name;
+/* const char *path;
+/*
+/* void dict_load_fp(dict_name, fp)
+/* const char *dict_name;
+/* VSTREAM *fp;
+/*
+/* const char *dict_flags_str(dict_flags)
+/* int dict_flags;
+/*
+/* int dict_flags_mask(names)
+/* const char *names;
+/* DESCRIPTION
+/* This module maintains a collection of name-value dictionaries.
+/* Each dictionary has its own name and has its own methods to read
+/* or update members. Examples of dictionaries that can be accessed
+/* in this manner are the global UNIX-style process environment,
+/* hash tables, NIS maps, DBM files, and so on. Dictionary values
+/* are not limited to strings but can be arbitrary objects as long
+/* as they can be represented by character pointers.
+/* FEATURES
+/* .fi
+/* .ad
+/* Notable features of this module are:
+/* .IP "macro expansion (string-valued dictionaries only)"
+/* Macros of the form $\fIname\fR can be expanded to the current
+/* value of \fIname\fR. The forms $(\fIname\fR) and ${\fIname\fR} are
+/* also supported.
+/* .IP "unknown names"
+/* An update request for an unknown dictionary name will trigger
+/* the instantiation of an in-memory dictionary with that name.
+/* A lookup request (including delete and sequence) for an
+/* unknown dictionary will result in a "not found" and "no
+/* error" result.
+/* .PP
+/* dict_register() adds a new dictionary, including access methods,
+/* to the list of known dictionaries, or increments the reference
+/* count for an existing (name, dictionary) pair. Otherwise, it is
+/* an error to pass an existing name (this would cause a memory leak).
+/*
+/* dict_handle() returns the generic dictionary handle of the
+/* named dictionary, or a null pointer when the named dictionary
+/* is not found.
+/*
+/* dict_unregister() decrements the reference count of the named
+/* dictionary. When the reference count reaches zero, dict_unregister()
+/* breaks the (name, dictionary) association and executes the
+/* dictionary's optional \fIremove\fR method.
+/*
+/* dict_update() updates the value of the named dictionary member.
+/* The dictionary member and the named dictionary are instantiated
+/* on the fly. The result value is zero (DICT_STAT_SUCCESS)
+/* when the update was made.
+/*
+/* dict_lookup() returns the value of the named member (i.e. without
+/* expanding macros in the member value). The \fIdict_name\fR argument
+/* specifies the dictionary to search. The result is a null pointer
+/* when no value is found, otherwise the result is owned by the
+/* underlying dictionary method. Make a copy if the result is to be
+/* modified, or if the result is to survive multiple dict_lookup() calls.
+/*
+/* dict_delete() removes the named member from the named dictionary.
+/* The result value is zero (DICT_STAT_SUCCESS) when the member
+/* was found.
+/*
+/* dict_sequence() steps through the named dictionary and returns
+/* keys and values in some implementation-defined order. The func
+/* argument is DICT_SEQ_FUN_FIRST to set the cursor to the first
+/* entry or DICT_SEQ_FUN_NEXT to select the next entry. The result
+/* is owned by the underlying dictionary method. Make a copy if the
+/* result is to be modified, or if the result is to survive multiple
+/* dict_sequence() calls. The result value is zero (DICT_STAT_SUCCESS)
+/* when a member was found.
+/*
+/* dict_eval() expands macro references in the specified string.
+/* The result is owned by the dictionary manager. Make a copy if the
+/* result is to survive multiple dict_eval() calls. When the
+/* \fIrecursive\fR argument is non-zero, macro references in macro
+/* lookup results are expanded recursively.
+/*
+/* dict_walk() iterates over all registered dictionaries in some
+/* arbitrary order, and invokes the specified action routine with
+/* as arguments:
+/* .IP "const char *dict_name"
+/* Dictionary name.
+/* .IP "DICT *dict_handle"
+/* Generic dictionary handle.
+/* .IP "char *context"
+/* Application context from the caller.
+/* .PP
+/* dict_changed_name() returns non-zero when any dictionary needs to
+/* be re-opened because it has changed or because it was unlinked.
+/* A non-zero result is the name of a changed dictionary.
+/*
+/* dict_load_file_xt() reads name-value entries from the named file.
+/* Lines that begin with whitespace are concatenated to the preceding
+/* line (the newline is deleted).
+/* Each entry is stored in the dictionary named by \fIdict_name\fR.
+/* The result is zero if the file could not be opened.
+/*
+/* dict_load_fp() reads name-value entries from an open stream.
+/* It has the same semantics as the dict_load_file_xt() function.
+/*
+/* dict_flags_str() returns a printable representation of the
+/* specified dictionary flags. The result is overwritten upon
+/* each call.
+/*
+/* dict_flags_mask() returns the bitmask for the specified
+/* comma/space-separated dictionary flag names.
+/* TRUST AND PROVENANCE
+/* .ad
+/* .fi
+/* Each dictionary has an owner attribute that contains (status,
+/* uid) information about the owner of a dictionary. The
+/* status is one of the following:
+/* .IP DICT_OWNER_TRUSTED
+/* The dictionary is owned by a trusted user. The uid is zero,
+/* and specifies a UNIX user ID.
+/* .IP DICT_OWNER_UNTRUSTED
+/* The dictionary is owned by an untrusted user. The uid is
+/* non-zero, and specifies a UNIX user ID.
+/* .IP DICT_OWNER_UNKNOWN
+/* The dictionary is owned by an unspecified user. For example,
+/* the origin is unauthenticated, or different parts of a
+/* dictionary aggregate (see below) are owned by different
+/* untrusted users. The uid is non-zero and does not specify
+/* a UNIX user ID.
+/* .PP
+/* Note that dictionary ownership does not necessarily imply
+/* ownership of lookup results. For example, a PCRE table may
+/* be owned by the trusted root user, but the result of $number
+/* expansion can contain data from an arbitrary remote SMTP
+/* client. See dict_open(3) for how to disallow $number
+/* expansions with security-sensitive operations.
+/*
+/* Two macros are available to help determine the provenance
+/* and trustworthiness of a dictionary aggregate. The macros
+/* are unsafe because they may evaluate arguments more than
+/* once.
+/*
+/* DICT_OWNER_AGGREGATE_INIT() initialize aggregate owner
+/* attributes to the highest trust level.
+/*
+/* DICT_OWNER_AGGREGATE_UPDATE() updates the aggregate owner
+/* attributes with the attributes of the specified source, and
+/* reduces the aggregate trust level as appropriate.
+/* SEE ALSO
+/* htable(3)
+/* BUGS
+/* DIAGNOSTICS
+/* Fatal errors: out of memory, malformed macro name.
+/*
+/* The lookup routine returns non-null when the request is
+/* satisfied. The update, delete and sequence routines return
+/* zero (DICT_STAT_SUCCESS) when the request is satisfied.
+/* The dict_error() function returns non-zero only when the
+/* last operation was not satisfied due to a dictionary access
+/* error. The result can have the following values:
+/* .IP DICT_ERR_NONE(zero)
+/* There was no dictionary access error. For example, the
+/* request was satisfied, the requested information did not
+/* exist in the dictionary, or the information already existed
+/* when it should not exist (collision).
+/* .IP DICT_ERR_RETRY(<0)
+/* The dictionary was temporarily unavailable. This can happen
+/* with network-based services.
+/* .IP DICT_ERR_CONFIG(<0)
+/* The dictionary was unavailable due to a configuration error.
+/* .PP
+/* Generally, a program is expected to test the function result
+/* value for "success" first. If the operation was not successful,
+/* a program is expected to test for a non-zero dict->error
+/* status to distinguish between a data notfound/collision
+/* condition or a dictionary access error.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System libraries. */
+
+#include "sys_defs.h"
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "htable.h"
+#include "mymalloc.h"
+#include "vstream.h"
+#include "vstring.h"
+#include "readlline.h"
+#include "mac_expand.h"
+#include "stringops.h"
+#include "iostuff.h"
+#include "name_mask.h"
+#include "dict.h"
+#include "dict_ht.h"
+#include "warn_stat.h"
+#include "line_number.h"
+
+static HTABLE *dict_table;
+
+ /*
+ * Each (name, dictionary) instance has a reference count. The count is part
+ * of the name, not the dictionary. The same dictionary may be registered
+ * under multiple names. The structure below keeps track of instances and
+ * reference counts.
+ */
+typedef struct {
+ DICT *dict;
+ int refcount;
+} DICT_NODE;
+
+#define dict_node(dict) \
+ (dict_table ? (DICT_NODE *) htable_find(dict_table, dict) : 0)
+
+/* Find a dictionary handle by name for lookup purposes. */
+
+#define DICT_FIND_FOR_LOOKUP(dict, dict_name) do { \
+ DICT_NODE *node; \
+ if ((node = dict_node(dict_name)) != 0) \
+ dict = node->dict; \
+ else \
+ dict = 0; \
+} while (0)
+
+/* Find a dictionary handle by name for update purposes. */
+
+#define DICT_FIND_FOR_UPDATE(dict, dict_name) do { \
+ DICT_NODE *node; \
+ if ((node = dict_node(dict_name)) == 0) { \
+ dict = dict_ht_open(dict_name, O_CREAT | O_RDWR, 0); \
+ dict_register(dict_name, dict); \
+ } else \
+ dict = node->dict; \
+} while (0)
+
+#define STR(x) vstring_str(x)
+
+/* dict_register - make association with dictionary */
+
+void dict_register(const char *dict_name, DICT *dict_info)
+{
+ const char *myname = "dict_register";
+ DICT_NODE *node;
+
+ if (dict_table == 0)
+ dict_table = htable_create(0);
+ if ((node = dict_node(dict_name)) == 0) {
+ node = (DICT_NODE *) mymalloc(sizeof(*node));
+ node->dict = dict_info;
+ node->refcount = 0;
+ htable_enter(dict_table, dict_name, (void *) node);
+ } else if (dict_info != node->dict)
+ msg_fatal("%s: dictionary name exists: %s", myname, dict_name);
+ node->refcount++;
+ if (msg_verbose > 1)
+ msg_info("%s: %s %d", myname, dict_name, node->refcount);
+}
+
+/* dict_handle - locate generic dictionary handle */
+
+DICT *dict_handle(const char *dict_name)
+{
+ DICT_NODE *node;
+
+ return ((node = dict_node(dict_name)) != 0 ? node->dict : 0);
+}
+
+/* dict_node_free - dict_unregister() callback */
+
+static void dict_node_free(void *ptr)
+{
+ DICT_NODE *node = (DICT_NODE *) ptr;
+ DICT *dict = node->dict;
+
+ if (dict->close)
+ dict->close(dict);
+ myfree((void *) node);
+}
+
+/* dict_unregister - break association with named dictionary */
+
+void dict_unregister(const char *dict_name)
+{
+ const char *myname = "dict_unregister";
+ DICT_NODE *node;
+
+ if ((node = dict_node(dict_name)) == 0)
+ msg_panic("non-existing dictionary: %s", dict_name);
+ if (msg_verbose > 1)
+ msg_info("%s: %s %d", myname, dict_name, node->refcount);
+ if (--(node->refcount) == 0)
+ htable_delete(dict_table, dict_name, dict_node_free);
+}
+
+/* dict_update - replace or add dictionary entry */
+
+int dict_update(const char *dict_name, const char *member, const char *value)
+{
+ const char *myname = "dict_update";
+ DICT *dict;
+
+ DICT_FIND_FOR_UPDATE(dict, dict_name);
+ if (msg_verbose > 1)
+ msg_info("%s: %s = %s", myname, member, value);
+ return (dict->update(dict, member, value));
+}
+
+/* dict_lookup - look up dictionary entry */
+
+const char *dict_lookup(const char *dict_name, const char *member)
+{
+ const char *myname = "dict_lookup";
+ DICT *dict;
+ const char *ret;
+
+ DICT_FIND_FOR_LOOKUP(dict, dict_name);
+ if (dict != 0) {
+ ret = dict->lookup(dict, member);
+ if (msg_verbose > 1)
+ msg_info("%s: %s = %s", myname, member, ret ? ret :
+ dict->error ? "(error)" : "(notfound)");
+ return (ret);
+ } else {
+ if (msg_verbose > 1)
+ msg_info("%s: %s = %s", myname, member, "(notfound)");
+ return (0);
+ }
+}
+
+/* dict_delete - delete dictionary entry */
+
+int dict_delete(const char *dict_name, const char *member)
+{
+ const char *myname = "dict_delete";
+ DICT *dict;
+
+ DICT_FIND_FOR_LOOKUP(dict, dict_name);
+ if (msg_verbose > 1)
+ msg_info("%s: delete %s", myname, member);
+ return (dict ? dict->delete(dict, member) : DICT_STAT_FAIL);
+}
+
+/* dict_sequence - traverse dictionary */
+
+int dict_sequence(const char *dict_name, const int func,
+ const char **member, const char **value)
+{
+ const char *myname = "dict_sequence";
+ DICT *dict;
+
+ DICT_FIND_FOR_LOOKUP(dict, dict_name);
+ if (msg_verbose > 1)
+ msg_info("%s: sequence func %d", myname, func);
+ return (dict ? dict->sequence(dict, func, member, value) : DICT_STAT_FAIL);
+}
+
+/* dict_error - return last error */
+
+int dict_error(const char *dict_name)
+{
+ DICT *dict;
+
+ DICT_FIND_FOR_LOOKUP(dict, dict_name);
+ return (dict ? dict->error : DICT_ERR_NONE);
+}
+
+/* dict_load_file_xt - read entries from text file */
+
+int dict_load_file_xt(const char *dict_name, const char *path)
+{
+ VSTREAM *fp;
+ struct stat st;
+ time_t before;
+ time_t after;
+
+ /*
+ * Read the file again if it is hot. This may result in reading a partial
+ * parameter name when a file changes in the middle of a read.
+ */
+ for (before = time((time_t *) 0); /* see below */ ; before = after) {
+ if ((fp = vstream_fopen(path, O_RDONLY, 0)) == 0)
+ return (0);
+ dict_load_fp(dict_name, fp);
+ if (fstat(vstream_fileno(fp), &st) < 0)
+ msg_fatal("fstat %s: %m", path);
+ if (vstream_ferror(fp) || vstream_fclose(fp))
+ msg_fatal("read %s: %m", path);
+ after = time((time_t *) 0);
+ if (st.st_mtime < before - 1 || st.st_mtime > after)
+ break;
+ if (msg_verbose > 1)
+ msg_info("pausing to let %s cool down", path);
+ doze(300000);
+ dict_unregister(dict_name);
+ }
+ 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..217deb7
--- /dev/null
+++ b/src/util/dict.h
@@ -0,0 +1,346 @@
+#ifndef _DICT_H_INCLUDED_
+#define _DICT_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict 3h
+/* SUMMARY
+/* dictionary manager
+/* SYNOPSIS
+/* #include <dict.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <setjmp.h>
+
+#ifdef NO_SIGSETJMP
+#define DICT_JMP_BUF jmp_buf
+#else
+#define DICT_JMP_BUF sigjmp_buf
+#endif
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <argv.h>
+#include <vstring.h>
+#include <myflock.h>
+
+ /*
+ * Provenance information.
+ */
+typedef struct DICT_OWNER {
+ int status; /* see below */
+ uid_t uid; /* use only if status == UNTRUSTED */
+} DICT_OWNER;
+
+ /*
+ * Note that trust levels are not in numerical order.
+ */
+#define DICT_OWNER_UNKNOWN (-1) /* ex: unauthenticated tcp, proxy */
+#define DICT_OWNER_TRUSTED (!1) /* ex: root-owned config file */
+#define DICT_OWNER_UNTRUSTED (!0) /* ex: non-root config file */
+
+ /*
+ * When combining tables with different provenance, we initialize to the
+ * highest trust level, and remember the lowest trust level that we find
+ * during aggregation. If we combine tables that are owned by different
+ * untrusted users, the resulting provenance is "unknown".
+ */
+#define DICT_OWNER_AGGREGATE_INIT(dst) { \
+ (dst).status = DICT_OWNER_TRUSTED; \
+ (dst).uid = 0; \
+ } while (0)
+
+ /*
+ * The following is derived from the 3x3 transition matrix.
+ */
+#define DICT_OWNER_AGGREGATE_UPDATE(dst, src) do { \
+ if ((dst).status == DICT_OWNER_TRUSTED \
+ || (src).status == DICT_OWNER_UNKNOWN) { \
+ (dst) = (src); \
+ } else if ((dst).status == (src).status \
+ && (dst).uid != (src).uid) { \
+ (dst).status = DICT_OWNER_UNKNOWN; \
+ (dst).uid = ~0; \
+ } \
+ } while (0)
+
+ /*
+ * Generic dictionary interface - in reality, a dictionary extends this
+ * structure with private members to maintain internal state.
+ */
+typedef struct DICT {
+ char *type; /* for diagnostics */
+ char *name; /* for diagnostics */
+ int flags; /* see below */
+ const char *(*lookup) (struct DICT *, const char *);
+ int (*update) (struct DICT *, const char *, const char *);
+ int (*delete) (struct DICT *, const char *);
+ int (*sequence) (struct DICT *, int, const char **, const char **);
+ int (*lock) (struct DICT *, int);
+ void (*close) (struct DICT *);
+ int lock_type; /* for read/write lock */
+ int lock_fd; /* for read/write lock */
+ int stat_fd; /* change detection */
+ time_t mtime; /* mod time at open */
+ VSTRING *fold_buf; /* key folding buffer */
+ DICT_OWNER owner; /* provenance */
+ int error; /* last operation only */
+ DICT_JMP_BUF *jbuf; /* exception handling */
+ struct DICT_UTF8_BACKUP *utf8_backup; /* see below */
+ struct VSTRING *file_buf; /* dict_file_to_buf() */
+ struct VSTRING *file_b64; /* dict_file_to_b64() */
+} DICT;
+
+extern DICT *dict_alloc(const char *, const char *, ssize_t);
+extern void dict_free(DICT *);
+
+extern DICT *dict_debug(DICT *);
+
+#define DICT_DEBUG(d) ((d)->flags & DICT_FLAG_DEBUG ? dict_debug(d) : (d))
+
+ /*
+ * See dict_open.c embedded manpage for flag definitions.
+ */
+#define DICT_FLAG_NONE (0)
+#define DICT_FLAG_DUP_WARN (1<<0) /* warn about dups if not supported */
+#define DICT_FLAG_DUP_IGNORE (1<<1) /* ignore dups if not supported */
+#define DICT_FLAG_TRY0NULL (1<<2) /* do not append 0 to key/value */
+#define DICT_FLAG_TRY1NULL (1<<3) /* append 0 to key/value */
+#define DICT_FLAG_FIXED (1<<4) /* fixed key map */
+#define DICT_FLAG_PATTERN (1<<5) /* keys are patterns */
+#define DICT_FLAG_LOCK (1<<6) /* use temp lock before access */
+#define DICT_FLAG_DUP_REPLACE (1<<7) /* replace dups if supported */
+#define DICT_FLAG_SYNC_UPDATE (1<<8) /* sync updates if supported */
+#define DICT_FLAG_DEBUG (1<<9) /* log access */
+/*#define DICT_FLAG_FOLD_KEY (1<<10) /* lowercase the lookup key */
+#define DICT_FLAG_NO_REGSUB (1<<11) /* disallow regexp substitution */
+#define DICT_FLAG_NO_PROXY (1<<12) /* disallow proxy mapping */
+#define DICT_FLAG_NO_UNAUTH (1<<13) /* disallow unauthenticated data */
+#define DICT_FLAG_FOLD_FIX (1<<14) /* case-fold key with fixed-case map */
+#define DICT_FLAG_FOLD_MUL (1<<15) /* case-fold key with multi-case map */
+#define DICT_FLAG_FOLD_ANY (DICT_FLAG_FOLD_FIX | DICT_FLAG_FOLD_MUL)
+#define DICT_FLAG_OPEN_LOCK (1<<16) /* perm lock if not multi-writer safe */
+#define DICT_FLAG_BULK_UPDATE (1<<17) /* optimize for bulk updates */
+#define DICT_FLAG_MULTI_WRITER (1<<18) /* multi-writer safe map */
+#define DICT_FLAG_UTF8_REQUEST (1<<19) /* activate UTF-8 if possible */
+#define DICT_FLAG_UTF8_ACTIVE (1<<20) /* UTF-8 proxy layer is present */
+#define DICT_FLAG_SRC_RHS_IS_FILE \
+ (1<<21) /* Map source RHS is a file */
+
+#define DICT_FLAG_UTF8_MASK (DICT_FLAG_UTF8_REQUEST)
+
+ /* IMPORTANT: Update the dict_mask[] table when the above changes */
+
+ /*
+ * The subsets of flags that control how a map is used. These are relevant
+ * mainly for proxymap support. Note: some categories overlap.
+ *
+ * DICT_FLAG_IMPL_MASK - flags that are set by the map implementation itself.
+ *
+ * DICT_FLAG_PARANOID - requestor flags that forbid the use of insecure map
+ * types for security-sensitive operations. These flags are checked by the
+ * map implementation itself upon open, lookup etc. requests.
+ *
+ * DICT_FLAG_RQST_MASK - all requestor flags, including paranoid flags, that
+ * the requestor may change between open, lookup etc. requests. These
+ * specify requestor properties, not map properties.
+ *
+ * DICT_FLAG_INST_MASK - none of the above flags. The requestor may not change
+ * these flags between open, lookup, etc. requests (although a map may make
+ * changes to its copy of some of these flags). The proxymap server opens
+ * only one map instance for all client requests with the same values of
+ * these flags, and the proxymap client uses its own saved copy of these
+ * flags. DICT_FLAG_SRC_RHS_IS_FILE is an example of such a flag.
+ */
+#define DICT_FLAG_PARANOID \
+ (DICT_FLAG_NO_REGSUB | DICT_FLAG_NO_PROXY | DICT_FLAG_NO_UNAUTH)
+#define DICT_FLAG_IMPL_MASK (DICT_FLAG_FIXED | DICT_FLAG_PATTERN | \
+ DICT_FLAG_MULTI_WRITER)
+#define DICT_FLAG_RQST_MASK (DICT_FLAG_FOLD_ANY | DICT_FLAG_LOCK | \
+ DICT_FLAG_DUP_REPLACE | DICT_FLAG_DUP_WARN | \
+ DICT_FLAG_DUP_IGNORE | DICT_FLAG_SYNC_UPDATE | \
+ DICT_FLAG_PARANOID | DICT_FLAG_UTF8_MASK)
+#define DICT_FLAG_INST_MASK ~(DICT_FLAG_IMPL_MASK | DICT_FLAG_RQST_MASK)
+
+ /*
+ * Feature tests.
+ */
+#define DICT_NEED_UTF8_ACTIVATION(enable, flags) \
+ ((enable) && ((flags) & DICT_FLAG_UTF8_MASK))
+
+ /*
+ * dict->error values. Errors must be negative; smtpd_check depends on this.
+ */
+#define DICT_ERR_NONE 0 /* no error */
+#define DICT_ERR_RETRY (-1) /* soft error */
+#define DICT_ERR_CONFIG (-2) /* configuration error */
+
+ /*
+ * Result values for exposed functions except lookup. FAIL/ERROR are
+ * suggested values, not for use in comparisons for equality.
+ */
+#define DICT_STAT_FAIL 1 /* any value > 0: notfound, conflict */
+#define DICT_STAT_SUCCESS 0 /* request satisfied */
+#define DICT_STAT_ERROR (-1) /* any value < 0: database error */
+
+ /*
+ * Set an error code and return a result value.
+ */
+#define DICT_ERR_VAL_RETURN(dict, err, val) do { \
+ (dict)->error = (err); \
+ return (val); \
+ } while (0)
+
+ /*
+ * Sequence function types.
+ */
+#define DICT_SEQ_FUN_FIRST 0 /* set cursor to first record */
+#define DICT_SEQ_FUN_NEXT 1 /* set cursor to next record */
+
+ /*
+ * Interface for dictionary types.
+ */
+extern ARGV *dict_mapnames(void);
+typedef void (*DICT_MAPNAMES_EXTEND_FN) (ARGV *);
+extern DICT_MAPNAMES_EXTEND_FN dict_mapnames_extend(DICT_MAPNAMES_EXTEND_FN);
+
+
+ /*
+ * High-level interface, with logical dictionary names.
+ */
+extern void dict_register(const char *, DICT *);
+extern DICT *dict_handle(const char *);
+extern void dict_unregister(const char *);
+extern int dict_update(const char *, const char *, const char *);
+extern const char *dict_lookup(const char *, const char *);
+extern int dict_delete(const char *, const char *);
+extern int dict_sequence(const char *, const int, const char **, const char **);
+extern int dict_load_file_xt(const char *, const char *);
+extern void dict_load_fp(const char *, VSTREAM *);
+extern const char *dict_eval(const char *, const char *, int);
+extern int dict_error(const char *);
+
+ /*
+ * Low-level interface, with physical dictionary handles.
+ */
+typedef DICT *(*DICT_OPEN_FN) (const char *, int, int);
+typedef struct {
+ const char *type;
+ DICT_OPEN_FN dict_fn;
+ struct MKMAP *(*mkmap_fn) (const char *);
+} DICT_OPEN_INFO;
+typedef const DICT_OPEN_INFO *(*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 DICT_OPEN_INFO *);
+extern const DICT_OPEN_INFO *dict_open_lookup(const char *);
+extern DICT_OPEN_EXTEND_FN dict_open_extend(DICT_OPEN_EXTEND_FN);
+
+#define dict_get(dp, key) ((const char *) (dp)->lookup((dp), (key)))
+#define dict_put(dp, key, val) (dp)->update((dp), (key), (val))
+#define dict_del(dp, key) (dp)->delete((dp), (key))
+#define dict_seq(dp, f, key, val) (dp)->sequence((dp), (f), (key), (val))
+#define dict_close(dp) (dp)->close(dp)
+typedef void (*DICT_WALK_ACTION) (const char *, DICT *, void *);
+extern void dict_walk(DICT_WALK_ACTION, void *);
+extern int dict_changed(void);
+extern const char *dict_changed_name(void);
+extern const char *dict_flags_str(int);
+extern int dict_flags_mask(const char *);
+extern void dict_type_override(DICT *, const char *);
+
+ /*
+ * Check and convert UTF-8 keys and values.
+ */
+typedef struct DICT_UTF8_BACKUP {
+ const char *(*lookup) (struct DICT *, const char *);
+ int (*update) (struct DICT *, const char *, const char *);
+ int (*delete) (struct DICT *, const char *);
+} DICT_UTF8_BACKUP;
+
+extern DICT *dict_utf8_activate(DICT *);
+
+ /*
+ * Driver for interactive or scripted tests.
+ */
+void dict_test(int, char **);
+
+ /*
+ * Behind-the-scenes support to continue execution with reduced
+ * functionality.
+ */
+extern int dict_allow_surrogate;
+extern DICT *PRINTFLIKE(5, 6) dict_surrogate(const char *, const char *, int, int, const char *,...);
+
+ /*
+ * This name is reserved for matchlist error handling.
+ */
+#define DICT_TYPE_NOFILE "non-existent"
+#define DICT_TYPE_NOUTF8 "non-UTF-8"
+
+ /*
+ * Duplicated from vstream(3). This should probably be abstracted out.
+ *
+ * Exception handling. We use pointer to jmp_buf to avoid a lot of unused
+ * baggage for streams that don't need this functionality.
+ *
+ * XXX sigsetjmp()/siglongjmp() save and restore the signal mask which can
+ * avoid surprises in code that manipulates signals, but unfortunately some
+ * systems have bugs in their implementation.
+ */
+#ifdef NO_SIGSETJMP
+#define dict_setjmp(dict) setjmp((dict)->jbuf[0])
+#define dict_longjmp(dict, val) longjmp((dict)->jbuf[0], (val))
+#else
+#define dict_setjmp(dict) sigsetjmp((dict)->jbuf[0], 1)
+#define dict_longjmp(dict, val) siglongjmp((dict)->jbuf[0], (val))
+#endif
+#define dict_isjmp(dict) ((dict)->jbuf != 0)
+
+ /*
+ * Temporary API. If exception handling proves to be useful,
+ * dict_jmp_alloc() should be integrated into dict_alloc().
+ */
+extern void dict_jmp_alloc(DICT *);
+
+ /*
+ * dict_file(3).
+ */
+extern struct VSTRING *dict_file_to_buf(DICT *, const char *);
+extern struct VSTRING *dict_file_to_b64(DICT *, const char *);
+extern struct VSTRING *dict_file_from_b64(DICT *, const char *);
+extern char *dict_file_get_error(DICT *);
+extern void dict_file_purge_buffers(DICT *);
+extern const char *dict_file_lookup(DICT *dict, const char *);
+
+ /*
+ * dict_stream(3)
+ */
+extern VSTREAM *dict_stream_open(const char *dict_type, const char *mapname,
+ int open_flags, int dict_flags, struct stat * st, VSTRING **why);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/dict_alloc.c b/src/util/dict_alloc.c
new file mode 100644
index 0000000..3285a38
--- /dev/null
+++ b/src/util/dict_alloc.c
@@ -0,0 +1,196 @@
+/*++
+/* NAME
+/* dict_alloc 3
+/* SUMMARY
+/* dictionary memory manager
+/* SYNOPSIS
+/* #include <dict.h>
+/*
+/* DICT *dict_alloc(dict_type, dict_name, size)
+/* const char *dict_type;
+/* const char *dict_name;
+/* ssize_t size;
+/*
+/* void dict_free(dict)
+/* DICT *ptr;
+/*
+/* void dict_jmp_alloc(dict)
+/* DICT *ptr;
+/* DESCRIPTION
+/* dict_alloc() allocates memory for a dictionary structure of
+/* \fIsize\fR bytes, initializes all generic dictionary
+/* properties to default settings,
+/* and installs default methods that do not support any operation.
+/* The caller is supposed to override the default methods with
+/* ones that it supports.
+/* The purpose of the default methods is to trap an attempt to
+/* invoke an unsupported method.
+/*
+/* One exception is the default lock function. When the
+/* dictionary provides a file handle for locking, the default
+/* lock function returns the result from myflock with the
+/* locking method specified in the lock_type member, otherwise
+/* it returns 0. Presently, the lock function is used only to
+/* implement the DICT_FLAG_OPEN_LOCK feature (lock the database
+/* exclusively after it is opened) for databases that are not
+/* multi-writer safe.
+/*
+/* dict_free() releases memory and cleans up after dict_alloc().
+/* It is up to the caller to dispose of any memory that was allocated
+/* by the caller.
+/*
+/* dict_jmp_alloc() implements preliminary support for exception
+/* handling. This will eventually be built into dict_alloc().
+/*
+/* Arguments:
+/* .IP dict_type
+/* The official name for this type of dictionary, as used by
+/* dict_open(3) etc. This is stored under the \fBtype\fR
+/* member.
+/* .IP dict_name
+/* Dictionary name. This is stored as the \fBname\fR member.
+/* .IP size
+/* The size in bytes of the dictionary subclass structure instance.
+/* SEE ALSO
+/* dict(3)
+/* DIAGNOSTICS
+/* Fatal errors: the process invokes a default method.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System libraries. */
+
+#include "sys_defs.h"
+
+/* Utility library. */
+
+#include "msg.h"
+#include "mymalloc.h"
+#include "myflock.h"
+#include "dict.h"
+
+/* dict_default_lookup - trap unimplemented operation */
+
+static const char *dict_default_lookup(DICT *dict, const char *unused_key)
+{
+ msg_fatal("table %s:%s: lookup operation is not supported",
+ dict->type, dict->name);
+}
+
+/* dict_default_update - trap unimplemented operation */
+
+static int dict_default_update(DICT *dict, const char *unused_key,
+ const char *unused_value)
+{
+ msg_fatal("table %s:%s: update operation is not supported",
+ dict->type, dict->name);
+}
+
+/* dict_default_delete - trap unimplemented operation */
+
+static int dict_default_delete(DICT *dict, const char *unused_key)
+{
+ msg_fatal("table %s:%s: delete operation is not supported",
+ dict->type, dict->name);
+}
+
+/* dict_default_sequence - trap unimplemented operation */
+
+static int dict_default_sequence(DICT *dict, int unused_function,
+ const char **unused_key, const char **unused_value)
+{
+ msg_fatal("table %s:%s: sequence operation is not supported",
+ dict->type, dict->name);
+}
+
+/* dict_default_lock - default lock handler */
+
+static int dict_default_lock(DICT *dict, int operation)
+{
+ if (dict->lock_fd >= 0) {
+ return (myflock(dict->lock_fd, dict->lock_type, operation));
+ } else {
+ return (0);
+ }
+}
+
+/* dict_default_close - trap unimplemented operation */
+
+static void dict_default_close(DICT *dict)
+{
+ msg_fatal("table %s:%s: close operation is not supported",
+ dict->type, dict->name);
+}
+
+/* dict_alloc - allocate dictionary object, initialize super-class */
+
+DICT *dict_alloc(const char *dict_type, const char *dict_name, ssize_t size)
+{
+ DICT *dict = (DICT *) mymalloc(size);
+
+ dict->type = mystrdup(dict_type);
+ dict->name = mystrdup(dict_name);
+ dict->flags = DICT_FLAG_FIXED;
+ dict->lookup = dict_default_lookup;
+ dict->update = dict_default_update;
+ dict->delete = dict_default_delete;
+ dict->sequence = dict_default_sequence;
+ dict->close = dict_default_close;
+ dict->lock = dict_default_lock;
+ dict->lock_type = INTERNAL_LOCK;
+ dict->lock_fd = -1;
+ dict->stat_fd = -1;
+ dict->mtime = 0;
+ dict->fold_buf = 0;
+ dict->owner.status = DICT_OWNER_UNKNOWN;
+ dict->owner.uid = INT_MAX;
+ dict->error = DICT_ERR_NONE;
+ dict->jbuf = 0;
+ dict->utf8_backup = 0;
+ dict->file_buf = 0;
+ dict->file_b64 = 0;
+ return dict;
+}
+
+/* dict_free - super-class destructor */
+
+void dict_free(DICT *dict)
+{
+ myfree(dict->type);
+ myfree(dict->name);
+ if (dict->jbuf)
+ myfree((void *) dict->jbuf);
+ if (dict->utf8_backup)
+ myfree((void *) dict->utf8_backup);
+ if (dict->file_buf)
+ vstring_free(dict->file_buf);
+ if (dict->file_b64)
+ vstring_free(dict->file_b64);
+ myfree((void *) dict);
+}
+
+ /*
+ * TODO: add a dict_flags() argument to dict_alloc() and handle jump buffer
+ * allocation there.
+ */
+
+/* dict_jmp_alloc - enable exception handling */
+
+void dict_jmp_alloc(DICT *dict)
+{
+ if (dict->jbuf == 0)
+ dict->jbuf = (DICT_JMP_BUF *) mymalloc(sizeof(DICT_JMP_BUF));
+}
diff --git a/src/util/dict_cache.c b/src/util/dict_cache.c
new file mode 100644
index 0000000..4a24089
--- /dev/null
+++ b/src/util/dict_cache.c
@@ -0,0 +1,1127 @@
+/*++
+/* NAME
+/* dict_cache 3
+/* SUMMARY
+/* External cache manager
+/* SYNOPSIS
+/* #include <dict_cache.h>
+/*
+/* DICT_CACHE *dict_cache_open(dbname, open_flags, dict_flags)
+/* const char *dbname;
+/* int open_flags;
+/* int dict_flags;
+/*
+/* void dict_cache_close(cache)
+/* DICT_CACHE *cache;
+/*
+/* const char *dict_cache_lookup(cache, cache_key)
+/* DICT_CACHE *cache;
+/* const char *cache_key;
+/*
+/* int dict_cache_update(cache, cache_key, cache_val)
+/* DICT_CACHE *cache;
+/* const char *cache_key;
+/* const char *cache_val;
+/*
+/* int dict_cache_delete(cache, cache_key)
+/* DICT_CACHE *cache;
+/* const char *cache_key;
+/*
+/* int dict_cache_sequence(cache, first_next, cache_key, cache_val)
+/* DICT_CACHE *cache;
+/* int first_next;
+/* const char **cache_key;
+/* const char **cache_val;
+/* AUXILIARY FUNCTIONS
+/* void dict_cache_control(cache, name, value, ...)
+/* DICT_CACHE *cache;
+/* int name;
+/*
+/* typedef int (*DICT_CACHE_VALIDATOR_FN) (const char *cache_key,
+/* const char *cache_val, void *context);
+/*
+/* const char *dict_cache_name(cache)
+/* DICT_CACHE *cache;
+/* DESCRIPTION
+/* This module maintains external cache files with support
+/* for expiration. The underlying table must implement the
+/* "lookup", "update", "delete" and "sequence" operations.
+/*
+/* Although this API is similar to the one documented in
+/* dict_open(3), there are subtle differences in the interaction
+/* between the iterators that access all cache elements, and
+/* other operations that access individual cache elements.
+/*
+/* In particular, when a "sequence" or "cleanup" operation is
+/* in progress the cache intercepts requests to delete the
+/* "current" entry, as this would cause some databases to
+/* mis-behave. Instead, the cache implements a "delete behind"
+/* strategy, and deletes such an entry after the "sequence"
+/* or "cleanup" operation moves on to the next cache element.
+/* The "delete behind" strategy also affects the cache lookup
+/* and update operations as detailed below.
+/*
+/* dict_cache_open() is a wrapper around the dict_open()
+/* function. It opens the specified cache and returns a handle
+/* that must be used for subsequent access. This function does
+/* not return in case of error.
+/*
+/* dict_cache_close() closes the specified cache and releases
+/* memory that was allocated by dict_cache_open(), and terminates
+/* any thread that was started with dict_cache_control().
+/*
+/* dict_cache_lookup() looks up the specified cache entry.
+/* The result value is a null pointer when the cache entry was
+/* not found, or when the entry is scheduled for "delete
+/* behind".
+/*
+/* dict_cache_update() updates the specified cache entry. If
+/* the entry is scheduled for "delete behind", the delete
+/* operation is canceled (because of this, the cache must be
+/* opened with DICT_FLAG_DUP_REPLACE). This function does not
+/* return in case of error.
+/*
+/* dict_cache_delete() removes the specified cache entry. If
+/* this is the "current" entry of a "sequence" operation, the
+/* entry is scheduled for "delete behind". The result value
+/* is zero when the entry was found.
+/*
+/* dict_cache_sequence() iterates over the specified cache and
+/* returns each entry in an implementation-defined order. The
+/* result value is zero when a cache entry was found.
+/*
+/* Important: programs must not use both dict_cache_sequence()
+/* and the built-in cache cleanup feature.
+/*
+/* dict_cache_control() provides control over the built-in
+/* cache cleanup feature and logging. The arguments are a list
+/* of macros with zero or more arguments, terminated with
+/* CA_DICT_CACHE_CTL_END which has none. The following lists
+/* the macros and corresponding argument types.
+/* .IP "CA_DICT_CACHE_CTL_FLAGS(int flags)"
+/* The arguments to this command are the bit-wise OR of zero
+/* or more of the following:
+/* .RS
+/* .IP CA_DICT_CACHE_CTL_FLAG_VERBOSE
+/* Enable verbose logging of cache activity.
+/* .IP CA_DICT_CACHE_CTL_FLAG_EXP_SUMMARY
+/* Log cache statistics after each cache cleanup run.
+/* .RE
+/* .IP "CA_DICT_CACHE_CTL_INTERVAL(int interval)"
+/* The interval between cache cleanup runs. Specify a null
+/* validator or interval to stop cache cleanup and log cache
+/* statistics if a cleanup run was in progress.
+/* .IP "CA_DICT_CACHE_CTL_VALIDATOR(DICT_CACHE_VALIDATOR_FN validator)"
+/* An application call-back routine that returns non-zero when
+/* a cache entry should be kept. The call-back function should
+/* not make changes to the cache. Specify a null validator or
+/* interval to stop cache cleanup.
+/* .IP "CA_DICT_CACHE_CTL_CONTEXT(void *context)"
+/* Application context that is passed to the validator function.
+/* .RE
+/* .PP
+/* dict_cache_name() returns the name of the specified cache.
+/*
+/* Arguments:
+/* .IP "dbname, open_flags, dict_flags"
+/* These are passed unchanged to dict_open(). The cache must
+/* be opened with DICT_FLAG_DUP_REPLACE.
+/* .IP cache
+/* Cache handle created with dict_cache_open().
+/* .IP cache_key
+/* Cache lookup key.
+/* .IP cache_val
+/* Information that is stored under a cache lookup key.
+/* .IP first_next
+/* One of DICT_SEQ_FUN_FIRST (first cache element) or
+/* DICT_SEQ_FUN_NEXT (next cache element).
+/* .sp
+/* Note: there is no "stop" request. To ensure that the "delete
+/* behind" strategy does not interfere with database access,
+/* allow dict_cache_sequence() to run to completion.
+/* .IP table
+/* A bare dictionary handle.
+/* DIAGNOSTICS
+/* When a request is satisfied, the lookup routine returns
+/* non-null, and the update, delete and sequence routines
+/* return zero. The cache->error value is zero when a request
+/* could not be satisfied because an item did not exist (delete,
+/* sequence) or if it could not be updated. The cache->error
+/* value is non-zero only when a request could not be satisfied,
+/* and the cause was a database error.
+/*
+/* Cache access errors are logged with a warning message. To
+/* avoid spamming the log, each type of operation logs no more
+/* than one cache access error per second, per cache. Specify
+/* the DICT_CACHE_FLAG_VERBOSE flag (see above) to log all
+/* warnings.
+/* BUGS
+/* There should be a way to suspend automatic program suicide
+/* until a cache cleanup run is completed. Some entries may
+/* never be removed when the process max_idle time is less
+/* than the time needed to make a full pass over the cache.
+/*
+/* The delete-behind strategy assumes that all updates are
+/* made by a single process. Otherwise, delete-behind may
+/* remove an entry that was updated after it was scheduled for
+/* deletion.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* HISTORY
+/* .ad
+/* .fi
+/* A predecessor of this code was written first for the Postfix
+/* tlsmgr(8) daemon.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <dict.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <dict_cache.h>
+
+/* Application-specific. */
+
+ /*
+ * XXX Deleting entries while enumerating a map can he tricky. Some map
+ * types have a concept of cursor and support a "delete the current element"
+ * operation. Some map types without cursors don't behave well when the
+ * current first/next entry is deleted (example: with Berkeley DB < 2, the
+ * "next" operation produces garbage). To avoid trouble, we delete an entry
+ * after advancing the current first/next position beyond it; we use the
+ * same strategy with application requests to delete the current entry.
+ */
+
+ /*
+ * Opaque data structure. Use dict_cache_name() to access the name of the
+ * underlying database.
+ */
+struct DICT_CACHE {
+ char *name; /* full name including proxy: */
+ int cache_flags; /* see below */
+ int user_flags; /* logging */
+ DICT *db; /* database handle */
+ int error; /* last operation only */
+
+ /* Delete-behind support. */
+ char *saved_curr_key; /* "current" cache lookup key */
+ char *saved_curr_val; /* "current" cache lookup result */
+
+ /* Cleanup support. */
+ int exp_interval; /* time between cleanup runs */
+ DICT_CACHE_VALIDATOR_FN exp_validator; /* expiration call-back */
+ void *exp_context; /* call-back context */
+ int retained; /* entries retained in cleanup run */
+ int dropped; /* entries removed in cleanup run */
+
+ /* Rate-limited logging support. */
+ int log_delay;
+ time_t upd_log_stamp; /* last update warning */
+ time_t get_log_stamp; /* last lookup warning */
+ time_t del_log_stamp; /* last delete warning */
+ time_t seq_log_stamp; /* last sequence warning */
+};
+
+#define DC_FLAG_DEL_SAVED_CURRENT_KEY (1<<0) /* delete-behind is scheduled */
+
+ /*
+ * Don't log cache access errors more than once per second.
+ */
+#define DC_DEF_LOG_DELAY 1
+
+ /*
+ * Macros to make obscure code more readable.
+ */
+#define DC_SCHEDULE_FOR_DELETE_BEHIND(cp) \
+ ((cp)->cache_flags |= DC_FLAG_DEL_SAVED_CURRENT_KEY)
+
+#define DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key) \
+ ((cp)->saved_curr_key && strcmp((cp)->saved_curr_key, (cache_key)) == 0)
+
+#define DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp) \
+ (/* NOT: (cp)->saved_curr_key && */ \
+ ((cp)->cache_flags & DC_FLAG_DEL_SAVED_CURRENT_KEY) != 0)
+
+#define DC_CANCEL_DELETE_BEHIND(cp) \
+ ((cp)->cache_flags &= ~DC_FLAG_DEL_SAVED_CURRENT_KEY)
+
+ /*
+ * Special key to store the time of the last cache cleanup run completion.
+ */
+#define DC_LAST_CACHE_CLEANUP_COMPLETED "_LAST_CACHE_CLEANUP_COMPLETED_"
+
+/* dict_cache_lookup - load entry from cache */
+
+const char *dict_cache_lookup(DICT_CACHE *cp, const char *cache_key)
+{
+ const char *myname = "dict_cache_lookup";
+ const char *cache_val;
+ DICT *db = cp->db;
+
+ /*
+ * Search for the cache entry. Don't return an entry that is scheduled
+ * for delete-behind.
+ */
+ if (DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp)
+ && DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key)) {
+ if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
+ msg_info("%s: key=%s (pretend not found - scheduled for deletion)",
+ myname, cache_key);
+ DICT_ERR_VAL_RETURN(cp, DICT_ERR_NONE, (char *) 0);
+ } else {
+ cache_val = dict_get(db, cache_key);
+ if (cache_val == 0 && db->error != 0)
+ msg_rate_delay(&cp->get_log_stamp, cp->log_delay, msg_warn,
+ "%s: cache lookup for '%s' failed due to error",
+ cp->name, cache_key);
+ if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
+ msg_info("%s: key=%s value=%s", myname, cache_key,
+ cache_val ? cache_val : db->error ?
+ "error" : "(not found)");
+ DICT_ERR_VAL_RETURN(cp, db->error, cache_val);
+ }
+}
+
+/* dict_cache_update - save entry to cache */
+
+int dict_cache_update(DICT_CACHE *cp, const char *cache_key,
+ const char *cache_val)
+{
+ const char *myname = "dict_cache_update";
+ DICT *db = cp->db;
+ int put_res;
+
+ /*
+ * Store the cache entry and cancel the delete-behind operation.
+ */
+ if (DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp)
+ && DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key)) {
+ if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
+ msg_info("%s: cancel delete-behind for key=%s", myname, cache_key);
+ DC_CANCEL_DELETE_BEHIND(cp);
+ }
+ if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
+ msg_info("%s: key=%s value=%s", myname, cache_key, cache_val);
+ put_res = dict_put(db, cache_key, cache_val);
+ if (put_res != 0)
+ msg_rate_delay(&cp->upd_log_stamp, cp->log_delay, msg_warn,
+ "%s: could not update entry for %s", cp->name, cache_key);
+ DICT_ERR_VAL_RETURN(cp, db->error, put_res);
+}
+
+/* dict_cache_delete - delete entry from cache */
+
+int dict_cache_delete(DICT_CACHE *cp, const char *cache_key)
+{
+ const char *myname = "dict_cache_delete";
+ int del_res;
+ DICT *db = cp->db;
+
+ /*
+ * Delete the entry, unless we would delete the current first/next entry.
+ * In that case, schedule the "current" entry for delete-behind to avoid
+ * mis-behavior by some databases.
+ */
+ if (DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key)) {
+ DC_SCHEDULE_FOR_DELETE_BEHIND(cp);
+ if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
+ msg_info("%s: key=%s (current entry - schedule for delete-behind)",
+ myname, cache_key);
+ DICT_ERR_VAL_RETURN(cp, DICT_ERR_NONE, DICT_STAT_SUCCESS);
+ } else {
+ del_res = dict_del(db, cache_key);
+ if (del_res != 0)
+ msg_rate_delay(&cp->del_log_stamp, cp->log_delay, msg_warn,
+ "%s: could not delete entry for %s", cp->name, cache_key);
+ if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
+ msg_info("%s: key=%s (%s)", myname, cache_key,
+ del_res == 0 ? "found" :
+ db->error ? "error" : "not found");
+ DICT_ERR_VAL_RETURN(cp, db->error, del_res);
+ }
+}
+
+/* dict_cache_sequence - look up the first/next cache entry */
+
+int dict_cache_sequence(DICT_CACHE *cp, int first_next,
+ const char **cache_key,
+ const char **cache_val)
+{
+ const char *myname = "dict_cache_sequence";
+ int seq_res;
+ const char *raw_cache_key;
+ const char *raw_cache_val;
+ char *previous_curr_key;
+ char *previous_curr_val;
+ DICT *db = cp->db;
+
+ /*
+ * Find the first or next database entry. Hide the record with the cache
+ * cleanup completion time stamp.
+ */
+ seq_res = dict_seq(db, first_next, &raw_cache_key, &raw_cache_val);
+ if (seq_res == 0
+ && strcmp(raw_cache_key, DC_LAST_CACHE_CLEANUP_COMPLETED) == 0)
+ seq_res =
+ dict_seq(db, DICT_SEQ_FUN_NEXT, &raw_cache_key, &raw_cache_val);
+ if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
+ msg_info("%s: key=%s value=%s", myname,
+ seq_res == 0 ? raw_cache_key : db->error ?
+ "(error)" : "(not found)",
+ seq_res == 0 ? raw_cache_val : db->error ?
+ "(error)" : "(not found)");
+ if (db->error)
+ msg_rate_delay(&cp->seq_log_stamp, cp->log_delay, msg_warn,
+ "%s: sequence error", cp->name);
+
+ /*
+ * Save the current cache_key and cache_val before they are clobbered by
+ * our own delete operation below. This also prevents surprises when the
+ * application accesses the database after this function returns.
+ *
+ * We also use the saved cache_key to protect the current entry against
+ * application delete requests.
+ */
+ previous_curr_key = cp->saved_curr_key;
+ previous_curr_val = cp->saved_curr_val;
+ if (seq_res == 0) {
+ cp->saved_curr_key = mystrdup(raw_cache_key);
+ cp->saved_curr_val = mystrdup(raw_cache_val);
+ } else {
+ cp->saved_curr_key = 0;
+ cp->saved_curr_val = 0;
+ }
+
+ /*
+ * Delete behind.
+ */
+ if (db->error == 0 && DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp)) {
+ DC_CANCEL_DELETE_BEHIND(cp);
+ if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
+ msg_info("%s: delete-behind key=%s value=%s",
+ myname, previous_curr_key, previous_curr_val);
+ if (dict_del(db, previous_curr_key) != 0)
+ msg_rate_delay(&cp->del_log_stamp, cp->log_delay, msg_warn,
+ "%s: could not delete entry for %s",
+ cp->name, previous_curr_key);
+ }
+
+ /*
+ * Clean up previous iteration key and value.
+ */
+ if (previous_curr_key)
+ myfree(previous_curr_key);
+ if (previous_curr_val)
+ myfree(previous_curr_val);
+
+ /*
+ * Return the result.
+ */
+ *cache_key = (cp)->saved_curr_key;
+ *cache_val = (cp)->saved_curr_val;
+ DICT_ERR_VAL_RETURN(cp, db->error, seq_res);
+}
+
+/* dict_cache_delete_behind_reset - reset "delete behind" state */
+
+static void dict_cache_delete_behind_reset(DICT_CACHE *cp)
+{
+#define FREE_AND_WIPE(s) do { if (s) { myfree(s); (s) = 0; } } while (0)
+
+ DC_CANCEL_DELETE_BEHIND(cp);
+ FREE_AND_WIPE(cp->saved_curr_key);
+ FREE_AND_WIPE(cp->saved_curr_val);
+}
+
+/* dict_cache_clean_stat_log_reset - log and reset cache cleanup statistics */
+
+static void dict_cache_clean_stat_log_reset(DICT_CACHE *cp,
+ const char *full_partial)
+{
+ if (cp->user_flags & DICT_CACHE_FLAG_STATISTICS)
+ msg_info("cache %s %s cleanup: retained=%d dropped=%d entries",
+ cp->name, full_partial, cp->retained, cp->dropped);
+ cp->retained = cp->dropped = 0;
+}
+
+/* dict_cache_clean_event - examine one cache entry */
+
+static void dict_cache_clean_event(int unused_event, void *cache_context)
+{
+ const char *myname = "dict_cache_clean_event";
+ DICT_CACHE *cp = (DICT_CACHE *) cache_context;
+ const char *cache_key;
+ const char *cache_val;
+ int next_interval;
+ VSTRING *stamp_buf;
+ int first_next;
+
+ /*
+ * We interleave cache cleanup with other processing, so that the
+ * application's service remains available, with perhaps increased
+ * latency.
+ */
+
+ /*
+ * Start a new cache cleanup run.
+ */
+ if (cp->saved_curr_key == 0) {
+ cp->retained = cp->dropped = 0;
+ first_next = DICT_SEQ_FUN_FIRST;
+ if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
+ msg_info("%s: start %s cache cleanup", myname, cp->name);
+ }
+
+ /*
+ * Continue a cache cleanup run in progress.
+ */
+ else {
+ first_next = DICT_SEQ_FUN_NEXT;
+ }
+
+ /*
+ * Examine one cache entry.
+ */
+ if (dict_cache_sequence(cp, first_next, &cache_key, &cache_val) == 0) {
+ if (cp->exp_validator(cache_key, cache_val, cp->exp_context) == 0) {
+ DC_SCHEDULE_FOR_DELETE_BEHIND(cp);
+ cp->dropped++;
+ if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
+ msg_info("%s: drop %s cache entry for %s",
+ myname, cp->name, cache_key);
+ } else {
+ cp->retained++;
+ if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
+ msg_info("%s: keep %s cache entry for %s",
+ myname, cp->name, cache_key);
+ }
+ next_interval = 0;
+ }
+
+ /*
+ * Cache cleanup completed. Report vital statistics.
+ */
+ else if (cp->error != 0) {
+ msg_warn("%s: cache cleanup scan terminated due to error", cp->name);
+ dict_cache_clean_stat_log_reset(cp, "partial");
+ next_interval = cp->exp_interval;
+ } else {
+ if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
+ msg_info("%s: done %s cache cleanup scan", myname, cp->name);
+ dict_cache_clean_stat_log_reset(cp, "full");
+ stamp_buf = vstring_alloc(100);
+ vstring_sprintf(stamp_buf, "%ld", (long) event_time());
+ dict_put(cp->db, DC_LAST_CACHE_CLEANUP_COMPLETED,
+ vstring_str(stamp_buf));
+ vstring_free(stamp_buf);
+ next_interval = cp->exp_interval;
+ }
+ event_request_timer(dict_cache_clean_event, cache_context, next_interval);
+}
+
+/* dict_cache_control - schedule or stop the cache cleanup thread */
+
+void dict_cache_control(DICT_CACHE *cp,...)
+{
+ const char *myname = "dict_cache_control";
+ const char *last_done;
+ time_t next_interval;
+ int cache_cleanup_is_active = (cp->exp_validator && cp->exp_interval);
+ va_list ap;
+ int name;
+
+ /*
+ * Update the control settings.
+ */
+ va_start(ap, cp);
+ while ((name = va_arg(ap, int)) > 0) {
+ switch (name) {
+ case DICT_CACHE_CTL_END:
+ break;
+ case DICT_CACHE_CTL_FLAGS:
+ cp->user_flags = va_arg(ap, int);
+ cp->log_delay = (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) ?
+ 0 : DC_DEF_LOG_DELAY;
+ break;
+ case DICT_CACHE_CTL_INTERVAL:
+ cp->exp_interval = va_arg(ap, int);
+ if (cp->exp_interval < 0)
+ msg_panic("%s: bad %s cache cleanup interval %d",
+ myname, cp->name, cp->exp_interval);
+ break;
+ case DICT_CACHE_CTL_VALIDATOR:
+ cp->exp_validator = va_arg(ap, DICT_CACHE_VALIDATOR_FN);
+ break;
+ case DICT_CACHE_CTL_CONTEXT:
+ cp->exp_context = va_arg(ap, void *);
+ break;
+ default:
+ msg_panic("%s: bad command: %d", myname, name);
+ }
+ }
+ va_end(ap);
+
+ /*
+ * Schedule the cache cleanup thread.
+ */
+ if (cp->exp_interval && cp->exp_validator) {
+
+ /*
+ * Sanity checks.
+ */
+ if (cache_cleanup_is_active)
+ msg_panic("%s: %s cache cleanup is already scheduled",
+ myname, cp->name);
+
+ /*
+ * The next start time depends on the last completion time.
+ */
+#define NEXT_START(last, delta) ((delta) + (unsigned long) atol(last))
+#define NOW (time((time_t *) 0)) /* NOT: event_time() */
+
+ if ((last_done = dict_get(cp->db, DC_LAST_CACHE_CLEANUP_COMPLETED)) == 0
+ || (next_interval = (NEXT_START(last_done, cp->exp_interval) - NOW)) < 0)
+ next_interval = 0;
+ if (next_interval > cp->exp_interval)
+ next_interval = cp->exp_interval;
+ if ((cp->user_flags & DICT_CACHE_FLAG_VERBOSE) && next_interval > 0)
+ msg_info("%s cache cleanup will start after %ds",
+ cp->name, (int) next_interval);
+ event_request_timer(dict_cache_clean_event, (void *) cp,
+ (int) next_interval);
+ }
+
+ /*
+ * Cancel the cache cleanup thread.
+ */
+ else if (cache_cleanup_is_active) {
+ if (cp->retained || cp->dropped)
+ dict_cache_clean_stat_log_reset(cp, "partial");
+ dict_cache_delete_behind_reset(cp);
+ event_cancel_timer(dict_cache_clean_event, (void *) cp);
+ }
+}
+
+/* dict_cache_open - open cache file */
+
+DICT_CACHE *dict_cache_open(const char *dbname, int open_flags, int dict_flags)
+{
+ DICT_CACHE *cp;
+ DICT *dict;
+
+ /*
+ * Open the database as requested. Don't attempt to second-guess the
+ * application.
+ */
+ dict = dict_open(dbname, open_flags, dict_flags);
+
+ /*
+ * Create the DICT_CACHE object.
+ */
+ cp = (DICT_CACHE *) mymalloc(sizeof(*cp));
+ cp->name = mystrdup(dbname);
+ cp->cache_flags = 0;
+ cp->user_flags = 0;
+ cp->db = dict;
+ cp->saved_curr_key = 0;
+ cp->saved_curr_val = 0;
+ cp->exp_interval = 0;
+ cp->exp_validator = 0;
+ cp->exp_context = 0;
+ cp->retained = 0;
+ cp->dropped = 0;
+ cp->log_delay = DC_DEF_LOG_DELAY;
+ cp->upd_log_stamp = cp->get_log_stamp =
+ cp->del_log_stamp = cp->seq_log_stamp = 0;
+
+ return (cp);
+}
+
+/* dict_cache_close - close cache file */
+
+void dict_cache_close(DICT_CACHE *cp)
+{
+
+ /*
+ * Cancel the cache cleanup thread. This also logs (and resets)
+ * statistics for a scan that is in progress.
+ */
+ dict_cache_control(cp, DICT_CACHE_CTL_INTERVAL, 0, DICT_CACHE_CTL_END);
+
+ /*
+ * Destroy the DICT_CACHE object.
+ */
+ myfree(cp->name);
+ dict_close(cp->db);
+ if (cp->saved_curr_key)
+ myfree(cp->saved_curr_key);
+ if (cp->saved_curr_val)
+ myfree(cp->saved_curr_val);
+ myfree((void *) cp);
+}
+
+/* dict_cache_name - get the cache name */
+
+const char *dict_cache_name(DICT_CACHE *cp)
+{
+
+ /*
+ * This is used for verbose logging or warning messages, so the cost of
+ * call is only made where needed (well sort off - code that does not
+ * execute still presents overhead for the processor pipeline, processor
+ * cache, etc).
+ */
+ return (cp->name);
+}
+
+ /*
+ * Test driver with support for interleaved access. First, enter a number of
+ * requests to look up, update or delete a sequence of cache entries, then
+ * interleave those sequences with the "run" command.
+ */
+#ifdef TEST
+#include <msg_vstream.h>
+#include <vstring_vstream.h>
+#include <argv.h>
+#include <stringops.h>
+
+#define DELIMS " "
+#define USAGE "\n\tTo manage settings:" \
+ "\n\tverbose <level> (verbosity level)" \
+ "\n\telapsed <level> (0=don't show elapsed time)" \
+ "\n\tlmdb_map_size <limit> (initial LMDB size limit)" \
+ "\n\tcache <type>:<name> (switch to named database)" \
+ "\n\tstatus (show map size, cache, pending requests)" \
+ "\n\n\tTo manage pending requests:" \
+ "\n\treset (discard pending requests)" \
+ "\n\trun (execute pending requests in interleaved order)" \
+ "\n\n\tTo add a pending request:" \
+ "\n\tquery <key-suffix> <count> (negative to reverse order)" \
+ "\n\tupdate <key-suffix> <count> (negative to reverse order)" \
+ "\n\tdelete <key-suffix> <count> (negative to reverse order)" \
+ "\n\tpurge <key-suffix>" \
+ "\n\tcount <key-suffix>"
+
+ /*
+ * For realism, open the cache with the same flags as postscreen(8) and
+ * verify(8).
+ */
+#define DICT_CACHE_OPEN_FLAGS (DICT_FLAG_DUP_REPLACE | DICT_FLAG_SYNC_UPDATE | \
+ DICT_FLAG_OPEN_LOCK)
+
+ /*
+ * Storage for one request to access a sequence of cache entries.
+ */
+typedef struct DICT_CACHE_SREQ {
+ int flags; /* per-request: reverse, purge */
+ char *cmd; /* command for status report */
+ void (*action) (struct DICT_CACHE_SREQ *, DICT_CACHE *, VSTRING *);
+ char *suffix; /* key suffix */
+ int done; /* progress indicator */
+ int todo; /* number of entries to process */
+ int first_next; /* first/next */
+} DICT_CACHE_SREQ;
+
+#define DICT_CACHE_SREQ_FLAG_PURGE (1<<1) /* purge instead of count */
+#define DICT_CACHE_SREQ_FLAG_REVERSE (1<<2) /* reverse instead of forward */
+
+#define DICT_CACHE_SREQ_LIMIT 10
+
+ /*
+ * All test requests combined.
+ */
+typedef struct DICT_CACHE_TEST {
+ int flags; /* exclusion flags */
+ int size; /* allocated slots */
+ int used; /* used slots */
+ DICT_CACHE_SREQ job_list[1]; /* actually, a bunch */
+} DICT_CACHE_TEST;
+
+#define DICT_CACHE_TEST_FLAG_ITER (1<<0) /* count or purge */
+
+#define STR(x) vstring_str(x)
+
+int show_elapsed = 1; /* show elapsed time */
+
+#ifdef HAS_LMDB
+extern size_t dict_lmdb_map_size; /* LMDB-specific */
+
+#endif
+
+/* usage - command-line usage message */
+
+static NORETURN usage(const char *progname)
+{
+ msg_fatal("usage: %s (no argument)", progname);
+}
+
+/* make_tagged_key - make tagged search key */
+
+static void make_tagged_key(VSTRING *bp, DICT_CACHE_SREQ *cp)
+{
+ if (cp->done < 0)
+ msg_panic("make_tagged_key: bad done count: %d", cp->done);
+ if (cp->todo < 1)
+ msg_panic("make_tagged_key: bad todo count: %d", cp->todo);
+ vstring_sprintf(bp, "%d-%s",
+ (cp->flags & DICT_CACHE_SREQ_FLAG_REVERSE) ?
+ cp->todo - cp->done - 1 : cp->done, cp->suffix);
+}
+
+/* create_requests - create request list */
+
+static DICT_CACHE_TEST *create_requests(int count)
+{
+ DICT_CACHE_TEST *tp;
+ DICT_CACHE_SREQ *cp;
+
+ tp = (DICT_CACHE_TEST *) mymalloc(sizeof(DICT_CACHE_TEST) +
+ (count - 1) *sizeof(DICT_CACHE_SREQ));
+ tp->flags = 0;
+ tp->size = count;
+ tp->used = 0;
+ for (cp = tp->job_list; cp < tp->job_list + count; cp++) {
+ cp->flags = 0;
+ cp->cmd = 0;
+ cp->action = 0;
+ cp->suffix = 0;
+ cp->todo = 0;
+ cp->first_next = DICT_SEQ_FUN_FIRST;
+ }
+ return (tp);
+}
+
+/* reset_requests - reset request list */
+
+static void reset_requests(DICT_CACHE_TEST *tp)
+{
+ DICT_CACHE_SREQ *cp;
+
+ tp->flags = 0;
+ tp->used = 0;
+ for (cp = tp->job_list; cp < tp->job_list + tp->size; cp++) {
+ cp->flags = 0;
+ if (cp->cmd) {
+ myfree(cp->cmd);
+ cp->cmd = 0;
+ }
+ cp->action = 0;
+ if (cp->suffix) {
+ myfree(cp->suffix);
+ cp->suffix = 0;
+ }
+ cp->todo = 0;
+ cp->first_next = DICT_SEQ_FUN_FIRST;
+ }
+}
+
+/* free_requests - destroy request list */
+
+static void free_requests(DICT_CACHE_TEST *tp)
+{
+ reset_requests(tp);
+ myfree((void *) tp);
+}
+
+/* run_requests - execute pending requests in interleaved order */
+
+static void run_requests(DICT_CACHE_TEST *tp, DICT_CACHE *dp, VSTRING *bp)
+{
+ DICT_CACHE_SREQ *cp;
+ int todo;
+ struct timeval start;
+ struct timeval finish;
+ struct timeval elapsed;
+
+ if (dp == 0) {
+ msg_warn("no cache");
+ return;
+ }
+ GETTIMEOFDAY(&start);
+ do {
+ todo = 0;
+ for (cp = tp->job_list; cp < tp->job_list + tp->used; cp++) {
+ if (cp->done < cp->todo) {
+ todo = 1;
+ cp->action(cp, dp, bp);
+ }
+ }
+ } while (todo);
+ GETTIMEOFDAY(&finish);
+ timersub(&finish, &start, &elapsed);
+ if (show_elapsed)
+ vstream_printf("Elapsed: %g\n",
+ elapsed.tv_sec + elapsed.tv_usec / 1000000.0);
+
+ reset_requests(tp);
+}
+
+/* show_status - show settings and pending requests */
+
+static void show_status(DICT_CACHE_TEST *tp, DICT_CACHE *dp)
+{
+ DICT_CACHE_SREQ *cp;
+
+#ifdef HAS_LMDB
+ vstream_printf("lmdb_map_size\t%ld\n", (long) dict_lmdb_map_size);
+#endif
+ vstream_printf("cache\t%s\n", dp ? dp->name : "(none)");
+
+ if (tp->used == 0)
+ vstream_printf("No pending requests\n");
+ else
+ vstream_printf("%s\t%s\t%s\t%s\t%s\t%s\n",
+ "cmd", "dir", "suffix", "count", "done", "first/next");
+
+ for (cp = tp->job_list; cp < tp->job_list + tp->used; cp++)
+ if (cp->todo > 0)
+ vstream_printf("%s\t%s\t%s\t%d\t%d\t%d\n",
+ cp->cmd,
+ (cp->flags & DICT_CACHE_SREQ_FLAG_REVERSE) ?
+ "reverse" : "forward",
+ cp->suffix ? cp->suffix : "(null)", cp->todo,
+ cp->done, cp->first_next);
+}
+
+/* query_action - lookup cache entry */
+
+static void query_action(DICT_CACHE_SREQ *cp, DICT_CACHE *dp, VSTRING *bp)
+{
+ const char *lookup;
+
+ make_tagged_key(bp, cp);
+ if ((lookup = dict_cache_lookup(dp, STR(bp))) == 0) {
+ if (dp->error)
+ msg_warn("query_action: query failed: %s: %m", STR(bp));
+ else
+ msg_warn("query_action: query failed: %s", STR(bp));
+ } else if (strcmp(STR(bp), lookup) != 0) {
+ msg_warn("lookup result \"%s\" differs from key \"%s\"",
+ lookup, STR(bp));
+ }
+ cp->done += 1;
+}
+
+/* update_action - update cache entry */
+
+static void update_action(DICT_CACHE_SREQ *cp, DICT_CACHE *dp, VSTRING *bp)
+{
+ make_tagged_key(bp, cp);
+ if (dict_cache_update(dp, STR(bp), STR(bp)) != 0) {
+ if (dp->error)
+ msg_warn("update_action: update failed: %s: %m", STR(bp));
+ else
+ msg_warn("update_action: update failed: %s", STR(bp));
+ }
+ cp->done += 1;
+}
+
+/* delete_action - delete cache entry */
+
+static void delete_action(DICT_CACHE_SREQ *cp, DICT_CACHE *dp, VSTRING *bp)
+{
+ make_tagged_key(bp, cp);
+ if (dict_cache_delete(dp, STR(bp)) != 0) {
+ if (dp->error)
+ msg_warn("delete_action: delete failed: %s: %m", STR(bp));
+ else
+ msg_warn("delete_action: delete failed: %s", STR(bp));
+ }
+ cp->done += 1;
+}
+
+/* iter_action - iterate over cache and act on entries with given suffix */
+
+static void iter_action(DICT_CACHE_SREQ *cp, DICT_CACHE *dp, VSTRING *bp)
+{
+ const char *cache_key;
+ const char *cache_val;
+ const char *what;
+ const char *suffix;
+
+ if (dict_cache_sequence(dp, cp->first_next, &cache_key, &cache_val) == 0) {
+ if (strcmp(cache_key, cache_val) != 0)
+ msg_warn("value \"%s\" differs from key \"%s\"",
+ cache_val, cache_key);
+ suffix = cache_key + strspn(cache_key, "0123456789");
+ if (suffix[0] == '-' && strcmp(suffix + 1, cp->suffix) == 0) {
+ cp->done += 1;
+ cp->todo = cp->done + 1; /* XXX */
+ if ((cp->flags & DICT_CACHE_SREQ_FLAG_PURGE)
+ && dict_cache_delete(dp, cache_key) != 0) {
+ if (dp->error)
+ msg_warn("purge_action: delete failed: %s: %m", STR(bp));
+ else
+ msg_warn("purge_action: delete failed: %s", STR(bp));
+ }
+ }
+ cp->first_next = DICT_SEQ_FUN_NEXT;
+ } else {
+ what = (cp->flags & DICT_CACHE_SREQ_FLAG_PURGE) ? "purge" : "count";
+ if (dp->error)
+ msg_warn("%s error after %d: %m", what, cp->done);
+ else
+ vstream_printf("suffix=%s %s=%d\n", cp->suffix, what, cp->done);
+ cp->todo = 0;
+ }
+}
+
+ /*
+ * Table-driven support.
+ */
+typedef struct DICT_CACHE_SREQ_INFO {
+ const char *name;
+ int argc;
+ void (*action) (DICT_CACHE_SREQ *, DICT_CACHE *, VSTRING *);
+ int test_flags;
+ int req_flags;
+} DICT_CACHE_SREQ_INFO;
+
+static DICT_CACHE_SREQ_INFO req_info[] = {
+ {"query", 3, query_action},
+ {"update", 3, update_action},
+ {"delete", 3, delete_action},
+ {"count", 2, iter_action, DICT_CACHE_TEST_FLAG_ITER},
+ {"purge", 2, iter_action, DICT_CACHE_TEST_FLAG_ITER, DICT_CACHE_SREQ_FLAG_PURGE},
+ 0,
+};
+
+/* add_request - add a request to the list */
+
+static void add_request(DICT_CACHE_TEST *tp, ARGV *argv)
+{
+ DICT_CACHE_SREQ_INFO *rp;
+ DICT_CACHE_SREQ *cp;
+ int req_flags;
+ int count;
+ char *cmd = argv->argv[0];
+ char *suffix = (argv->argc > 1 ? argv->argv[1] : 0);
+ char *todo = (argv->argc > 2 ? argv->argv[2] : "1"); /* XXX */
+
+ if (tp->used >= tp->size) {
+ msg_warn("%s: request list is full", cmd);
+ return;
+ }
+ for (rp = req_info; /* See below */ ; rp++) {
+ if (rp->name == 0) {
+ vstream_printf("usage: %s\n", USAGE);
+ return;
+ }
+ if (strcmp(rp->name, argv->argv[0]) == 0
+ && rp->argc == argv->argc)
+ break;
+ }
+ req_flags = rp->req_flags;
+ if (todo[0] == '-') {
+ req_flags |= DICT_CACHE_SREQ_FLAG_REVERSE;
+ todo += 1;
+ }
+ if (!alldig(todo) || (count = atoi(todo)) == 0) {
+ msg_warn("%s: bad count: %s", cmd, todo);
+ return;
+ }
+ if (tp->flags & rp->test_flags) {
+ msg_warn("%s: command conflicts with other command", cmd);
+ return;
+ }
+ tp->flags |= rp->test_flags;
+ cp = tp->job_list + tp->used;
+ cp->cmd = mystrdup(cmd);
+ cp->action = rp->action;
+ if (suffix)
+ cp->suffix = mystrdup(suffix);
+ cp->done = 0;
+ cp->flags = req_flags;
+ cp->todo = count;
+ tp->used += 1;
+}
+
+/* main - main program */
+
+int main(int argc, char **argv)
+{
+ DICT_CACHE_TEST *test_job;
+ VSTRING *inbuf = vstring_alloc(100);
+ char *bufp;
+ ARGV *args;
+ DICT_CACHE *cache = 0;
+ int stdin_is_tty;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ if (argc != 1)
+ usage(argv[0]);
+
+
+ test_job = create_requests(DICT_CACHE_SREQ_LIMIT);
+
+ stdin_is_tty = isatty(0);
+
+ for (;;) {
+ if (stdin_is_tty) {
+ vstream_printf("> ");
+ vstream_fflush(VSTREAM_OUT);
+ }
+ if (vstring_fgets_nonl(inbuf, VSTREAM_IN) == 0)
+ break;
+ bufp = vstring_str(inbuf);
+ if (!stdin_is_tty) {
+ vstream_printf("> %s\n", bufp);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ if (*bufp == '#')
+ continue;
+ args = argv_split(bufp, DELIMS);
+ if (argc == 0) {
+ vstream_printf("usage: %s\n", USAGE);
+ vstream_fflush(VSTREAM_OUT);
+ continue;
+ }
+ if (strcmp(args->argv[0], "verbose") == 0 && args->argc == 2) {
+ msg_verbose = atoi(args->argv[1]);
+ } else if (strcmp(args->argv[0], "elapsed") == 0 && args->argc == 2) {
+ show_elapsed = atoi(args->argv[1]);
+#ifdef HAS_LMDB
+ } else if (strcmp(args->argv[0], "lmdb_map_size") == 0 && args->argc == 2) {
+ dict_lmdb_map_size = atol(args->argv[1]);
+#endif
+ } else if (strcmp(args->argv[0], "cache") == 0 && args->argc == 2) {
+ if (cache)
+ dict_cache_close(cache);
+ cache = dict_cache_open(args->argv[1], O_CREAT | O_RDWR,
+ DICT_CACHE_OPEN_FLAGS);
+ } else if (strcmp(args->argv[0], "reset") == 0 && args->argc == 1) {
+ reset_requests(test_job);
+ } else if (strcmp(args->argv[0], "run") == 0 && args->argc == 1) {
+ run_requests(test_job, cache, inbuf);
+ } else if (strcmp(args->argv[0], "status") == 0 && args->argc == 1) {
+ show_status(test_job, cache);
+ } else {
+ add_request(test_job, args);
+ }
+ vstream_fflush(VSTREAM_OUT);
+ argv_free(args);
+ }
+
+ vstring_free(inbuf);
+ free_requests(test_job);
+ if (cache)
+ dict_cache_close(cache);
+ return (0);
+}
+
+#endif
diff --git a/src/util/dict_cache.h b/src/util/dict_cache.h
new file mode 100644
index 0000000..2d403b5
--- /dev/null
+++ b/src/util/dict_cache.h
@@ -0,0 +1,67 @@
+#ifndef _DICT_CACHE_H_INCLUDED_
+#define _DICT_CACHE_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_cache 3h
+/* SUMMARY
+/* External cache manager
+/* SYNOPSIS
+/* #include <dict_cache.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+#include <check_arg.h>
+
+ /*
+ * External interface.
+ */
+typedef struct DICT_CACHE DICT_CACHE;
+typedef int (*DICT_CACHE_VALIDATOR_FN) (const char *, const char *, void *);
+
+extern DICT_CACHE *dict_cache_open(const char *, int, int);
+extern void dict_cache_close(DICT_CACHE *);
+extern const char *dict_cache_lookup(DICT_CACHE *, const char *);
+extern int dict_cache_update(DICT_CACHE *, const char *, const char *);
+extern int dict_cache_delete(DICT_CACHE *, const char *);
+extern int dict_cache_sequence(DICT_CACHE *, int, const char **, const char **);
+extern void dict_cache_control(DICT_CACHE *,...);
+extern const char *dict_cache_name(DICT_CACHE *);
+
+#define DICT_CACHE_FLAG_VERBOSE (1<<0) /* verbose operation */
+#define DICT_CACHE_FLAG_STATISTICS (1<<1) /* log cache statistics */
+
+/* Legacy API: type-unchecked argument, internal use. */
+#define DICT_CACHE_CTL_END 0 /* list terminator */
+#define DICT_CACHE_CTL_FLAGS 1 /* see above */
+#define DICT_CACHE_CTL_INTERVAL 2 /* cleanup interval */
+#define DICT_CACHE_CTL_VALIDATOR 3 /* call-back validator */
+#define DICT_CACHE_CTL_CONTEXT 4 /* call-back context */
+
+/* Safer API: type-checked arguments, external use. */
+#define CA_DICT_CACHE_CTL_END DICT_CACHE_CTL_END
+#define CA_DICT_CACHE_CTL_FLAGS(v) DICT_CACHE_CTL_FLAGS, CHECK_VAL(DICT_CACHE, int, (v))
+#define CA_DICT_CACHE_CTL_INTERVAL(v) DICT_CACHE_CTL_INTERVAL, CHECK_VAL(DICT_CACHE, int, (v))
+#define CA_DICT_CACHE_CTL_VALIDATOR(v) DICT_CACHE_CTL_VALIDATOR, CHECK_VAL(DICT_CACHE, DICT_CACHE_VALIDATOR_FN, (v))
+#define CA_DICT_CACHE_CTL_CONTEXT(v) DICT_CACHE_CTL_CONTEXT, CHECK_PTR(DICT_CACHE, void, (v))
+
+CHECK_VAL_HELPER_DCL(DICT_CACHE, int);
+CHECK_VAL_HELPER_DCL(DICT_CACHE, DICT_CACHE_VALIDATOR_FN);
+CHECK_PTR_HELPER_DCL(DICT_CACHE, void);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/dict_cdb.c b/src/util/dict_cdb.c
new file mode 100644
index 0000000..a9133cc
--- /dev/null
+++ b/src/util/dict_cdb.c
@@ -0,0 +1,446 @@
+/*++
+/* NAME
+/* dict_cdb 3
+/* SUMMARY
+/* dictionary manager interface to CDB files
+/* SYNOPSIS
+/* #include <dict_cdb.h>
+/*
+/* DICT *dict_cdb_open(path, open_flags, dict_flags)
+/* const char *path;
+/* int open_flags;
+/* int dict_flags;
+/*
+/* DESCRIPTION
+/* dict_cdb_open() opens the specified CDB database. The result is
+/* a pointer to a structure that can be used to access the dictionary
+/* using the generic methods documented in dict_open(3).
+/*
+/* Arguments:
+/* .IP path
+/* The database pathname, not including the ".cdb" suffix.
+/* .IP open_flags
+/* Flags passed to open(). Specify O_RDONLY or O_WRONLY|O_CREAT|O_TRUNC.
+/* .IP dict_flags
+/* Flags used by the dictionary interface.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* DIAGNOSTICS
+/* Fatal errors: cannot open file, write error, out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Michael Tokarev <mjt@tls.msk.ru> based on dict_db.c by
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#include "sys_defs.h"
+
+/* System library. */
+
+#include <sys/stat.h>
+#include <limits.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "mymalloc.h"
+#include "vstring.h"
+#include "stringops.h"
+#include "iostuff.h"
+#include "myflock.h"
+#include "stringops.h"
+#include "dict.h"
+#include "dict_cdb.h"
+#include "warn_stat.h"
+
+#ifdef HAS_CDB
+
+#include <cdb.h>
+#ifndef TINYCDB_VERSION
+#include <cdb_make.h>
+#endif
+#ifndef cdb_fileno
+#define cdb_fileno(c) ((c)->fd)
+#endif
+
+#ifndef CDB_SUFFIX
+#define CDB_SUFFIX ".cdb"
+#endif
+#ifndef CDB_TMP_SUFFIX
+#define CDB_TMP_SUFFIX CDB_SUFFIX ".tmp"
+#endif
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ struct cdb cdb; /* cdb structure */
+} DICT_CDBQ; /* query interface */
+
+typedef struct {
+ DICT dict; /* generic members */
+ struct cdb_make cdbm; /* cdb_make structure */
+ char *cdb_path; /* cdb pathname (.cdb) */
+ char *tmp_path; /* temporary pathname (.tmp) */
+} DICT_CDBM; /* rebuild interface */
+
+/* dict_cdbq_lookup - find database entry, query mode */
+
+static const char *dict_cdbq_lookup(DICT *dict, const char *name)
+{
+ DICT_CDBQ *dict_cdbq = (DICT_CDBQ *) dict;
+ unsigned vlen;
+ int status = 0;
+ static char *buf;
+ static unsigned len;
+ const char *result = 0;
+
+ dict->error = 0;
+
+ /* CDB is constant, so do not try to acquire a lock. */
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+
+ /*
+ * See if this CDB file was written with one null byte appended to key
+ * and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ status = cdb_find(&dict_cdbq->cdb, name, strlen(name) + 1);
+ if (status > 0)
+ dict->flags &= ~DICT_FLAG_TRY0NULL;
+ }
+
+ /*
+ * See if this CDB file was written with no null byte appended to key and
+ * value.
+ */
+ if (status == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
+ status = cdb_find(&dict_cdbq->cdb, name, strlen(name));
+ if (status > 0)
+ dict->flags &= ~DICT_FLAG_TRY1NULL;
+ }
+ if (status < 0)
+ msg_fatal("error reading %s: %m", dict->name);
+
+ if (status) {
+ vlen = cdb_datalen(&dict_cdbq->cdb);
+ if (len < vlen) {
+ if (buf == 0)
+ buf = mymalloc(vlen + 1);
+ else
+ buf = myrealloc(buf, vlen + 1);
+ len = vlen;
+ }
+ if (cdb_read(&dict_cdbq->cdb, buf, vlen,
+ cdb_datapos(&dict_cdbq->cdb)) < 0)
+ msg_fatal("error reading %s: %m", dict->name);
+ buf[vlen] = '\0';
+ result = buf;
+ }
+ /* No locking so not release the lock. */
+
+ return (result);
+}
+
+/* dict_cdbq_close - close data base, query mode */
+
+static void dict_cdbq_close(DICT *dict)
+{
+ DICT_CDBQ *dict_cdbq = (DICT_CDBQ *) dict;
+
+ cdb_free(&dict_cdbq->cdb);
+ close(dict->stat_fd);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_cdbq_open - open data base, query mode */
+
+static DICT *dict_cdbq_open(const char *path, int dict_flags)
+{
+ DICT_CDBQ *dict_cdbq;
+ struct stat st;
+ char *cdb_path;
+ int fd;
+
+ /*
+ * Let the optimizer worry about eliminating redundant code.
+ */
+#define DICT_CDBQ_OPEN_RETURN(d) do { \
+ DICT *__d = (d); \
+ myfree(cdb_path); \
+ return (__d); \
+ } while (0)
+
+ cdb_path = concatenate(path, CDB_SUFFIX, (char *) 0);
+
+ if ((fd = open(cdb_path, O_RDONLY)) < 0)
+ DICT_CDBQ_OPEN_RETURN(dict_surrogate(DICT_TYPE_CDB, path,
+ O_RDONLY, dict_flags,
+ "open database %s: %m", cdb_path));
+
+ dict_cdbq = (DICT_CDBQ *) dict_alloc(DICT_TYPE_CDB,
+ cdb_path, sizeof(*dict_cdbq));
+#if defined(TINYCDB_VERSION)
+ if (cdb_init(&(dict_cdbq->cdb), fd) != 0)
+ msg_fatal("dict_cdbq_open: unable to init %s: %m", cdb_path);
+#else
+ cdb_init(&(dict_cdbq->cdb), fd);
+#endif
+ dict_cdbq->dict.lookup = dict_cdbq_lookup;
+ dict_cdbq->dict.close = dict_cdbq_close;
+ dict_cdbq->dict.stat_fd = fd;
+ if (fstat(fd, &st) < 0)
+ msg_fatal("dict_dbq_open: fstat: %m");
+ dict_cdbq->dict.mtime = st.st_mtime;
+ dict_cdbq->dict.owner.uid = st.st_uid;
+ dict_cdbq->dict.owner.status = (st.st_uid != 0);
+ close_on_exec(fd, CLOSE_ON_EXEC);
+
+ /*
+ * Warn if the source file is newer than the indexed file, except when
+ * the source file changed only seconds ago.
+ */
+ if (stat(path, &st) == 0
+ && st.st_mtime > dict_cdbq->dict.mtime
+ && st.st_mtime < time((time_t *) 0) - 100)
+ msg_warn("database %s is older than source file %s", cdb_path, path);
+
+ /*
+ * If undecided about appending a null byte to key and value, choose to
+ * try both in query mode.
+ */
+ if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ dict_flags |= DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL;
+ dict_cdbq->dict.flags = dict_flags | DICT_FLAG_FIXED;
+ if (dict_flags & DICT_FLAG_FOLD_FIX)
+ dict_cdbq->dict.fold_buf = vstring_alloc(10);
+
+ DICT_CDBQ_OPEN_RETURN(DICT_DEBUG (&dict_cdbq->dict));
+}
+
+/* dict_cdbm_update - add database entry, create mode */
+
+static int dict_cdbm_update(DICT *dict, const char *name, const char *value)
+{
+ DICT_CDBM *dict_cdbm = (DICT_CDBM *) dict;
+ unsigned ksize, vsize;
+ int r;
+
+ dict->error = 0;
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+ ksize = strlen(name);
+ vsize = strlen(value);
+
+ /*
+ * Optionally append a null byte to key and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ ksize++;
+ vsize++;
+ }
+
+ /*
+ * Do the add operation. No locking is done.
+ */
+#ifdef TINYCDB_VERSION
+#ifndef CDB_PUT_ADD
+#error please upgrate tinycdb to at least 0.5 version
+#endif
+ if (dict->flags & DICT_FLAG_DUP_IGNORE)
+ r = CDB_PUT_ADD;
+ else if (dict->flags & DICT_FLAG_DUP_REPLACE)
+ r = CDB_PUT_REPLACE;
+ else
+ r = CDB_PUT_INSERT;
+ r = cdb_make_put(&dict_cdbm->cdbm, name, ksize, value, vsize, r);
+ if (r < 0)
+ msg_fatal("error writing %s: %m", dict_cdbm->tmp_path);
+ else if (r > 0) {
+ if (dict->flags & (DICT_FLAG_DUP_IGNORE | DICT_FLAG_DUP_REPLACE))
+ /* void */ ;
+ else if (dict->flags & DICT_FLAG_DUP_WARN)
+ msg_warn("%s: duplicate entry: \"%s\"",
+ dict_cdbm->dict.name, name);
+ else
+ msg_fatal("%s: duplicate entry: \"%s\"",
+ dict_cdbm->dict.name, name);
+ }
+ return (r);
+#else
+ if (cdb_make_add(&dict_cdbm->cdbm, name, ksize, value, vsize) < 0)
+ msg_fatal("error writing %s: %m", dict_cdbm->tmp_path);
+ return (0);
+#endif
+}
+
+/* dict_cdbm_close - close data base and rename file.tmp to file.cdb */
+
+static void dict_cdbm_close(DICT *dict)
+{
+ DICT_CDBM *dict_cdbm = (DICT_CDBM *) dict;
+ int fd = cdb_fileno(&dict_cdbm->cdbm);
+
+ /*
+ * Note: if FCNTL locking is used, closing any file descriptor on a
+ * locked file cancels all locks that the process may have on that file.
+ * CDB is FCNTL locking safe, because it uses the same file descriptor
+ * for database I/O and locking.
+ */
+ if (cdb_make_finish(&dict_cdbm->cdbm) < 0)
+ msg_fatal("finish database %s: %m", dict_cdbm->tmp_path);
+ if (rename(dict_cdbm->tmp_path, dict_cdbm->cdb_path) < 0)
+ msg_fatal("rename database from %s to %s: %m",
+ dict_cdbm->tmp_path, dict_cdbm->cdb_path);
+ if (close(fd) < 0) /* releases a lock */
+ msg_fatal("close database %s: %m", dict_cdbm->cdb_path);
+ myfree(dict_cdbm->cdb_path);
+ myfree(dict_cdbm->tmp_path);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_cdbm_open - create database as file.tmp */
+
+static DICT *dict_cdbm_open(const char *path, int dict_flags)
+{
+ DICT_CDBM *dict_cdbm;
+ char *cdb_path;
+ char *tmp_path;
+ int fd;
+ struct stat st0, st1;
+
+ /*
+ * Let the optimizer worry about eliminating redundant code.
+ */
+#define DICT_CDBM_OPEN_RETURN(d) do { \
+ DICT *__d = (d); \
+ if (cdb_path) \
+ myfree(cdb_path); \
+ if (tmp_path) \
+ myfree(tmp_path); \
+ return (__d); \
+ } while (0)
+
+ cdb_path = concatenate(path, CDB_SUFFIX, (char *) 0);
+ tmp_path = concatenate(path, CDB_TMP_SUFFIX, (char *) 0);
+
+ /*
+ * Repeat until we have opened *and* locked *existing* file. Since the
+ * new (tmp) file will be renamed to be .cdb file, locking here is
+ * somewhat funny to work around possible race conditions. Note that we
+ * can't open a file with O_TRUNC as we can't know if another process
+ * isn't creating it at the same time.
+ */
+ for (;;) {
+ if ((fd = open(tmp_path, O_RDWR | O_CREAT, 0644)) < 0)
+ DICT_CDBM_OPEN_RETURN(dict_surrogate(DICT_TYPE_CDB, path,
+ O_RDWR, dict_flags,
+ "open database %s: %m",
+ tmp_path));
+ if (fstat(fd, &st0) < 0)
+ msg_fatal("fstat(%s): %m", tmp_path);
+
+ /*
+ * Get an exclusive lock - we're going to change the database so we
+ * can't have any spectators.
+ */
+ if (myflock(fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("lock %s: %m", tmp_path);
+
+ if (stat(tmp_path, &st1) < 0)
+ msg_fatal("stat(%s): %m", tmp_path);
+
+ /*
+ * Compare file's state before and after lock: should be the same,
+ * and nlinks should be >0, or else we opened non-existing file...
+ */
+ if (st0.st_ino == st1.st_ino && st0.st_dev == st1.st_dev
+ && st0.st_rdev == st1.st_rdev && st0.st_nlink == st1.st_nlink
+ && st0.st_nlink > 0)
+ break; /* successfully opened */
+
+ close(fd);
+
+ }
+
+#ifndef NO_FTRUNCATE
+ if (st0.st_size)
+ ftruncate(fd, 0);
+#endif
+
+ dict_cdbm = (DICT_CDBM *) dict_alloc(DICT_TYPE_CDB, path,
+ sizeof(*dict_cdbm));
+ if (cdb_make_start(&dict_cdbm->cdbm, fd) < 0)
+ msg_fatal("initialize database %s: %m", tmp_path);
+ dict_cdbm->dict.close = dict_cdbm_close;
+ dict_cdbm->dict.update = dict_cdbm_update;
+ dict_cdbm->cdb_path = cdb_path;
+ dict_cdbm->tmp_path = tmp_path;
+ cdb_path = tmp_path = 0; /* DICT_CDBM_OPEN_RETURN() */
+ dict_cdbm->dict.owner.uid = st1.st_uid;
+ dict_cdbm->dict.owner.status = (st1.st_uid != 0);
+ close_on_exec(fd, CLOSE_ON_EXEC);
+
+ /*
+ * If undecided about appending a null byte to key and value, choose a
+ * default to not append a null byte when creating a cdb.
+ */
+ if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ dict_flags |= DICT_FLAG_TRY0NULL;
+ else if ((dict_flags & DICT_FLAG_TRY1NULL)
+ && (dict_flags & DICT_FLAG_TRY0NULL))
+ dict_flags &= ~DICT_FLAG_TRY0NULL;
+ dict_cdbm->dict.flags = dict_flags | DICT_FLAG_FIXED;
+ if (dict_flags & DICT_FLAG_FOLD_FIX)
+ dict_cdbm->dict.fold_buf = vstring_alloc(10);
+
+ DICT_CDBM_OPEN_RETURN(DICT_DEBUG (&dict_cdbm->dict));
+}
+
+/* dict_cdb_open - open data base for query mode or create mode */
+
+DICT *dict_cdb_open(const char *path, int open_flags, int dict_flags)
+{
+ switch (open_flags & (O_RDONLY | O_RDWR | O_WRONLY | O_CREAT | O_TRUNC)) {
+ case O_RDONLY: /* query mode */
+ return dict_cdbq_open(path, dict_flags);
+ case O_WRONLY | O_CREAT | O_TRUNC: /* create mode */
+ case O_RDWR | O_CREAT | O_TRUNC: /* sloppiness */
+ return dict_cdbm_open(path, dict_flags);
+ default:
+ msg_fatal("dict_cdb_open: inappropriate open flags for cdb database"
+ " - specify O_RDONLY or O_WRONLY|O_CREAT|O_TRUNC");
+ }
+}
+
+#endif /* HAS_CDB */
diff --git a/src/util/dict_cdb.h b/src/util/dict_cdb.h
new file mode 100644
index 0000000..696ffbf
--- /dev/null
+++ b/src/util/dict_cdb.h
@@ -0,0 +1,44 @@
+#ifndef _DICT_CDB_H_INCLUDED_
+#define _DICT_CDB_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_cdb 3h
+/* SUMMARY
+/* dictionary manager interface to CDB files
+/* SYNOPSIS
+/* #include <dict_cdb.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+#include <mkmap.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_CDB "cdb"
+
+extern DICT *dict_cdb_open(const char *, int, int);
+extern MKMAP *mkmap_cdb_open(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 /* _DICT_CDB_H_INCLUDED_ */
diff --git a/src/util/dict_cidr.c b/src/util/dict_cidr.c
new file mode 100644
index 0000000..726d11c
--- /dev/null
+++ b/src/util/dict_cidr.c
@@ -0,0 +1,362 @@
+/*++
+/* NAME
+/* dict_cidr 3
+/* SUMMARY
+/* Dictionary interface for CIDR data
+/* SYNOPSIS
+/* #include <dict_cidr.h>
+/*
+/* DICT *dict_cidr_open(name, open_flags, dict_flags)
+/* const char *name;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_cidr_open() opens the named file and stores
+/* the key/value pairs where the key must be either a
+/* "naked" IP address or a netblock in CIDR notation.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* cidr_table(5) CIDR table configuration
+/* AUTHOR(S)
+/* Jozsef Kadlecsik
+/* kadlec@blackhole.kfki.hu
+/* KFKI Research Institute for Particle and Nuclear Physics
+/* POB. 49
+/* 1525 Budapest, Hungary
+/*
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <readlline.h>
+#include <dict.h>
+#include <myaddrinfo.h>
+#include <cidr_match.h>
+#include <dict_cidr.h>
+#include <warn_stat.h>
+#include <mvect.h>
+
+/* Application-specific. */
+
+ /*
+ * Each rule in a CIDR table is parsed and stored in a linked list.
+ */
+typedef struct DICT_CIDR_ENTRY {
+ CIDR_MATCH cidr_info; /* must be first */
+ char *value; /* lookup result */
+ int lineno;
+} DICT_CIDR_ENTRY;
+
+typedef struct {
+ DICT dict; /* generic members */
+ DICT_CIDR_ENTRY *head; /* first entry */
+} DICT_CIDR;
+
+/* dict_cidr_lookup - CIDR table lookup */
+
+static const char *dict_cidr_lookup(DICT *dict, const char *key)
+{
+ DICT_CIDR *dict_cidr = (DICT_CIDR *) dict;
+ DICT_CIDR_ENTRY *entry;
+
+ if (msg_verbose)
+ msg_info("dict_cidr_lookup: %s: %s", dict->name, key);
+
+ dict->error = 0;
+
+ if ((entry = (DICT_CIDR_ENTRY *)
+ cidr_match_execute(&(dict_cidr->head->cidr_info), key)) != 0)
+ return (entry->value);
+ return (0);
+}
+
+/* dict_cidr_close - close the CIDR table */
+
+static void dict_cidr_close(DICT *dict)
+{
+ DICT_CIDR *dict_cidr = (DICT_CIDR *) dict;
+ DICT_CIDR_ENTRY *entry;
+ DICT_CIDR_ENTRY *next;
+
+ for (entry = dict_cidr->head; entry; entry = next) {
+ next = (DICT_CIDR_ENTRY *) entry->cidr_info.next;
+ myfree(entry->value);
+ myfree((void *) entry);
+ }
+ dict_free(dict);
+}
+
+/* dict_cidr_parse_rule - parse CIDR table rule into network, mask and value */
+
+static DICT_CIDR_ENTRY *dict_cidr_parse_rule(DICT *dict, char *p, int lineno,
+ int nesting, VSTRING *why)
+{
+ DICT_CIDR_ENTRY *rule;
+ char *pattern;
+ char *value;
+ CIDR_MATCH cidr_info;
+ MAI_HOSTADDR_STR hostaddr;
+ int match = 1;
+
+ /*
+ * IF must be followed by a pattern.
+ */
+ if (strncasecmp(p, "IF", 2) == 0 && !ISALNUM(p[2])) {
+ p += 2;
+ for (;;) {
+ if (*p == '!')
+ match = !match;
+ else if (!ISSPACE(*p))
+ break;
+ p++;
+ }
+ if (*p == 0) {
+ vstring_sprintf(why, "no address pattern");
+ return (0);
+ }
+ trimblanks(p, 0)[0] = 0; /* Trim trailing blanks */
+ if (cidr_match_parse_if(&cidr_info, p, match, why) != 0)
+ return (0);
+ value = "";
+ }
+
+ /*
+ * ENDIF must not be followed by other text.
+ */
+ else if (strncasecmp(p, "ENDIF", 5) == 0 && !ISALNUM(p[5])) {
+ p += 5;
+ while (*p && ISSPACE(*p)) /* Skip whitespace */
+ p++;
+ if (*p != 0) {
+ vstring_sprintf(why, "garbage after ENDIF");
+ return (0);
+ }
+ if (nesting == 0) {
+ vstring_sprintf(why, "ENDIF without IF");
+ return (0);
+ }
+ cidr_match_endif(&cidr_info);
+ value = "";
+ }
+
+ /*
+ * An address pattern.
+ */
+ else {
+
+ /*
+ * Process negation operators.
+ */
+ for (;;) {
+ if (*p == '!')
+ match = !match;
+ else if (!ISSPACE(*p))
+ break;
+ p++;
+ }
+
+ /*
+ * Split the rule into key and value. We already eliminated leading
+ * whitespace, comments, empty lines or lines with whitespace only.
+ * This means a null key can't happen but we will handle this anyway.
+ */
+ pattern = p;
+ while (*p && !ISSPACE(*p)) /* Skip over key */
+ p++;
+ if (*p) /* Terminate key */
+ *p++ = 0;
+ while (*p && ISSPACE(*p)) /* Skip whitespace */
+ p++;
+ value = p;
+ trimblanks(value, 0)[0] = 0; /* Trim trailing blanks */
+ if (*pattern == 0) {
+ vstring_sprintf(why, "no address pattern");
+ return (0);
+ }
+
+ /*
+ * Parse the pattern, destroying it in the process.
+ */
+ if (cidr_match_parse(&cidr_info, pattern, match, why) != 0)
+ return (0);
+
+ if (*value == 0) {
+ vstring_sprintf(why, "no lookup result");
+ return (0);
+ }
+ }
+
+ /*
+ * Optionally replace the value file the contents of a file.
+ */
+ if (dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) {
+ VSTRING *base64_buf;
+ char *err;
+
+ if ((base64_buf = dict_file_to_b64(dict, value)) == 0) {
+ err = dict_file_get_error(dict);
+ vstring_strcpy(why, err);
+ myfree(err);
+ return (0);
+ }
+ value = vstring_str(base64_buf);
+ }
+
+ /*
+ * Bundle up the result.
+ */
+ rule = (DICT_CIDR_ENTRY *) mymalloc(sizeof(DICT_CIDR_ENTRY));
+ rule->cidr_info = cidr_info;
+ rule->value = mystrdup(value);
+ rule->lineno = lineno;
+
+ if (msg_verbose) {
+ if (inet_ntop(cidr_info.addr_family, cidr_info.net_bytes,
+ hostaddr.buf, sizeof(hostaddr.buf)) == 0)
+ msg_fatal("inet_ntop: %m");
+ msg_info("dict_cidr_open: add %s/%d %s",
+ hostaddr.buf, cidr_info.mask_shift, rule->value);
+ }
+ return (rule);
+}
+
+/* dict_cidr_open - parse CIDR table */
+
+DICT *dict_cidr_open(const char *mapname, int open_flags, int dict_flags)
+{
+ const char myname[] = "dict_cidr_open";
+ DICT_CIDR *dict_cidr;
+ VSTREAM *map_fp = 0;
+ struct stat st;
+ VSTRING *line_buffer = 0;
+ VSTRING *why = 0;
+ DICT_CIDR_ENTRY *rule;
+ DICT_CIDR_ENTRY *last_rule = 0;
+ int last_line = 0;
+ int lineno;
+ int nesting = 0;
+ DICT_CIDR_ENTRY **rule_stack = 0;
+ MVECT mvect;
+
+ /*
+ * Let the optimizer worry about eliminating redundant code.
+ */
+#define DICT_CIDR_OPEN_RETURN(d) do { \
+ DICT *__d = (d); \
+ if (map_fp != 0 && vstream_fclose(map_fp)) \
+ msg_fatal("cidr map %s: read error: %m", mapname); \
+ if (line_buffer != 0) \
+ vstring_free(line_buffer); \
+ if (why != 0) \
+ vstring_free(why); \
+ return (__d); \
+ } while (0)
+
+ /*
+ * Sanity checks.
+ */
+ if (open_flags != O_RDONLY)
+ DICT_CIDR_OPEN_RETURN(dict_surrogate(DICT_TYPE_CIDR, mapname,
+ open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_CIDR, mapname));
+
+ /*
+ * Open the configuration file.
+ */
+ if ((map_fp = dict_stream_open(DICT_TYPE_CIDR, mapname, O_RDONLY,
+ dict_flags, &st, &why)) == 0)
+ DICT_CIDR_OPEN_RETURN(dict_surrogate(DICT_TYPE_CIDR, mapname,
+ open_flags, dict_flags,
+ "%s", vstring_str(why)));
+ line_buffer = vstring_alloc(100);
+ why = vstring_alloc(100);
+
+ /*
+ * XXX Eliminate unnecessary queries by setting a flag that says "this
+ * map matches network addresses only".
+ */
+ dict_cidr = (DICT_CIDR *) dict_alloc(DICT_TYPE_CIDR, mapname,
+ sizeof(*dict_cidr));
+ dict_cidr->dict.lookup = dict_cidr_lookup;
+ dict_cidr->dict.close = dict_cidr_close;
+ dict_cidr->dict.flags = dict_flags | DICT_FLAG_PATTERN;
+ dict_cidr->head = 0;
+
+ dict_cidr->dict.owner.uid = st.st_uid;
+ dict_cidr->dict.owner.status = (st.st_uid != 0);
+
+ while (readllines(line_buffer, map_fp, &last_line, &lineno)) {
+ rule = dict_cidr_parse_rule(&dict_cidr->dict,
+ vstring_str(line_buffer), lineno,
+ nesting, why);
+ if (rule == 0) {
+ msg_warn("cidr map %s, line %d: %s: skipping this rule",
+ mapname, lineno, vstring_str(why));
+ continue;
+ }
+ if (rule->cidr_info.op == CIDR_MATCH_OP_IF) {
+ if (rule_stack == 0)
+ rule_stack = (DICT_CIDR_ENTRY **) mvect_alloc(&mvect,
+ sizeof(*rule_stack), nesting + 1,
+ (MVECT_FN) 0, (MVECT_FN) 0);
+ else
+ rule_stack =
+ (DICT_CIDR_ENTRY **) mvect_realloc(&mvect, nesting + 1);
+ rule_stack[nesting] = rule;
+ nesting++;
+ } else if (rule->cidr_info.op == CIDR_MATCH_OP_ENDIF) {
+ DICT_CIDR_ENTRY *if_rule;
+
+ if (nesting-- <= 0)
+ /* Already handled in dict_cidr_parse_rule(). */
+ msg_panic("%s: ENDIF without IF", myname);
+ if_rule = rule_stack[nesting];
+ if (if_rule->cidr_info.op != CIDR_MATCH_OP_IF)
+ msg_panic("%s: unexpected rule stack element type %d",
+ myname, if_rule->cidr_info.op);
+ if_rule->cidr_info.block_end = &(rule->cidr_info);
+ }
+ if (last_rule == 0)
+ dict_cidr->head = rule;
+ else
+ last_rule->cidr_info.next = &(rule->cidr_info);
+ last_rule = rule;
+ }
+
+ while (nesting-- > 0)
+ msg_warn("cidr map %s, line %d: IF has no matching ENDIF",
+ mapname, rule_stack[nesting]->lineno);
+
+ if (rule_stack)
+ (void) mvect_free(&mvect);
+
+ dict_file_purge_buffers(&dict_cidr->dict);
+ DICT_CIDR_OPEN_RETURN(DICT_DEBUG (&dict_cidr->dict));
+}
diff --git a/src/util/dict_cidr.h b/src/util/dict_cidr.h
new file mode 100644
index 0000000..308a765
--- /dev/null
+++ b/src/util/dict_cidr.h
@@ -0,0 +1,43 @@
+#ifndef _DICT_CIDR_H_INCLUDED_
+#define _DICT_CIDR_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_cidr 3h
+/* SUMMARY
+/* Dictionary manager interface to handle cidr data.
+/* SYNOPSIS
+/* #include <dict_cidr.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+extern DICT *dict_cidr_open(const char *, int, int);
+
+#define DICT_TYPE_CIDR "cidr"
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Jozsef Kadlecsik
+/* kadlec@blackhole.kfki.hu
+/* KFKI Research Institute for Particle and Nuclear Physics
+/* POB. 49
+/* 1525 Budapest 114, Hungary
+/*--*/
+
+#endif
diff --git a/src/util/dict_cidr.in b/src/util/dict_cidr.in
new file mode 100644
index 0000000..ee20c17
--- /dev/null
+++ b/src/util/dict_cidr.in
@@ -0,0 +1,23 @@
+get 172.16.0.0
+get 172.16.0.1
+get 172.16.7.255
+get 172.16.8.1
+get 172.16.17.1
+get 172.17.1.1
+get 172.17.1.2
+get 2001:240:5c7:0:2d0:b7ff:fe88:2ca7
+get 2001:240:5c7:0:2d0:b7ff:febe:ca9f
+get 1.1.1.1
+get 1:1:1:1:1:1:1:1
+get 1.2.3.3
+get 1.2.3.4
+get 1.2.3.5
+get 1.2.3.6
+get 1.2.3.7
+get 1.2.3.8
+get ::f3
+get ::f4
+get ::f5
+get ::f6
+get ::f7
+get ::f8
diff --git a/src/util/dict_cidr.map b/src/util/dict_cidr.map
new file mode 100644
index 0000000..ac0f9b5
--- /dev/null
+++ b/src/util/dict_cidr.map
@@ -0,0 +1,50 @@
+172.16.0.0/21 554 match bad netblock 172.16.0.0/21
+172.16.8.0/21 554 match bad netblock 172.16.8.0/21
+172.16.0.0/16 554 match bad netblock 172.16.0.0/16
+172.17.1.1 554 match bad naked address
+172.16.1.3/21 whatever
+172.16.1.3/33 whatever
+172.999.0.0/21 whatever
+172.16.1.999 whatever
+172.16.1.4
+if 1.2.0.0/16
+if 1.2.3.4/30
+1.2.3.3 1.2.3.3 can't happen
+1.2.3.4 1.2.3.4 can happen
+1.2.3.5 1.2.3.5 can happen
+1.2.3.6 1.2.3.6 can happen
+1.2.3.7 1.2.3.7 can happen
+1.2.3.8 1.2.3.8 can't happen
+endif
+endif
+if !1.2.3.4/30
+1.2.3.3 1.2.3.3 can happen
+1.2.3.8 1.2.3.8 can happen
+endif
+if ::f4/126
+::f3 ::f3 can't happen
+::f4 ::f4 can happen
+::f5 ::f5 can happen
+::f6 ::f6 can happen
+::f7 ::f7 can happen
+::f8 ::f8 can't happen
+endif
+if !::f4/126
+::f3 ::f3 can happen
+::f8 ::f8 can happen
+endif
+2001:240:5c7:0:2d0:b7ff:fe88:2ca7 match 2001:240:5c7:0:2d0:b7ff:fe88:2ca7
+2001:240:5c7::/64 match netblock 2001:240:5c7::/64
+1.0.0.0/0 match 0.0.0.0/0
+! ! 0.0.0.0/0 match 0.0.0.0/0
+1::/0 match ::/0
+::/0 match ::/0
+[1234 can't happen
+[1234]junk can't happen
+172.16.1.3/3x can't happen
+endif
+endif
+if 1:2::3:4
+if 1:2::3:5
+if !
+!
diff --git a/src/util/dict_cidr.ref b/src/util/dict_cidr.ref
new file mode 100644
index 0000000..305e3fd
--- /dev/null
+++ b/src/util/dict_cidr.ref
@@ -0,0 +1,63 @@
+./dict_open: warning: cidr map dict_cidr.map, line 5: non-null host address bits in "172.16.1.3/21", perhaps you should use "172.16.0.0/21" instead: skipping this rule
+./dict_open: warning: cidr map dict_cidr.map, line 6: bad mask length in "172.16.1.3/33": skipping this rule
+./dict_open: warning: cidr map dict_cidr.map, line 7: bad network value in "172.999.0.0/21": skipping this rule
+./dict_open: warning: cidr map dict_cidr.map, line 8: bad address pattern: "172.16.1.999": skipping this rule
+./dict_open: warning: cidr map dict_cidr.map, line 9: no lookup result: skipping this rule
+./dict_open: warning: cidr map dict_cidr.map, line 38: non-null host address bits in "1.0.0.0/0", perhaps you should use "0.0.0.0/0" instead: skipping this rule
+./dict_open: warning: cidr map dict_cidr.map, line 40: non-null host address bits in "1::/0", perhaps you should use "::/0" instead: skipping this rule
+./dict_open: warning: cidr map dict_cidr.map, line 42: missing ']' character after "[1234": skipping this rule
+./dict_open: warning: cidr map dict_cidr.map, line 43: garbage after "[1234]": skipping this rule
+./dict_open: warning: cidr map dict_cidr.map, line 44: bad mask value in "172.16.1.3/3x": skipping this rule
+./dict_open: warning: cidr map dict_cidr.map, line 45: ENDIF without IF: skipping this rule
+./dict_open: warning: cidr map dict_cidr.map, line 46: ENDIF without IF: skipping this rule
+./dict_open: warning: cidr map dict_cidr.map, line 49: no address pattern: skipping this rule
+./dict_open: warning: cidr map dict_cidr.map, line 50: no address pattern: skipping this rule
+./dict_open: warning: cidr map dict_cidr.map, line 48: IF has no matching ENDIF
+./dict_open: warning: cidr map dict_cidr.map, line 47: IF has no matching ENDIF
+owner=untrusted (uid=USER)
+> get 172.16.0.0
+172.16.0.0=554 match bad netblock 172.16.0.0/21
+> get 172.16.0.1
+172.16.0.1=554 match bad netblock 172.16.0.0/21
+> get 172.16.7.255
+172.16.7.255=554 match bad netblock 172.16.0.0/21
+> get 172.16.8.1
+172.16.8.1=554 match bad netblock 172.16.8.0/21
+> get 172.16.17.1
+172.16.17.1=554 match bad netblock 172.16.0.0/16
+> get 172.17.1.1
+172.17.1.1=554 match bad naked address
+> get 172.17.1.2
+172.17.1.2=match 0.0.0.0/0
+> get 2001:240:5c7:0:2d0:b7ff:fe88:2ca7
+2001:240:5c7:0:2d0:b7ff:fe88:2ca7=match 2001:240:5c7:0:2d0:b7ff:fe88:2ca7
+> get 2001:240:5c7:0:2d0:b7ff:febe:ca9f
+2001:240:5c7:0:2d0:b7ff:febe:ca9f=match netblock 2001:240:5c7::/64
+> get 1.1.1.1
+1.1.1.1=match 0.0.0.0/0
+> get 1:1:1:1:1:1:1:1
+1:1:1:1:1:1:1:1=match ::/0
+> get 1.2.3.3
+1.2.3.3=1.2.3.3 can happen
+> get 1.2.3.4
+1.2.3.4=1.2.3.4 can happen
+> get 1.2.3.5
+1.2.3.5=1.2.3.5 can happen
+> get 1.2.3.6
+1.2.3.6=1.2.3.6 can happen
+> get 1.2.3.7
+1.2.3.7=1.2.3.7 can happen
+> get 1.2.3.8
+1.2.3.8=1.2.3.8 can happen
+> get ::f3
+::f3=::f3 can happen
+> get ::f4
+::f4=::f4 can happen
+> get ::f5
+::f5=::f5 can happen
+> get ::f6
+::f6=::f6 can happen
+> get ::f7
+::f7=::f7 can happen
+> get ::f8
+::f8=::f8 can happen
diff --git a/src/util/dict_cidr_file.in b/src/util/dict_cidr_file.in
new file mode 100644
index 0000000..531d735
--- /dev/null
+++ b/src/util/dict_cidr_file.in
@@ -0,0 +1,3 @@
+get 1.1.1.1
+get 2.2.2.2
+get 3.3.3.3
diff --git a/src/util/dict_cidr_file.map b/src/util/dict_cidr_file.map
new file mode 100644
index 0000000..e85e8ca
--- /dev/null
+++ b/src/util/dict_cidr_file.map
@@ -0,0 +1,3 @@
+1.1.1.1 dict_cidr_file1
+2.2.2.2 dict_cidr_file2
+3.3.3.3 dict_cidr_file3
diff --git a/src/util/dict_cidr_file.ref b/src/util/dict_cidr_file.ref
new file mode 100644
index 0000000..3e9e792
--- /dev/null
+++ b/src/util/dict_cidr_file.ref
@@ -0,0 +1,8 @@
+./dict_open: warning: cidr map dict_cidr_file.map, line 3: open dict_cidr_file3: No such file or directory: skipping this rule
+owner=untrusted (uid=USER)
+> get 1.1.1.1
+1.1.1.1=dGhpcy1pcy1maWxlMQo=
+> get 2.2.2.2
+2.2.2.2=dGhpcy1pcy1maWxlMgo=
+> get 3.3.3.3
+3.3.3.3: not found
diff --git a/src/util/dict_db.c b/src/util/dict_db.c
new file mode 100644
index 0000000..b2d0c33
--- /dev/null
+++ b/src/util/dict_db.c
@@ -0,0 +1,880 @@
+/*++
+/* NAME
+/* dict_db 3
+/* SUMMARY
+/* dictionary manager interface to DB files
+/* SYNOPSIS
+/* #include <dict_db.h>
+/*
+/* extern int dict_db_cache_size;
+/*
+/* DEFINE_DICT_DB_CACHE_SIZE;
+/*
+/* DICT *dict_hash_open(path, open_flags, dict_flags)
+/* const char *path;
+/* int open_flags;
+/* int dict_flags;
+/*
+/* DICT *dict_btree_open(path, open_flags, dict_flags)
+/* const char *path;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_XXX_open() opens the specified DB database. The result is
+/* a pointer to a structure that can be used to access the dictionary
+/* using the generic methods documented in dict_open(3).
+/*
+/* The dict_db_cache_size variable specifies a non-default per-table
+/* I/O buffer size. The default buffer size is adequate for reading.
+/* For better performance while creating a large table, specify a large
+/* buffer size before opening the file.
+/*
+/* This variable cannot be exported via the dict(3) API and
+/* must therefore be defined in the calling program by invoking
+/* the DEFINE_DICT_DB_CACHE_SIZE macro at the global level.
+/*
+/* Arguments:
+/* .IP path
+/* The database pathname, not including the ".db" suffix.
+/* .IP open_flags
+/* Flags passed to dbopen().
+/* .IP dict_flags
+/* Flags used by the dictionary interface.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* DIAGNOSTICS
+/* Fatal errors: cannot open file, write error, out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#include "sys_defs.h"
+
+#ifdef HAS_DB
+
+/* System library. */
+
+#include <sys/stat.h>
+#include <limits.h>
+#ifdef PATH_DB_H
+#include PATH_DB_H
+#else
+#include <db.h>
+#endif
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#if defined(_DB_185_H_) && defined(USE_FCNTL_LOCK)
+#error "Error: this system must not use the db 1.85 compatibility interface"
+#endif
+
+#ifndef DB_VERSION_MAJOR
+#define DB_VERSION_MAJOR 1
+#define DICT_DB_GET(db, key, val, flag) db->get(db, key, val, flag)
+#define DICT_DB_PUT(db, key, val, flag) db->put(db, key, val, flag)
+#define DICT_DB_DEL(db, key, flag) db->del(db, key, flag)
+#define DICT_DB_SYNC(db, flag) db->sync(db, flag)
+#define DICT_DB_CLOSE(db) db->close(db)
+#define DONT_CLOBBER R_NOOVERWRITE
+#endif
+
+#if DB_VERSION_MAJOR > 1
+#define DICT_DB_GET(db, key, val, flag) sanitize(db->get(db, 0, key, val, flag))
+#define DICT_DB_PUT(db, key, val, flag) sanitize(db->put(db, 0, key, val, flag))
+#define DICT_DB_DEL(db, key, flag) sanitize(db->del(db, 0, key, flag))
+#define DICT_DB_SYNC(db, flag) ((errno = db->sync(db, flag)) ? -1 : 0)
+#define DICT_DB_CLOSE(db) ((errno = db->close(db, 0)) ? -1 : 0)
+#define DONT_CLOBBER DB_NOOVERWRITE
+#endif
+
+#if (DB_VERSION_MAJOR == 2 && DB_VERSION_MINOR < 6)
+#define DICT_DB_CURSOR(db, curs) (db)->cursor((db), NULL, (curs))
+#else
+#define DICT_DB_CURSOR(db, curs) (db)->cursor((db), NULL, (curs), 0)
+#endif
+
+#ifndef DB_FCNTL_LOCKING
+#define DB_FCNTL_LOCKING 0
+#endif
+
+/* Utility library. */
+
+#include "msg.h"
+#include "mymalloc.h"
+#include "vstring.h"
+#include "stringops.h"
+#include "iostuff.h"
+#include "myflock.h"
+#include "dict.h"
+#include "dict_db.h"
+#include "warn_stat.h"
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ DB *db; /* open db file */
+#if DB_VERSION_MAJOR > 2
+ DB_ENV *dbenv;
+#endif
+#if DB_VERSION_MAJOR > 1
+ DBC *cursor; /* dict_db_sequence() */
+#endif
+ VSTRING *key_buf; /* key result */
+ VSTRING *val_buf; /* value result */
+} DICT_DB;
+
+#define SCOPY(buf, data, size) \
+ vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size))
+
+#define DICT_DB_NELM 4096
+
+#if DB_VERSION_MAJOR > 1
+
+/* sanitize - sanitize db_get/put/del result */
+
+static int sanitize(int status)
+{
+
+ /*
+ * XXX This is unclean but avoids a lot of clutter elsewhere. Categorize
+ * results into non-fatal errors (i.e., errors that we can deal with),
+ * success, or fatal error (i.e., all other errors).
+ */
+ switch (status) {
+
+ case DB_NOTFOUND: /* get, del */
+ case DB_KEYEXIST: /* put */
+ return (1); /* non-fatal */
+
+ case 0:
+ return (0); /* success */
+
+ case DB_KEYEMPTY: /* get, others? */
+ status = EINVAL;
+ /* FALLTHROUGH */
+ default:
+ errno = status;
+ return (-1); /* fatal */
+ }
+}
+
+#endif
+
+/* dict_db_lookup - find database entry */
+
+static const char *dict_db_lookup(DICT *dict, const char *name)
+{
+ DICT_DB *dict_db = (DICT_DB *) dict;
+ DB *db = dict_db->db;
+ DBT db_key;
+ DBT db_value;
+ int status;
+ const char *result = 0;
+
+ dict->error = 0;
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_db_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ memset(&db_key, 0, sizeof(db_key));
+ memset(&db_value, 0, sizeof(db_value));
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+
+ /*
+ * Acquire a shared lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_db->dict.name);
+
+ /*
+ * See if this DB file was written with one null byte appended to key and
+ * value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ db_key.data = (void *) name;
+ db_key.size = strlen(name) + 1;
+ if ((status = DICT_DB_GET(db, &db_key, &db_value, 0)) < 0)
+ msg_fatal("error reading %s: %m", dict_db->dict.name);
+ if (status == 0) {
+ dict->flags &= ~DICT_FLAG_TRY0NULL;
+ result = SCOPY(dict_db->val_buf, db_value.data, db_value.size);
+ }
+ }
+
+ /*
+ * See if this DB file was written with no null byte appended to key and
+ * value.
+ */
+ if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
+ db_key.data = (void *) name;
+ db_key.size = strlen(name);
+ if ((status = DICT_DB_GET(db, &db_key, &db_value, 0)) < 0)
+ msg_fatal("error reading %s: %m", dict_db->dict.name);
+ if (status == 0) {
+ dict->flags &= ~DICT_FLAG_TRY1NULL;
+ result = SCOPY(dict_db->val_buf, db_value.data, db_value.size);
+ }
+ }
+
+ /*
+ * Release the shared lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name);
+
+ return (result);
+}
+
+/* dict_db_update - add or update database entry */
+
+static int dict_db_update(DICT *dict, const char *name, const char *value)
+{
+ DICT_DB *dict_db = (DICT_DB *) dict;
+ DB *db = dict_db->db;
+ DBT db_key;
+ DBT db_value;
+ int status;
+
+ dict->error = 0;
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_db_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+ memset(&db_key, 0, sizeof(db_key));
+ memset(&db_value, 0, sizeof(db_value));
+ db_key.data = (void *) name;
+ db_value.data = (void *) value;
+ db_key.size = strlen(name);
+ db_value.size = strlen(value);
+
+ /*
+ * If undecided about appending a null byte to key and value, choose a
+ * default depending on the platform.
+ */
+ if ((dict->flags & DICT_FLAG_TRY1NULL)
+ && (dict->flags & DICT_FLAG_TRY0NULL)) {
+#ifdef DB_NO_TRAILING_NULL
+ dict->flags &= ~DICT_FLAG_TRY1NULL;
+#else
+ dict->flags &= ~DICT_FLAG_TRY0NULL;
+#endif
+ }
+
+ /*
+ * Optionally append a null byte to key and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ db_key.size++;
+ db_value.size++;
+ }
+
+ /*
+ * Acquire an exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_db->dict.name);
+
+ /*
+ * Do the update.
+ */
+ if ((status = DICT_DB_PUT(db, &db_key, &db_value,
+ (dict->flags & DICT_FLAG_DUP_REPLACE) ? 0 : DONT_CLOBBER)) < 0)
+ msg_fatal("error writing %s: %m", dict_db->dict.name);
+ if (status) {
+ if (dict->flags & DICT_FLAG_DUP_IGNORE)
+ /* void */ ;
+ else if (dict->flags & DICT_FLAG_DUP_WARN)
+ msg_warn("%s: duplicate entry: \"%s\"", dict_db->dict.name, name);
+ else
+ msg_fatal("%s: duplicate entry: \"%s\"", dict_db->dict.name, name);
+ }
+ if (dict->flags & DICT_FLAG_SYNC_UPDATE)
+ if (DICT_DB_SYNC(db, 0) < 0)
+ msg_fatal("%s: flush dictionary: %m", dict_db->dict.name);
+
+ /*
+ * Release the exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name);
+
+ return (status);
+}
+
+/* delete one entry from the dictionary */
+
+static int dict_db_delete(DICT *dict, const char *name)
+{
+ DICT_DB *dict_db = (DICT_DB *) dict;
+ DB *db = dict_db->db;
+ DBT db_key;
+ int status = 1;
+ int flags = 0;
+
+ dict->error = 0;
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_db_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+ memset(&db_key, 0, sizeof(db_key));
+
+ /*
+ * Acquire an exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_db->dict.name);
+
+ /*
+ * See if this DB file was written with one null byte appended to key and
+ * value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ db_key.data = (void *) name;
+ db_key.size = strlen(name) + 1;
+ if ((status = DICT_DB_DEL(db, &db_key, flags)) < 0)
+ msg_fatal("error deleting from %s: %m", dict_db->dict.name);
+ if (status == 0)
+ dict->flags &= ~DICT_FLAG_TRY0NULL;
+ }
+
+ /*
+ * See if this DB file was written with no null byte appended to key and
+ * value.
+ */
+ if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
+ db_key.data = (void *) name;
+ db_key.size = strlen(name);
+ if ((status = DICT_DB_DEL(db, &db_key, flags)) < 0)
+ msg_fatal("error deleting from %s: %m", dict_db->dict.name);
+ if (status == 0)
+ dict->flags &= ~DICT_FLAG_TRY1NULL;
+ }
+ if (dict->flags & DICT_FLAG_SYNC_UPDATE)
+ if (DICT_DB_SYNC(db, 0) < 0)
+ msg_fatal("%s: flush dictionary: %m", dict_db->dict.name);
+
+ /*
+ * Release the exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name);
+
+ return status;
+}
+
+/* dict_db_sequence - traverse the dictionary */
+
+static int dict_db_sequence(DICT *dict, int function,
+ const char **key, const char **value)
+{
+ const char *myname = "dict_db_sequence";
+ DICT_DB *dict_db = (DICT_DB *) dict;
+ DB *db = dict_db->db;
+ DBT db_key;
+ DBT db_value;
+ int status = 0;
+ int db_function;
+
+ dict->error = 0;
+
+#if DB_VERSION_MAJOR > 1
+
+ /*
+ * Initialize.
+ */
+ memset(&db_key, 0, sizeof(db_key));
+ memset(&db_value, 0, sizeof(db_value));
+
+ /*
+ * Determine the function.
+ */
+ switch (function) {
+ case DICT_SEQ_FUN_FIRST:
+ if (dict_db->cursor == 0)
+ DICT_DB_CURSOR(db, &(dict_db->cursor));
+ db_function = DB_FIRST;
+ break;
+ case DICT_SEQ_FUN_NEXT:
+ if (dict_db->cursor == 0)
+ msg_panic("%s: no cursor", myname);
+ db_function = DB_NEXT;
+ break;
+ default:
+ msg_panic("%s: invalid function %d", myname, function);
+ }
+
+ /*
+ * Acquire a shared lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_db->dict.name);
+
+ /*
+ * Database lookup.
+ */
+ status =
+ dict_db->cursor->c_get(dict_db->cursor, &db_key, &db_value, db_function);
+ if (status != 0 && status != DB_NOTFOUND)
+ msg_fatal("error [%d] seeking %s: %m", status, dict_db->dict.name);
+
+ /*
+ * Release the shared lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name);
+
+ if (status == 0) {
+
+ /*
+ * Copy the result so it is guaranteed null terminated.
+ */
+ *key = SCOPY(dict_db->key_buf, db_key.data, db_key.size);
+ *value = SCOPY(dict_db->val_buf, db_value.data, db_value.size);
+ }
+ return (status);
+#else
+
+ /*
+ * determine the function
+ */
+ switch (function) {
+ case DICT_SEQ_FUN_FIRST:
+ db_function = R_FIRST;
+ break;
+ case DICT_SEQ_FUN_NEXT:
+ db_function = R_NEXT;
+ break;
+ default:
+ msg_panic("%s: invalid function %d", myname, function);
+ }
+
+ /*
+ * Acquire a shared lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_db->dict.name);
+
+ if ((status = db->seq(db, &db_key, &db_value, db_function)) < 0)
+ msg_fatal("error seeking %s: %m", dict_db->dict.name);
+
+ /*
+ * Release the shared lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name);
+
+ if (status == 0) {
+
+ /*
+ * Copy the result so that it is guaranteed null terminated.
+ */
+ *key = SCOPY(dict_db->key_buf, db_key.data, db_key.size);
+ *value = SCOPY(dict_db->val_buf, db_value.data, db_value.size);
+ }
+ return status;
+#endif
+}
+
+/* dict_db_close - close data base */
+
+static void dict_db_close(DICT *dict)
+{
+ DICT_DB *dict_db = (DICT_DB *) dict;
+
+#if DB_VERSION_MAJOR > 1
+ if (dict_db->cursor)
+ dict_db->cursor->c_close(dict_db->cursor);
+#endif
+ if (DICT_DB_SYNC(dict_db->db, 0) < 0)
+ msg_fatal("flush database %s: %m", dict_db->dict.name);
+
+ /*
+ * With some Berkeley DB implementations, close fails with a bogus ENOENT
+ * error, while it reports no errors with put+sync, no errors with
+ * del+sync, and no errors with the sync operation just before this
+ * comment. This happens in programs that never fork and that never share
+ * the database with other processes. The bogus close error has been
+ * reported for programs that use the first/next iterator. Instead of
+ * making Postfix look bad because it reports errors that other programs
+ * ignore, I'm going to report the bogus error as a non-error.
+ */
+ if (DICT_DB_CLOSE(dict_db->db) < 0)
+ msg_info("close database %s: %m (possible Berkeley DB bug)",
+ dict_db->dict.name);
+#if DB_VERSION_MAJOR > 2
+ dict_db->dbenv->close(dict_db->dbenv, 0);
+#endif
+ if (dict_db->key_buf)
+ vstring_free(dict_db->key_buf);
+ if (dict_db->val_buf)
+ vstring_free(dict_db->val_buf);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+#if DB_VERSION_MAJOR > 2
+
+/* dict_db_new_env - workaround for undocumented ./DB_CONFIG read */
+
+static DB_ENV *dict_db_new_env(const char *db_path)
+{
+ VSTRING *db_home_buf;
+ DB_ENV *dbenv;
+ u_int32_t cache_size_gbytes;
+ u_int32_t cache_size_bytes;
+ int ncache;
+
+ if ((errno = db_env_create(&dbenv, 0)) != 0)
+ msg_fatal("create DB environment: %m");
+#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 7)
+ if ((errno = dbenv->get_cachesize(dbenv, &cache_size_gbytes,
+ &cache_size_bytes, &ncache)) != 0)
+ msg_fatal("get DB cache size: %m");
+ if (cache_size_gbytes == 0 && cache_size_bytes < dict_db_cache_size) {
+ if ((errno = dbenv->set_cache_max(dbenv, cache_size_gbytes,
+ dict_db_cache_size)) != 0)
+ msg_fatal("set DB max cache size %d: %m", dict_db_cache_size);
+ if ((errno = dbenv->set_cachesize(dbenv, cache_size_gbytes,
+ dict_db_cache_size, ncache)) != 0)
+ msg_fatal("set DB cache size %d: %m", dict_db_cache_size);
+ }
+#endif
+ /* XXX db_home is also the default directory for the .db file. */
+ db_home_buf = vstring_alloc(100);
+ if ((errno = dbenv->open(dbenv, sane_dirname(db_home_buf, db_path),
+ DB_INIT_MPOOL | DB_CREATE | DB_PRIVATE, 0)) != 0)
+ msg_fatal("open DB environment: %m");
+ vstring_free(db_home_buf);
+ return (dbenv);
+}
+
+#endif
+
+/* dict_db_open - open data base */
+
+static DICT *dict_db_open(const char *class, const char *path, int open_flags,
+ int type, void *tweak, int dict_flags)
+{
+ DICT_DB *dict_db;
+ struct stat st;
+ DB *db = 0;
+ char *db_path = 0;
+ VSTRING *db_base_buf = 0;
+ int lock_fd = -1;
+ int dbfd;
+
+#if DB_VERSION_MAJOR > 1
+ int db_flags;
+
+#endif
+#if DB_VERSION_MAJOR > 2
+ DB_ENV *dbenv = 0;
+
+#endif
+
+ /*
+ * Mismatches between #include file and library are a common cause for
+ * trouble.
+ */
+#if DB_VERSION_MAJOR > 1
+ int major_version;
+ int minor_version;
+ int patch_version;
+
+ (void) db_version(&major_version, &minor_version, &patch_version);
+ if (major_version != DB_VERSION_MAJOR || minor_version != DB_VERSION_MINOR)
+ return (dict_surrogate(class, path, open_flags, dict_flags,
+ "incorrect version of Berkeley DB: "
+ "compiled against %d.%d.%d, run-time linked against %d.%d.%d",
+ DB_VERSION_MAJOR, DB_VERSION_MINOR, DB_VERSION_PATCH,
+ major_version, minor_version, patch_version));
+ if (msg_verbose) {
+ msg_info("Compiled against Berkeley DB: %d.%d.%d\n",
+ DB_VERSION_MAJOR, DB_VERSION_MINOR, DB_VERSION_PATCH);
+ msg_info("Run-time linked against Berkeley DB: %d.%d.%d\n",
+ major_version, minor_version, patch_version);
+ }
+#else
+ if (msg_verbose)
+ msg_info("Compiled against Berkeley DB version 1");
+#endif
+
+ db_path = concatenate(path, ".db", (char *) 0);
+
+ /*
+ * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in
+ * the time domain) locking while accessing individual database records.
+ *
+ * Programs such as postmap/postalias use their own large-grained (in the
+ * time domain) locks while rewriting the entire file.
+ *
+ * XXX DB version 4.1 will not open a zero-length file. This means we must
+ * open an existing file without O_CREAT|O_TRUNC, and that we must let
+ * db_open() create a non-existent file for us.
+ */
+#define LOCK_OPEN_FLAGS(f) ((f) & ~(O_CREAT|O_TRUNC))
+#if DB_VERSION_MAJOR <= 2
+#define FREE_RETURN(e) do { \
+ DICT *_dict = (e); if (db) DICT_DB_CLOSE(db); \
+ if (lock_fd >= 0) (void) close(lock_fd); \
+ if (db_base_buf) vstring_free(db_base_buf); \
+ if (db_path) myfree(db_path); return (_dict); \
+ } while (0)
+#else
+#define FREE_RETURN(e) do { \
+ DICT *_dict = (e); if (db) DICT_DB_CLOSE(db); \
+ if (dbenv) dbenv->close(dbenv, 0); \
+ if (lock_fd >= 0) (void) close(lock_fd); \
+ if (db_base_buf) vstring_free(db_base_buf); \
+ if (db_path) myfree(db_path); \
+ return (_dict); \
+ } while (0)
+#endif
+
+ if (dict_flags & DICT_FLAG_LOCK) {
+ if ((lock_fd = open(db_path, LOCK_OPEN_FLAGS(open_flags), 0644)) < 0) {
+ if (errno != ENOENT)
+ FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags,
+ "open database %s: %m", db_path));
+ } else {
+ if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
+ msg_fatal("shared-lock database %s for open: %m", db_path);
+ }
+ }
+
+ /*
+ * Use the DB 1.x programming interface. This is the default interface
+ * with 4.4BSD systems. It is also available via the db_185 compatibility
+ * interface, but that interface does not have the undocumented feature
+ * that we need to make file locking safe with POSIX fcntl() locking.
+ */
+#if DB_VERSION_MAJOR < 2
+ if ((db = dbopen(db_path, open_flags, 0644, type, tweak)) == 0)
+ FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags,
+ "open database %s: %m", db_path));
+ dbfd = db->fd(db);
+#endif
+
+ /*
+ * Use the DB 2.x programming interface. Jump a couple extra hoops.
+ */
+#if DB_VERSION_MAJOR == 2
+ db_flags = DB_FCNTL_LOCKING;
+ if (open_flags == O_RDONLY)
+ db_flags |= DB_RDONLY;
+ if (open_flags & O_CREAT)
+ db_flags |= DB_CREATE;
+ if (open_flags & O_TRUNC)
+ db_flags |= DB_TRUNCATE;
+ if ((errno = db_open(db_path, type, db_flags, 0644, 0, tweak, &db)) != 0)
+ FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags,
+ "open database %s: %m", db_path));
+ if (db == 0)
+ msg_panic("db_open null result");
+ if ((errno = db->fd(db, &dbfd)) != 0)
+ msg_fatal("get database file descriptor: %m");
+#endif
+
+ /*
+ * Use the DB 3.x programming interface. Jump even more hoops.
+ */
+#if DB_VERSION_MAJOR > 2
+ db_flags = DB_FCNTL_LOCKING;
+ if (open_flags == O_RDONLY)
+ db_flags |= DB_RDONLY;
+ if (open_flags & O_CREAT)
+ db_flags |= DB_CREATE;
+ if (open_flags & O_TRUNC)
+ db_flags |= DB_TRUNCATE;
+ if ((errno = db_create(&db, dbenv = dict_db_new_env(db_path), 0)) != 0)
+ msg_fatal("create DB database: %m");
+ if (db == 0)
+ msg_panic("db_create null result");
+ if (type == DB_HASH && db->set_h_nelem(db, DICT_DB_NELM) != 0)
+ msg_fatal("set DB hash element count %d: %m", DICT_DB_NELM);
+ db_base_buf = vstring_alloc(100);
+#if DB_VERSION_MAJOR == 18 || DB_VERSION_MAJOR == 6 || DB_VERSION_MAJOR == 5 || \
+ (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR > 0)
+ if ((errno = db->open(db, 0, sane_basename(db_base_buf, db_path),
+ 0, type, db_flags, 0644)) != 0)
+ FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags,
+ "open database %s: %m", db_path));
+#elif (DB_VERSION_MAJOR == 3 || DB_VERSION_MAJOR == 4)
+ if ((errno = db->open(db, sane_basename(db_base_buf, db_path), 0,
+ type, db_flags, 0644)) != 0)
+ FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags,
+ "open database %s: %m", db_path));
+#else
+#error "Unsupported Berkeley DB version"
+#endif
+ vstring_free(db_base_buf);
+ if ((errno = db->fd(db, &dbfd)) != 0)
+ msg_fatal("get database file descriptor: %m");
+#endif
+ if ((dict_flags & DICT_FLAG_LOCK) && lock_fd >= 0) {
+ if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("unlock database %s for open: %m", db_path);
+ if (close(lock_fd) < 0)
+ msg_fatal("close database %s: %m", db_path);
+ lock_fd = -1;
+ }
+ dict_db = (DICT_DB *) dict_alloc(class, db_path, sizeof(*dict_db));
+ dict_db->dict.lookup = dict_db_lookup;
+ dict_db->dict.update = dict_db_update;
+ dict_db->dict.delete = dict_db_delete;
+ dict_db->dict.sequence = dict_db_sequence;
+ dict_db->dict.close = dict_db_close;
+ dict_db->dict.lock_fd = dbfd;
+ dict_db->dict.stat_fd = dbfd;
+ if (fstat(dict_db->dict.stat_fd, &st) < 0)
+ msg_fatal("dict_db_open: fstat: %m");
+ dict_db->dict.mtime = st.st_mtime;
+ dict_db->dict.owner.uid = st.st_uid;
+ dict_db->dict.owner.status = (st.st_uid != 0);
+
+ /*
+ * Warn if the source file is newer than the indexed file, except when
+ * the source file changed only seconds ago.
+ */
+ if ((dict_flags & DICT_FLAG_LOCK) != 0
+ && stat(path, &st) == 0
+ && st.st_mtime > dict_db->dict.mtime
+ && st.st_mtime < time((time_t *) 0) - 100)
+ msg_warn("database %s is older than source file %s", db_path, path);
+
+ close_on_exec(dict_db->dict.lock_fd, CLOSE_ON_EXEC);
+ close_on_exec(dict_db->dict.stat_fd, CLOSE_ON_EXEC);
+ dict_db->dict.flags = dict_flags | DICT_FLAG_FIXED;
+ if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ dict_db->dict.flags |= (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL);
+ if (dict_flags & DICT_FLAG_FOLD_FIX)
+ dict_db->dict.fold_buf = vstring_alloc(10);
+ dict_db->db = db;
+#if DB_VERSION_MAJOR > 2
+ dict_db->dbenv = dbenv;
+#endif
+#if DB_VERSION_MAJOR > 1
+ dict_db->cursor = 0;
+#endif
+ dict_db->key_buf = 0;
+ dict_db->val_buf = 0;
+
+ myfree(db_path);
+ return (DICT_DEBUG (&dict_db->dict));
+}
+
+/* dict_hash_open - create association with data base */
+
+DICT *dict_hash_open(const char *path, int open_flags, int dict_flags)
+{
+#if DB_VERSION_MAJOR < 2
+ HASHINFO tweak;
+
+ memset((void *) &tweak, 0, sizeof(tweak));
+ tweak.nelem = DICT_DB_NELM;
+ tweak.cachesize = dict_db_cache_size;
+#endif
+#if DB_VERSION_MAJOR == 2
+ DB_INFO tweak;
+
+ memset((void *) &tweak, 0, sizeof(tweak));
+ tweak.h_nelem = DICT_DB_NELM;
+ tweak.db_cachesize = dict_db_cache_size;
+#endif
+#if DB_VERSION_MAJOR > 2
+ void *tweak;
+
+ tweak = 0;
+#endif
+ return (dict_db_open(DICT_TYPE_HASH, path, open_flags, DB_HASH,
+ (void *) &tweak, dict_flags));
+}
+
+/* dict_btree_open - create association with data base */
+
+DICT *dict_btree_open(const char *path, int open_flags, int dict_flags)
+{
+#if DB_VERSION_MAJOR < 2
+ BTREEINFO tweak;
+
+ memset((void *) &tweak, 0, sizeof(tweak));
+ tweak.cachesize = dict_db_cache_size;
+#endif
+#if DB_VERSION_MAJOR == 2
+ DB_INFO tweak;
+
+ memset((void *) &tweak, 0, sizeof(tweak));
+ tweak.db_cachesize = dict_db_cache_size;
+#endif
+#if DB_VERSION_MAJOR > 2
+ void *tweak;
+
+ tweak = 0;
+#endif
+
+ return (dict_db_open(DICT_TYPE_BTREE, path, open_flags, DB_BTREE,
+ (void *) &tweak, dict_flags));
+}
+
+#endif
diff --git a/src/util/dict_db.h b/src/util/dict_db.h
new file mode 100644
index 0000000..0030f73
--- /dev/null
+++ b/src/util/dict_db.h
@@ -0,0 +1,58 @@
+#ifndef _DICT_DB_H_INCLUDED_
+#define _DICT_DB_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_db 3h
+/* SUMMARY
+/* dictionary manager interface to DB files
+/* SYNOPSIS
+/* #include <dict_db.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+#include <mkmap.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_HASH "hash"
+#define DICT_TYPE_BTREE "btree"
+
+extern DICT *dict_hash_open(const char *, int, int);
+extern DICT *dict_btree_open(const char *, int, int);
+extern MKMAP *mkmap_hash_open(const char *);
+extern MKMAP *mkmap_btree_open(const char *);
+
+ /*
+ * XXX Should be part of the DICT interface.
+ *
+ * You can override the default dict_db_cache_size setting before calling
+ * dict_hash_open() or dict_btree_open(). This is done in mkmap_db_open() to
+ * set a larger memory pool for database (re)builds.
+ */
+extern int dict_db_cache_size;
+
+#define DEFINE_DICT_DB_CACHE_SIZE int dict_db_cache_size = (128 * 1024)
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/dict_dbm.c b/src/util/dict_dbm.c
new file mode 100644
index 0000000..e47b7ee
--- /dev/null
+++ b/src/util/dict_dbm.c
@@ -0,0 +1,503 @@
+/*++
+/* NAME
+/* dict_dbm 3
+/* SUMMARY
+/* dictionary manager interface to DBM files
+/* SYNOPSIS
+/* #include <dict_dbm.h>
+/*
+/* DICT *dict_dbm_open(path, open_flags, dict_flags)
+/* const char *name;
+/* const char *path;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_dbm_open() opens the named DBM database and makes it available
+/* via the generic interface described in dict_open(3).
+/* DIAGNOSTICS
+/* Fatal errors: cannot open file, file write error, out of memory.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* ndbm(3) data base subroutines
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#include "sys_defs.h"
+
+#ifdef HAS_DBM
+
+/* System library. */
+
+#include <sys/stat.h>
+#ifdef PATH_NDBM_H
+#include PATH_NDBM_H
+#else
+#include <ndbm.h>
+#endif
+#ifdef R_FIRST
+#error "Error: you are including the Berkeley DB version of ndbm.h"
+#error "To build with Postfix NDBM support, delete the Berkeley DB ndbm.h file"
+#endif
+#include <string.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "mymalloc.h"
+#include "htable.h"
+#include "iostuff.h"
+#include "vstring.h"
+#include "myflock.h"
+#include "stringops.h"
+#include "dict.h"
+#include "dict_dbm.h"
+#include "warn_stat.h"
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ DBM *dbm; /* open database */
+ VSTRING *key_buf; /* key buffer */
+ VSTRING *val_buf; /* result buffer */
+} DICT_DBM;
+
+#define SCOPY(buf, data, size) \
+ vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size))
+
+/* dict_dbm_lookup - find database entry */
+
+static const char *dict_dbm_lookup(DICT *dict, const char *name)
+{
+ DICT_DBM *dict_dbm = (DICT_DBM *) dict;
+ datum dbm_key;
+ datum dbm_value;
+ const char *result = 0;
+
+ dict->error = 0;
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_dbm_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+
+ /*
+ * Acquire an exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name);
+
+ /*
+ * See if this DBM file was written with one null byte appended to key
+ * and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ dbm_key.dptr = (void *) name;
+ dbm_key.dsize = strlen(name) + 1;
+ dbm_value = dbm_fetch(dict_dbm->dbm, dbm_key);
+ if (dbm_value.dptr != 0) {
+ dict->flags &= ~DICT_FLAG_TRY0NULL;
+ result = SCOPY(dict_dbm->val_buf, dbm_value.dptr, dbm_value.dsize);
+ }
+ }
+
+ /*
+ * See if this DBM file was written with no null byte appended to key and
+ * value.
+ */
+ if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
+ dbm_key.dptr = (void *) name;
+ dbm_key.dsize = strlen(name);
+ dbm_value = dbm_fetch(dict_dbm->dbm, dbm_key);
+ if (dbm_value.dptr != 0) {
+ dict->flags &= ~DICT_FLAG_TRY1NULL;
+ result = SCOPY(dict_dbm->val_buf, dbm_value.dptr, dbm_value.dsize);
+ }
+ }
+
+ /*
+ * Release the exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name);
+
+ return (result);
+}
+
+/* dict_dbm_update - add or update database entry */
+
+static int dict_dbm_update(DICT *dict, const char *name, const char *value)
+{
+ DICT_DBM *dict_dbm = (DICT_DBM *) dict;
+ datum dbm_key;
+ datum dbm_value;
+ int status;
+
+ dict->error = 0;
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_dbm_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+ dbm_key.dptr = (void *) name;
+ dbm_value.dptr = (void *) value;
+ dbm_key.dsize = strlen(name);
+ dbm_value.dsize = strlen(value);
+
+ /*
+ * If undecided about appending a null byte to key and value, choose a
+ * default depending on the platform.
+ */
+ if ((dict->flags & DICT_FLAG_TRY1NULL)
+ && (dict->flags & DICT_FLAG_TRY0NULL)) {
+#ifdef DBM_NO_TRAILING_NULL
+ dict->flags &= ~DICT_FLAG_TRY1NULL;
+#else
+ dict->flags &= ~DICT_FLAG_TRY0NULL;
+#endif
+ }
+
+ /*
+ * Optionally append a null byte to key and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ dbm_key.dsize++;
+ dbm_value.dsize++;
+ }
+
+ /*
+ * Acquire an exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name);
+
+ /*
+ * Do the update.
+ */
+ if ((status = dbm_store(dict_dbm->dbm, dbm_key, dbm_value,
+ (dict->flags & DICT_FLAG_DUP_REPLACE) ? DBM_REPLACE : DBM_INSERT)) < 0)
+ msg_fatal("error writing DBM database %s: %m", dict_dbm->dict.name);
+ if (status) {
+ if (dict->flags & DICT_FLAG_DUP_IGNORE)
+ /* void */ ;
+ else if (dict->flags & DICT_FLAG_DUP_WARN)
+ msg_warn("%s: duplicate entry: \"%s\"", dict_dbm->dict.name, name);
+ else
+ msg_fatal("%s: duplicate entry: \"%s\"", dict_dbm->dict.name, name);
+ }
+
+ /*
+ * Release the exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name);
+
+ return (status);
+}
+
+/* dict_dbm_delete - delete one entry from the dictionary */
+
+static int dict_dbm_delete(DICT *dict, const char *name)
+{
+ DICT_DBM *dict_dbm = (DICT_DBM *) dict;
+ datum dbm_key;
+ int status = 1;
+
+ dict->error = 0;
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_dbm_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+
+ /*
+ * Acquire an exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name);
+
+ /*
+ * See if this DBM file was written with one null byte appended to key
+ * and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ dbm_key.dptr = (void *) name;
+ dbm_key.dsize = strlen(name) + 1;
+ dbm_clearerr(dict_dbm->dbm);
+ if ((status = dbm_delete(dict_dbm->dbm, dbm_key)) < 0) {
+ if (dbm_error(dict_dbm->dbm) != 0) /* fatal error */
+ msg_fatal("error deleting from %s: %m", dict_dbm->dict.name);
+ status = 1; /* not found */
+ } else {
+ dict->flags &= ~DICT_FLAG_TRY0NULL; /* found */
+ }
+ }
+
+ /*
+ * See if this DBM file was written with no null byte appended to key and
+ * value.
+ */
+ if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
+ dbm_key.dptr = (void *) name;
+ dbm_key.dsize = strlen(name);
+ dbm_clearerr(dict_dbm->dbm);
+ if ((status = dbm_delete(dict_dbm->dbm, dbm_key)) < 0) {
+ if (dbm_error(dict_dbm->dbm) != 0) /* fatal error */
+ msg_fatal("error deleting from %s: %m", dict_dbm->dict.name);
+ status = 1; /* not found */
+ } else {
+ dict->flags &= ~DICT_FLAG_TRY1NULL; /* found */
+ }
+ }
+
+ /*
+ * Release the exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name);
+
+ return (status);
+}
+
+/* traverse the dictionary */
+
+static int dict_dbm_sequence(DICT *dict, int function,
+ const char **key, const char **value)
+{
+ const char *myname = "dict_dbm_sequence";
+ DICT_DBM *dict_dbm = (DICT_DBM *) dict;
+ datum dbm_key;
+ datum dbm_value;
+ int status;
+
+ dict->error = 0;
+
+ /*
+ * Acquire a shared lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name);
+
+ /*
+ * Determine and execute the seek function. It returns the key.
+ */
+ switch (function) {
+ case DICT_SEQ_FUN_FIRST:
+ dbm_key = dbm_firstkey(dict_dbm->dbm);
+ break;
+ case DICT_SEQ_FUN_NEXT:
+ dbm_key = dbm_nextkey(dict_dbm->dbm);
+ break;
+ default:
+ msg_panic("%s: invalid function: %d", myname, function);
+ }
+
+ if (dbm_key.dptr != 0 && dbm_key.dsize > 0) {
+
+ /*
+ * Copy the key so that it is guaranteed null terminated.
+ */
+ *key = SCOPY(dict_dbm->key_buf, dbm_key.dptr, dbm_key.dsize);
+
+ /*
+ * Fetch the corresponding value.
+ */
+ dbm_value = dbm_fetch(dict_dbm->dbm, dbm_key);
+
+ if (dbm_value.dptr != 0 && dbm_value.dsize > 0) {
+
+ /*
+ * Copy the value so that it is guaranteed null terminated.
+ */
+ *value = SCOPY(dict_dbm->val_buf, dbm_value.dptr, dbm_value.dsize);
+ status = 0;
+ } else {
+
+ /*
+ * Determine if we have hit the last record or an error
+ * condition.
+ */
+ if (dbm_error(dict_dbm->dbm))
+ msg_fatal("error seeking %s: %m", dict_dbm->dict.name);
+ status = 1; /* no error: eof/not found
+ * (should not happen!) */
+ }
+ } else {
+
+ /*
+ * Determine if we have hit the last record or an error condition.
+ */
+ if (dbm_error(dict_dbm->dbm))
+ msg_fatal("error seeking %s: %m", dict_dbm->dict.name);
+ status = 1; /* no error: eof/not found */
+ }
+
+ /*
+ * Release the shared lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name);
+
+ return (status);
+}
+
+/* dict_dbm_close - disassociate from data base */
+
+static void dict_dbm_close(DICT *dict)
+{
+ DICT_DBM *dict_dbm = (DICT_DBM *) dict;
+
+ dbm_close(dict_dbm->dbm);
+ if (dict_dbm->key_buf)
+ vstring_free(dict_dbm->key_buf);
+ if (dict_dbm->val_buf)
+ vstring_free(dict_dbm->val_buf);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_dbm_open - open DBM data base */
+
+DICT *dict_dbm_open(const char *path, int open_flags, int dict_flags)
+{
+ DICT_DBM *dict_dbm;
+ struct stat st;
+ DBM *dbm;
+ char *dbm_path = 0;
+ int lock_fd;
+
+ /*
+ * Let the optimizer worry about eliminating redundant code.
+ */
+#define DICT_DBM_OPEN_RETURN(d) do { \
+ DICT *__d = (d); \
+ if (dbm_path != 0) \
+ myfree(dbm_path); \
+ return (__d); \
+ } while (0)
+
+ /*
+ * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in
+ * the time domain) locking while accessing individual database records.
+ *
+ * Programs such as postmap/postalias use their own large-grained (in the
+ * time domain) locks while rewriting the entire file.
+ */
+ if (dict_flags & DICT_FLAG_LOCK) {
+ dbm_path = concatenate(path, ".dir", (char *) 0);
+ if ((lock_fd = open(dbm_path, open_flags, 0644)) < 0)
+ DICT_DBM_OPEN_RETURN(dict_surrogate(DICT_TYPE_DBM, path,
+ open_flags, dict_flags,
+ "open database %s: %m",
+ dbm_path));
+ if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
+ msg_fatal("shared-lock database %s for open: %m", dbm_path);
+ }
+
+ /*
+ * XXX SunOS 5.x has no const in dbm_open() prototype.
+ */
+ if ((dbm = dbm_open((char *) path, open_flags, 0644)) == 0)
+ DICT_DBM_OPEN_RETURN(dict_surrogate(DICT_TYPE_DBM, path,
+ open_flags, dict_flags,
+ "open database %s.{dir,pag}: %m",
+ path));
+
+ if (dict_flags & DICT_FLAG_LOCK) {
+ if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("unlock database %s for open: %m", dbm_path);
+ if (close(lock_fd) < 0)
+ msg_fatal("close database %s: %m", dbm_path);
+ }
+ dict_dbm = (DICT_DBM *) dict_alloc(DICT_TYPE_DBM, path, sizeof(*dict_dbm));
+ dict_dbm->dict.lookup = dict_dbm_lookup;
+ dict_dbm->dict.update = dict_dbm_update;
+ dict_dbm->dict.delete = dict_dbm_delete;
+ dict_dbm->dict.sequence = dict_dbm_sequence;
+ dict_dbm->dict.close = dict_dbm_close;
+ dict_dbm->dict.lock_fd = dbm_dirfno(dbm);
+ dict_dbm->dict.stat_fd = dbm_pagfno(dbm);
+ if (dict_dbm->dict.lock_fd == dict_dbm->dict.stat_fd)
+ msg_fatal("open database %s: cannot support GDBM", path);
+ if (fstat(dict_dbm->dict.stat_fd, &st) < 0)
+ msg_fatal("dict_dbm_open: fstat: %m");
+ dict_dbm->dict.mtime = st.st_mtime;
+ dict_dbm->dict.owner.uid = st.st_uid;
+ dict_dbm->dict.owner.status = (st.st_uid != 0);
+
+ /*
+ * Warn if the source file is newer than the indexed file, except when
+ * the source file changed only seconds ago.
+ */
+ if ((dict_flags & DICT_FLAG_LOCK) != 0
+ && stat(path, &st) == 0
+ && st.st_mtime > dict_dbm->dict.mtime
+ && st.st_mtime < time((time_t *) 0) - 100)
+ msg_warn("database %s is older than source file %s", dbm_path, path);
+
+ close_on_exec(dbm_pagfno(dbm), CLOSE_ON_EXEC);
+ close_on_exec(dbm_dirfno(dbm), CLOSE_ON_EXEC);
+ dict_dbm->dict.flags = dict_flags | DICT_FLAG_FIXED;
+ if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0)
+ dict_dbm->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL);
+ if (dict_flags & DICT_FLAG_FOLD_FIX)
+ dict_dbm->dict.fold_buf = vstring_alloc(10);
+ dict_dbm->dbm = dbm;
+ dict_dbm->key_buf = 0;
+ dict_dbm->val_buf = 0;
+
+ DICT_DBM_OPEN_RETURN(DICT_DEBUG (&dict_dbm->dict));
+}
+
+#endif
diff --git a/src/util/dict_dbm.h b/src/util/dict_dbm.h
new file mode 100644
index 0000000..f954255
--- /dev/null
+++ b/src/util/dict_dbm.h
@@ -0,0 +1,44 @@
+#ifndef _DICT_DBM_H_INCLUDED_
+#define _DICT_DBM_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_dbm 3h
+/* SUMMARY
+/* dictionary manager interface to DBM files
+/* SYNOPSIS
+/* #include <dict_dbm.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+#include <mkmap.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_DBM "dbm"
+
+extern DICT *dict_dbm_open(const char *, int, int);
+extern MKMAP *mkmap_dbm_open(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/dict_debug.c b/src/util/dict_debug.c
new file mode 100644
index 0000000..46634d4
--- /dev/null
+++ b/src/util/dict_debug.c
@@ -0,0 +1,150 @@
+/*++
+/* NAME
+/* dict_debug 3
+/* SUMMARY
+/* dictionary manager, logging proxy
+/* SYNOPSIS
+/* #include <dict.h>
+/*
+/* DICT *dict_debug(dict_handle)
+/* DICT *dict_handle;
+/*
+/* DICT *DICT_DEBUG(dict_handle)
+/* DICT *dict_handle;
+/* DESCRIPTION
+/* dict_debug() encapsulates the given dictionary object and returns
+/* a proxy object that logs all access to the encapsulated object.
+/* This is more convenient than having to add logging capability
+/* to each individual dictionary access method.
+/*
+/* DICT_DEBUG() is an unsafe macro that returns the original object if
+/* the object's debugging flag is not set, and that otherwise encapsulates
+/* the object with dict_debug(). This macro simplifies usage by avoiding
+/* clumsy expressions. The macro evaluates its argument multiple times.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System libraries. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <dict.h>
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* the proxy service */
+ DICT *real_dict; /* encapsulated object */
+} DICT_DEBUG;
+
+/* dict_debug_lookup - log lookup operation */
+
+static const char *dict_debug_lookup(DICT *dict, const char *key)
+{
+ DICT_DEBUG *dict_debug = (DICT_DEBUG *) dict;
+ DICT *real_dict = dict_debug->real_dict;
+ const char *result;
+
+ real_dict->flags = dict->flags;
+ result = dict_get(real_dict, key);
+ dict->flags = real_dict->flags;
+ msg_info("%s:%s lookup: \"%s\" = \"%s\"", dict->type, dict->name, key,
+ result ? result : real_dict->error ? "error" : "not_found");
+ DICT_ERR_VAL_RETURN(dict, real_dict->error, result);
+}
+
+/* dict_debug_update - log update operation */
+
+static int dict_debug_update(DICT *dict, const char *key, const char *value)
+{
+ DICT_DEBUG *dict_debug = (DICT_DEBUG *) dict;
+ DICT *real_dict = dict_debug->real_dict;
+ int result;
+
+ real_dict->flags = dict->flags;
+ result = dict_put(real_dict, key, value);
+ dict->flags = real_dict->flags;
+ msg_info("%s:%s update: \"%s\" = \"%s\": %s", dict->type, dict->name,
+ key, value, result == 0 ? "success" : real_dict->error ?
+ "error" : "failed");
+ DICT_ERR_VAL_RETURN(dict, real_dict->error, result);
+}
+
+/* dict_debug_delete - log delete operation */
+
+static int dict_debug_delete(DICT *dict, const char *key)
+{
+ DICT_DEBUG *dict_debug = (DICT_DEBUG *) dict;
+ DICT *real_dict = dict_debug->real_dict;
+ int result;
+
+ real_dict->flags = dict->flags;
+ result = dict_del(real_dict, key);
+ dict->flags = real_dict->flags;
+ msg_info("%s:%s delete: \"%s\": %s", dict->type, dict->name, key,
+ result == 0 ? "success" : real_dict->error ?
+ "error" : "failed");
+ DICT_ERR_VAL_RETURN(dict, real_dict->error, result);
+}
+
+/* dict_debug_sequence - log sequence operation */
+
+static int dict_debug_sequence(DICT *dict, int function,
+ const char **key, const char **value)
+{
+ DICT_DEBUG *dict_debug = (DICT_DEBUG *) dict;
+ DICT *real_dict = dict_debug->real_dict;
+ int result;
+
+ real_dict->flags = dict->flags;
+ result = dict_seq(real_dict, function, key, value);
+ dict->flags = real_dict->flags;
+ if (result == 0)
+ msg_info("%s:%s sequence: \"%s\" = \"%s\"", dict->type, dict->name,
+ *key, *value);
+ else
+ msg_info("%s:%s sequence: found EOF", dict->type, dict->name);
+ DICT_ERR_VAL_RETURN(dict, real_dict->error, result);
+}
+
+/* dict_debug_close - log operation */
+
+static void dict_debug_close(DICT *dict)
+{
+ DICT_DEBUG *dict_debug = (DICT_DEBUG *) dict;
+
+ dict_close(dict_debug->real_dict);
+ dict_free(dict);
+}
+
+/* dict_debug - encapsulate dictionary object and install proxies */
+
+DICT *dict_debug(DICT *real_dict)
+{
+ DICT_DEBUG *dict_debug;
+
+ dict_debug = (DICT_DEBUG *) dict_alloc(real_dict->type,
+ real_dict->name, sizeof(*dict_debug));
+ dict_debug->dict.flags = real_dict->flags; /* XXX not synchronized */
+ dict_debug->dict.lookup = dict_debug_lookup;
+ dict_debug->dict.update = dict_debug_update;
+ dict_debug->dict.delete = dict_debug_delete;
+ dict_debug->dict.sequence = dict_debug_sequence;
+ dict_debug->dict.close = dict_debug_close;
+ dict_debug->real_dict = real_dict;
+ return (&dict_debug->dict);
+}
diff --git a/src/util/dict_env.c b/src/util/dict_env.c
new file mode 100644
index 0000000..1635b8d
--- /dev/null
+++ b/src/util/dict_env.c
@@ -0,0 +1,112 @@
+/*++
+/* NAME
+/* dict_env 3
+/* SUMMARY
+/* dictionary manager interface to environment variables
+/* SYNOPSIS
+/* #include <dict_env.h>
+/*
+/* DICT *dict_env_open(name, dummy, dict_flags)
+/* const char *name;
+/* int dummy;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_env_open() opens the environment variable array and
+/* makes it accessible via the generic operations documented
+/* in dict_open(3). The \fIname\fR and \fIdummy\fR arguments
+/* are ignored.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* safe_getenv(3) safe getenv() interface
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <stdio.h> /* sprintf() prototype */
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include "mymalloc.h"
+#include "msg.h"
+#include "safe.h"
+#include "stringops.h"
+#include "dict.h"
+#include "dict_env.h"
+
+/* dict_env_update - update environment array */
+
+static int dict_env_update(DICT *dict, const char *name, const char *value)
+{
+ dict->error = 0;
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+ if (setenv(name, value, 1))
+ msg_fatal("setenv: %m");
+
+ return (DICT_STAT_SUCCESS);
+}
+
+/* dict_env_lookup - access environment array */
+
+static const char *dict_env_lookup(DICT *dict, const char *name)
+{
+ dict->error = 0;
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+ return (safe_getenv(name));
+}
+
+/* dict_env_close - close environment dictionary */
+
+static void dict_env_close(DICT *dict)
+{
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_env_open - make association with environment array */
+
+DICT *dict_env_open(const char *name, int unused_flags, int dict_flags)
+{
+ DICT *dict;
+
+ dict = dict_alloc(DICT_TYPE_ENVIRON, name, sizeof(*dict));
+ dict->lookup = dict_env_lookup;
+ dict->update = dict_env_update;
+ dict->close = dict_env_close;
+ dict->flags = dict_flags | DICT_FLAG_FIXED;
+ if (dict_flags & DICT_FLAG_FOLD_FIX)
+ dict->fold_buf = vstring_alloc(10);
+ dict->owner.status = DICT_OWNER_TRUSTED;
+ return (DICT_DEBUG (dict));
+}
diff --git a/src/util/dict_env.h b/src/util/dict_env.h
new file mode 100644
index 0000000..9abfa4f
--- /dev/null
+++ b/src/util/dict_env.h
@@ -0,0 +1,37 @@
+#ifndef _DICT_ENV_H_INCLUDED_
+#define _DICT_ENV_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_env 3h
+/* SUMMARY
+/* dictionary manager interface to environment variables
+/* SYNOPSIS
+/* #include <dict_env.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_ENVIRON "environ"
+
+extern DICT *dict_env_open(const char *, int, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/dict_fail.c b/src/util/dict_fail.c
new file mode 100644
index 0000000..c8d9a19
--- /dev/null
+++ b/src/util/dict_fail.c
@@ -0,0 +1,115 @@
+/*++
+/* NAME
+/* dict_fail 3
+/* SUMMARY
+/* dictionary manager interface to 'always fail' table
+/* SYNOPSIS
+/* #include <dict_fail.h>
+/*
+/* DICT *dict_fail_open(name, open_flags, dict_flags)
+/* const char *name;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_fail_open() implements a dummy dictionary that fails
+/* all operations. The name can be used for logging.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <msg.h>
+#include <dict.h>
+#include <dict_fail.h>
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ int dict_errno; /* fixed error result */
+} DICT_FAIL;
+
+/* dict_fail_sequence - fail lookup */
+
+static int dict_fail_sequence(DICT *dict, int unused_func,
+ const char **key, const char **value)
+{
+ DICT_FAIL *dp = (DICT_FAIL *) dict;
+
+ errno = 0;
+ DICT_ERR_VAL_RETURN(dict, dp->dict_errno, DICT_STAT_ERROR);
+}
+
+/* dict_fail_update - fail lookup */
+
+static int dict_fail_update(DICT *dict, const char *unused_name,
+ const char *unused_value)
+{
+ DICT_FAIL *dp = (DICT_FAIL *) dict;
+
+ errno = 0;
+ DICT_ERR_VAL_RETURN(dict, dp->dict_errno, DICT_STAT_ERROR);
+}
+
+/* dict_fail_lookup - fail lookup */
+
+static const char *dict_fail_lookup(DICT *dict, const char *unused_name)
+{
+ DICT_FAIL *dp = (DICT_FAIL *) dict;
+
+ errno = 0;
+ DICT_ERR_VAL_RETURN(dict, dp->dict_errno, (char *) 0);
+}
+
+/* dict_fail_delete - fail delete */
+
+static int dict_fail_delete(DICT *dict, const char *unused_name)
+{
+ DICT_FAIL *dp = (DICT_FAIL *) dict;
+
+ errno = 0;
+ DICT_ERR_VAL_RETURN(dict, dp->dict_errno, DICT_STAT_ERROR);
+}
+
+/* dict_fail_close - close fail dictionary */
+
+static void dict_fail_close(DICT *dict)
+{
+ dict_free(dict);
+}
+
+/* dict_fail_open - make association with fail variable */
+
+DICT *dict_fail_open(const char *name, int open_flags, int dict_flags)
+{
+ DICT_FAIL *dp;
+
+ dp = (DICT_FAIL *) dict_alloc(DICT_TYPE_FAIL, name, sizeof(*dp));
+ dp->dict.lookup = dict_fail_lookup;
+ if (open_flags & O_RDWR) {
+ dp->dict.update = dict_fail_update;
+ dp->dict.delete = dict_fail_delete;
+ }
+ dp->dict.sequence = dict_fail_sequence;
+ dp->dict.close = dict_fail_close;
+ dp->dict.flags = dict_flags | DICT_FLAG_PATTERN;
+ dp->dict_errno = DICT_ERR_RETRY;
+ dp->dict.owner.status = DICT_OWNER_TRUSTED;
+ return (DICT_DEBUG (&dp->dict));
+}
diff --git a/src/util/dict_fail.h b/src/util/dict_fail.h
new file mode 100644
index 0000000..046ff68
--- /dev/null
+++ b/src/util/dict_fail.h
@@ -0,0 +1,42 @@
+#ifndef _DICT_FAIL_H_INCLUDED_
+#define _DICT_FAIL_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_fail 3h
+/* SUMMARY
+/* dictionary manager interface to 'always fail' table
+/* SYNOPSIS
+/* #include <dict_fail.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+#include <mkmap.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_FAIL "fail"
+
+extern DICT *dict_fail_open(const char *, int, int);
+extern MKMAP *mkmap_fail_open(const char *);
+
+/* 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
+/*--*/
+
+#endif
diff --git a/src/util/dict_file.c b/src/util/dict_file.c
new file mode 100644
index 0000000..c6ea74c
--- /dev/null
+++ b/src/util/dict_file.c
@@ -0,0 +1,231 @@
+/*++
+/* NAME
+/* dict_file_to_buf 3
+/* SUMMARY
+/* include content from file as blob
+/* SYNOPSIS
+/* #include <dict.h>
+/*
+/* VSTRING *dict_file_to_buf(
+/* DICT *dict,
+/* const char *pathnames)
+/*
+/* VSTRING *dict_file_to_b64(
+/* DICT *dict,
+/* const char *pathnames)
+/*
+/* VSTRING *dict_file_from_b64(
+/* DICT *dict,
+/* const char *value)
+/*
+/* char *dict_file_get_error(
+/* DICT *dict)
+/*
+/* void dict_file_purge_buffers(
+/* DICT *dict)
+/*
+/* const char *dict_file_lookup(
+/* DICT *dict)
+/* DESCRIPTION
+/* dict_file_to_buf() reads the content of the specified files,
+/* with names separated by CHARS_COMMA_SP, while inserting a
+/* gratuitous newline character between files. It returns a
+/* pointer to a buffer which is owned by the DICT, or a null
+/* pointer in case of error.
+/*
+/* dict_file_to_b64() invokes dict_file_to_buf() and converts
+/* the result to base64. It returns a pointer to a buffer which
+/* is owned by the DICT, or a null pointer in case of error.
+/*
+/* dict_file_from_b64() converts a value from base64. It returns
+/* a pointer to a buffer which is owned by the DICT, or a null
+/* pointer in case of error.
+/*
+/* dict_file_purge_buffers() disposes of dict_file-related
+/* memory that are associated with this DICT.
+/*
+/* dict_file_get_error() should be called only after error;
+/* it returns a description of the problem. Storage is owned
+/* by the caller.
+/*
+/* dict_file_lookup() wraps the dictionary lookup method and
+/* decodes the base64 lookup result. The dictionary must be
+/* opened with DICT_FLAG_SRC_RHS_IS_FILE. Sets dict->error to
+/* DICT_ERR_CONFIG if the content is invalid. Decoding is not
+/* built into the dict->lookup() method, because that would
+/* complicate the implementation of map nesting (inline, thash),
+/* map composition (pipemap, unionmap), and map proxying.
+/* DIAGNOSTICS
+/* Panic: interface violation.
+/*
+/* In case of error the VSTRING result value is a null pointer, and
+/* an error description can be retrieved with dict_file_get_error().
+/* The storage is owned by the caller.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <string.h>
+
+ /*
+ * Utility library.
+ */
+#include <base64_code.h>
+#include <dict.h>
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* dict_file_to_buf - read files into a buffer */
+
+VSTRING *dict_file_to_buf(DICT *dict, const char *pathnames)
+{
+ struct stat st;
+ VSTREAM *fp = 0;
+ ARGV *argv;
+ char **cpp;
+
+ /* dict_file_to_buf() postcondition: dict->file_buf exists. */
+ if (dict->file_buf == 0)
+ dict->file_buf = vstring_alloc(100);
+
+#define DICT_FILE_RETURN(retval) do { \
+ argv_free(argv); \
+ if (fp) vstream_fclose(fp); \
+ return (retval); \
+ } while (0);
+
+ argv = argv_split(pathnames, CHARS_COMMA_SP);
+ if (argv->argc == 0) {
+ vstring_sprintf(dict->file_buf, "empty pathname list: >>%s<<'",
+ pathnames);
+ DICT_FILE_RETURN(0);
+ }
+ VSTRING_RESET(dict->file_buf);
+ for (cpp = argv->argv; *cpp; cpp++) {
+ if ((fp = vstream_fopen(*cpp, O_RDONLY, 0)) == 0
+ || fstat(vstream_fileno(fp), &st) < 0) {
+ vstring_sprintf(dict->file_buf, "open %s: %m", *cpp);
+ DICT_FILE_RETURN(0);
+ }
+ if (st.st_size > SSIZE_T_MAX - LEN(dict->file_buf)) {
+ vstring_sprintf(dict->file_buf, "file too large: %s", pathnames);
+ DICT_FILE_RETURN(0);
+ }
+ if (vstream_fread_app(fp, dict->file_buf, st.st_size) != st.st_size) {
+ vstring_sprintf(dict->file_buf, "read %s: %m", *cpp);
+ DICT_FILE_RETURN(0);
+ }
+ (void) vstream_fclose(fp);
+ fp = 0;
+ if (cpp[1] != 0)
+ VSTRING_ADDCH(dict->file_buf, '\n');
+ }
+ VSTRING_TERMINATE(dict->file_buf);
+ DICT_FILE_RETURN(dict->file_buf);
+}
+
+/* dict_file_to_b64 - read files into a base64-encoded buffer */
+
+VSTRING *dict_file_to_b64(DICT *dict, const char *pathnames)
+{
+ ssize_t helper;
+
+ if (dict_file_to_buf(dict, pathnames) == 0)
+ return (0);
+ if (dict->file_b64 == 0)
+ dict->file_b64 = vstring_alloc(100);
+ helper = (LEN(dict->file_buf) + 2) / 3;
+ if (helper > SSIZE_T_MAX / 4) {
+ vstring_sprintf(dict->file_buf, "file too large: %s", pathnames);
+ return (0);
+ }
+ VSTRING_RESET(dict->file_b64);
+ VSTRING_SPACE(dict->file_b64, helper * 4);
+ return (base64_encode(dict->file_b64, STR(dict->file_buf),
+ LEN(dict->file_buf)));
+}
+
+/* dict_file_from_b64 - convert value from base64 */
+
+VSTRING *dict_file_from_b64(DICT *dict, const char *value)
+{
+ ssize_t helper;
+ VSTRING *result;
+
+ if (dict->file_buf == 0)
+ dict->file_buf = vstring_alloc(100);
+ helper = strlen(value) / 4;
+ VSTRING_RESET(dict->file_buf);
+ VSTRING_SPACE(dict->file_buf, helper * 3);
+ result = base64_decode(dict->file_buf, value, strlen(value));
+ if (result == 0)
+ vstring_sprintf(dict->file_buf, "malformed BASE64 value: %.30s", value);
+ return (result);
+}
+
+/* dict_file_get_error - return error text */
+
+char *dict_file_get_error(DICT *dict)
+{
+ if (dict->file_buf == 0)
+ msg_panic("dict_file_get_error: no buffer");
+ return (mystrdup(STR(dict->file_buf)));
+}
+
+/* dict_file_purge_buffers - purge file buffers */
+
+void dict_file_purge_buffers(DICT *dict)
+{
+ if (dict->file_buf) {
+ vstring_free(dict->file_buf);
+ dict->file_buf = 0;
+ }
+ if (dict->file_b64) {
+ vstring_free(dict->file_b64);
+ dict->file_b64 = 0;
+ }
+}
+
+/* dict_file_lookup - look up and decode dictionary entry */
+
+const char *dict_file_lookup(DICT *dict, const char *key)
+{
+ const char myname[] = "dict_file_lookup";
+ const char *res;
+ VSTRING *unb64;
+ char *err;
+
+ if ((dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) == 0)
+ msg_panic("%s: dictionary opened without DICT_FLAG_SRC_RHS_IS_FILE",
+ myname);
+ if ((res = dict->lookup(dict, key)) == 0)
+ return (0);
+ if ((unb64 = dict_file_from_b64(dict, res)) == 0) {
+ err = dict_file_get_error(dict);
+ msg_warn("table %s:%s: key %s: %s", dict->type, dict->name, key, err);
+ myfree(err);
+ dict->error = DICT_ERR_CONFIG;
+ return (0);
+ }
+ return STR(unb64);
+}
diff --git a/src/util/dict_ht.c b/src/util/dict_ht.c
new file mode 100644
index 0000000..74020d5
--- /dev/null
+++ b/src/util/dict_ht.c
@@ -0,0 +1,171 @@
+/*++
+/* NAME
+/* dict_ht 3
+/* SUMMARY
+/* dictionary manager interface to hash tables
+/* SYNOPSIS
+/* #include <dict_ht.h>
+/*
+/* DICT *dict_ht_open(name, open_flags, dict_flags)
+/* const char *name;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_ht_open() creates a memory-resident hash table and
+/* makes it accessible via the generic dictionary operations
+/* documented in dict_open(3). The open_flags argument is
+/* ignored.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+
+/* Utility library. */
+
+#include "mymalloc.h"
+#include "htable.h"
+#include "dict.h"
+#include "dict_ht.h"
+#include "stringops.h"
+#include "vstring.h"
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ HTABLE *table; /* hash table */
+} DICT_HT;
+
+/* dict_ht_delete - delete hash-table entry */
+
+static int dict_ht_delete(DICT *dict, const char *name)
+{
+ DICT_HT *dict_ht = (DICT_HT *) dict;
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+ if (htable_locate(dict_ht->table, name) == 0) {
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_FAIL);
+ } else {
+ htable_delete(dict_ht->table, name, myfree);
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_SUCCESS);
+ }
+}
+
+/* dict_ht_lookup - find hash-table entry */
+
+static const char *dict_ht_lookup(DICT *dict, const char *name)
+{
+ DICT_HT *dict_ht = (DICT_HT *) dict;
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, htable_find(dict_ht->table, name));
+}
+
+/* dict_ht_update - add or update hash-table entry */
+
+static int dict_ht_update(DICT *dict, const char *name, const char *value)
+{
+ DICT_HT *dict_ht = (DICT_HT *) dict;
+ HTABLE_INFO *ht;
+ char *saved_value = mystrdup(value);
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+ if ((ht = htable_locate(dict_ht->table, name)) != 0) {
+ myfree(ht->value);
+ } else {
+ ht = htable_enter(dict_ht->table, name, (void *) 0);
+ }
+ ht->value = saved_value;
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_SUCCESS);
+}
+
+/* dict_ht_sequence - first/next iterator */
+
+static int dict_ht_sequence(DICT *dict, int how, const char **name,
+ const char **value)
+{
+ DICT_HT *dict_ht = (DICT_HT *) dict;
+ HTABLE_INFO *ht;
+
+ ht = htable_sequence(dict_ht->table,
+ how == DICT_SEQ_FUN_FIRST ? HTABLE_SEQ_FIRST :
+ how == DICT_SEQ_FUN_NEXT ? HTABLE_SEQ_NEXT :
+ HTABLE_SEQ_STOP);
+ if (ht != 0) {
+ *name = ht->key;
+ *value = ht->value;
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_SUCCESS);
+ } else {
+ *name = 0;
+ *value = 0;
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_FAIL);
+ }
+}
+
+/* dict_ht_close - disassociate from hash table */
+
+static void dict_ht_close(DICT *dict)
+{
+ DICT_HT *dict_ht = (DICT_HT *) dict;
+
+ htable_free(dict_ht->table, myfree);
+ if (dict_ht->dict.fold_buf)
+ vstring_free(dict_ht->dict.fold_buf);
+ dict_free(dict);
+}
+
+/* dict_ht_open - create association with hash table */
+
+DICT *dict_ht_open(const char *name, int unused_open_flags, int dict_flags)
+{
+ DICT_HT *dict_ht;
+
+ dict_ht = (DICT_HT *) dict_alloc(DICT_TYPE_HT, name, sizeof(*dict_ht));
+ dict_ht->dict.lookup = dict_ht_lookup;
+ dict_ht->dict.update = dict_ht_update;
+ dict_ht->dict.delete = dict_ht_delete;
+ dict_ht->dict.sequence = dict_ht_sequence;
+ dict_ht->dict.close = dict_ht_close;
+ dict_ht->dict.flags = dict_flags | DICT_FLAG_FIXED;
+ if (dict_flags & DICT_FLAG_FOLD_FIX)
+ dict_ht->dict.fold_buf = vstring_alloc(10);
+ dict_ht->table = htable_create(0);
+ dict_ht->dict.owner.status = DICT_OWNER_TRUSTED;
+ return (&dict_ht->dict);
+}
diff --git a/src/util/dict_ht.h b/src/util/dict_ht.h
new file mode 100644
index 0000000..7cb31aa
--- /dev/null
+++ b/src/util/dict_ht.h
@@ -0,0 +1,38 @@
+#ifndef _DICT_HT_H_INCLUDED_
+#define _DICT_HT_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_ht 3h
+/* SUMMARY
+/* dictionary manager interface to hash tables
+/* SYNOPSIS
+/* #include <dict_ht.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <htable.h>
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_HT "internal"
+
+extern DICT *dict_ht_open(const char *, int, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/dict_inline.c b/src/util/dict_inline.c
new file mode 100644
index 0000000..72339b2
--- /dev/null
+++ b/src/util/dict_inline.c
@@ -0,0 +1,150 @@
+/*++
+/* NAME
+/* dict_inline 3
+/* SUMMARY
+/* dictionary manager interface for inline table
+/* SYNOPSIS
+/* #include <dict_inline.h>
+/*
+/* DICT *dict_inline_open(name, open_flags, dict_flags)
+/* const char *name;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_inline_open() opens a read-only, in-memory table.
+/* Example: "\fBinline:{\fIkey_1=value_1, ..., key_n=value_n\fR}".
+/* The longer form with { key = value } allows values that
+/* contain whitespace or comma.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <dict.h>
+#include <dict_ht.h>
+#include <dict_inline.h>
+
+/* Application-specific. */
+
+/* dict_inline_open - open inline table */
+
+DICT *dict_inline_open(const char *name, int open_flags, int dict_flags)
+{
+ DICT *dict;
+ char *cp, *saved_name = 0;
+ size_t len;
+ char *nameval, *vname, *value;
+ const char *err = 0;
+ char *free_me = 0;
+ int count = 0;
+
+ /*
+ * Clarity first. Let the optimizer worry about redundant code.
+ */
+#define DICT_INLINE_RETURN(x) do { \
+ DICT *__d = (x); \
+ if (saved_name != 0) \
+ myfree(saved_name); \
+ if (free_me != 0) \
+ myfree(free_me); \
+ return (__d); \
+ } while (0)
+
+ /*
+ * Sanity checks.
+ */
+ if (open_flags != O_RDONLY)
+ DICT_INLINE_RETURN(dict_surrogate(DICT_TYPE_INLINE, name,
+ open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_INLINE, name));
+
+ /*
+ * UTF-8 syntax check.
+ */
+ if (DICT_NEED_UTF8_ACTIVATION(util_utf8_enable, dict_flags)
+ && allascii(name) == 0
+ && valid_utf8_string(name, strlen(name)) == 0)
+ DICT_INLINE_RETURN(dict_surrogate(DICT_TYPE_INLINE, name,
+ open_flags, dict_flags,
+ "bad UTF-8 syntax: \"%s:%s\"; "
+ "need \"%s:{name=value...}\"",
+ DICT_TYPE_INLINE, name,
+ DICT_TYPE_INLINE));
+
+ /*
+ * Parse the table into its constituent name=value pairs.
+ */
+ if ((len = balpar(name, CHARS_BRACE)) == 0 || name[len] != 0
+ || *(cp = saved_name = mystrndup(name + 1, len - 2)) == 0)
+ DICT_INLINE_RETURN(dict_surrogate(DICT_TYPE_INLINE, name,
+ open_flags, dict_flags,
+ "bad syntax: \"%s:%s\"; "
+ "need \"%s:{name=value...}\"",
+ DICT_TYPE_INLINE, name,
+ DICT_TYPE_INLINE));
+
+ /*
+ * Reuse the "internal" dictionary type.
+ */
+ dict = dict_open3(DICT_TYPE_HT, name, open_flags, dict_flags);
+ dict_type_override(dict, DICT_TYPE_INLINE);
+ while ((nameval = mystrtokq(&cp, CHARS_COMMA_SP, CHARS_BRACE)) != 0) {
+ if (nameval[0] == CHARS_BRACE[0])
+ err = free_me = extpar(&nameval, CHARS_BRACE, EXTPAR_FLAG_STRIP);
+ if (err != 0 || (err = split_qnameval(nameval, &vname, &value)) != 0)
+ break;
+
+ if ((dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) != 0) {
+ VSTRING *base64_buf;
+
+ if ((base64_buf = dict_file_to_b64(dict, value)) == 0) {
+ err = free_me = dict_file_get_error(dict);
+ break;
+ }
+ value = vstring_str(base64_buf);
+ }
+ /* No duplicate checks. See comments in dict_thash.c. */
+ dict->update(dict, vname, value);
+ count += 1;
+ }
+ if (err != 0 || count == 0) {
+ dict->close(dict);
+ DICT_INLINE_RETURN(dict_surrogate(DICT_TYPE_INLINE, name,
+ open_flags, dict_flags,
+ "%s: \"%s:%s\"; "
+ "need \"%s:{name=%s...}\"",
+ err != 0 ? err : "empty table",
+ DICT_TYPE_INLINE, name,
+ DICT_TYPE_INLINE,
+ (dict_flags & DICT_FLAG_SRC_RHS_IS_FILE) ?
+ "filename" : "value"));
+ }
+ dict->owner.status = DICT_OWNER_TRUSTED;
+
+ dict_file_purge_buffers(dict);
+ DICT_INLINE_RETURN(DICT_DEBUG (dict));
+}
diff --git a/src/util/dict_inline.h b/src/util/dict_inline.h
new file mode 100644
index 0000000..9879acf
--- /dev/null
+++ b/src/util/dict_inline.h
@@ -0,0 +1,37 @@
+#ifndef _DICT_INLINE_H_INCLUDED_
+#define _DICT_INLINE_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_inline 3h
+/* SUMMARY
+/* dictionary manager interface for inline tables
+/* SYNOPSIS
+/* #include <dict_inline.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_INLINE "inline"
+
+extern DICT *dict_inline_open(const char *, int, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/dict_inline.ref b/src/util/dict_inline.ref
new file mode 100644
index 0000000..e64e6d0
--- /dev/null
+++ b/src/util/dict_inline.ref
@@ -0,0 +1,24 @@
+./dict_open: error: empty table: "inline:{ }"; need "inline:{name=value...}"
+owner=trusted (uid=2147483647)
+./dict_open: error: missing '=' after attribute name: "inline:{ foo = xx }"; need "inline:{name=value...}"
+owner=trusted (uid=2147483647)
+./dict_open: error: bad syntax: "inline:{ foo=xx }x"; need "inline:{name=value...}"
+owner=trusted (uid=2147483647)
+./dict_open: error: bad syntax: "inline:{ foo=xx x"; need "inline:{name=value...}"
+owner=trusted (uid=2147483647)
+./dict_open: error: syntax error after '}' in "{x=y}x": "inline:{ foo=xx {x=y}x}"; need "inline:{name=value...}"
+owner=trusted (uid=2147483647)
+owner=trusted (uid=2147483647)
+> get foo
+foo=XX
+> get bar
+bar=lotsa stuff
+> get baz
+baz: not found
+owner=trusted (uid=2147483647)
+> get foo
+foo=XX
+> get bar
+bar=lotsa stuff
+> get baz
+baz: not found
diff --git a/src/util/dict_inline_cidr.ref b/src/util/dict_inline_cidr.ref
new file mode 100644
index 0000000..56c00c5
--- /dev/null
+++ b/src/util/dict_inline_cidr.ref
@@ -0,0 +1,4 @@
+> get 1.2.3.4
+1.2.3.4=got 1.2.3.4
+> get 4.3.2.1
+4.3.2.1: not found
diff --git a/src/util/dict_inline_file.ref b/src/util/dict_inline_file.ref
new file mode 100644
index 0000000..9d49e95
--- /dev/null
+++ b/src/util/dict_inline_file.ref
@@ -0,0 +1,12 @@
+./dict_open: error: open xx: No such file or directory: "inline:{ foo=xx, bar=yy }"; need "inline:{name=filename...}"
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: inline:{ foo=xx, bar=yy } is unavailable. open xx: No such file or directory: "inline:{ foo=xx, bar=yy }"; need "inline:{name=filename...}"
+foo: error
+owner=trusted (uid=2147483647)
+> get file1
+file1=dGhpcy1pcy1maWxlMQo=
+> get file2
+file2=dGhpcy1pcy1maWxlMgo=
+> get file3
+file3: not found
diff --git a/src/util/dict_inline_pcre.ref b/src/util/dict_inline_pcre.ref
new file mode 100644
index 0000000..0c381f1
--- /dev/null
+++ b/src/util/dict_inline_pcre.ref
@@ -0,0 +1,4 @@
+> get foo
+foo=got foo
+> get bar
+bar: not found
diff --git a/src/util/dict_inline_regexp.ref b/src/util/dict_inline_regexp.ref
new file mode 100644
index 0000000..0c381f1
--- /dev/null
+++ b/src/util/dict_inline_regexp.ref
@@ -0,0 +1,4 @@
+> get foo
+foo=got foo
+> get bar
+bar: not found
diff --git a/src/util/dict_lmdb.c b/src/util/dict_lmdb.c
new file mode 100644
index 0000000..bed20e0
--- /dev/null
+++ b/src/util/dict_lmdb.c
@@ -0,0 +1,706 @@
+/*++
+/* NAME
+/* dict_lmdb 3
+/* SUMMARY
+/* dictionary manager interface to OpenLDAP LMDB files
+/* SYNOPSIS
+/* #include <dict_lmdb.h>
+/*
+/* extern size_t dict_lmdb_map_size;
+/*
+/* DEFINE_DICT_LMDB_MAP_SIZE;
+/*
+/* DICT *dict_lmdb_open(path, open_flags, dict_flags)
+/* const char *name;
+/* const char *path;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_lmdb_open() opens the named LMDB database and makes
+/* it available via the generic interface described in
+/* dict_open(3).
+/*
+/* The dict_lmdb_map_size variable specifies the initial
+/* database memory map size. When a map becomes full its size
+/* is doubled, and other programs pick up the size change.
+/*
+/* This variable cannot be exported via the dict(3) API and
+/* must therefore be defined in the calling program by invoking
+/* the DEFINE_DICT_LMDB_MAP_SIZE macro at the global level.
+/* DIAGNOSTICS
+/* Fatal errors: cannot open file, file write error, out of
+/* memory.
+/*
+/* If a jump buffer is specified with dict_setjmp(), then the LMDB
+/* client will call dict_longjmp() to return to that execution
+/* context after a recoverable error.
+/* BUGS
+/* The on-the-fly map resize operations require no concurrent
+/* activity in the same database by other threads in the same
+/* memory address space.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Howard Chu
+/* Symas Corporation
+/*
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#include <sys_defs.h>
+
+#ifdef HAS_LMDB
+
+/* System library. */
+
+#include <sys/stat.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <htable.h>
+#include <iostuff.h>
+#include <vstring.h>
+#include <myflock.h>
+#include <stringops.h>
+#include <slmdb.h>
+#include <dict.h>
+#include <dict_lmdb.h>
+#include <warn_stat.h>
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ SLMDB slmdb; /* sane LMDB API */
+ VSTRING *key_buf; /* key buffer */
+ VSTRING *val_buf; /* value buffer */
+} DICT_LMDB;
+
+ /*
+ * The LMDB database filename suffix happens to equal our DICT_TYPE_LMDB
+ * prefix, but that doesn't mean it is kosher to use DICT_TYPE_LMDB where a
+ * suffix is needed, so we define an explicit suffix here.
+ */
+#define DICT_LMDB_SUFFIX "lmdb"
+
+ /*
+ * Make a safe string copy that is guaranteed to be null-terminated.
+ */
+#define SCOPY(buf, data, size) \
+ vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size))
+
+ /*
+ * Postfix writers recover from a "map full" error by increasing the memory
+ * map size with a factor DICT_LMDB_SIZE_INCR (up to some limit) and
+ * retrying the transaction.
+ *
+ * Each dict(3) API call is retried no more than a few times. For bulk-mode
+ * transactions the number of retries is proportional to the size of the
+ * address space.
+ *
+ * We do not expose these details to the Postfix user interface. The purpose of
+ * Postfix is to solve problems, not punt them to the user.
+ */
+#define DICT_LMDB_SIZE_INCR 2 /* Increase size by 1 bit on retry */
+#define DICT_LMDB_SIZE_MAX SSIZE_T_MAX
+
+#define DICT_LMDB_API_RETRY_LIMIT 2 /* Retries per dict(3) API call */
+#define DICT_LMDB_BULK_RETRY_LIMIT \
+ ((int) (2 * sizeof(size_t) * CHAR_BIT)) /* Retries per bulk-mode
+ * transaction */
+
+/* #define msg_verbose 1 */
+
+/* dict_lmdb_lookup - find database entry */
+
+static const char *dict_lmdb_lookup(DICT *dict, const char *name)
+{
+ DICT_LMDB *dict_lmdb = (DICT_LMDB *) dict;
+ MDB_val mdb_key;
+ MDB_val mdb_value;
+ const char *result = 0;
+ int status;
+ ssize_t klen;
+
+ dict->error = 0;
+ klen = strlen(name);
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_lmdb_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+
+ /*
+ * Acquire a shared lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_SHARED) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict->name);
+
+ /*
+ * See if this LMDB file was written with one null byte appended to key
+ * and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ mdb_key.mv_data = (void *) name;
+ mdb_key.mv_size = klen + 1;
+ status = slmdb_get(&dict_lmdb->slmdb, &mdb_key, &mdb_value);
+ if (status == 0) {
+ dict->flags &= ~DICT_FLAG_TRY0NULL;
+ result = SCOPY(dict_lmdb->val_buf, mdb_value.mv_data,
+ mdb_value.mv_size);
+ } else if (status != MDB_NOTFOUND) {
+ msg_fatal("error reading %s:%s: %s",
+ dict_lmdb->dict.type, dict_lmdb->dict.name,
+ mdb_strerror(status));
+ }
+ }
+
+ /*
+ * See if this LMDB file was written with no null byte appended to key
+ * and value.
+ */
+ if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
+ mdb_key.mv_data = (void *) name;
+ mdb_key.mv_size = klen;
+ status = slmdb_get(&dict_lmdb->slmdb, &mdb_key, &mdb_value);
+ if (status == 0) {
+ dict->flags &= ~DICT_FLAG_TRY1NULL;
+ result = SCOPY(dict_lmdb->val_buf, mdb_value.mv_data,
+ mdb_value.mv_size);
+ } else if (status != MDB_NOTFOUND) {
+ msg_fatal("error reading %s:%s: %s",
+ dict_lmdb->dict.type, dict_lmdb->dict.name,
+ mdb_strerror(status));
+ }
+ }
+
+ /*
+ * Release the shared lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict->name);
+
+ return (result);
+}
+
+/* dict_lmdb_update - add or update database entry */
+
+static int dict_lmdb_update(DICT *dict, const char *name, const char *value)
+{
+ DICT_LMDB *dict_lmdb = (DICT_LMDB *) dict;
+ MDB_val mdb_key;
+ MDB_val mdb_value;
+ int status;
+
+ dict->error = 0;
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_lmdb_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+ mdb_key.mv_data = (void *) name;
+ mdb_value.mv_data = (void *) value;
+ mdb_key.mv_size = strlen(name);
+ mdb_value.mv_size = strlen(value);
+
+ /*
+ * If undecided about appending a null byte to key and value, choose a
+ * default depending on the platform.
+ */
+ if ((dict->flags & DICT_FLAG_TRY1NULL)
+ && (dict->flags & DICT_FLAG_TRY0NULL)) {
+#ifdef LMDB_NO_TRAILING_NULL
+ dict->flags &= ~DICT_FLAG_TRY1NULL;
+#else
+ dict->flags &= ~DICT_FLAG_TRY0NULL;
+#endif
+ }
+
+ /*
+ * Optionally append a null byte to key and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ mdb_key.mv_size++;
+ mdb_value.mv_size++;
+ }
+
+ /*
+ * Acquire an exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict->name);
+
+ /*
+ * Do the update.
+ */
+ status = slmdb_put(&dict_lmdb->slmdb, &mdb_key, &mdb_value,
+ (dict->flags & DICT_FLAG_DUP_REPLACE) ? 0 : MDB_NOOVERWRITE);
+ if (status != 0) {
+ if (status == MDB_KEYEXIST) {
+ if (dict->flags & DICT_FLAG_DUP_IGNORE)
+ /* void */ ;
+ else if (dict->flags & DICT_FLAG_DUP_WARN)
+ msg_warn("%s:%s: duplicate entry: \"%s\"",
+ dict_lmdb->dict.type, dict_lmdb->dict.name, name);
+ else
+ msg_fatal("%s:%s: duplicate entry: \"%s\"",
+ dict_lmdb->dict.type, dict_lmdb->dict.name, name);
+ } else {
+ msg_fatal("error updating %s:%s: %s",
+ dict_lmdb->dict.type, dict_lmdb->dict.name,
+ mdb_strerror(status));
+ }
+ }
+
+ /*
+ * Release the exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict->name);
+
+ return (status);
+}
+
+/* dict_lmdb_delete - delete one entry from the dictionary */
+
+static int dict_lmdb_delete(DICT *dict, const char *name)
+{
+ DICT_LMDB *dict_lmdb = (DICT_LMDB *) dict;
+ MDB_val mdb_key;
+ int status = 1;
+ ssize_t klen;
+
+ dict->error = 0;
+ klen = strlen(name);
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_lmdb_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+
+ /*
+ * Acquire an exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict->name);
+
+ /*
+ * See if this LMDB file was written with one null byte appended to key
+ * and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ mdb_key.mv_data = (void *) name;
+ mdb_key.mv_size = klen + 1;
+ status = slmdb_del(&dict_lmdb->slmdb, &mdb_key);
+ if (status != 0) {
+ if (status == MDB_NOTFOUND)
+ status = 1;
+ else
+ msg_fatal("error deleting from %s:%s: %s",
+ dict_lmdb->dict.type, dict_lmdb->dict.name,
+ mdb_strerror(status));
+ } else {
+ dict->flags &= ~DICT_FLAG_TRY0NULL; /* found */
+ }
+ }
+
+ /*
+ * See if this LMDB file was written with no null byte appended to key
+ * and value.
+ */
+ if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
+ mdb_key.mv_data = (void *) name;
+ mdb_key.mv_size = klen;
+ status = slmdb_del(&dict_lmdb->slmdb, &mdb_key);
+ if (status != 0) {
+ if (status == MDB_NOTFOUND)
+ status = 1;
+ else
+ msg_fatal("error deleting from %s:%s: %s",
+ dict_lmdb->dict.type, dict_lmdb->dict.name,
+ mdb_strerror(status));
+ } else {
+ dict->flags &= ~DICT_FLAG_TRY1NULL; /* found */
+ }
+ }
+
+ /*
+ * Release the exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict->name);
+
+ return (status);
+}
+
+/* dict_lmdb_sequence - traverse the dictionary */
+
+static int dict_lmdb_sequence(DICT *dict, int function,
+ const char **key, const char **value)
+{
+ const char *myname = "dict_lmdb_sequence";
+ DICT_LMDB *dict_lmdb = (DICT_LMDB *) dict;
+ MDB_val mdb_key;
+ MDB_val mdb_value;
+ MDB_cursor_op op;
+ int status;
+
+ dict->error = 0;
+
+ /*
+ * Determine the seek function.
+ */
+ switch (function) {
+ case DICT_SEQ_FUN_FIRST:
+ op = MDB_FIRST;
+ break;
+ case DICT_SEQ_FUN_NEXT:
+ op = MDB_NEXT;
+ break;
+ default:
+ msg_panic("%s: invalid function: %d", myname, function);
+ }
+
+ /*
+ * Acquire a shared lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_SHARED) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict->name);
+
+ /*
+ * Database lookup.
+ */
+ status = slmdb_cursor_get(&dict_lmdb->slmdb, &mdb_key, &mdb_value, op);
+
+ switch (status) {
+
+ /*
+ * Copy the key and value so they are guaranteed null terminated.
+ */
+ case 0:
+ *key = SCOPY(dict_lmdb->key_buf, mdb_key.mv_data, mdb_key.mv_size);
+ if (mdb_value.mv_data != 0 && mdb_value.mv_size > 0)
+ *value = SCOPY(dict_lmdb->val_buf, mdb_value.mv_data,
+ mdb_value.mv_size);
+ else
+ *value = ""; /* XXX */
+ break;
+
+ /*
+ * End-of-database.
+ */
+ case MDB_NOTFOUND:
+ status = 1;
+ /* Not: mdb_cursor_close(). Wrong abstraction level. */
+ break;
+
+ /*
+ * Bust.
+ */
+ default:
+ msg_fatal("error seeking %s:%s: %s",
+ dict_lmdb->dict.type, dict_lmdb->dict.name,
+ mdb_strerror(status));
+ }
+
+ /*
+ * Release the shared lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict->name);
+
+ return (status);
+}
+
+/* dict_lmdb_close - disassociate from data base */
+
+static void dict_lmdb_close(DICT *dict)
+{
+ DICT_LMDB *dict_lmdb = (DICT_LMDB *) dict;
+
+ slmdb_close(&dict_lmdb->slmdb);
+ if (dict_lmdb->key_buf)
+ vstring_free(dict_lmdb->key_buf);
+ if (dict_lmdb->val_buf)
+ vstring_free(dict_lmdb->val_buf);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_lmdb_longjmp - repeat bulk transaction */
+
+static void dict_lmdb_longjmp(void *context, int val)
+{
+ DICT_LMDB *dict_lmdb = (DICT_LMDB *) context;
+
+ dict_longjmp(&dict_lmdb->dict, val);
+}
+
+/* dict_lmdb_notify - debug logging */
+
+static void dict_lmdb_notify(void *context, int error_code,...)
+{
+ DICT_LMDB *dict_lmdb = (DICT_LMDB *) context;
+ va_list ap;
+
+ va_start(ap, error_code);
+ switch (error_code) {
+ case MDB_SUCCESS:
+ msg_info("database %s:%s: using size limit %lu during open",
+ dict_lmdb->dict.type, dict_lmdb->dict.name,
+ (unsigned long) va_arg(ap, size_t));
+ break;
+ case MDB_MAP_FULL:
+ msg_info("database %s:%s: using size limit %lu after MDB_MAP_FULL",
+ dict_lmdb->dict.type, dict_lmdb->dict.name,
+ (unsigned long) va_arg(ap, size_t));
+ break;
+ case MDB_MAP_RESIZED:
+ msg_info("database %s:%s: using size limit %lu after MDB_MAP_RESIZED",
+ dict_lmdb->dict.type, dict_lmdb->dict.name,
+ (unsigned long) va_arg(ap, size_t));
+ break;
+ case MDB_READERS_FULL:
+ msg_info("database %s:%s: pausing after MDB_READERS_FULL",
+ dict_lmdb->dict.type, dict_lmdb->dict.name);
+ break;
+ default:
+ msg_warn("unknown MDB error code: %d", error_code);
+ break;
+ }
+ va_end(ap);
+}
+
+/* dict_lmdb_assert - report LMDB internal assertion failure */
+
+static void dict_lmdb_assert(void *context, const char *text)
+{
+ DICT_LMDB *dict_lmdb = (DICT_LMDB *) context;
+
+ msg_fatal("%s:%s: internal error: %s",
+ dict_lmdb->dict.type, dict_lmdb->dict.name, text);
+}
+
+/* dict_lmdb_open - open LMDB data base */
+
+DICT *dict_lmdb_open(const char *path, int open_flags, int dict_flags)
+{
+ DICT_LMDB *dict_lmdb;
+ DICT *dict;
+ struct stat st;
+ SLMDB slmdb;
+ char *mdb_path;
+ int mdb_flags, slmdb_flags, status;
+ int db_fd;
+
+ /*
+ * Let the optimizer worry about eliminating redundant code.
+ */
+#define DICT_LMDB_OPEN_RETURN(d) do { \
+ DICT *__d = (d); \
+ myfree(mdb_path); \
+ return (__d); \
+ } while (0)
+
+ mdb_path = concatenate(path, "." DICT_TYPE_LMDB, (char *) 0);
+
+ /*
+ * Impedance adapters.
+ */
+ mdb_flags = MDB_NOSUBDIR | MDB_NOLOCK;
+ if (open_flags == O_RDONLY)
+ mdb_flags |= MDB_RDONLY;
+
+ slmdb_flags = 0;
+ if (dict_flags & DICT_FLAG_BULK_UPDATE)
+ slmdb_flags |= SLMDB_FLAG_BULK;
+
+ /*
+ * Security violation.
+ *
+ * By default, LMDB 0.9.9 writes uninitialized heap memory to a
+ * world-readable database file, as chunks of up to 4096 bytes. This is a
+ * huge memory disclosure vulnerability: memory content that a program
+ * does not intend to share ends up in a world-readable file. The content
+ * of uninitialized heap memory depends on program execution history.
+ * That history includes code execution in other libraries that are
+ * linked into the program.
+ *
+ * This is a problem whenever the user who writes the database file differs
+ * from the user who reads the database file. For example, a privileged
+ * writer and an unprivileged reader. In the case of Postfix, the
+ * postmap(1) and postalias(1) commands would leak uninitialized heap
+ * memory, as chunks of up to 4096 bytes, from a root-privileged process
+ * that writes to a database file, to unprivileged processes that read
+ * from that database file.
+ *
+ * As a workaround the postmap(1) and postalias(1) commands turn on
+ * MDB_WRITEMAP which disables the use of malloc() in LMDB. However, that
+ * does not address several disclosures of stack memory. We don't enable
+ * this workaround for Postfix databases are maintained by Postfix daemon
+ * processes, because those are accessible only by the postfix user.
+ *
+ * LMDB 0.9.10 by default does not write uninitialized heap memory to file
+ * (specify MDB_NOMEMINIT to revert that change). We use the MDB_WRITEMAP
+ * workaround for older LMDB versions.
+ */
+#ifndef MDB_NOMEMINIT
+ if (dict_flags & DICT_FLAG_BULK_UPDATE) /* XXX Good enough */
+ mdb_flags |= MDB_WRITEMAP;
+#endif
+
+ /*
+ * Gracefully handle most database open errors.
+ */
+ if ((status = slmdb_init(&slmdb, dict_lmdb_map_size, DICT_LMDB_SIZE_INCR,
+ DICT_LMDB_SIZE_MAX)) != 0
+ || (status = slmdb_open(&slmdb, mdb_path, open_flags, mdb_flags,
+ slmdb_flags)) != 0) {
+ /* This leaks a little memory that would have been used otherwise. */
+ dict = dict_surrogate(DICT_TYPE_LMDB, path, open_flags, dict_flags,
+ "open database %s: %s", mdb_path, mdb_strerror(status));
+ DICT_LMDB_OPEN_RETURN(dict);
+ }
+
+ /*
+ * XXX Persistent locking belongs in mkmap_lmdb.
+ *
+ * We just need to acquire exclusive access momentarily. This establishes
+ * that no readers are accessing old (obsoleted by copy-on-write) txn
+ * snapshots, so we are free to reuse all eligible old pages. Downgrade
+ * the lock right after acquiring it. This is sufficient to keep out
+ * other writers until we are done.
+ */
+ db_fd = slmdb_fd(&slmdb);
+ if (dict_flags & DICT_FLAG_BULK_UPDATE) {
+ if (myflock(db_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("%s: lock dictionary: %m", mdb_path);
+ if (myflock(db_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_SHARED) < 0)
+ msg_fatal("%s: unlock dictionary: %m", mdb_path);
+ }
+
+ /*
+ * Bundle up. From here on no more assignments to slmdb.
+ */
+ dict_lmdb = (DICT_LMDB *) dict_alloc(DICT_TYPE_LMDB, path, sizeof(*dict_lmdb));
+ dict_lmdb->slmdb = slmdb;
+ dict_lmdb->dict.lookup = dict_lmdb_lookup;
+ dict_lmdb->dict.update = dict_lmdb_update;
+ dict_lmdb->dict.delete = dict_lmdb_delete;
+ dict_lmdb->dict.sequence = dict_lmdb_sequence;
+ dict_lmdb->dict.close = dict_lmdb_close;
+
+ if (fstat(db_fd, &st) < 0)
+ msg_fatal("dict_lmdb_open: fstat: %m");
+ dict_lmdb->dict.lock_fd = dict_lmdb->dict.stat_fd = db_fd;
+ dict_lmdb->dict.lock_type = MYFLOCK_STYLE_FCNTL;
+ dict_lmdb->dict.mtime = st.st_mtime;
+ dict_lmdb->dict.owner.uid = st.st_uid;
+ dict_lmdb->dict.owner.status = (st.st_uid != 0);
+
+ dict_lmdb->key_buf = 0;
+ dict_lmdb->val_buf = 0;
+
+ /*
+ * Warn if the source file is newer than the indexed file, except when
+ * the source file changed only seconds ago.
+ */
+ if ((dict_flags & DICT_FLAG_LOCK) != 0
+ && stat(path, &st) == 0
+ && st.st_mtime > dict_lmdb->dict.mtime
+ && st.st_mtime < time((time_t *) 0) - 100)
+ msg_warn("database %s is older than source file %s", mdb_path, path);
+
+#define DICT_LMDB_IMPL_FLAGS (DICT_FLAG_FIXED | DICT_FLAG_MULTI_WRITER)
+
+ dict_lmdb->dict.flags = dict_flags | DICT_LMDB_IMPL_FLAGS;
+ if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0)
+ dict_lmdb->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL);
+ if (dict_flags & DICT_FLAG_FOLD_FIX)
+ dict_lmdb->dict.fold_buf = vstring_alloc(10);
+
+ if (dict_flags & DICT_FLAG_BULK_UPDATE)
+ dict_jmp_alloc(&dict_lmdb->dict);
+
+ /*
+ * The following requests return an error result only if we have serious
+ * memory corruption problem.
+ */
+ if (slmdb_control(&dict_lmdb->slmdb,
+ CA_SLMDB_CTL_API_RETRY_LIMIT(DICT_LMDB_API_RETRY_LIMIT),
+ CA_SLMDB_CTL_BULK_RETRY_LIMIT(DICT_LMDB_BULK_RETRY_LIMIT),
+ CA_SLMDB_CTL_LONGJMP_FN(dict_lmdb_longjmp),
+ CA_SLMDB_CTL_NOTIFY_FN(msg_verbose ?
+ dict_lmdb_notify : (SLMDB_NOTIFY_FN) 0),
+ CA_SLMDB_CTL_ASSERT_FN(dict_lmdb_assert),
+ CA_SLMDB_CTL_CB_CONTEXT((void *) dict_lmdb),
+ CA_SLMDB_CTL_END) != 0)
+ msg_panic("dict_lmdb_open: slmdb_control: %m");
+
+ if (msg_verbose)
+ dict_lmdb_notify((void *) dict_lmdb, MDB_SUCCESS,
+ slmdb_curr_limit(&dict_lmdb->slmdb));
+
+ DICT_LMDB_OPEN_RETURN(DICT_DEBUG (&dict_lmdb->dict));
+}
+
+#endif
diff --git a/src/util/dict_lmdb.h b/src/util/dict_lmdb.h
new file mode 100644
index 0000000..026bab5
--- /dev/null
+++ b/src/util/dict_lmdb.h
@@ -0,0 +1,50 @@
+#ifndef _DICT_LMDB_H_INCLUDED_
+#define _DICT_LMDB_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_lmdb 3h
+/* SUMMARY
+/* dictionary manager interface to OpenLDAP LMDB files
+/* SYNOPSIS
+/* #include <dict_lmdb.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+#include <mkmap.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_LMDB "lmdb"
+
+extern DICT *dict_lmdb_open(const char *, int, int);
+extern MKMAP *mkmap_lmdb_open(const char *);
+
+ /*
+ * 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
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/dict_ni.c b/src/util/dict_ni.c
new file mode 100644
index 0000000..3f62559
--- /dev/null
+++ b/src/util/dict_ni.c
@@ -0,0 +1,194 @@
+/*++
+/* NAME
+/* dict_ni 3
+/* SUMMARY
+/* dictionary manager interface to NetInfo
+/* SYNOPSIS
+/* #include <dict_ni.h>
+/*
+/* DICT *dict_ni_open(path, dummy, dict_flags)
+/* char *path;
+/* int dummy;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_ni_open() `opens' the named NetInfo database. The result is
+/* a pointer to a structure that can be used to access the dictionary
+/* using the generic methods documented in dict_open(3).
+/* DIAGNOSTICS
+/* dict_ni_register() returns 0 in case of success, -1 in case
+/* of problems.
+/* Fatal errors: NetInfo errors, out of memory.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* netinfo(3N) data base subroutines
+/* AUTHOR(S)
+/* Pieter Schoenmakers
+/* Eindhoven University of Technology
+/* P.O. Box 513
+/* 5600 MB Eindhoven
+/* The Netherlands
+/*--*/
+
+#include "sys_defs.h"
+
+#ifdef HAS_NETINFO
+
+/* System library. */
+
+#include <stdio.h>
+#include <netinfo/ni.h>
+
+/* Utility library. */
+
+#include "dict.h"
+#include "dict_ni.h"
+#include "msg.h"
+#include "mymalloc.h"
+#include "stringops.h"
+
+typedef struct {
+ DICT dict; /* my super */
+ char *path; /* directory path */
+} DICT_NI;
+
+ /*
+ * We'd like other possibilities, but that is not possible in the current
+ * dictionary setup... An example of a different setup: use `members' for
+ * multi-valued lookups (to be compatible with /aliases), and `value' for
+ * single-valued tables.
+ */
+#define NETINFO_PROP_KEY "name"
+#define NETINFO_PROP_VALUE "members"
+#define NETINFO_VALUE_SEP ","
+
+#define NETINFO_MAX_DOMAIN_DEPTH 100
+
+/* Hard worker doing lookups. Returned value is statically allocated and
+ reused each call. */
+static const char *dict_ni_do_lookup(char *path, char *key_prop,
+ const char *key_value, char *val_prop)
+{
+ unsigned int result_cap = 0;
+ static char *result = 0;
+
+ char *return_val = 0;
+ ni_namelist values;
+ int depth = 0;
+ void *domain;
+ void *next_domain;
+ char *query;
+ ni_status r;
+ ni_id dir;
+
+ if (msg_verbose)
+ msg_info("ni_lookup %s %s=%s", path, key_prop, key_value);
+
+ r = ni_open(NULL, ".", &domain);
+ if (r != NI_OK) {
+ msg_warn("ni_open `.': %d", r);
+ return NULL;
+ }
+ query = mymalloc(strlen(path) + strlen(key_prop) + 3 + strlen(key_value));
+ sprintf(query, "%s/%s=%s", path, key_prop, key_value);
+
+ for (;;) {
+
+ /*
+ * What does it _mean_ if we find the directory but not the value?
+ */
+ if (ni_pathsearch(domain, &dir, query) == NI_OK
+ && ni_lookupprop(domain, &dir, val_prop, &values) == NI_OK)
+ if (values.ni_namelist_len <= 0)
+ ni_namelist_free(&values);
+ else {
+ unsigned int i, l, n;
+
+ for (i = l = 0; i < values.ni_namelist_len; i++)
+ l += 1 + strlen(values.ni_namelist_val[i]);
+ if (result_cap < l) {
+ if (result)
+ myfree(result);
+ result_cap = l + 100;
+ result = mymalloc(result_cap);
+ }
+ for (i = l = 0; i < values.ni_namelist_len; i++) {
+ n = strlen(values.ni_namelist_val[i]);
+ memcpy(result + l, values.ni_namelist_val[i], n);
+ l += n;
+ if (i < values.ni_namelist_len - 1)
+ result[l++] = ',';
+ }
+ result[l] = '\0';
+ return_val = result;
+ break;
+ }
+
+ if (++depth >= NETINFO_MAX_DOMAIN_DEPTH) {
+ msg_warn("ni_open: domain depth limit");
+ break;
+ }
+ r = ni_open(domain, "..", &next_domain);
+ if (r != NI_OK) {
+ if (r != NI_FAILED)
+ msg_warn("ni_open `..': %d", r);
+ break;
+ }
+ ni_free(domain);
+ domain = next_domain;
+ }
+
+ ni_free(domain);
+ myfree(query);
+
+ return return_val;
+}
+
+/* dict_ni_lookup - find table entry */
+
+static const char *dict_ni_lookup(DICT *dict, const char *key)
+{
+ DICT_NI *d = (DICT_NI *) dict;
+
+ dict->error = 0;
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, key);
+ key = lowercase(vstring_str(dict->fold_buf));
+ }
+ return dict_ni_do_lookup(d->dict.name, NETINFO_PROP_KEY,
+ key, NETINFO_PROP_VALUE);
+}
+
+/* dict_ni_close - disassociate from NetInfo map */
+
+static void dict_ni_close(DICT *dict)
+{
+ DICT_NI *d = (DICT_NI *) dict;
+
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_ni_open - create association with NetInfo map */
+
+DICT *dict_ni_open(const char *path, int unused_flags, int dict_flags)
+{
+ DICT_NI *d = (void *) dict_alloc(DICT_TYPE_NETINFO, path, sizeof(*d));
+
+ d->dict.lookup = dict_ni_lookup;
+ d->dict.close = dict_ni_close;
+ d->dict.flags = dict_flags | DICT_FLAG_FIXED;
+ if (dict_flags & DICT_FLAG_FOLD_FIX)
+ d->dict.fold_buf = vstring_alloc(10);
+ d->dict.owner.status = DICT_OWNER_TRUSTED;
+
+ return (DICT_DEBUG (&d->dict));
+}
+
+#endif
diff --git a/src/util/dict_ni.h b/src/util/dict_ni.h
new file mode 100644
index 0000000..652b422
--- /dev/null
+++ b/src/util/dict_ni.h
@@ -0,0 +1,34 @@
+#ifndef _DICT_NI_H_INCLUDED_
+#define _DICT_NI_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_ni 3h
+/* SUMMARY
+/* dictionary manager interface to NetInfo maps
+/* SYNOPSIS
+/* #include <dict_ni.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_NETINFO "netinfo"
+
+extern DICT *dict_ni_open(const char *, int, int);
+
+/* AUTHOR(S)
+/* Pieter Schoenmakers
+/* Eindhoven University of Technology
+/* P.O. Box 513
+/* 5600 MB Eindhoven
+/* The Netherlands
+/*--*/
+
+#endif
diff --git a/src/util/dict_nis.c b/src/util/dict_nis.c
new file mode 100644
index 0000000..357011f
--- /dev/null
+++ b/src/util/dict_nis.c
@@ -0,0 +1,247 @@
+/*++
+/* NAME
+/* dict_nis 3
+/* SUMMARY
+/* dictionary manager interface to NIS maps
+/* SYNOPSIS
+/* #include <dict_nis.h>
+/*
+/* DICT *dict_nis_open(map, open_flags, dict_flags)
+/* const char *map;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_nis_open() makes the specified NIS map accessible via
+/* the generic dictionary operations described in dict_open(3).
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* DIAGNOSTICS
+/* Fatal errors: out of memory, attempt to update NIS map.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+#ifdef HAS_NIS
+
+#include <rpcsvc/ypclnt.h>
+#ifndef YPERR_BUSY
+#define YPERR_BUSY 16
+#endif
+#ifndef YPERR_ACCESS
+#define YPERR_ACCESS 15
+#endif
+
+#endif
+
+/* Utility library. */
+
+#include "msg.h"
+#include "mymalloc.h"
+#include "vstring.h"
+#include "stringops.h"
+#include "dict.h"
+#include "dict_nis.h"
+
+#ifdef HAS_NIS
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+} DICT_NIS;
+
+ /*
+ * Class variables, so that multiple maps can share this info.
+ */
+static char dict_nis_disabled[1];
+static char *dict_nis_domain;
+
+/* dict_nis_init - NIS binding */
+
+static void dict_nis_init(void)
+{
+ const char *myname = "dict_nis_init";
+
+ if (yp_get_default_domain(&dict_nis_domain) != 0
+ || dict_nis_domain == 0 || *dict_nis_domain == 0
+ || strcasecmp(dict_nis_domain, "(none)") == 0) {
+ dict_nis_domain = dict_nis_disabled;
+ msg_warn("%s: NIS domain name not set - NIS lookups disabled", myname);
+ }
+ if (msg_verbose)
+ msg_info("%s: NIS domain %s", myname, dict_nis_domain);
+}
+
+/* dict_nis_strerror - map error number to string */
+
+static char *dict_nis_strerror(int err)
+{
+
+ /*
+ * Grr. There should be a standard function for this.
+ */
+ switch (err) {
+ case YPERR_BADARGS:
+ return ("args to function are bad");
+ case YPERR_RPC:
+ return ("RPC failure - domain has been unbound");
+ case YPERR_DOMAIN:
+ return ("can't bind to server on this domain");
+ case YPERR_MAP:
+ return ("no such map in server's domain");
+ case YPERR_KEY:
+ return ("no such key in map");
+ case YPERR_YPERR:
+ return ("internal yp server or client error");
+ case YPERR_RESRC:
+ return ("resource allocation failure");
+ case YPERR_NOMORE:
+ return ("no more records in map database");
+ case YPERR_PMAP:
+ return ("can't communicate with portmapper");
+ case YPERR_YPBIND:
+ return ("can't communicate with ypbind");
+ case YPERR_YPSERV:
+ return ("can't communicate with ypserv");
+ case YPERR_NODOM:
+ return ("local domain name not set");
+ case YPERR_BADDB:
+ return ("yp database is bad");
+ case YPERR_VERS:
+ return ("yp version mismatch");
+ case YPERR_ACCESS:
+ return ("access violation");
+ case YPERR_BUSY:
+ return ("database busy");
+ default:
+ return ("unknown NIS lookup error");
+ }
+}
+
+/* dict_nis_lookup - find table entry */
+
+static const char *dict_nis_lookup(DICT *dict, const char *key)
+{
+ DICT_NIS *dict_nis = (DICT_NIS *) dict;
+ static char *result;
+ int result_len;
+ int err;
+ static VSTRING *buf;
+
+ dict->error = 0;
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_nis_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ if (dict_nis_domain == dict_nis_disabled)
+ return (0);
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, key);
+ key = lowercase(vstring_str(dict->fold_buf));
+ }
+
+ /*
+ * See if this NIS map was written with one null byte appended to key and
+ * value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ err = yp_match(dict_nis_domain, dict_nis->dict.name,
+ (void *) key, strlen(key) + 1,
+ &result, &result_len);
+ if (err == 0) {
+ dict->flags &= ~DICT_FLAG_TRY0NULL;
+ return (result);
+ }
+ }
+
+ /*
+ * See if this NIS map was written with no null byte appended to key and
+ * value. This should never be the case, but better play safe.
+ */
+ if (dict->flags & DICT_FLAG_TRY0NULL) {
+ err = yp_match(dict_nis_domain, dict_nis->dict.name,
+ (void *) key, strlen(key),
+ &result, &result_len);
+ if (err == 0) {
+ dict->flags &= ~DICT_FLAG_TRY1NULL;
+ if (buf == 0)
+ buf = vstring_alloc(10);
+ vstring_strncpy(buf, result, result_len);
+ return (vstring_str(buf));
+ }
+ }
+
+ /*
+ * When the NIS lookup fails for reasons other than "key not found", keep
+ * logging warnings, and hope that someone will eventually notice the
+ * problem and fix it.
+ */
+ if (err != YPERR_KEY) {
+ msg_warn("lookup %s, NIS domain %s, map %s: %s",
+ key, dict_nis_domain, dict_nis->dict.name,
+ dict_nis_strerror(err));
+ dict->error = DICT_ERR_RETRY;
+ }
+ return (0);
+}
+
+/* dict_nis_close - close NIS map */
+
+static void dict_nis_close(DICT *dict)
+{
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_nis_open - open NIS map */
+
+DICT *dict_nis_open(const char *map, int open_flags, int dict_flags)
+{
+ DICT_NIS *dict_nis;
+
+ if (open_flags != O_RDONLY)
+ return (dict_surrogate(DICT_TYPE_NIS, map, open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_NIS, map));
+
+ dict_nis = (DICT_NIS *) dict_alloc(DICT_TYPE_NIS, map, sizeof(*dict_nis));
+ dict_nis->dict.lookup = dict_nis_lookup;
+ dict_nis->dict.close = dict_nis_close;
+ dict_nis->dict.flags = dict_flags | DICT_FLAG_FIXED;
+ if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ dict_nis->dict.flags |= (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL);
+ if (dict_flags & DICT_FLAG_FOLD_FIX)
+ dict_nis->dict.fold_buf = vstring_alloc(10);
+ if (dict_nis_domain == 0)
+ dict_nis_init();
+ dict_nis->dict.owner.status = DICT_OWNER_TRUSTED;
+ return (DICT_DEBUG (&dict_nis->dict));
+}
+
+#endif
diff --git a/src/util/dict_nis.h b/src/util/dict_nis.h
new file mode 100644
index 0000000..97aa7cd
--- /dev/null
+++ b/src/util/dict_nis.h
@@ -0,0 +1,37 @@
+#ifndef _DIST_NIS_H_INCLUDED_
+#define _DIST_NIS_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_nis 3h
+/* SUMMARY
+/* dictionary manager interface to NIS maps
+/* SYNOPSIS
+/* #include <dict_nis.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_NIS "nis"
+
+extern DICT *dict_nis_open(const char *, int, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/dict_nisplus.c b/src/util/dict_nisplus.c
new file mode 100644
index 0000000..1f5e126
--- /dev/null
+++ b/src/util/dict_nisplus.c
@@ -0,0 +1,304 @@
+/*++
+/* NAME
+/* dict_nisplus 3
+/* SUMMARY
+/* dictionary manager interface to NIS+ maps
+/* SYNOPSIS
+/* #include <dict_nisplus.h>
+/*
+/* DICT *dict_nisplus_open(map, open_flags, dict_flags)
+/* const char *map;
+/* int dummy;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_nisplus_open() makes the specified NIS+ map accessible via
+/* the generic dictionary operations described in dict_open(3).
+/* The \fIdummy\fR argument is not used.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* DIAGNOSTICS
+/* Fatal errors:
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Geoff Gibbs
+/* UK-HGMP-RC
+/* Hinxton
+/* Cambridge
+/* CB10 1SB, UK
+/*
+/* based on the code for dict_nis.c et al by :-
+/*
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#ifdef HAS_NISPLUS
+#include <rpcsvc/nis.h> /* for nis_list */
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <dict.h>
+#include <dict_nisplus.h>
+
+#ifdef HAS_NISPLUS
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ char *template; /* parsed query template */
+ int column; /* NIS+ field number (start at 1) */
+} DICT_NISPLUS;
+
+ /*
+ * Begin quote from nis+(1):
+ *
+ * The following text represents a context-free grammar that defines the
+ * set of legal NIS+ names. The terminals in this grammar are the
+ * characters `.' (dot), `[' (open bracket), `]' (close bracket), `,'
+ * (comma), `=' (equals) and whitespace. Angle brackets (`<' and `>'),
+ * which delineate non- terminals, are not part of the grammar. The
+ * character `|' (vertical bar) is used to separate alternate productions
+ * and should be read as ``this production OR this production''.
+ *
+ * name ::= . | <simple name> | <indexed name>
+ *
+ * simple name ::= <string>. | <string>.<simple name>
+ *
+ * indexed name ::= <search criterion>,<simple name>
+ *
+ * search criterion ::= [ <attribute list> ]
+ *
+ * attribute list ::= <attribute> | <attribute>,<attribute list>
+ *
+ * attribute ::= <string> = <string>
+ *
+ * string ::= ISO Latin 1 character set except the character
+ * '/' (slash). The initial character may not be a terminal character or
+ * the characters '@' (at), '+' (plus), or (`-') hyphen.
+ *
+ * Terminals that appear in strings must be quoted with `"' (double quote).
+ * The `"' character may be quoted by quoting it with itself `""'.
+ *
+ * End quote fron nis+(1).
+ *
+ * This NIS client always quotes the entire query string (the value part of
+ * [attribute=value],file.domain.) so the issue with initial characters
+ * should not be applicable. One wonders what restrictions are applicable
+ * when a string is quoted, but the manual doesn't specify what can appear
+ * between quotes, and we don't want to get burned.
+ */
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+
+/* dict_nisplus_lookup - find table entry */
+
+static const char *dict_nisplus_lookup(DICT *dict, const char *key)
+{
+ const char *myname = "dict_nisplus_lookup";
+ DICT_NISPLUS *dict_nisplus = (DICT_NISPLUS *) dict;
+ static VSTRING *quoted_key;
+ static VSTRING *query;
+ static VSTRING *retval;
+ nis_result *reply;
+ int count;
+ const char *cp;
+ int last_col;
+ int ch;
+
+ dict->error = 0;
+
+ /*
+ * Initialize.
+ */
+ if (quoted_key == 0) {
+ query = vstring_alloc(100);
+ retval = vstring_alloc(100);
+ quoted_key = vstring_alloc(100);
+ }
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, key);
+ key = lowercase(vstring_str(dict->fold_buf));
+ }
+
+ /*
+ * Check that the lookup key does not contain characters disallowed by
+ * nis+(1).
+ *
+ * XXX Many client implementations don't seem to care about disallowed
+ * characters.
+ */
+ VSTRING_RESET(quoted_key);
+ VSTRING_ADDCH(quoted_key, '"');
+ for (cp = key; (ch = *(unsigned const char *) cp) != 0; cp++) {
+ if ((ISASCII(ch) && !ISPRINT(ch)) || (ch > 126 && ch < 160)) {
+ msg_warn("map %s:%s: lookup key with non-printing character 0x%x:"
+ " ignoring this request",
+ dict->type, dict->name, ch);
+ return (0);
+ } else if (ch == '"') {
+ VSTRING_ADDCH(quoted_key, '"');
+ }
+ VSTRING_ADDCH(quoted_key, ch);
+ }
+ VSTRING_ADDCH(quoted_key, '"');
+ VSTRING_TERMINATE(quoted_key);
+
+ /*
+ * Plug the key into the query template, which typically looks something
+ * like the following: [alias=%s],mail_aliases.org_dir.my.nisplus.domain.
+ *
+ * XXX The nis+ documentation defines a length limit for simple names like
+ * a.b.c., but defines no length limit for (the components of) indexed
+ * names such as [x=y],a.b.c. Our query length is limited because Postfix
+ * addresses (in envelopes or in headers) have a finite length.
+ */
+ vstring_sprintf(query, dict_nisplus->template, STR(quoted_key));
+ reply = nis_list(STR(query), FOLLOW_LINKS | FOLLOW_PATH, NULL, NULL);
+
+ /*
+ * When lookup succeeds, the result may be ambiguous, or the requested
+ * column may not exist.
+ */
+ if (reply->status == NIS_SUCCESS) {
+ if ((count = NIS_RES_NUMOBJ(reply)) != 1) {
+ msg_warn("ambiguous match (%d results) for %s in NIS+ map %s:"
+ " ignoring this request",
+ count, key, dict_nisplus->dict.name);
+ nis_freeresult(reply);
+ return (0);
+ } else {
+ last_col = NIS_RES_OBJECT(reply)->zo_data
+ .objdata_u.en_data.en_cols.en_cols_len - 1;
+ if (dict_nisplus->column > last_col)
+ msg_fatal("requested column %d > max column %d in table %s",
+ dict_nisplus->column, last_col,
+ dict_nisplus->dict.name);
+ vstring_strcpy(retval,
+ NIS_RES_OBJECT(reply)->zo_data.objdata_u
+ .en_data.en_cols.en_cols_val[dict_nisplus->column]
+ .ec_value.ec_value_val);
+ if (msg_verbose)
+ msg_info("%s: %s, column %d -> %s", myname, STR(query),
+ dict_nisplus->column, STR(retval));
+ nis_freeresult(reply);
+ return (STR(retval));
+ }
+ }
+
+ /*
+ * When the NIS+ lookup fails for reasons other than "key not found",
+ * keep logging warnings, and hope that someone will eventually notice
+ * the problem and fix it.
+ */
+ else {
+ if (reply->status != NIS_NOTFOUND
+ && reply->status != NIS_PARTIAL) {
+ msg_warn("lookup %s, NIS+ map %s: %s",
+ key, dict_nisplus->dict.name,
+ nis_sperrno(reply->status));
+ dict->error = DICT_ERR_RETRY;
+ } else {
+ if (msg_verbose)
+ msg_info("%s: not found: query %s", myname, STR(query));
+ }
+ nis_freeresult(reply);
+ return (0);
+ }
+}
+
+/* dict_nisplus_close - close NISPLUS map */
+
+static void dict_nisplus_close(DICT *dict)
+{
+ DICT_NISPLUS *dict_nisplus = (DICT_NISPLUS *) dict;
+
+ myfree(dict_nisplus->template);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_nisplus_open - open NISPLUS map */
+
+DICT *dict_nisplus_open(const char *map, int open_flags, int dict_flags)
+{
+ const char *myname = "dict_nisplus_open";
+ DICT_NISPLUS *dict_nisplus;
+ char *col_field;
+
+ /*
+ * Sanity check.
+ */
+ if (open_flags != O_RDONLY)
+ return (dict_surrogate(DICT_TYPE_NISPLUS, map, open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_NISPLUS, map));
+
+ /*
+ * Initialize. This is a read-only map with fixed strings, not with
+ * regular expressions.
+ */
+ dict_nisplus = (DICT_NISPLUS *)
+ dict_alloc(DICT_TYPE_NISPLUS, map, sizeof(*dict_nisplus));
+ dict_nisplus->dict.lookup = dict_nisplus_lookup;
+ dict_nisplus->dict.close = dict_nisplus_close;
+ dict_nisplus->dict.flags = dict_flags | DICT_FLAG_FIXED;
+ if (dict_flags & DICT_FLAG_FOLD_FIX)
+ dict_nisplus->dict.fold_buf = vstring_alloc(10);
+ dict_nisplus->dict.owner.status = DICT_OWNER_TRUSTED;
+
+ /*
+ * Convert the query template into an indexed name and column number. The
+ * query template looks like:
+ *
+ * [attribute=%s;attribute=value...];simple.name.:column
+ *
+ * One instance of %s gets to be replaced by a version of the lookup key;
+ * other attributes must specify fixed values. The reason for using ';'
+ * is that the comma character is special in main.cf. When no column
+ * number is given at the end of the map name, we use a default column.
+ */
+ dict_nisplus->template = mystrdup(map);
+ translit(dict_nisplus->template, ";", ",");
+ if ((col_field = strstr(dict_nisplus->template, ".:")) != 0) {
+ col_field[1] = 0;
+ col_field += 2;
+ if (!alldig(col_field) || (dict_nisplus->column = atoi(col_field)) < 1)
+ msg_fatal("bad column field in NIS+ map name: %s", map);
+ } else {
+ dict_nisplus->column = 1;
+ }
+ if (msg_verbose)
+ msg_info("%s: opened NIS+ table %s for column %d",
+ myname, dict_nisplus->template, dict_nisplus->column);
+ return (DICT_DEBUG (&dict_nisplus->dict));
+}
+
+#endif
diff --git a/src/util/dict_nisplus.h b/src/util/dict_nisplus.h
new file mode 100644
index 0000000..1fd31c9
--- /dev/null
+++ b/src/util/dict_nisplus.h
@@ -0,0 +1,37 @@
+#ifndef _DICT_NISPLUS_H_INCLUDED_
+#define _DICT_NISPLUS_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_nisplus 3h
+/* SUMMARY
+/* dictionary manager interface to NIS+ maps
+/* SYNOPSIS
+/* #include <dict_nisplus.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_NISPLUS "nisplus"
+
+extern DICT *dict_nisplus_open(const char *, int, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/dict_open.c b/src/util/dict_open.c
new file mode 100644
index 0000000..c3b90d4
--- /dev/null
+++ b/src/util/dict_open.c
@@ -0,0 +1,617 @@
+/*++
+/* NAME
+/* dict_open 3
+/* SUMMARY
+/* low-level dictionary interface
+/* SYNOPSIS
+/* #include <dict.h>
+/*
+/* DICT *dict_open(dict_spec, open_flags, dict_flags)
+/* const char *dict_spec;
+/* int open_flags;
+/* int dict_flags;
+/*
+/* DICT *dict_open3(dict_type, dict_name, open_flags, dict_flags)
+/* const char *dict_type;
+/* const char *dict_name;
+/* int open_flags;
+/* int dict_flags;
+/*
+/* int dict_put(dict, key, value)
+/* DICT *dict;
+/* const char *key;
+/* const char *value;
+/*
+/* const char *dict_get(dict, key)
+/* DICT *dict;
+/* const char *key;
+/*
+/* int dict_del(dict, key)
+/* DICT *dict;
+/* const char *key;
+/*
+/* int dict_seq(dict, func, key, value)
+/* DICT *dict;
+/* int func;
+/* const char **key;
+/* const char **value;
+/*
+/* void dict_close(dict)
+/* DICT *dict;
+/*
+/* typedef struct {
+/* .in +4
+/* char *type;
+/* DICT_OPEN_FN dict_fn;
+/* MKMAP_OPEN_FN mkmap_fn; /* See <mkmap.h> */
+/* .in -4
+/* } DICT_OPEN_INFO;
+/*
+/* typedef DICT *(*DICT_OPEN_FN) (const char *, int, int);
+/*
+/* void dict_open_register(open_info)
+/* DICT_OPEN_INFO *open_info;
+/*
+/* const DICT_OPEN_INFO *dict_open_lookup(dict_type)
+/* const char *dict_type;
+/*
+/* typedef DICT_OPEN_INFO (*DICT_OPEN_EXTEND_FN)(char *);
+/*
+/* 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.
+/* NOTE: this function does not copy its argument.
+/*
+/* dict_open_lookup() returns a pointer to the DICT_OPEN_INFO
+/* for the specified dictionary type, or a null pointer if the
+/* requested information is not found.
+/*
+/* dict_open_extend() registers a call-back function that looks
+/* up the dictionary open() function for a type that is not
+/* registered, or null in case of error. The result value is
+/* the last previously-registered call-back or null.
+/*
+/* dict_mapnames() returns a sorted list with the names of all available
+/* dictionary types.
+/*
+/* dict_mapnames_extend() registers a call-back function that
+/* enumerates additional dictionary type names. The result
+/* will be sorted by dict_mapnames(). The result value
+/* is the last previously-registered call-back or null.
+/*
+/* dict_setjmp() saves processing context and makes that context
+/* available for use with dict_longjmp(). Normally, dict_setjmp()
+/* returns zero. A non-zero result means that dict_setjmp()
+/* returned through a dict_longjmp() call; the result is the
+/* \fIval\fR argument given to dict_longjmp(). dict_isjmp()
+/* returns non-zero when dict_setjmp() and dict_longjmp()
+/* are enabled for a given dictionary.
+/*
+/* NB: non-local jumps such as dict_longjmp() are not safe for
+/* jumping out of any routine that manipulates DICT data.
+/* longjmp() like calls are best avoided in signal handlers.
+/*
+/* dict_type_override() changes the symbolic dictionary type.
+/* This is used by dictionaries whose internals are based on
+/* some other dictionary type.
+/* DIAGNOSTICS
+/* Fatal error: open error, unsupported dictionary type, attempt to
+/* update non-writable dictionary.
+/*
+/* The lookup routine returns non-null when the request is
+/* satisfied. The update, delete and sequence routines return
+/* zero (DICT_STAT_SUCCESS) when the request is satisfied.
+/* The dict->errno value is non-zero only when the last operation
+/* was not satisfied due to a dictionary access error. This
+/* can have the following values:
+/* .IP DICT_ERR_NONE(zero)
+/* There was no dictionary access error. For example, the
+/* request was satisfied, the requested information did not
+/* exist in the dictionary, or the information already existed
+/* when it should not exist (collision).
+/* .IP DICT_ERR_RETRY(<0)
+/* The dictionary was temporarily unavailable. This can happen
+/* with network-based services.
+/* .IP DICT_ERR_CONFIG(<0)
+/* The dictionary was unavailable due to a configuration error.
+/* .PP
+/* Generally, a program is expected to test the function result
+/* value for "success" first. If the operation was not successful,
+/* a program is expected to test for a non-zero dict->error
+/* status to distinguish between a data notfound/collision
+/* condition or a dictionary access error.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <argv.h>
+#include <mymalloc.h>
+#include <msg.h>
+#include <dict.h>
+#include <dict_cdb.h>
+#include <dict_env.h>
+#include <dict_unix.h>
+#include <dict_tcp.h>
+#include <dict_sdbm.h>
+#include <dict_dbm.h>
+#include <dict_db.h>
+#include <dict_lmdb.h>
+#include <dict_nis.h>
+#include <dict_nisplus.h>
+#include <dict_ni.h>
+#include <dict_pcre.h>
+#include <dict_regexp.h>
+#include <dict_static.h>
+#include <dict_cidr.h>
+#include <dict_ht.h>
+#include <dict_thash.h>
+#include <dict_sockmap.h>
+#include <dict_fail.h>
+#include <dict_pipe.h>
+#include <dict_random.h>
+#include <dict_union.h>
+#include <dict_inline.h>
+#include <stringops.h>
+#include <split_at.h>
+#include <htable.h>
+#include <myflock.h>
+#include <mkmap.h>
+
+ /*
+ * lookup table for available map types.
+ */
+static const DICT_OPEN_INFO dict_open_info[] = {
+ DICT_TYPE_ENVIRON, dict_env_open, 0,
+ DICT_TYPE_HT, dict_ht_open, 0,
+ DICT_TYPE_UNIX, dict_unix_open, 0,
+ DICT_TYPE_TCP, dict_tcp_open, 0,
+#ifdef HAS_DBM
+ DICT_TYPE_DBM, dict_dbm_open, mkmap_dbm_open,
+#endif
+#ifdef HAS_DB
+ DICT_TYPE_HASH, dict_hash_open, mkmap_hash_open,
+ DICT_TYPE_BTREE, dict_btree_open, mkmap_btree_open,
+#endif
+#ifdef HAS_NIS
+ DICT_TYPE_NIS, dict_nis_open, 0,
+#endif
+#ifdef HAS_NISPLUS
+ DICT_TYPE_NISPLUS, dict_nisplus_open, 0,
+#endif
+#ifdef HAS_NETINFO
+ DICT_TYPE_NETINFO, dict_ni_open, 0,
+#endif
+#ifdef HAS_POSIX_REGEXP
+ DICT_TYPE_REGEXP, dict_regexp_open, 0,
+#endif
+ DICT_TYPE_STATIC, dict_static_open, 0,
+ DICT_TYPE_CIDR, dict_cidr_open, 0,
+ DICT_TYPE_THASH, dict_thash_open, 0,
+ DICT_TYPE_SOCKMAP, dict_sockmap_open, 0,
+ DICT_TYPE_FAIL, dict_fail_open, mkmap_fail_open,
+ DICT_TYPE_PIPE, dict_pipe_open, 0,
+ DICT_TYPE_RANDOM, dict_random_open, 0,
+ DICT_TYPE_UNION, dict_union_open, 0,
+ DICT_TYPE_INLINE, dict_inline_open, 0,
+#ifndef USE_DYNAMIC_MAPS
+#ifdef HAS_PCRE
+ DICT_TYPE_PCRE, dict_pcre_open, 0,
+#endif
+#ifdef HAS_CDB
+ DICT_TYPE_CDB, dict_cdb_open, mkmap_cdb_open,
+#endif
+#ifdef HAS_SDBM
+ DICT_TYPE_SDBM, dict_sdbm_open, mkmap_sdbm_open,
+#endif
+#ifdef HAS_LMDB
+ DICT_TYPE_LMDB, dict_lmdb_open, mkmap_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 global variables here to control database cache sizes.
+ * When a database driver is dynamically loaded, global control variables
+ * cannot simply be owned by the loadable objects because that would result
+ * in build-time linker errors.
+ */
+DEFINE_DICT_LMDB_MAP_SIZE;
+DEFINE_DICT_DB_CACHE_SIZE;
+
+ /*
+ * Replace obscure code with a more readable expression.
+ */
+#define NEED_DICT_OPEN_INIT() (dict_open_hash == 0)
+
+/* dict_open_init - one-off initialization */
+
+static void dict_open_init(void)
+{
+ const char *myname = "dict_open_init";
+ const DICT_OPEN_INFO *dp;
+
+ if (!NEED_DICT_OPEN_INIT())
+ 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";
+ const DICT_OPEN_INFO *dp;
+ 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 (NEED_DICT_OPEN_INIT())
+ dict_open_init();
+ if ((dp = dict_open_lookup(dict_type)) == 0)
+ return (dict_surrogate(dict_type, dict_name, open_flags, dict_flags,
+ "unsupported dictionary type: %s", dict_type));
+ if ((dict = dp->dict_fn(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 DICT_OPEN_INFO *dp)
+{
+ const char *myname = "dict_open_register";
+
+ if (msg_verbose > 1)
+ msg_info("%s: %s", myname, dp->type);
+ if (NEED_DICT_OPEN_INIT())
+ dict_open_init();
+ if (htable_find(dict_open_hash, dp->type))
+ msg_panic("%s: dictionary type exists: %s", myname, dp->type);
+ (void) htable_enter(dict_open_hash, dp->type, (void *) dp);
+}
+
+/* dict_open_lookup - look up DICT_OPEN_INFO for dictionary type */
+
+const DICT_OPEN_INFO *dict_open_lookup(const char *dict_type)
+{
+ const char myname[] = "dict_open_lookup";
+ const DICT_OPEN_INFO *dp;
+
+ if (msg_verbose > 1)
+ msg_info("%s: %s", myname, dict_type);
+ if ((dp = (DICT_OPEN_INFO *) htable_find(dict_open_hash, dict_type)) == 0
+ && dict_open_extend_hook != 0
+ && (dp = dict_open_extend_hook(dict_type)) != 0)
+ dict_open_register(dp);
+ return (dp);
+}
+
+/* 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_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 (NEED_DICT_OPEN_INIT())
+ 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);
+ argv_qsort(mapnames, (ARGV_COMPAR_FN) 0);
+ /* In case some drivers have been loaded dynamically. */
+ argv_uniq(mapnames, (ARGV_COMPAR_FN) 0);
+ 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..20eee02
--- /dev/null
+++ b/src/util/dict_pcre.c
@@ -0,0 +1,1121 @@
+/*++
+/* NAME
+/* dict_pcre 3
+/* SUMMARY
+/* dictionary manager interface to PCRE regular expression library
+/* SYNOPSIS
+/* #include <dict_pcre.h>
+/*
+/* DICT *dict_pcre_open(name, dummy, dict_flags)
+/* const char *name;
+/* int dummy;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_pcre_open() opens the named file and compiles the contained
+/* regular expressions. The result object can be used to match strings
+/* against the table.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* pcre_table(5) PCRE table configuration
+/* AUTHOR(S)
+/* Andrew McNamara
+/* andrewm@connect.com.au
+/* connect.com.au Pty. Ltd.
+/* Level 3, 213 Miller St
+/* North Sydney, NSW, Australia
+/*
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#include "sys_defs.h"
+
+#ifdef HAS_PCRE
+
+/* System library. */
+
+#include <sys/stat.h>
+#include <stdio.h> /* sprintf() prototype */
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+#if HAS_PCRE == 1
+#include <pcre.h>
+#elif HAS_PCRE == 2
+#define PCRE2_CODE_UNIT_WIDTH 8
+#include <pcre2.h>
+#else
+#error "define HAS_PCRE=2 or HAS_PCRE=1"
+#endif
+
+/* Utility library. */
+
+#include "mymalloc.h"
+#include "msg.h"
+#include "safe.h"
+#include "vstream.h"
+#include "vstring.h"
+#include "stringops.h"
+#include "readlline.h"
+#include "dict.h"
+#include "dict_pcre.h"
+#include "mac_parse.h"
+#include "warn_stat.h"
+#include "mvect.h"
+
+ /*
+ * Backwards compatibility.
+ */
+#if HAS_PCRE == 1
+ /* PCRE Legacy JIT supprt. */
+#ifdef PCRE_STUDY_JIT_COMPILE
+#define DICT_PCRE_FREE_STUDY(x) pcre_free_study(x)
+#else
+#define DICT_PCRE_FREE_STUDY(x) pcre_free((char *) (x))
+#endif
+
+ /* PCRE Compiled pattern. */
+#define DICT_PCRE_CODE pcre
+#define DICT_PCRE_CODE_FREE(x) myfree((void *) (x))
+
+ /* Old-style hints versus new-style match_data. */
+#define DICT_PCRE_MATCH_HINT_TYPE pcre_extra *
+#define DICT_PCRE_MATCH_HINT_NAME hints
+#define DICT_PCRE_MATCH_HINT(x) ((x)->DICT_PCRE_MATCH_HINT_NAME)
+#define DICT_PCRE_MATCH_HINT_FREE(x) do { \
+ if (DICT_PCRE_MATCH_HINT(x)) \
+ DICT_PCRE_FREE_STUDY(DICT_PCRE_MATCH_HINT(x)); \
+ } while (0)
+
+ /* PCRE Pattern options. */
+#define DICT_PCRE_CASELESS PCRE_CASELESS
+#define DICT_PCRE_MULTILINE PCRE_MULTILINE
+#define DICT_PCRE_DOTALL PCRE_DOTALL
+#define DICT_PCRE_EXTENDED PCRE_EXTENDED
+#define DICT_PCRE_ANCHORED PCRE_ANCHORED
+#define DICT_PCRE_DOLLAR_ENDONLY PCRE_DOLLAR_ENDONLY
+#define DICT_PCRE_UNGREEDY PCRE_UNGREEDY
+#define DICT_PCRE_EXTRA PCRE_EXTRA
+
+ /* PCRE Number of captures in pattern. */
+#ifdef PCRE_INFO_CAPTURECOUNT
+#define DICT_PCRE_CAPTURECOUNT_T int
+#endif
+
+#else /* HAS_PCRE */
+
+ /* PCRE2 Compiled pattern. */
+#define DICT_PCRE_CODE pcre2_code
+#define DICT_PCRE_CODE_FREE(x) pcre2_code_free(x)
+
+ /* PCRE2 Old-style hints versus new-style match_data. */
+#define DICT_PCRE_MATCH_HINT_TYPE pcre2_match_data *
+#define DICT_PCRE_MATCH_HINT_NAME match_data
+#define DICT_PCRE_MATCH_HINT(x) ((x)->DICT_PCRE_MATCH_HINT_NAME)
+#define DICT_PCRE_MATCH_HINT_FREE(x) \
+ pcre2_match_data_free(DICT_PCRE_MATCH_HINT(x))
+
+ /* PCRE2 Pattern options. */
+#define DICT_PCRE_CASELESS PCRE2_CASELESS
+#define DICT_PCRE_MULTILINE PCRE2_MULTILINE
+#define DICT_PCRE_DOTALL PCRE2_DOTALL
+#define DICT_PCRE_EXTENDED PCRE2_EXTENDED
+#define DICT_PCRE_ANCHORED PCRE2_ANCHORED
+#define DICT_PCRE_DOLLAR_ENDONLY PCRE2_DOLLAR_ENDONLY
+#define DICT_PCRE_UNGREEDY PCRE2_UNGREEDY
+#define DICT_PCRE_EXTRA 0
+
+ /* PCRE2 Number of captures in pattern. */
+#define DICT_PCRE_CAPTURECOUNT_T uint32_t
+
+#endif /* HAS_PCRE */
+
+ /*
+ * Support for IF/ENDIF based on an idea by Bert Driehuis.
+ */
+#define DICT_PCRE_OP_MATCH 1 /* Match this regexp */
+#define DICT_PCRE_OP_IF 2 /* Increase if/endif nesting on match */
+#define DICT_PCRE_OP_ENDIF 3 /* Decrease if/endif nesting on match */
+
+ /*
+ * Max strings captured by regexp - essentially the max number of (..)
+ */
+#if HAS_PCRE == 1
+#define PCRE_MAX_CAPTURE 99
+#endif
+
+ /*
+ * Regular expression before and after compilation.
+ */
+typedef struct {
+ char *regexp; /* regular expression */
+ int options; /* options */
+ int match; /* positive or negative match */
+} DICT_PCRE_REGEXP;
+
+typedef struct {
+ DICT_PCRE_CODE *pattern; /* the compiled pattern */
+ DICT_PCRE_MATCH_HINT_TYPE DICT_PCRE_MATCH_HINT_NAME;
+} DICT_PCRE_ENGINE;
+
+ /*
+ * Compiled generic rule, and subclasses that derive from it.
+ */
+typedef struct DICT_PCRE_RULE {
+ int op; /* DICT_PCRE_OP_MATCH/IF/ENDIF */
+ int lineno; /* source file line number */
+ struct DICT_PCRE_RULE *next; /* next rule in dict */
+} DICT_PCRE_RULE;
+
+typedef struct {
+ DICT_PCRE_RULE rule; /* generic part */
+ DICT_PCRE_CODE *pattern; /* compiled pattern */
+ DICT_PCRE_MATCH_HINT_TYPE DICT_PCRE_MATCH_HINT_NAME;
+ char *replacement; /* replacement string */
+ int match; /* positive or negative match */
+ size_t max_sub; /* largest $number in replacement */
+} DICT_PCRE_MATCH_RULE;
+
+typedef struct {
+ DICT_PCRE_RULE rule; /* generic members */
+ DICT_PCRE_CODE *pattern; /* compiled pattern */
+ DICT_PCRE_MATCH_HINT_TYPE DICT_PCRE_MATCH_HINT_NAME;
+ int match; /* positive or negative match */
+ struct DICT_PCRE_RULE *endif_rule; /* matching endif rule */
+} DICT_PCRE_IF_RULE;
+
+ /*
+ * PCRE map.
+ */
+typedef struct {
+ DICT dict; /* generic members */
+ DICT_PCRE_RULE *head;
+ VSTRING *expansion_buf; /* lookup result */
+} DICT_PCRE;
+
+#if HAS_PCRE == 1
+static int dict_pcre_init = 0; /* flag need to init pcre library */
+
+#endif
+
+/*
+ * Context for $number expansion callback.
+ */
+typedef struct {
+ DICT_PCRE *dict_pcre; /* the dictionary handle */
+#if HAS_PCRE == 1
+ DICT_PCRE_MATCH_RULE *match_rule; /* the rule we matched */
+#endif
+ const char *lookup_string; /* string against which we match */
+#if HAS_PCRE == 1
+ int offsets[PCRE_MAX_CAPTURE * 3]; /* Cut substrings */
+#else /* HAS_PCRE */
+ PCRE2_SIZE *ovector; /* matched string offsets */
+#endif /* HAS_PCRE */
+ int matches; /* Count of cuts */
+} DICT_PCRE_EXPAND_CONTEXT;
+
+ /*
+ * Context for $number pre-scan callback.
+ */
+typedef struct {
+ const char *mapname; /* name of regexp map */
+ int lineno; /* where in file */
+ size_t max_sub; /* Largest $n seen */
+ char *literal; /* constant result, $$ -> $ */
+} DICT_PCRE_PRESCAN_CONTEXT;
+
+ /*
+ * Compatibility.
+ */
+#ifndef MAC_PARSE_OK
+#define MAC_PARSE_OK 0
+#endif
+
+ /*
+ * Macros to make dense code more accessible.
+ */
+#define NULL_STARTOFFSET (0)
+#define NULL_EXEC_OPTIONS (0)
+
+/* dict_pcre_expand - replace $number with matched text */
+
+static int dict_pcre_expand(int type, VSTRING *buf, void *ptr)
+{
+ DICT_PCRE_EXPAND_CONTEXT *ctxt = (DICT_PCRE_EXPAND_CONTEXT *) ptr;
+ DICT_PCRE *dict_pcre = ctxt->dict_pcre;
+ int n;
+
+#if HAS_PCRE == 1
+ DICT_PCRE_MATCH_RULE *match_rule = ctxt->match_rule;
+ const char *pp;
+ int ret;
+
+#else
+ PCRE2_SPTR start;
+ PCRE2_SIZE length;
+
+#endif
+
+ /*
+ * Replace $0-${99} with strings cut from matched text.
+ */
+ if (type == MAC_PARSE_VARNAME) {
+ n = atoi(vstring_str(buf));
+#if HAS_PCRE == 1
+ ret = pcre_get_substring(ctxt->lookup_string, ctxt->offsets,
+ ctxt->matches, n, &pp);
+ if (ret < 0) {
+ if (ret == PCRE_ERROR_NOSUBSTRING)
+ return (MAC_PARSE_UNDEF);
+ else
+ msg_fatal("pcre map %s, line %d: pcre_get_substring error: %d",
+ dict_pcre->dict.name, match_rule->rule.lineno, ret);
+ }
+ if (*pp == 0) {
+ myfree((void *) pp);
+ return (MAC_PARSE_UNDEF);
+ }
+ vstring_strcat(dict_pcre->expansion_buf, pp);
+ myfree((void *) pp);
+ return (MAC_PARSE_OK);
+#else
+ start = (unsigned char *) ctxt->lookup_string + ctxt->ovector[2 * n];
+ length = ctxt->ovector[2 * n + 1] - ctxt->ovector[2 * n];
+ if (length == 0)
+ return (MAC_PARSE_UNDEF);
+ vstring_strncat(dict_pcre->expansion_buf, (char *) start, length);
+ return (MAC_PARSE_OK);
+#endif
+ }
+
+ /*
+ * Straight text - duplicate with no substitution.
+ */
+ else {
+ vstring_strcat(dict_pcre->expansion_buf, vstring_str(buf));
+ return (MAC_PARSE_OK);
+ }
+}
+
+#if HAS_PCRE == 2
+
+#define DICT_PCRE_GET_ERROR_BUF_LEN 256
+
+/* dict_pcre_get_error - convert PCRE2 error number or text */
+
+static char *dict_pcre_get_error(VSTRING *buf, int errval)
+{
+ ssize_t len;
+
+ VSTRING_SPACE(buf, DICT_PCRE_GET_ERROR_BUF_LEN);
+ if ((len = pcre2_get_error_message(errval,
+ (unsigned char *) vstring_str(buf),
+ DICT_PCRE_GET_ERROR_BUF_LEN)) > 0) {
+ vstring_set_payload_size(buf, len);
+ } else
+ vstring_sprintf(buf, "unexpected pcre2 error code %d", errval);
+ return (vstring_str(buf));
+}
+
+#endif /* HAS_PCRE == 2 */
+
+/* dict_pcre_exec_error - report matching error */
+
+static void dict_pcre_exec_error(const char *mapname, int lineno, int errval)
+{
+#if HAS_PCRE == 1
+ switch (errval) {
+ case 0:
+ msg_warn("pcre map %s, line %d: too many (...)",
+ mapname, lineno);
+ return;
+ case PCRE_ERROR_NULL:
+ case PCRE_ERROR_BADOPTION:
+ msg_warn("pcre map %s, line %d: bad args to re_exec",
+ mapname, lineno);
+ return;
+ case PCRE_ERROR_BADMAGIC:
+ case PCRE_ERROR_UNKNOWN_NODE:
+ msg_warn("pcre map %s, line %d: corrupt compiled regexp",
+ mapname, lineno);
+ return;
+#ifdef PCRE_ERROR_NOMEMORY
+ case PCRE_ERROR_NOMEMORY:
+ msg_warn("pcre map %s, line %d: out of memory",
+ mapname, lineno);
+ return;
+#endif
+#ifdef PCRE_ERROR_MATCHLIMIT
+ case PCRE_ERROR_MATCHLIMIT:
+ msg_warn("pcre map %s, line %d: backtracking limit exceeded",
+ mapname, lineno);
+ return;
+#endif
+#ifdef PCRE_ERROR_BADUTF8
+ case PCRE_ERROR_BADUTF8:
+ msg_warn("pcre map %s, line %d: bad UTF-8 sequence in search string",
+ mapname, lineno);
+ return;
+#endif
+#ifdef PCRE_ERROR_BADUTF8_OFFSET
+ case PCRE_ERROR_BADUTF8_OFFSET:
+ msg_warn("pcre map %s, line %d: bad UTF-8 start offset in search string",
+ mapname, lineno);
+ return;
+#endif
+ default:
+ msg_warn("pcre map %s, line %d: unknown pcre_exec error: %d",
+ mapname, lineno, errval);
+ return;
+ }
+#else /* HAS_PCRE */
+ VSTRING *buf = vstring_alloc(DICT_PCRE_GET_ERROR_BUF_LEN);
+
+ msg_warn("pcre map %s, line %d: %s", mapname, lineno,
+ dict_pcre_get_error(buf, errval));
+ vstring_free(buf);
+#endif /* HAS_PCRE */
+}
+
+ /*
+ * Inlined to reduce function call overhead in the time-critical loop.
+ */
+#if HAS_PCRE == 1
+#define DICT_PCRE_EXEC(ctxt, map, line, pattern, hints, match, str, len) \
+ ((ctxt).matches = pcre_exec((pattern), (hints), (str), (len), \
+ NULL_STARTOFFSET, NULL_EXEC_OPTIONS, \
+ (ctxt).offsets, PCRE_MAX_CAPTURE * 3), \
+ (ctxt).matches > 0 ? (match) : \
+ (ctxt).matches == PCRE_ERROR_NOMATCH ? !(match) : \
+ (dict_pcre_exec_error((map), (line), (ctxt).matches), 0))
+#else
+#define DICT_PCRE_EXEC(ctxt, map, line, pattern, match_data, match, str, len) \
+ ((ctxt).matches = pcre2_match((pattern), (unsigned char *) (str), (len), \
+ NULL_STARTOFFSET, NULL_EXEC_OPTIONS, \
+ (match_data), (pcre2_match_context *) 0), \
+ (ctxt).matches > 0 ? (match) : \
+ (ctxt).matches == PCRE2_ERROR_NOMATCH ? !(match) : \
+ (dict_pcre_exec_error((map), (line), (ctxt).matches), 0))
+#endif
+
+/* dict_pcre_lookup - match string and perform optional substitution */
+
+static const char *dict_pcre_lookup(DICT *dict, const char *lookup_string)
+{
+ DICT_PCRE *dict_pcre = (DICT_PCRE *) dict;
+ DICT_PCRE_RULE *rule;
+ DICT_PCRE_IF_RULE *if_rule;
+ DICT_PCRE_MATCH_RULE *match_rule;
+ int lookup_len = strlen(lookup_string);
+ DICT_PCRE_EXPAND_CONTEXT ctxt;
+
+ dict->error = 0;
+
+ if (msg_verbose)
+ msg_info("dict_pcre_lookup: %s: %s", dict->name, lookup_string);
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_MUL) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, lookup_string);
+ lookup_string = lowercase(vstring_str(dict->fold_buf));
+ }
+ for (rule = dict_pcre->head; rule; rule = rule->next) {
+
+ switch (rule->op) {
+
+ /*
+ * Search for a matching expression.
+ */
+ case DICT_PCRE_OP_MATCH:
+ match_rule = (DICT_PCRE_MATCH_RULE *) rule;
+ if (!DICT_PCRE_EXEC(ctxt, dict->name, rule->lineno,
+ match_rule->pattern,
+ DICT_PCRE_MATCH_HINT(match_rule),
+ match_rule->match, lookup_string, lookup_len))
+ continue;
+
+ /*
+ * Skip $number substitutions when the replacement text contains
+ * no $number strings, as learned during the compile time
+ * pre-scan. The pre-scan already replaced $$ by $.
+ */
+ if (match_rule->max_sub == 0)
+ return match_rule->replacement;
+
+ /*
+ * We've got a match. Perform substitution on replacement string.
+ */
+ if (dict_pcre->expansion_buf == 0)
+ dict_pcre->expansion_buf = vstring_alloc(10);
+ VSTRING_RESET(dict_pcre->expansion_buf);
+ ctxt.dict_pcre = dict_pcre;
+#if HAS_PCRE == 1
+ ctxt.match_rule = match_rule;
+#else
+ ctxt.ovector = pcre2_get_ovector_pointer(match_rule->match_data);
+#endif
+ ctxt.lookup_string = lookup_string;
+
+ if (mac_parse(match_rule->replacement, dict_pcre_expand,
+ (void *) &ctxt) & MAC_PARSE_ERROR)
+ msg_fatal("pcre map %s, line %d: bad replacement syntax",
+ dict->name, rule->lineno);
+
+ VSTRING_TERMINATE(dict_pcre->expansion_buf);
+ return (vstring_str(dict_pcre->expansion_buf));
+
+ /*
+ * Conditional. XXX We provide space for matched substring info
+ * because PCRE uses part of it as workspace for backtracking.
+ * PCRE will allocate memory if it runs out of backtracking
+ * storage.
+ */
+ case DICT_PCRE_OP_IF:
+ if_rule = (DICT_PCRE_IF_RULE *) rule;
+ if (DICT_PCRE_EXEC(ctxt, dict->name, rule->lineno,
+ if_rule->pattern,
+ DICT_PCRE_MATCH_HINT(if_rule),
+ if_rule->match, lookup_string, lookup_len))
+ continue;
+ /* An IF without matching ENDIF has no "endif" rule. */
+ if ((rule = if_rule->endif_rule) == 0)
+ return (0);
+ /* FALLTHROUGH */
+
+ /*
+ * ENDIF after IF.
+ */
+ case DICT_PCRE_OP_ENDIF:
+ continue;
+
+ default:
+ msg_panic("dict_pcre_lookup: impossible operation %d", rule->op);
+ }
+ }
+ return (0);
+}
+
+/* dict_pcre_close - close pcre dictionary */
+
+static void dict_pcre_close(DICT *dict)
+{
+ DICT_PCRE *dict_pcre = (DICT_PCRE *) dict;
+ DICT_PCRE_RULE *rule;
+ DICT_PCRE_RULE *next;
+ DICT_PCRE_MATCH_RULE *match_rule;
+ DICT_PCRE_IF_RULE *if_rule;
+
+ for (rule = dict_pcre->head; rule; rule = next) {
+ next = rule->next;
+ switch (rule->op) {
+ case DICT_PCRE_OP_MATCH:
+ match_rule = (DICT_PCRE_MATCH_RULE *) rule;
+ if (match_rule->pattern)
+ DICT_PCRE_CODE_FREE(match_rule->pattern);
+ DICT_PCRE_MATCH_HINT_FREE(match_rule);
+ if (match_rule->replacement)
+ myfree((void *) match_rule->replacement);
+ break;
+ case DICT_PCRE_OP_IF:
+ if_rule = (DICT_PCRE_IF_RULE *) rule;
+ if (if_rule->pattern)
+ DICT_PCRE_CODE_FREE(if_rule->pattern);
+ DICT_PCRE_MATCH_HINT_FREE(if_rule);
+ break;
+ case DICT_PCRE_OP_ENDIF:
+ break;
+ default:
+ msg_panic("dict_pcre_close: unknown operation %d", rule->op);
+ }
+ myfree((void *) rule);
+ }
+ if (dict_pcre->expansion_buf)
+ vstring_free(dict_pcre->expansion_buf);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_pcre_get_pattern - extract pattern from rule */
+
+static int dict_pcre_get_pattern(const char *mapname, int lineno, char **bufp,
+ DICT_PCRE_REGEXP *pattern)
+{
+ char *p = *bufp;
+ char re_delimiter;
+
+ /*
+ * Process negation operators.
+ */
+ pattern->match = 1;
+ for (;;) {
+ if (*p == '!')
+ pattern->match = !pattern->match;
+ else if (!ISSPACE(*p))
+ break;
+ p++;
+ }
+ if (*p == 0) {
+ msg_warn("pcre map %s, line %d: no regexp: skipping this rule",
+ mapname, lineno);
+ return (0);
+ }
+ re_delimiter = *p++;
+ pattern->regexp = p;
+
+ /*
+ * Search for second delimiter, handling backslash escape.
+ */
+ while (*p) {
+ if (*p == '\\') {
+ ++p;
+ if (*p == 0)
+ break;
+ } else if (*p == re_delimiter)
+ break;
+ ++p;
+ }
+
+ if (!*p) {
+ msg_warn("pcre map %s, line %d: no closing regexp delimiter \"%c\": "
+ "ignoring this rule", mapname, lineno, re_delimiter);
+ return (0);
+ }
+ *p++ = 0; /* Null term the regexp */
+
+ /*
+ * Parse any regexp options.
+ */
+ pattern->options = DICT_PCRE_CASELESS | DICT_PCRE_DOTALL;
+ while (*p && !ISSPACE(*p)) {
+ switch (*p) {
+ case 'i':
+ pattern->options ^= DICT_PCRE_CASELESS;
+ break;
+ case 'm':
+ pattern->options ^= DICT_PCRE_MULTILINE;
+ break;
+ case 's':
+ pattern->options ^= DICT_PCRE_DOTALL;
+ break;
+ case 'x':
+ pattern->options ^= DICT_PCRE_EXTENDED;
+ break;
+ case 'A':
+ pattern->options ^= DICT_PCRE_ANCHORED;
+ break;
+ case 'E':
+ pattern->options ^= DICT_PCRE_DOLLAR_ENDONLY;
+ break;
+ case 'U':
+ pattern->options ^= DICT_PCRE_UNGREEDY;
+ break;
+ case 'X':
+#if DICT_PCRE_EXTRA != 0
+ pattern->options ^= DICT_PCRE_EXTRA;
+#else
+ msg_warn("pcre map %s, line %d: ignoring obsolete regexp "
+ "option \"%c\"", mapname, lineno, *p);
+#endif
+ break;
+ default:
+ msg_warn("pcre map %s, line %d: unknown regexp option \"%c\": "
+ "skipping this rule", mapname, lineno, *p);
+ return (0);
+ }
+ ++p;
+ }
+ *bufp = p;
+ return (1);
+}
+
+/* dict_pcre_prescan - sanity check $number instances in replacement text */
+
+static int dict_pcre_prescan(int type, VSTRING *buf, void *context)
+{
+ DICT_PCRE_PRESCAN_CONTEXT *ctxt = (DICT_PCRE_PRESCAN_CONTEXT *) context;
+ size_t n;
+
+ /*
+ * Keep a copy of literal text (with $$ already replaced by $) if and
+ * only if the replacement text contains no $number expression. This way
+ * we can avoid having to scan the replacement text at lookup time.
+ */
+ if (type == MAC_PARSE_VARNAME) {
+ if (ctxt->literal) {
+ myfree(ctxt->literal);
+ ctxt->literal = 0;
+ }
+ if (!alldig(vstring_str(buf))) {
+ msg_warn("pcre map %s, line %d: non-numeric replacement index \"%s\"",
+ ctxt->mapname, ctxt->lineno, vstring_str(buf));
+ return (MAC_PARSE_ERROR);
+ }
+ n = atoi(vstring_str(buf));
+ if (n < 1) {
+ msg_warn("pcre map %s, line %d: out of range replacement index \"%s\"",
+ ctxt->mapname, ctxt->lineno, vstring_str(buf));
+ return (MAC_PARSE_ERROR);
+ }
+ if (n > ctxt->max_sub)
+ ctxt->max_sub = n;
+ } else if (type == MAC_PARSE_LITERAL && ctxt->max_sub == 0) {
+ if (ctxt->literal)
+ msg_panic("pcre map %s, line %d: multiple literals but no $number",
+ ctxt->mapname, ctxt->lineno);
+ ctxt->literal = mystrdup(vstring_str(buf));
+ }
+ return (MAC_PARSE_OK);
+}
+
+/* dict_pcre_compile - compile pattern */
+
+static int dict_pcre_compile(const char *mapname, int lineno,
+ DICT_PCRE_REGEXP *pattern,
+ DICT_PCRE_ENGINE *engine)
+{
+#if HAS_PCRE == 1
+ const char *error;
+ int errptr;
+
+ engine->pattern = pcre_compile(pattern->regexp, pattern->options,
+ &error, &errptr, NULL);
+ if (engine->pattern == 0) {
+ msg_warn("pcre map %s, line %d: error in regex at offset %d: %s",
+ mapname, lineno, errptr, error);
+ return (0);
+ }
+ engine->hints = pcre_study(engine->pattern, 0, &error);
+ if (error != 0) {
+ msg_warn("pcre map %s, line %d: error while studying regex: %s",
+ mapname, lineno, error);
+ DICT_PCRE_CODE_FREE(engine->pattern);
+ return (0);
+ }
+#else
+ int error;
+ size_t errptr;
+
+ engine->pattern = pcre2_compile((unsigned char *) pattern->regexp,
+ PCRE2_ZERO_TERMINATED,
+ pattern->options, &error, &errptr, NULL);
+ if (engine->pattern == 0) {
+ VSTRING *buf = vstring_alloc(DICT_PCRE_GET_ERROR_BUF_LEN);
+
+ msg_warn("pcre map %s, line %d: error in regex at offset %lu: %s",
+ mapname, lineno, (unsigned long) errptr,
+ dict_pcre_get_error(buf, error));
+ vstring_free(buf);
+ return (0);
+ }
+ engine->match_data = pcre2_match_data_create_from_pattern(
+ engine->pattern, (void *) 0);
+#endif
+ return (1);
+}
+
+/* dict_pcre_rule_alloc - fill in a generic rule structure */
+
+static DICT_PCRE_RULE *dict_pcre_rule_alloc(int op, int lineno, size_t size)
+{
+ DICT_PCRE_RULE *rule;
+
+ rule = (DICT_PCRE_RULE *) mymalloc(size);
+ rule->op = op;
+ rule->lineno = lineno;
+ rule->next = 0;
+
+ return (rule);
+}
+
+/* dict_pcre_parse_rule - parse and compile one rule */
+
+static DICT_PCRE_RULE *dict_pcre_parse_rule(DICT *dict, const char *mapname,
+ int lineno, char *line,
+ int nesting)
+{
+ char *p;
+
+#ifdef DICT_PCRE_CAPTURECOUNT_T
+ DICT_PCRE_CAPTURECOUNT_T actual_sub;
+
+#endif
+#if 0
+ uint32_t namecount;
+
+#endif
+
+ p = line;
+
+ /*
+ * An ordinary match rule takes one pattern and replacement text.
+ */
+ if (!ISALNUM(*p)) {
+ DICT_PCRE_REGEXP regexp;
+ DICT_PCRE_ENGINE engine;
+ DICT_PCRE_PRESCAN_CONTEXT prescan_context;
+ DICT_PCRE_MATCH_RULE *match_rule;
+
+ /*
+ * Get the pattern string and options.
+ */
+ if (dict_pcre_get_pattern(mapname, lineno, &p, &regexp) == 0)
+ return (0);
+
+ /*
+ * Get the replacement text.
+ */
+ while (*p && ISSPACE(*p))
+ ++p;
+ if (!*p)
+ msg_warn("pcre map %s, line %d: no replacement text: "
+ "using empty string", mapname, lineno);
+
+ /*
+ * Sanity check the $number instances in the replacement text.
+ */
+ prescan_context.mapname = mapname;
+ prescan_context.lineno = lineno;
+ prescan_context.max_sub = 0;
+ prescan_context.literal = 0;
+
+ /*
+ * The optimizer will eliminate code duplication and/or dead code.
+ */
+#define CREATE_MATCHOP_ERROR_RETURN(rval) do { \
+ if (prescan_context.literal) \
+ myfree(prescan_context.literal); \
+ return (rval); \
+ } while (0)
+
+ if (dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) {
+ VSTRING *base64_buf;
+ char *err;
+
+ if ((base64_buf = dict_file_to_b64(dict, p)) == 0) {
+ err = dict_file_get_error(dict);
+ msg_warn("pcre map %s, line %d: %s: skipping this rule",
+ mapname, lineno, err);
+ myfree(err);
+ CREATE_MATCHOP_ERROR_RETURN(0);
+ }
+ p = vstring_str(base64_buf);
+ }
+ if (mac_parse(p, dict_pcre_prescan, (void *) &prescan_context)
+ & MAC_PARSE_ERROR) {
+ msg_warn("pcre map %s, line %d: bad replacement syntax: "
+ "skipping this rule", mapname, lineno);
+ CREATE_MATCHOP_ERROR_RETURN(0);
+ }
+
+ /*
+ * Substring replacement not possible with negative regexps.
+ */
+ if (prescan_context.max_sub > 0 && regexp.match == 0) {
+ msg_warn("pcre map %s, line %d: $number found in negative match "
+ "replacement text: skipping this rule", mapname, lineno);
+ CREATE_MATCHOP_ERROR_RETURN(0);
+ }
+ if (prescan_context.max_sub > 0 && (dict->flags & DICT_FLAG_NO_REGSUB)) {
+ msg_warn("pcre map %s, line %d: "
+ "regular expression substitution is not allowed: "
+ "skipping this rule", mapname, lineno);
+ CREATE_MATCHOP_ERROR_RETURN(0);
+ }
+
+ /*
+ * Compile the pattern.
+ */
+ if (dict_pcre_compile(mapname, lineno, &regexp, &engine) == 0)
+ CREATE_MATCHOP_ERROR_RETURN(0);
+#ifdef DICT_PCRE_CAPTURECOUNT_T
+#if HAS_PCRE == 1
+ if (pcre_fullinfo(engine.pattern, engine.hints,
+ PCRE_INFO_CAPTURECOUNT,
+ (void *) &actual_sub) != 0)
+ msg_panic("pcre map %s, line %d: pcre_fullinfo failed",
+ mapname, lineno);
+#else /* HAS_PCRE */
+#if 0
+ if (pcre2_pattern_info(
+ engine.pattern, PCRE2_INFO_NAMECOUNT, &namecount) != 0)
+ msg_panic("pcre map %s, line %d: pcre2_pattern_info failed",
+ mapname, lineno);
+ if (namecount > 0) {
+ msg_warn("pcre map %s, line %d: named substrings are not supported",
+ mapname, lineno);
+ if (engine.pattern)
+ DICT_PCRE_CODE_FREE(engine.pattern);
+ DICT_PCRE_MATCH_HINT_FREE(&engine);
+ CREATE_MATCHOP_ERROR_RETURN(0);
+ }
+#endif
+ if (pcre2_pattern_info(engine.pattern, PCRE2_INFO_CAPTURECOUNT,
+ (void *) &actual_sub) != 0)
+ msg_panic("pcre map %s, line %d: pcre2_pattern_info failed",
+ mapname, lineno);
+#endif /* HAS_PCRE */
+ if (prescan_context.max_sub > actual_sub) {
+ msg_warn("pcre map %s, line %d: out of range replacement index \"%d\": "
+ "skipping this rule", mapname, lineno,
+ (int) prescan_context.max_sub);
+ if (engine.pattern)
+ DICT_PCRE_CODE_FREE(engine.pattern);
+ DICT_PCRE_MATCH_HINT_FREE(&engine);
+ CREATE_MATCHOP_ERROR_RETURN(0);
+ }
+#endif /* DICT_PCRE_CAPTURECOUNT_T */
+
+ /*
+ * Save the result.
+ */
+ match_rule = (DICT_PCRE_MATCH_RULE *)
+ dict_pcre_rule_alloc(DICT_PCRE_OP_MATCH, lineno,
+ sizeof(DICT_PCRE_MATCH_RULE));
+ match_rule->match = regexp.match;
+ match_rule->max_sub = prescan_context.max_sub;
+ if (prescan_context.literal)
+ match_rule->replacement = prescan_context.literal;
+ else
+ match_rule->replacement = mystrdup(p);
+ match_rule->pattern = engine.pattern;
+ DICT_PCRE_MATCH_HINT(match_rule) = DICT_PCRE_MATCH_HINT(&engine);
+ return ((DICT_PCRE_RULE *) match_rule);
+ }
+
+ /*
+ * The IF operator takes one pattern but no replacement text.
+ */
+ else if (strncasecmp(p, "IF", 2) == 0 && !ISALNUM(p[2])) {
+ DICT_PCRE_REGEXP regexp;
+ DICT_PCRE_ENGINE engine;
+ DICT_PCRE_IF_RULE *if_rule;
+
+ p += 2;
+
+ /*
+ * Get the pattern.
+ */
+ while (*p && ISSPACE(*p))
+ p++;
+ if (!dict_pcre_get_pattern(mapname, lineno, &p, &regexp))
+ return (0);
+
+ /*
+ * Warn about out-of-place text.
+ */
+ while (*p && ISSPACE(*p))
+ ++p;
+ if (*p) {
+ msg_warn("pcre map %s, line %d: ignoring extra text after "
+ "IF statement: \"%s\"", mapname, lineno, p);
+ msg_warn("pcre map %s, line %d: do not prepend whitespace"
+ " to statements between IF and ENDIF", mapname, lineno);
+ }
+
+ /*
+ * Compile the pattern.
+ */
+ if (dict_pcre_compile(mapname, lineno, &regexp, &engine) == 0)
+ return (0);
+
+ /*
+ * Save the result.
+ */
+ if_rule = (DICT_PCRE_IF_RULE *)
+ dict_pcre_rule_alloc(DICT_PCRE_OP_IF, lineno,
+ sizeof(DICT_PCRE_IF_RULE));
+ if_rule->match = regexp.match;
+ if_rule->pattern = engine.pattern;
+ DICT_PCRE_MATCH_HINT(if_rule) = DICT_PCRE_MATCH_HINT(&engine);
+ if_rule->endif_rule = 0;
+ return ((DICT_PCRE_RULE *) if_rule);
+ }
+
+ /*
+ * The ENDIF operator takes no patterns and no replacement text.
+ */
+ else if (strncasecmp(p, "ENDIF", 5) == 0 && !ISALNUM(p[5])) {
+ DICT_PCRE_RULE *rule;
+
+ p += 5;
+
+ /*
+ * Warn about out-of-place ENDIFs.
+ */
+ if (nesting == 0) {
+ msg_warn("pcre map %s, line %d: ignoring ENDIF without matching IF",
+ mapname, lineno);
+ return (0);
+ }
+
+ /*
+ * Warn about out-of-place text.
+ */
+ while (*p && ISSPACE(*p))
+ ++p;
+ if (*p)
+ msg_warn("pcre map %s, line %d: ignoring extra text after ENDIF",
+ mapname, lineno);
+
+ /*
+ * Save the result.
+ */
+ rule = dict_pcre_rule_alloc(DICT_PCRE_OP_ENDIF, lineno,
+ sizeof(DICT_PCRE_RULE));
+ return (rule);
+ }
+
+ /*
+ * Unrecognized input.
+ */
+ else {
+ msg_warn("pcre map %s, line %d: ignoring unrecognized request",
+ mapname, lineno);
+ return (0);
+ }
+}
+
+/* dict_pcre_open - load and compile a file containing regular expressions */
+
+DICT *dict_pcre_open(const char *mapname, int open_flags, int dict_flags)
+{
+ const char myname[] = "dict_pcre_open";
+ DICT_PCRE *dict_pcre;
+ VSTREAM *map_fp = 0;
+ struct stat st;
+ VSTRING *why = 0;
+ VSTRING *line_buffer = 0;
+ DICT_PCRE_RULE *last_rule = 0;
+ DICT_PCRE_RULE *rule;
+ int last_line = 0;
+ int lineno;
+ int nesting = 0;
+ char *p;
+ DICT_PCRE_RULE **rule_stack = 0;
+ MVECT mvect;
+
+ /*
+ * Let the optimizer worry about eliminating redundant code.
+ */
+#define DICT_PCRE_OPEN_RETURN(d) do { \
+ DICT *__d = (d); \
+ if (map_fp != 0) \
+ vstream_fclose(map_fp); \
+ if (line_buffer != 0) \
+ vstring_free(line_buffer); \
+ if (why != 0) \
+ vstring_free(why); \
+ return (__d); \
+ } while (0)
+
+ /*
+ * Sanity checks.
+ */
+ if (open_flags != O_RDONLY)
+ DICT_PCRE_OPEN_RETURN(dict_surrogate(DICT_TYPE_PCRE, mapname,
+ open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_PCRE, mapname));
+
+ /*
+ * Open the configuration file.
+ */
+ if ((map_fp = dict_stream_open(DICT_TYPE_PCRE, mapname, O_RDONLY,
+ dict_flags, &st, &why)) == 0)
+ DICT_PCRE_OPEN_RETURN(dict_surrogate(DICT_TYPE_PCRE, mapname,
+ open_flags, dict_flags,
+ "%s", vstring_str(why)));
+ line_buffer = vstring_alloc(100);
+
+ dict_pcre = (DICT_PCRE *) dict_alloc(DICT_TYPE_PCRE, mapname,
+ sizeof(*dict_pcre));
+ dict_pcre->dict.lookup = dict_pcre_lookup;
+ dict_pcre->dict.close = dict_pcre_close;
+ dict_pcre->dict.flags = dict_flags | DICT_FLAG_PATTERN;
+ if (dict_flags & DICT_FLAG_FOLD_MUL)
+ dict_pcre->dict.fold_buf = vstring_alloc(10);
+ dict_pcre->head = 0;
+ dict_pcre->expansion_buf = 0;
+
+#if HAS_PCRE == 1
+ if (dict_pcre_init == 0) {
+ pcre_malloc = (void *(*) (size_t)) mymalloc;
+ pcre_free = (void (*) (void *)) myfree;
+ dict_pcre_init = 1;
+ }
+#endif
+ dict_pcre->dict.owner.uid = st.st_uid;
+ dict_pcre->dict.owner.status = (st.st_uid != 0);
+
+ /*
+ * Parse the pcre table.
+ */
+ while (readllines(line_buffer, map_fp, &last_line, &lineno)) {
+ p = vstring_str(line_buffer);
+ trimblanks(p, 0)[0] = 0; /* Trim space at end */
+ if (*p == 0)
+ continue;
+ rule = dict_pcre_parse_rule(&dict_pcre->dict, mapname, lineno,
+ p, nesting);
+ if (rule == 0)
+ continue;
+ if (rule->op == DICT_PCRE_OP_IF) {
+ if (rule_stack == 0)
+ rule_stack = (DICT_PCRE_RULE **) mvect_alloc(&mvect,
+ sizeof(*rule_stack), nesting + 1,
+ (MVECT_FN) 0, (MVECT_FN) 0);
+ else
+ rule_stack =
+ (DICT_PCRE_RULE **) mvect_realloc(&mvect, nesting + 1);
+ rule_stack[nesting] = rule;
+ nesting++;
+ } else if (rule->op == DICT_PCRE_OP_ENDIF) {
+ DICT_PCRE_IF_RULE *if_rule;
+
+ if (nesting-- <= 0)
+ /* Already handled in dict_pcre_parse_rule(). */
+ msg_panic("%s: ENDIF without IF", myname);
+ if (rule_stack[nesting]->op != DICT_PCRE_OP_IF)
+ msg_panic("%s: unexpected rule stack element type %d",
+ myname, rule_stack[nesting]->op);
+ if_rule = (DICT_PCRE_IF_RULE *) rule_stack[nesting];
+ if_rule->endif_rule = rule;
+ }
+ if (last_rule == 0)
+ dict_pcre->head = rule;
+ else
+ last_rule->next = rule;
+ last_rule = rule;
+ }
+
+ while (nesting-- > 0)
+ msg_warn("pcre map %s, line %d: IF has no matching ENDIF",
+ mapname, rule_stack[nesting]->lineno);
+
+ if (rule_stack)
+ (void) mvect_free(&mvect);
+
+ dict_file_purge_buffers(&dict_pcre->dict);
+ DICT_PCRE_OPEN_RETURN(DICT_DEBUG (&dict_pcre->dict));
+}
+
+#endif /* HAS_PCRE */
diff --git a/src/util/dict_pcre.h b/src/util/dict_pcre.h
new file mode 100644
index 0000000..3aa4955
--- /dev/null
+++ b/src/util/dict_pcre.h
@@ -0,0 +1,43 @@
+#ifndef _DICT_PCRE_H_INCLUDED_
+#define _DICT_PCRE_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_pcre 3h
+/* SUMMARY
+/* dictionary manager interface to PCRE regular expression library
+/* SYNOPSIS
+/* #include <dict_pcre.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_PCRE "pcre"
+
+extern DICT *dict_pcre_open(const char *, int, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Andrew McNamara
+/* andrewm@connect.com.au
+/* connect.com.au Pty. Ltd.
+/* Level 3, 213 Miller St
+/* North Sydney, NSW, Australia
+/*--*/
+
+#endif
diff --git a/src/util/dict_pcre.in b/src/util/dict_pcre.in
new file mode 100644
index 0000000..b0644bd
--- /dev/null
+++ b/src/util/dict_pcre.in
@@ -0,0 +1,15 @@
+get true
+get true1
+get true2
+get truefalse2
+get 3
+get true3
+get c
+get d
+get 1235
+get 1234
+get 123
+get bar/find
+get bar/whynot
+get bar/elbereth
+get say/elbereth
diff --git a/src/util/dict_pcre.map b/src/util/dict_pcre.map
new file mode 100644
index 0000000..23c976a
--- /dev/null
+++ b/src/util/dict_pcre.map
@@ -0,0 +1,28 @@
+if /true/ fodder
+/1/ 1
+if /false/
+/2/ 2
+endif fodder
+/3/ 3
+endif
+/a/!/b/ a!b
+/c/
+/(1)(2)(3)(5)/ ($1)($2)($3)($4)($5)
+/(1)(2)(3)(4)/ ($1)($2)($3)($4)
+/(1)(2)(3)/ ($1)($2)($3)
+# trailing whitespace below
+if /bar/
+if !/xyzzy/
+/(elbereth)/ ($1)
+!/(bogus)/ ($1)
+!/find/ Don't have a liquor license
+endif
+endif
+# trailing whitespace above
+!
+# dangling endif and if
+endif
+endif
+if /./
+if /./
+/(/ unused
diff --git a/src/util/dict_pcre.ref b/src/util/dict_pcre.ref
new file mode 100644
index 0000000..9b14678
--- /dev/null
+++ b/src/util/dict_pcre.ref
@@ -0,0 +1,44 @@
+./dict_open: warning: pcre map dict_pcre.map, line 1: ignoring extra text after IF statement: "fodder"
+./dict_open: warning: pcre map dict_pcre.map, line 1: do not prepend whitespace to statements between IF and ENDIF
+./dict_open: warning: pcre map dict_pcre.map, line 5: ignoring extra text after ENDIF
+./dict_open: warning: pcre map dict_pcre.map, line 8: unknown regexp option "!": skipping this rule
+./dict_open: warning: pcre map dict_pcre.map, line 9: no replacement text: using empty string
+./dict_open: warning: pcre map dict_pcre.map, line 10: out of range replacement index "5": skipping this rule
+./dict_open: warning: pcre map dict_pcre.map, line 17: $number found in negative match replacement text: skipping this rule
+./dict_open: warning: pcre map dict_pcre.map, line 22: no regexp: skipping this rule
+./dict_open: warning: pcre map dict_pcre.map, line 24: ignoring ENDIF without matching IF
+./dict_open: warning: pcre map dict_pcre.map, line 25: ignoring ENDIF without matching IF
+./dict_open: warning: pcre map dict_pcre.map, line 28: error in regex at offset 1: missing closing parenthesis
+./dict_open: warning: pcre map dict_pcre.map, line 27: IF has no matching ENDIF
+./dict_open: warning: pcre map dict_pcre.map, line 26: IF has no matching ENDIF
+owner=untrusted (uid=USER)
+> get true
+true: not found
+> get true1
+true1=1
+> get true2
+true2: not found
+> get truefalse2
+truefalse2=2
+> get 3
+3: not found
+> get true3
+true3=3
+> get c
+c=
+> get d
+d: not found
+> get 1235
+1235=(1)(2)(3)
+> get 1234
+1234=(1)(2)(3)(4)
+> get 123
+123=(1)(2)(3)
+> get bar/find
+bar/find: not found
+> get bar/whynot
+bar/whynot=Don't have a liquor license
+> get bar/elbereth
+bar/elbereth=(elbereth)
+> get say/elbereth
+say/elbereth: not found
diff --git a/src/util/dict_pcre_file.in b/src/util/dict_pcre_file.in
new file mode 100644
index 0000000..28c0bd5
--- /dev/null
+++ b/src/util/dict_pcre_file.in
@@ -0,0 +1,4 @@
+get file1
+get file2
+get file3
+get files12
diff --git a/src/util/dict_pcre_file.map b/src/util/dict_pcre_file.map
new file mode 100644
index 0000000..4fd12e6
--- /dev/null
+++ b/src/util/dict_pcre_file.map
@@ -0,0 +1,6 @@
+/file1/ dict_pcre_file1
+/file2/ dict_pcre_file2
+/file3/ dict_pcre_file3
+/files12/ dict_pcre_file1, dict_pcre_file2
+/files13/ dict_pcre_file1, dict_pcre_file3
+/file-comma/ ,
diff --git a/src/util/dict_pcre_file.ref b/src/util/dict_pcre_file.ref
new file mode 100644
index 0000000..727306d
--- /dev/null
+++ b/src/util/dict_pcre_file.ref
@@ -0,0 +1,12 @@
+./dict_open: warning: pcre map dict_pcre_file.map, line 3: open dict_pcre_file3: No such file or directory: skipping this rule
+./dict_open: warning: pcre map dict_pcre_file.map, line 5: open dict_pcre_file3: No such file or directory: skipping this rule
+./dict_open: warning: pcre map dict_pcre_file.map, line 6: empty pathname list: >>,<<': skipping this rule
+owner=untrusted (uid=USER)
+> get file1
+file1=dGhpcy1pcy1maWxlMQo=
+> get file2
+file2=dGhpcy1pcy1maWxlMgo=
+> get file3
+file3: not found
+> get files12
+files12=dGhpcy1pcy1maWxlMQoKdGhpcy1pcy1maWxlMgo=
diff --git a/src/util/dict_pipe.c b/src/util/dict_pipe.c
new file mode 100644
index 0000000..8ce0faa
--- /dev/null
+++ b/src/util/dict_pipe.c
@@ -0,0 +1,189 @@
+/*++
+/* NAME
+/* dict_pipe 3
+/* SUMMARY
+/* dictionary manager interface for pipelined tables
+/* SYNOPSIS
+/* #include <dict_pipe.h>
+/*
+/* DICT *dict_pipe_open(name, open_flags, dict_flags)
+/* const char *name;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_pipe_open() opens a pipeline of one or more tables.
+/* Example: "\fBpipemap:{\fItype_1:name_1, ..., type_n:name_n\fR}".
+/*
+/* Each "pipemap:" query is given to the first table. Each
+/* lookup result becomes the query for the next table in the
+/* pipeline, and the last table produces the final result.
+/* When any table lookup produces no result, the pipeline
+/* produces no result.
+/*
+/* The first and last characters of the "pipemap:" table name
+/* must be '{' and '}'. Within these, individual maps are
+/* separated with comma or whitespace.
+/*
+/* The open_flags and dict_flags arguments are passed on to
+/* the underlying dictionaries.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include "mymalloc.h"
+#include "htable.h"
+#include "dict.h"
+#include "dict_pipe.h"
+#include "stringops.h"
+#include "vstring.h"
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ ARGV *map_pipe; /* pipelined tables */
+ VSTRING *qr_buf; /* query/reply buffer */
+} DICT_PIPE;
+
+#define STR(x) vstring_str(x)
+
+/* dict_pipe_lookup - search pipelined tables */
+
+static const char *dict_pipe_lookup(DICT *dict, const char *query)
+{
+ static const char myname[] = "dict_pipe_lookup";
+ DICT_PIPE *dict_pipe = (DICT_PIPE *) dict;
+ DICT *map;
+ char **cpp;
+ char *dict_type_name;
+ const char *result = 0;
+
+ vstring_strcpy(dict_pipe->qr_buf, query);
+ for (cpp = dict_pipe->map_pipe->argv; (dict_type_name = *cpp) != 0; cpp++) {
+ if ((map = dict_handle(dict_type_name)) == 0)
+ msg_panic("%s: dictionary \"%s\" not found", myname, dict_type_name);
+ if ((result = dict_get(map, STR(dict_pipe->qr_buf))) == 0)
+ DICT_ERR_VAL_RETURN(dict, map->error, result);
+ vstring_strcpy(dict_pipe->qr_buf, result);
+ }
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, STR(dict_pipe->qr_buf));
+}
+
+/* dict_pipe_close - disassociate from pipelined tables */
+
+static void dict_pipe_close(DICT *dict)
+{
+ DICT_PIPE *dict_pipe = (DICT_PIPE *) dict;
+ char **cpp;
+ char *dict_type_name;
+
+ for (cpp = dict_pipe->map_pipe->argv; (dict_type_name = *cpp) != 0; cpp++)
+ dict_unregister(dict_type_name);
+ argv_free(dict_pipe->map_pipe);
+ vstring_free(dict_pipe->qr_buf);
+ dict_free(dict);
+}
+
+/* dict_pipe_open - open pipelined tables */
+
+DICT *dict_pipe_open(const char *name, int open_flags, int dict_flags)
+{
+ static const char myname[] = "dict_pipe_open";
+ DICT_PIPE *dict_pipe;
+ char *saved_name = 0;
+ char *dict_type_name;
+ ARGV *argv = 0;
+ char **cpp;
+ DICT *dict;
+ int match_flags = 0;
+ struct DICT_OWNER aggr_owner;
+ size_t len;
+
+ /*
+ * Clarity first. Let the optimizer worry about redundant code.
+ */
+#define DICT_PIPE_RETURN(x) do { \
+ if (saved_name != 0) \
+ myfree(saved_name); \
+ if (argv != 0) \
+ argv_free(argv); \
+ return (x); \
+ } while (0)
+
+ /*
+ * Sanity checks.
+ */
+ if (open_flags != O_RDONLY)
+ DICT_PIPE_RETURN(dict_surrogate(DICT_TYPE_PIPE, name,
+ open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_PIPE, name));
+
+ /*
+ * Split the table name into its constituent parts.
+ */
+ if ((len = balpar(name, CHARS_BRACE)) == 0 || name[len] != 0
+ || *(saved_name = mystrndup(name + 1, len - 2)) == 0
+ || ((argv = argv_splitq(saved_name, CHARS_COMMA_SP, CHARS_BRACE)),
+ (argv->argc == 0)))
+ DICT_PIPE_RETURN(dict_surrogate(DICT_TYPE_PIPE, name,
+ open_flags, dict_flags,
+ "bad syntax: \"%s:%s\"; "
+ "need \"%s:{type:name...}\"",
+ DICT_TYPE_PIPE, name,
+ DICT_TYPE_PIPE));
+
+ /*
+ * The least-trusted table in the pipeline determines the over-all trust
+ * level. The first table determines the pattern-matching flags.
+ */
+ DICT_OWNER_AGGREGATE_INIT(aggr_owner);
+ for (cpp = argv->argv; (dict_type_name = *cpp) != 0; cpp++) {
+ if (msg_verbose)
+ msg_info("%s: %s", myname, dict_type_name);
+ if (strchr(dict_type_name, ':') == 0)
+ DICT_PIPE_RETURN(dict_surrogate(DICT_TYPE_PIPE, name,
+ open_flags, dict_flags,
+ "bad syntax: \"%s:%s\"; "
+ "need \"%s:{type:name...}\"",
+ DICT_TYPE_PIPE, name,
+ DICT_TYPE_PIPE));
+ if ((dict = dict_handle(dict_type_name)) == 0)
+ dict = dict_open(dict_type_name, open_flags, dict_flags);
+ dict_register(dict_type_name, dict);
+ DICT_OWNER_AGGREGATE_UPDATE(aggr_owner, dict->owner);
+ if (cpp == argv->argv)
+ match_flags = dict->flags & (DICT_FLAG_FIXED | DICT_FLAG_PATTERN);
+ }
+
+ /*
+ * Bundle up the result.
+ */
+ dict_pipe =
+ (DICT_PIPE *) dict_alloc(DICT_TYPE_PIPE, name, sizeof(*dict_pipe));
+ dict_pipe->dict.lookup = dict_pipe_lookup;
+ dict_pipe->dict.close = dict_pipe_close;
+ dict_pipe->dict.flags = dict_flags | match_flags;
+ dict_pipe->dict.owner = aggr_owner;
+ dict_pipe->qr_buf = vstring_alloc(100);
+ dict_pipe->map_pipe = argv;
+ argv = 0;
+ DICT_PIPE_RETURN(DICT_DEBUG (&dict_pipe->dict));
+}
diff --git a/src/util/dict_pipe.h b/src/util/dict_pipe.h
new file mode 100644
index 0000000..8d03009
--- /dev/null
+++ b/src/util/dict_pipe.h
@@ -0,0 +1,37 @@
+#ifndef _DICT_PIPE_H_INCLUDED_
+#define _DICT_PIPE_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_pipe 3h
+/* SUMMARY
+/* dictionary manager interface for pipelined tables
+/* SYNOPSIS
+/* #include <dict_pipe.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_PIPE "pipemap"
+
+extern DICT *dict_pipe_open(const char *, int, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/dict_pipe_test.in b/src/util/dict_pipe_test.in
new file mode 100644
index 0000000..9626dcd
--- /dev/null
+++ b/src/util/dict_pipe_test.in
@@ -0,0 +1,9 @@
+${VALGRIND} ./dict_open 'pipemap:{inline:{k1=v1,k2=v2},inline:{v2=v3}}' read <<EOF
+get k0
+get k1
+get k2
+EOF
+${VALGRIND} ./dict_open 'pipemap:{inline:{k1=v1},fail:fail}' read <<EOF
+get k0
+get k1
+EOF
diff --git a/src/util/dict_pipe_test.ref b/src/util/dict_pipe_test.ref
new file mode 100644
index 0000000..ecb865a
--- /dev/null
+++ b/src/util/dict_pipe_test.ref
@@ -0,0 +1,14 @@
++ ./dict_open pipemap:{inline:{k1=v1,k2=v2},inline:{v2=v3}} read
+owner=trusted (uid=2147483647)
+> get k0
+k0: not found
+> get k1
+k1: not found
+> get k2
+k2=v3
++ ./dict_open pipemap:{inline:{k1=v1},fail:fail} read
+owner=trusted (uid=2147483647)
+> get k0
+k0: not found
+> get k1
+k1: error
diff --git a/src/util/dict_random.c b/src/util/dict_random.c
new file mode 100644
index 0000000..36f79b3
--- /dev/null
+++ b/src/util/dict_random.c
@@ -0,0 +1,179 @@
+/*++
+/* NAME
+/* dict_random 3
+/* SUMMARY
+/* dictionary manager interface for randomized tables
+/* SYNOPSIS
+/* #include <dict_random.h>
+/*
+/* DICT *dict_random_open(name, open_flags, dict_flags)
+/* const char *name;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_random_open() opens an in-memory, read-only, table.
+/* Example: "\fBrandmap:{\fIresult_1, ... ,result_n}\fR".
+/*
+/* Each table query returns a random choice from the specified
+/* results. Other table access methods are not supported.
+/*
+/* The first and last characters of the "randmap:" table name
+/* must be '{' and '}'. Within these, individual maps are
+/* separated with comma or whitespace.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <myrand.h>
+#include <stringops.h>
+#include <dict_random.h>
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ ARGV *replies; /* reply values */
+} DICT_RANDOM;
+
+#define STR(x) vstring_str(x)
+
+/* dict_random_lookup - find randomized-table entry */
+
+static const char *dict_random_lookup(DICT *dict, const char *unused_query)
+{
+ DICT_RANDOM *dict_random = (DICT_RANDOM *) dict;
+
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE,
+ dict_random->replies->argv[myrand() % dict_random->replies->argc]);
+}
+
+/* dict_random_close - disassociate from randomized table */
+
+static void dict_random_close(DICT *dict)
+{
+ DICT_RANDOM *dict_random = (DICT_RANDOM *) dict;
+
+ if (dict_random->replies)
+ argv_free(dict_random->replies);
+ dict_free(dict);
+}
+
+static char *dict_random_parse_name(DICT *dict, ARGV **argv,
+ const char *string,
+ const char *delim,
+ const char *parens)
+{
+ ARGV *argvp = argv_alloc(1);
+ char *saved_string = mystrdup(string);
+ char *bp = saved_string;
+ char *arg;
+ VSTRING *b64 = 0;
+ char *err = 0;
+
+ while ((arg = mystrtokq(&bp, delim, parens)) != 0) {
+ if (dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) {
+ if ((b64 = dict_file_to_b64(dict, arg)) != 0) {
+ argv_add(argvp, vstring_str(b64), (char *) 0);
+ } else {
+ err = dict_file_get_error(dict);
+ break;
+ }
+ } else {
+ argv_add(argvp, arg, (char *) 0);
+ }
+ }
+ argv_terminate(argvp);
+ myfree(saved_string);
+ *argv = argvp;
+ return (err);
+}
+
+/* dict_random_open - open a randomized table */
+
+DICT *dict_random_open(const char *name, int open_flags, int dict_flags)
+{
+ DICT_RANDOM *dict_random;
+ char *saved_name = 0;
+ size_t len;
+ char *err = 0;
+
+ /*
+ * Clarity first. Let the optimizer worry about redundant code.
+ */
+#define DICT_RANDOM_RETURN(x) do { \
+ DICT *__d = (x); \
+ if (saved_name != 0) \
+ myfree(saved_name); \
+ if (err != 0) \
+ myfree(err); \
+ return (__d); \
+ } while (0)
+
+ /*
+ * Sanity checks.
+ */
+ if (open_flags != O_RDONLY)
+ DICT_RANDOM_RETURN(dict_surrogate(DICT_TYPE_RANDOM, name,
+ open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_RANDOM, name));
+
+ /*
+ * Bundle up preliminary results.
+ */
+ dict_random =
+ (DICT_RANDOM *) dict_alloc(DICT_TYPE_RANDOM, name, sizeof(*dict_random));
+ dict_random->dict.lookup = dict_random_lookup;
+ dict_random->dict.close = dict_random_close;
+ dict_random->dict.flags = dict_flags | DICT_FLAG_PATTERN;
+ dict_random->replies = 0;
+ dict_random->dict.owner.status = DICT_OWNER_TRUSTED;
+ dict_random->dict.owner.uid = 0;
+
+ /*
+ * Split the table name into its constituent parts.
+ */
+ if ((len = balpar(name, CHARS_BRACE)) == 0 || name[len] != 0
+ || *(saved_name = mystrndup(name + 1, len - 2)) == 0
+ || (err = dict_random_parse_name(&dict_random->dict,
+ &dict_random->replies, saved_name,
+ CHARS_COMMA_SP, CHARS_BRACE)) != 0
+ || dict_random->replies->argc == 0) {
+ dict_random_close(&dict_random->dict);
+ DICT_RANDOM_RETURN(err == 0 ?
+ dict_surrogate(DICT_TYPE_RANDOM, name,
+ open_flags, dict_flags,
+ "bad syntax: \"%s:%s\"; "
+ "need \"%s:{value...}\"",
+ DICT_TYPE_RANDOM, name,
+ DICT_TYPE_RANDOM) :
+ dict_surrogate(DICT_TYPE_RANDOM, name,
+ open_flags, dict_flags,
+ "%s", err));
+ }
+ dict_file_purge_buffers(&dict_random->dict);
+ DICT_RANDOM_RETURN(DICT_DEBUG (&dict_random->dict));
+}
diff --git a/src/util/dict_random.h b/src/util/dict_random.h
new file mode 100644
index 0000000..b143d11
--- /dev/null
+++ b/src/util/dict_random.h
@@ -0,0 +1,37 @@
+#ifndef _DICT_RANDOM_H_INCLUDED_
+#define _DICT_RANDOM_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_random 3h
+/* SUMMARY
+/* dictionary manager interface for randomized tables
+/* SYNOPSIS
+/* #include <dict_random.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_RANDOM "randmap"
+
+extern DICT *dict_random_open(const char *, int, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/dict_random.ref b/src/util/dict_random.ref
new file mode 100644
index 0000000..0993409
--- /dev/null
+++ b/src/util/dict_random.ref
@@ -0,0 +1,20 @@
+owner=trusted (uid=0)
+> get foo
+foo=123
+> get bar
+bar=123
+./dict_open: error: bad syntax: "randmap:123"; need "randmap:{value...}"
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: randmap:123 is unavailable. bad syntax: "randmap:123"; need "randmap:{value...}"
+foo: error
+./dict_open: error: bad syntax: "randmap:{ 123 "; need "randmap:{value...}"
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: randmap:{ 123 is unavailable. bad syntax: "randmap:{ 123 "; need "randmap:{value...}"
+foo: error
+./dict_open: error: bad syntax: "randmap:{ 123 }x"; need "randmap:{value...}"
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: randmap:{ 123 }x is unavailable. bad syntax: "randmap:{ 123 }x"; need "randmap:{value...}"
+foo: error
diff --git a/src/util/dict_random_file.ref b/src/util/dict_random_file.ref
new file mode 100644
index 0000000..cb01873
--- /dev/null
+++ b/src/util/dict_random_file.ref
@@ -0,0 +1,10 @@
+./dict_open: error: open fooxx: No such file or directory
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: randmap:{fooxx} is unavailable. open fooxx: No such file or directory
+foo: error
+owner=trusted (uid=0)
+> get foo
+foo=dGhpcy1pcy1maWxlMQo=
+> get bar
+bar=dGhpcy1pcy1maWxlMQo=
diff --git a/src/util/dict_regexp.c b/src/util/dict_regexp.c
new file mode 100644
index 0000000..661d939
--- /dev/null
+++ b/src/util/dict_regexp.c
@@ -0,0 +1,874 @@
+/*++
+/* NAME
+/* dict_regexp 3
+/* SUMMARY
+/* dictionary manager interface to REGEXP regular expression library
+/* SYNOPSIS
+/* #include <dict_regexp.h>
+/*
+/* DICT *dict_regexp_open(name, dummy, dict_flags)
+/* const char *name;
+/* int dummy;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_regexp_open() opens the named file and compiles the contained
+/* regular expressions. The result object can be used to match strings
+/* against the table.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* regexp_table(5) regular expression table configuration
+/* AUTHOR(S)
+/* LaMont Jones
+/* lamont@hp.com
+/*
+/* Based on PCRE dictionary contributed by Andrew McNamara
+/* andrewm@connect.com.au
+/* connect.com.au Pty. Ltd.
+/* Level 3, 213 Miller St
+/* North Sydney, NSW, Australia
+/*
+/* Heavily rewritten by Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+
+#ifdef HAS_POSIX_REGEXP
+
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <regex.h>
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include "mymalloc.h"
+#include "msg.h"
+#include "safe.h"
+#include "vstream.h"
+#include "vstring.h"
+#include "stringops.h"
+#include "readlline.h"
+#include "dict.h"
+#include "dict_regexp.h"
+#include "mac_parse.h"
+#include "warn_stat.h"
+#include "mvect.h"
+
+ /*
+ * Support for IF/ENDIF based on an idea by Bert Driehuis.
+ */
+#define DICT_REGEXP_OP_MATCH 1 /* Match this regexp */
+#define DICT_REGEXP_OP_IF 2 /* Increase if/endif nesting on match */
+#define DICT_REGEXP_OP_ENDIF 3 /* Decrease if/endif nesting on match */
+
+ /*
+ * Regular expression before compiling.
+ */
+typedef struct {
+ char *regexp; /* regular expression */
+ int options; /* regcomp() options */
+ int match; /* positive or negative match */
+} DICT_REGEXP_PATTERN;
+
+ /*
+ * Compiled generic rule, and subclasses that derive from it.
+ */
+typedef struct DICT_REGEXP_RULE {
+ int op; /* DICT_REGEXP_OP_MATCH/IF/ENDIF */
+ int lineno; /* source file line number */
+ struct DICT_REGEXP_RULE *next; /* next rule in dict */
+} DICT_REGEXP_RULE;
+
+typedef struct {
+ DICT_REGEXP_RULE rule; /* generic part */
+ regex_t *first_exp; /* compiled primary pattern */
+ int first_match; /* positive or negative match */
+ regex_t *second_exp; /* compiled secondary pattern */
+ int second_match; /* positive or negative match */
+ char *replacement; /* replacement text */
+ size_t max_sub; /* largest $number in replacement */
+} DICT_REGEXP_MATCH_RULE;
+
+typedef struct {
+ DICT_REGEXP_RULE rule; /* generic members */
+ regex_t *expr; /* the condition */
+ int match; /* positive or negative match */
+ struct DICT_REGEXP_RULE *endif_rule;/* matching endif rule */
+} DICT_REGEXP_IF_RULE;
+
+ /*
+ * Regexp map.
+ */
+typedef struct {
+ DICT dict; /* generic members */
+ regmatch_t *pmatch; /* matched substring info */
+ DICT_REGEXP_RULE *head; /* first rule */
+ VSTRING *expansion_buf; /* lookup result */
+} DICT_REGEXP;
+
+ /*
+ * Macros to make dense code more readable.
+ */
+#define NULL_SUBSTITUTIONS (0)
+#define NULL_MATCH_RESULT ((regmatch_t *) 0)
+
+ /*
+ * Context for $number expansion callback.
+ */
+typedef struct {
+ DICT_REGEXP *dict_regexp; /* the dictionary handle */
+ DICT_REGEXP_MATCH_RULE *match_rule; /* the rule we matched */
+ const char *lookup_string; /* matched text */
+} DICT_REGEXP_EXPAND_CONTEXT;
+
+ /*
+ * Context for $number pre-scan callback.
+ */
+typedef struct {
+ const char *mapname; /* name of regexp map */
+ int lineno; /* where in file */
+ size_t max_sub; /* largest $number seen */
+ char *literal; /* constant result, $$ -> $ */
+} DICT_REGEXP_PRESCAN_CONTEXT;
+
+ /*
+ * Compatibility.
+ */
+#ifndef MAC_PARSE_OK
+#define MAC_PARSE_OK 0
+#endif
+
+/* dict_regexp_expand - replace $number with substring from matched text */
+
+static int dict_regexp_expand(int type, VSTRING *buf, void *ptr)
+{
+ DICT_REGEXP_EXPAND_CONTEXT *ctxt = (DICT_REGEXP_EXPAND_CONTEXT *) ptr;
+ DICT_REGEXP_MATCH_RULE *match_rule = ctxt->match_rule;
+ DICT_REGEXP *dict_regexp = ctxt->dict_regexp;
+ regmatch_t *pmatch;
+ size_t n;
+
+ /*
+ * Replace $number by the corresponding substring from the matched text.
+ * We pre-scanned the replacement text at compile time, so any out of
+ * range $number means that something impossible has happened.
+ */
+ if (type == MAC_PARSE_VARNAME) {
+ n = atoi(vstring_str(buf));
+ if (n < 1 || n > match_rule->max_sub)
+ msg_panic("regexp map %s, line %d: out of range replacement index \"%s\"",
+ dict_regexp->dict.name, match_rule->rule.lineno,
+ vstring_str(buf));
+ pmatch = dict_regexp->pmatch + n;
+ if (pmatch->rm_so < 0 || pmatch->rm_so == pmatch->rm_eo)
+ return (MAC_PARSE_UNDEF); /* empty or not matched */
+ vstring_strncat(dict_regexp->expansion_buf,
+ ctxt->lookup_string + pmatch->rm_so,
+ pmatch->rm_eo - pmatch->rm_so);
+ return (MAC_PARSE_OK);
+ }
+
+ /*
+ * Straight text - duplicate with no substitution.
+ */
+ else {
+ vstring_strcat(dict_regexp->expansion_buf, vstring_str(buf));
+ return (MAC_PARSE_OK);
+ }
+}
+
+/* dict_regexp_regerror - report regexp compile/execute error */
+
+static void dict_regexp_regerror(const char *mapname, int lineno, int error,
+ const regex_t *expr)
+{
+ char errbuf[256];
+
+ (void) regerror(error, expr, errbuf, sizeof(errbuf));
+ msg_warn("regexp map %s, line %d: %s", mapname, lineno, errbuf);
+}
+
+ /*
+ * Inlined to reduce function call overhead in the time-critical loop.
+ */
+#define DICT_REGEXP_REGEXEC(err, map, line, expr, match, str, nsub, pmatch) \
+ ((err) = regexec((expr), (str), (nsub), (pmatch), 0), \
+ ((err) == REG_NOMATCH ? !(match) : \
+ (err) == 0 ? (match) : \
+ (dict_regexp_regerror((map), (line), (err), (expr)), 0)))
+
+/* dict_regexp_lookup - match string and perform optional substitution */
+
+static const char *dict_regexp_lookup(DICT *dict, const char *lookup_string)
+{
+ DICT_REGEXP *dict_regexp = (DICT_REGEXP *) dict;
+ DICT_REGEXP_RULE *rule;
+ DICT_REGEXP_IF_RULE *if_rule;
+ DICT_REGEXP_MATCH_RULE *match_rule;
+ DICT_REGEXP_EXPAND_CONTEXT expand_context;
+ int error;
+
+ dict->error = 0;
+
+ if (msg_verbose)
+ msg_info("dict_regexp_lookup: %s: %s", dict->name, lookup_string);
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_MUL) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, lookup_string);
+ lookup_string = lowercase(vstring_str(dict->fold_buf));
+ }
+ for (rule = dict_regexp->head; rule; rule = rule->next) {
+
+ switch (rule->op) {
+
+ /*
+ * Search for the first matching primary expression. Limit the
+ * overhead for substring substitution to the bare minimum.
+ */
+ case DICT_REGEXP_OP_MATCH:
+ match_rule = (DICT_REGEXP_MATCH_RULE *) rule;
+ if (!DICT_REGEXP_REGEXEC(error, dict->name, rule->lineno,
+ match_rule->first_exp,
+ match_rule->first_match,
+ lookup_string,
+ match_rule->max_sub > 0 ?
+ match_rule->max_sub + 1 : 0,
+ dict_regexp->pmatch))
+ continue;
+ if (match_rule->second_exp
+ && !DICT_REGEXP_REGEXEC(error, dict->name, rule->lineno,
+ match_rule->second_exp,
+ match_rule->second_match,
+ lookup_string,
+ NULL_SUBSTITUTIONS,
+ NULL_MATCH_RESULT))
+ continue;
+
+ /*
+ * Skip $number substitutions when the replacement text contains
+ * no $number strings, as learned during the compile time
+ * pre-scan. The pre-scan already replaced $$ by $.
+ */
+ if (match_rule->max_sub == 0)
+ return (match_rule->replacement);
+
+ /*
+ * Perform $number substitutions on the replacement text. We
+ * pre-scanned the replacement text at compile time. Any macro
+ * expansion errors at this point mean something impossible has
+ * happened.
+ */
+ if (!dict_regexp->expansion_buf)
+ dict_regexp->expansion_buf = vstring_alloc(10);
+ VSTRING_RESET(dict_regexp->expansion_buf);
+ expand_context.lookup_string = lookup_string;
+ expand_context.match_rule = match_rule;
+ expand_context.dict_regexp = dict_regexp;
+
+ if (mac_parse(match_rule->replacement, dict_regexp_expand,
+ (void *) &expand_context) & MAC_PARSE_ERROR)
+ msg_panic("regexp map %s, line %d: bad replacement syntax",
+ dict->name, rule->lineno);
+ VSTRING_TERMINATE(dict_regexp->expansion_buf);
+ return (vstring_str(dict_regexp->expansion_buf));
+
+ /*
+ * Conditional.
+ */
+ case DICT_REGEXP_OP_IF:
+ if_rule = (DICT_REGEXP_IF_RULE *) rule;
+ if (DICT_REGEXP_REGEXEC(error, dict->name, rule->lineno,
+ if_rule->expr, if_rule->match, lookup_string,
+ NULL_SUBSTITUTIONS, NULL_MATCH_RESULT))
+ continue;
+ /* An IF without matching ENDIF has no "endif" rule. */
+ if ((rule = if_rule->endif_rule) == 0)
+ return (0);
+ /* FALLTHROUGH */
+
+ /*
+ * ENDIF after IF.
+ */
+ case DICT_REGEXP_OP_ENDIF:
+ continue;
+
+ default:
+ msg_panic("dict_regexp_lookup: impossible operation %d", rule->op);
+ }
+ }
+ return (0);
+}
+
+/* dict_regexp_close - close regexp dictionary */
+
+static void dict_regexp_close(DICT *dict)
+{
+ DICT_REGEXP *dict_regexp = (DICT_REGEXP *) dict;
+ DICT_REGEXP_RULE *rule;
+ DICT_REGEXP_RULE *next;
+ DICT_REGEXP_MATCH_RULE *match_rule;
+ DICT_REGEXP_IF_RULE *if_rule;
+
+ for (rule = dict_regexp->head; rule; rule = next) {
+ next = rule->next;
+ switch (rule->op) {
+ case DICT_REGEXP_OP_MATCH:
+ match_rule = (DICT_REGEXP_MATCH_RULE *) rule;
+ if (match_rule->first_exp) {
+ regfree(match_rule->first_exp);
+ myfree((void *) match_rule->first_exp);
+ }
+ if (match_rule->second_exp) {
+ regfree(match_rule->second_exp);
+ myfree((void *) match_rule->second_exp);
+ }
+ if (match_rule->replacement)
+ myfree((void *) match_rule->replacement);
+ break;
+ case DICT_REGEXP_OP_IF:
+ if_rule = (DICT_REGEXP_IF_RULE *) rule;
+ if (if_rule->expr) {
+ regfree(if_rule->expr);
+ myfree((void *) if_rule->expr);
+ }
+ break;
+ case DICT_REGEXP_OP_ENDIF:
+ break;
+ default:
+ msg_panic("dict_regexp_close: unknown operation %d", rule->op);
+ }
+ myfree((void *) rule);
+ }
+ if (dict_regexp->pmatch)
+ myfree((void *) dict_regexp->pmatch);
+ if (dict_regexp->expansion_buf)
+ vstring_free(dict_regexp->expansion_buf);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_regexp_get_pat - extract one pattern with options from rule */
+
+static int dict_regexp_get_pat(const char *mapname, int lineno, char **bufp,
+ DICT_REGEXP_PATTERN *pat)
+{
+ char *p = *bufp;
+ char re_delim;
+
+ /*
+ * Process negation operators.
+ */
+ pat->match = 1;
+ for (;;) {
+ if (*p == '!')
+ pat->match = !pat->match;
+ else if (!ISSPACE(*p))
+ break;
+ p++;
+ }
+ if (*p == 0) {
+ msg_warn("regexp map %s, line %d: no regexp: skipping this rule",
+ mapname, lineno);
+ return (0);
+ }
+
+ /*
+ * Search for the closing delimiter, handling backslash escape.
+ */
+ re_delim = *p++;
+ pat->regexp = p;
+ while (*p) {
+ if (*p == '\\') {
+ if (p[1])
+ p++;
+ else
+ break;
+ } else if (*p == re_delim) {
+ break;
+ }
+ ++p;
+ }
+ if (!*p) {
+ msg_warn("regexp map %s, line %d: no closing regexp delimiter \"%c\": "
+ "skipping this rule", mapname, lineno, re_delim);
+ return (0);
+ }
+ *p++ = 0; /* null terminate */
+
+ /*
+ * Search for options.
+ */
+ pat->options = REG_EXTENDED | REG_ICASE;
+ while (*p && !ISSPACE(*p) && *p != '!') {
+ switch (*p) {
+ case 'i':
+ pat->options ^= REG_ICASE;
+ break;
+ case 'm':
+ pat->options ^= REG_NEWLINE;
+ break;
+ case 'x':
+ pat->options ^= REG_EXTENDED;
+ break;
+ default:
+ msg_warn("regexp map %s, line %d: unknown regexp option \"%c\": "
+ "skipping this rule", mapname, lineno, *p);
+ return (0);
+ }
+ ++p;
+ }
+ *bufp = p;
+ return (1);
+}
+
+/* dict_regexp_get_pats - get the primary and second patterns and flags */
+
+static int dict_regexp_get_pats(const char *mapname, int lineno, char **p,
+ DICT_REGEXP_PATTERN *first_pat,
+ DICT_REGEXP_PATTERN *second_pat)
+{
+
+ /*
+ * Get the primary and optional secondary patterns and their flags.
+ */
+ if (dict_regexp_get_pat(mapname, lineno, p, first_pat) == 0)
+ return (0);
+ if (**p == '!') {
+#if 0
+ static int bitrot_warned = 0;
+
+ if (bitrot_warned == 0) {
+ msg_warn("regexp file %s, line %d: /pattern1/!/pattern2/ goes away,"
+ " use \"if !/pattern2/ ... /pattern1/ ... endif\" instead",
+ mapname, lineno);
+ bitrot_warned = 1;
+ }
+#endif
+ if (dict_regexp_get_pat(mapname, lineno, p, second_pat) == 0)
+ return (0);
+ } else {
+ second_pat->regexp = 0;
+ }
+ return (1);
+}
+
+/* dict_regexp_prescan - find largest $number in replacement text */
+
+static int dict_regexp_prescan(int type, VSTRING *buf, void *context)
+{
+ DICT_REGEXP_PRESCAN_CONTEXT *ctxt = (DICT_REGEXP_PRESCAN_CONTEXT *) context;
+ size_t n;
+
+ /*
+ * Keep a copy of literal text (with $$ already replaced by $) if and
+ * only if the replacement text contains no $number expression. This way
+ * we can avoid having to scan the replacement text at lookup time.
+ */
+ if (type == MAC_PARSE_VARNAME) {
+ if (ctxt->literal) {
+ myfree(ctxt->literal);
+ ctxt->literal = 0;
+ }
+ if (!alldig(vstring_str(buf))) {
+ msg_warn("regexp map %s, line %d: non-numeric replacement index \"%s\"",
+ ctxt->mapname, ctxt->lineno, vstring_str(buf));
+ return (MAC_PARSE_ERROR);
+ }
+ n = atoi(vstring_str(buf));
+ if (n < 1) {
+ msg_warn("regexp map %s, line %d: out-of-range replacement index \"%s\"",
+ ctxt->mapname, ctxt->lineno, vstring_str(buf));
+ return (MAC_PARSE_ERROR);
+ }
+ if (n > ctxt->max_sub)
+ ctxt->max_sub = n;
+ } else if (type == MAC_PARSE_LITERAL && ctxt->max_sub == 0) {
+ if (ctxt->literal)
+ msg_panic("regexp map %s, line %d: multiple literals but no $number",
+ ctxt->mapname, ctxt->lineno);
+ ctxt->literal = mystrdup(vstring_str(buf));
+ }
+ return (MAC_PARSE_OK);
+}
+
+/* dict_regexp_compile_pat - compile one pattern */
+
+static regex_t *dict_regexp_compile_pat(const char *mapname, int lineno,
+ DICT_REGEXP_PATTERN *pat)
+{
+ int error;
+ regex_t *expr;
+
+ expr = (regex_t *) mymalloc(sizeof(*expr));
+ error = regcomp(expr, pat->regexp, pat->options);
+ if (error != 0) {
+ dict_regexp_regerror(mapname, lineno, error, expr);
+ myfree((void *) expr);
+ return (0);
+ }
+ return (expr);
+}
+
+/* dict_regexp_rule_alloc - fill in a generic rule structure */
+
+static DICT_REGEXP_RULE *dict_regexp_rule_alloc(int op, int lineno, size_t size)
+{
+ DICT_REGEXP_RULE *rule;
+
+ rule = (DICT_REGEXP_RULE *) mymalloc(size);
+ rule->op = op;
+ rule->lineno = lineno;
+ rule->next = 0;
+
+ return (rule);
+}
+
+/* dict_regexp_parseline - parse one rule */
+
+static DICT_REGEXP_RULE *dict_regexp_parseline(DICT *dict, const char *mapname,
+ int lineno, char *line,
+ int nesting)
+{
+ char *p;
+
+ p = line;
+
+ /*
+ * An ordinary rule takes one or two patterns and replacement text.
+ */
+ if (!ISALNUM(*p)) {
+ DICT_REGEXP_PATTERN first_pat;
+ DICT_REGEXP_PATTERN second_pat;
+ DICT_REGEXP_PRESCAN_CONTEXT prescan_context;
+ regex_t *first_exp = 0;
+ regex_t *second_exp;
+ DICT_REGEXP_MATCH_RULE *match_rule;
+
+ /*
+ * Get the primary and the optional secondary patterns.
+ */
+ if (!dict_regexp_get_pats(mapname, lineno, &p, &first_pat, &second_pat))
+ return (0);
+
+ /*
+ * Get the replacement text.
+ */
+ while (*p && ISSPACE(*p))
+ ++p;
+ if (!*p) {
+ msg_warn("regexp map %s, line %d: no replacement text: "
+ "using empty string", mapname, lineno);
+ }
+
+ /*
+ * Find the highest-numbered $number in the replacement text. We can
+ * speed up pattern matching 1) by passing hints to the regexp
+ * compiler, setting the REG_NOSUB flag when the replacement text
+ * contains no $number string; 2) by passing hints to the regexp
+ * execution code, limiting the amount of text that is made available
+ * for substitution.
+ */
+ prescan_context.mapname = mapname;
+ prescan_context.lineno = lineno;
+ prescan_context.max_sub = 0;
+ prescan_context.literal = 0;
+
+ /*
+ * The optimizer will eliminate code duplication and/or dead code.
+ */
+#define CREATE_MATCHOP_ERROR_RETURN(rval) do { \
+ if (first_exp) { \
+ regfree(first_exp); \
+ myfree((void *) first_exp); \
+ } \
+ if (prescan_context.literal) \
+ myfree(prescan_context.literal); \
+ return (rval); \
+ } while (0)
+
+ if (dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) {
+ VSTRING *base64_buf;
+ char *err;
+
+ if ((base64_buf = dict_file_to_b64(dict, p)) == 0) {
+ err = dict_file_get_error(dict);
+ msg_warn("regexp map %s, line %d: %s: skipping this rule",
+ mapname, lineno, err);
+ myfree(err);
+ CREATE_MATCHOP_ERROR_RETURN(0);
+ }
+ p = vstring_str(base64_buf);
+ }
+ if (mac_parse(p, dict_regexp_prescan, (void *) &prescan_context)
+ & MAC_PARSE_ERROR) {
+ msg_warn("regexp map %s, line %d: bad replacement syntax: "
+ "skipping this rule", mapname, lineno);
+ CREATE_MATCHOP_ERROR_RETURN(0);
+ }
+
+ /*
+ * Compile the primary and the optional secondary pattern. Speed up
+ * execution when no matched text needs to be substituted into the
+ * result string, or when the highest numbered substring is less than
+ * the total number of () subpatterns.
+ */
+ if (prescan_context.max_sub == 0)
+ first_pat.options |= REG_NOSUB;
+ if (prescan_context.max_sub > 0 && first_pat.match == 0) {
+ msg_warn("regexp map %s, line %d: $number found in negative match "
+ "replacement text: skipping this rule", mapname, lineno);
+ CREATE_MATCHOP_ERROR_RETURN(0);
+ }
+ if (prescan_context.max_sub > 0 && (dict->flags & DICT_FLAG_NO_REGSUB)) {
+ msg_warn("regexp map %s, line %d: "
+ "regular expression substitution is not allowed: "
+ "skipping this rule", mapname, lineno);
+ CREATE_MATCHOP_ERROR_RETURN(0);
+ }
+ if ((first_exp = dict_regexp_compile_pat(mapname, lineno,
+ &first_pat)) == 0)
+ CREATE_MATCHOP_ERROR_RETURN(0);
+ if (prescan_context.max_sub > first_exp->re_nsub) {
+ msg_warn("regexp map %s, line %d: out of range replacement index \"%d\": "
+ "skipping this rule", mapname, lineno,
+ (int) prescan_context.max_sub);
+ CREATE_MATCHOP_ERROR_RETURN(0);
+ }
+ if (second_pat.regexp != 0) {
+ second_pat.options |= REG_NOSUB;
+ if ((second_exp = dict_regexp_compile_pat(mapname, lineno,
+ &second_pat)) == 0)
+ CREATE_MATCHOP_ERROR_RETURN(0);
+ } else {
+ second_exp = 0;
+ }
+ match_rule = (DICT_REGEXP_MATCH_RULE *)
+ dict_regexp_rule_alloc(DICT_REGEXP_OP_MATCH, lineno,
+ sizeof(DICT_REGEXP_MATCH_RULE));
+ match_rule->first_exp = first_exp;
+ match_rule->first_match = first_pat.match;
+ match_rule->max_sub = prescan_context.max_sub;
+ match_rule->second_exp = second_exp;
+ match_rule->second_match = second_pat.match;
+ if (prescan_context.literal)
+ match_rule->replacement = prescan_context.literal;
+ else
+ match_rule->replacement = mystrdup(p);
+ return ((DICT_REGEXP_RULE *) match_rule);
+ }
+
+ /*
+ * The IF operator takes one pattern but no replacement text.
+ */
+ else if (strncasecmp(p, "IF", 2) == 0 && !ISALNUM(p[2])) {
+ DICT_REGEXP_PATTERN pattern;
+ regex_t *expr;
+ DICT_REGEXP_IF_RULE *if_rule;
+
+ p += 2;
+ while (*p && ISSPACE(*p))
+ p++;
+ if (!dict_regexp_get_pat(mapname, lineno, &p, &pattern))
+ return (0);
+ while (*p && ISSPACE(*p))
+ ++p;
+ if (*p) {
+ msg_warn("regexp map %s, line %d: ignoring extra text after"
+ " IF statement: \"%s\"", mapname, lineno, p);
+ msg_warn("regexp map %s, line %d: do not prepend whitespace"
+ " to statements between IF and ENDIF", mapname, lineno);
+ }
+ if ((expr = dict_regexp_compile_pat(mapname, lineno, &pattern)) == 0)
+ return (0);
+ if_rule = (DICT_REGEXP_IF_RULE *)
+ dict_regexp_rule_alloc(DICT_REGEXP_OP_IF, lineno,
+ sizeof(DICT_REGEXP_IF_RULE));
+ if_rule->expr = expr;
+ if_rule->match = pattern.match;
+ if_rule->endif_rule = 0;
+ return ((DICT_REGEXP_RULE *) if_rule);
+ }
+
+ /*
+ * The ENDIF operator takes no patterns and no replacement text.
+ */
+ else if (strncasecmp(p, "ENDIF", 5) == 0 && !ISALNUM(p[5])) {
+ DICT_REGEXP_RULE *rule;
+
+ p += 5;
+ if (nesting == 0) {
+ msg_warn("regexp map %s, line %d: ignoring ENDIF without matching IF",
+ mapname, lineno);
+ return (0);
+ }
+ while (*p && ISSPACE(*p))
+ ++p;
+ if (*p)
+ msg_warn("regexp map %s, line %d: ignoring extra text after ENDIF",
+ mapname, lineno);
+ rule = dict_regexp_rule_alloc(DICT_REGEXP_OP_ENDIF, lineno,
+ sizeof(DICT_REGEXP_RULE));
+ return (rule);
+ }
+
+ /*
+ * Unrecognized input.
+ */
+ else {
+ msg_warn("regexp map %s, line %d: ignoring unrecognized request",
+ mapname, lineno);
+ return (0);
+ }
+}
+
+/* dict_regexp_open - load and compile a file containing regular expressions */
+
+DICT *dict_regexp_open(const char *mapname, int open_flags, int dict_flags)
+{
+ const char myname[] = "dict_regexp_open";
+ DICT_REGEXP *dict_regexp;
+ VSTREAM *map_fp = 0;
+ struct stat st;
+ VSTRING *why = 0;
+ VSTRING *line_buffer = 0;
+ DICT_REGEXP_RULE *rule;
+ DICT_REGEXP_RULE *last_rule = 0;
+ int lineno;
+ int last_line = 0;
+ size_t max_sub = 0;
+ int nesting = 0;
+ char *p;
+ DICT_REGEXP_RULE **rule_stack = 0;
+ MVECT mvect;
+
+ /*
+ * Let the optimizer worry about eliminating redundant code.
+ */
+#define DICT_REGEXP_OPEN_RETURN(d) do { \
+ DICT *__d = (d); \
+ if (line_buffer != 0) \
+ vstring_free(line_buffer); \
+ if (map_fp != 0) \
+ vstream_fclose(map_fp); \
+ if (why != 0) \
+ vstring_free(why); \
+ return (__d); \
+ } while (0)
+
+ /*
+ * Sanity checks.
+ */
+ if (open_flags != O_RDONLY)
+ DICT_REGEXP_OPEN_RETURN(dict_surrogate(DICT_TYPE_REGEXP,
+ mapname, open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_REGEXP, mapname));
+
+ /*
+ * Open the configuration file.
+ */
+ if ((map_fp = dict_stream_open(DICT_TYPE_REGEXP, mapname, O_RDONLY,
+ dict_flags, &st, &why)) == 0)
+ DICT_REGEXP_OPEN_RETURN(dict_surrogate(DICT_TYPE_REGEXP, mapname,
+ open_flags, dict_flags,
+ "%s", vstring_str(why)));
+ line_buffer = vstring_alloc(100);
+
+ dict_regexp = (DICT_REGEXP *) dict_alloc(DICT_TYPE_REGEXP, mapname,
+ sizeof(*dict_regexp));
+ dict_regexp->dict.lookup = dict_regexp_lookup;
+ dict_regexp->dict.close = dict_regexp_close;
+ dict_regexp->dict.flags = dict_flags | DICT_FLAG_PATTERN;
+ if (dict_flags & DICT_FLAG_FOLD_MUL)
+ dict_regexp->dict.fold_buf = vstring_alloc(10);
+ dict_regexp->head = 0;
+ dict_regexp->pmatch = 0;
+ dict_regexp->expansion_buf = 0;
+ dict_regexp->dict.owner.uid = st.st_uid;
+ dict_regexp->dict.owner.status = (st.st_uid != 0);
+
+ /*
+ * Parse the regexp table.
+ */
+ while (readllines(line_buffer, map_fp, &last_line, &lineno)) {
+ p = vstring_str(line_buffer);
+ trimblanks(p, 0)[0] = 0;
+ if (*p == 0)
+ continue;
+ rule = dict_regexp_parseline(&dict_regexp->dict, mapname, lineno,
+ p, nesting);
+ if (rule == 0)
+ continue;
+ if (rule->op == DICT_REGEXP_OP_MATCH) {
+ if (((DICT_REGEXP_MATCH_RULE *) rule)->max_sub > max_sub)
+ max_sub = ((DICT_REGEXP_MATCH_RULE *) rule)->max_sub;
+ } else if (rule->op == DICT_REGEXP_OP_IF) {
+ if (rule_stack == 0)
+ rule_stack = (DICT_REGEXP_RULE **) mvect_alloc(&mvect,
+ sizeof(*rule_stack), nesting + 1,
+ (MVECT_FN) 0, (MVECT_FN) 0);
+ else
+ rule_stack =
+ (DICT_REGEXP_RULE **) mvect_realloc(&mvect, nesting + 1);
+ rule_stack[nesting] = rule;
+ nesting++;
+ } else if (rule->op == DICT_REGEXP_OP_ENDIF) {
+ DICT_REGEXP_IF_RULE *if_rule;
+
+ if (nesting-- <= 0)
+ /* Already handled in dict_regexp_parseline(). */
+ msg_panic("%s: ENDIF without IF", myname);
+ if (rule_stack[nesting]->op != DICT_REGEXP_OP_IF)
+ msg_panic("%s: unexpected rule stack element type %d",
+ myname, rule_stack[nesting]->op);
+ if_rule = (DICT_REGEXP_IF_RULE *) rule_stack[nesting];
+ if_rule->endif_rule = rule;
+ }
+ if (last_rule == 0)
+ dict_regexp->head = rule;
+ else
+ last_rule->next = rule;
+ last_rule = rule;
+ }
+
+ while (nesting-- > 0)
+ msg_warn("regexp map %s, line %d: IF has no matching ENDIF",
+ mapname, rule_stack[nesting]->lineno);
+
+ if (rule_stack)
+ (void) mvect_free(&mvect);
+
+ /*
+ * Allocate space for only as many matched substrings as used in the
+ * replacement text.
+ */
+ if (max_sub > 0)
+ dict_regexp->pmatch =
+ (regmatch_t *) mymalloc(sizeof(regmatch_t) * (max_sub + 1));
+
+ dict_file_purge_buffers(&dict_regexp->dict);
+ DICT_REGEXP_OPEN_RETURN(DICT_DEBUG (&dict_regexp->dict));
+}
+
+#endif
diff --git a/src/util/dict_regexp.h b/src/util/dict_regexp.h
new file mode 100644
index 0000000..2874b27
--- /dev/null
+++ b/src/util/dict_regexp.h
@@ -0,0 +1,42 @@
+#ifndef _DICT_REGEXP_H_INCLUDED_
+#define _DICT_REGEXP_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_regexp 3h
+/* SUMMARY
+/* dictionary manager interface to REGEXP regular expression library
+/* SYNOPSIS
+/* #include <dict_regexp.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_REGEXP "regexp"
+
+extern DICT *dict_regexp_open(const char *, int, int);
+
+/* AUTHOR(S)
+/* LaMont Jones
+/* lamont@hp.com
+/*
+/* Based on PCRE dictionary contributed by Andrew McNamara
+/* andrewm@connect.com.au
+/* connect.com.au Pty. Ltd.
+/* Level 3, 213 Miller St
+/* North Sydney, NSW, Australia
+/*
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/dict_regexp.in b/src/util/dict_regexp.in
new file mode 100644
index 0000000..d5f3b2e
--- /dev/null
+++ b/src/util/dict_regexp.in
@@ -0,0 +1,17 @@
+get true
+get true1
+get true2
+get truefalse2
+get 3
+get true3
+get c
+get d
+get ab
+get aa
+get 1235
+get 1234
+get 123
+get bar/find
+get bar/whynot
+get bar/elbereth
+get say/elbereth
diff --git a/src/util/dict_regexp.map b/src/util/dict_regexp.map
new file mode 100644
index 0000000..7a6cefb
--- /dev/null
+++ b/src/util/dict_regexp.map
@@ -0,0 +1,27 @@
+if /true/ fodder
+/1/ 1
+if /false/
+/2/ 2
+endif fodder
+/3/ 3
+endif
+/a/!/b/ a!b
+/c/
+/(1)(2)(3)(5)/ ($1)($2)($3)($4)($5)
+/(1)(2)(3)(4)/ ($1)($2)($3)($4)
+/(1)(2)(3)/ ($1)($2)($3)
+# trailing whitespace below
+if /bar/
+if !/xyzzy/
+/(elbereth)/ ($1)
+!/(bogus)/ ($1)
+!/find/ Don't have a liquor license
+endif
+endif
+# trailing whitespace above
+!
+# dangling endif and if
+endif
+endif
+if /./
+if /./
diff --git a/src/util/dict_regexp.ref b/src/util/dict_regexp.ref
new file mode 100644
index 0000000..2a08a3b
--- /dev/null
+++ b/src/util/dict_regexp.ref
@@ -0,0 +1,46 @@
+./dict_open: warning: regexp map dict_regexp.map, line 1: ignoring extra text after IF statement: "fodder"
+./dict_open: warning: regexp map dict_regexp.map, line 1: do not prepend whitespace to statements between IF and ENDIF
+./dict_open: warning: regexp map dict_regexp.map, line 5: ignoring extra text after ENDIF
+./dict_open: warning: regexp map dict_regexp.map, line 9: no replacement text: using empty string
+./dict_open: warning: regexp map dict_regexp.map, line 10: out of range replacement index "5": skipping this rule
+./dict_open: warning: regexp map dict_regexp.map, line 17: $number found in negative match replacement text: skipping this rule
+./dict_open: warning: regexp map dict_regexp.map, line 22: no regexp: skipping this rule
+./dict_open: warning: regexp map dict_regexp.map, line 24: ignoring ENDIF without matching IF
+./dict_open: warning: regexp map dict_regexp.map, line 25: ignoring ENDIF without matching IF
+./dict_open: warning: regexp map dict_regexp.map, line 27: IF has no matching ENDIF
+./dict_open: warning: regexp map dict_regexp.map, line 26: IF has no matching ENDIF
+owner=untrusted (uid=USER)
+> get true
+true: not found
+> get true1
+true1=1
+> get true2
+true2: not found
+> get truefalse2
+truefalse2=2
+> get 3
+3: not found
+> get true3
+true3=3
+> get c
+c=
+> get d
+d: not found
+> get ab
+ab: not found
+> get aa
+aa=a!b
+> get 1235
+1235=(1)(2)(3)
+> get 1234
+1234=(1)(2)(3)(4)
+> get 123
+123=(1)(2)(3)
+> get bar/find
+bar/find: not found
+> get bar/whynot
+bar/whynot=Don't have a liquor license
+> get bar/elbereth
+bar/elbereth=(elbereth)
+> get say/elbereth
+say/elbereth: not found
diff --git a/src/util/dict_regexp_file.in b/src/util/dict_regexp_file.in
new file mode 100644
index 0000000..fef4146
--- /dev/null
+++ b/src/util/dict_regexp_file.in
@@ -0,0 +1,3 @@
+get file1
+get file2
+get file3
diff --git a/src/util/dict_regexp_file.map b/src/util/dict_regexp_file.map
new file mode 100644
index 0000000..41c6b6a
--- /dev/null
+++ b/src/util/dict_regexp_file.map
@@ -0,0 +1,3 @@
+/file1/ dict_regexp_file1
+/file2/ dict_regexp_file2
+/file3/ dict_regexp_file3
diff --git a/src/util/dict_regexp_file.ref b/src/util/dict_regexp_file.ref
new file mode 100644
index 0000000..2218143
--- /dev/null
+++ b/src/util/dict_regexp_file.ref
@@ -0,0 +1,8 @@
+./dict_open: warning: regexp map dict_regexp_file.map, line 3: open dict_regexp_file3: No such file or directory: skipping this rule
+owner=untrusted (uid=USER)
+> get file1
+file1=dGhpcy1pcy1maWxlMQo=
+> get file2
+file2=dGhpcy1pcy1maWxlMgo=
+> get file3
+file3: not found
diff --git a/src/util/dict_sdbm.c b/src/util/dict_sdbm.c
new file mode 100644
index 0000000..23371dc
--- /dev/null
+++ b/src/util/dict_sdbm.c
@@ -0,0 +1,483 @@
+/*++
+/* NAME
+/* dict_sdbm 3
+/* SUMMARY
+/* dictionary manager interface to SDBM files
+/* SYNOPSIS
+/* #include <dict_sdbm.h>
+/*
+/* DICT *dict_sdbm_open(path, open_flags, dict_flags)
+/* const char *name;
+/* const char *path;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_sdbm_open() opens the named SDBM database and makes it available
+/* via the generic interface described in dict_open(3).
+/* DIAGNOSTICS
+/* Fatal errors: cannot open file, file write error, out of memory.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* sdbm(3) data base subroutines
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#include "sys_defs.h"
+
+/* System library. */
+
+#include <sys/stat.h>
+#include <string.h>
+#include <unistd.h>
+#ifdef HAS_SDBM
+#include <sdbm.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <htable.h>
+#include <iostuff.h>
+#include <vstring.h>
+#include <myflock.h>
+#include <stringops.h>
+#include <dict.h>
+#include <dict_sdbm.h>
+#include <warn_stat.h>
+
+#ifdef HAS_SDBM
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ SDBM *dbm; /* open database */
+ VSTRING *key_buf; /* key buffer */
+ VSTRING *val_buf; /* result buffer */
+} DICT_SDBM;
+
+#define SCOPY(buf, data, size) \
+ vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size))
+
+/* dict_sdbm_lookup - find database entry */
+
+static const char *dict_sdbm_lookup(DICT *dict, const char *name)
+{
+ DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict;
+ datum dbm_key;
+ datum dbm_value;
+ const char *result = 0;
+
+ dict->error = 0;
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_sdbm_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+
+ /*
+ * Acquire an exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name);
+
+ /*
+ * See if this DBM file was written with one null byte appended to key
+ * and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ dbm_key.dptr = (void *) name;
+ dbm_key.dsize = strlen(name) + 1;
+ dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key);
+ if (dbm_value.dptr != 0) {
+ dict->flags &= ~DICT_FLAG_TRY0NULL;
+ result = SCOPY(dict_sdbm->val_buf, dbm_value.dptr, dbm_value.dsize);
+ }
+ }
+
+ /*
+ * See if this DBM file was written with no null byte appended to key and
+ * value.
+ */
+ if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
+ dbm_key.dptr = (void *) name;
+ dbm_key.dsize = strlen(name);
+ dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key);
+ if (dbm_value.dptr != 0) {
+ dict->flags &= ~DICT_FLAG_TRY1NULL;
+ result = SCOPY(dict_sdbm->val_buf, dbm_value.dptr, dbm_value.dsize);
+ }
+ }
+
+ /*
+ * Release the exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name);
+
+ return (result);
+}
+
+/* dict_sdbm_update - add or update database entry */
+
+static int dict_sdbm_update(DICT *dict, const char *name, const char *value)
+{
+ DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict;
+ datum dbm_key;
+ datum dbm_value;
+ int status;
+
+ dict->error = 0;
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_sdbm_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+ dbm_key.dptr = (void *) name;
+ dbm_value.dptr = (void *) value;
+ dbm_key.dsize = strlen(name);
+ dbm_value.dsize = strlen(value);
+
+ /*
+ * If undecided about appending a null byte to key and value, choose a
+ * default depending on the platform.
+ */
+ if ((dict->flags & DICT_FLAG_TRY1NULL)
+ && (dict->flags & DICT_FLAG_TRY0NULL)) {
+#ifdef DBM_NO_TRAILING_NULL
+ dict->flags &= ~DICT_FLAG_TRY1NULL;
+#else
+ dict->flags &= ~DICT_FLAG_TRY0NULL;
+#endif
+ }
+
+ /*
+ * Optionally append a null byte to key and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ dbm_key.dsize++;
+ dbm_value.dsize++;
+ }
+
+ /*
+ * Acquire an exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name);
+
+ /*
+ * Do the update.
+ */
+ if ((status = sdbm_store(dict_sdbm->dbm, dbm_key, dbm_value,
+ (dict->flags & DICT_FLAG_DUP_REPLACE) ? DBM_REPLACE : DBM_INSERT)) < 0)
+ msg_fatal("error writing SDBM database %s: %m", dict_sdbm->dict.name);
+ if (status) {
+ if (dict->flags & DICT_FLAG_DUP_IGNORE)
+ /* void */ ;
+ else if (dict->flags & DICT_FLAG_DUP_WARN)
+ msg_warn("%s: duplicate entry: \"%s\"", dict_sdbm->dict.name, name);
+ else
+ msg_fatal("%s: duplicate entry: \"%s\"", dict_sdbm->dict.name, name);
+ }
+
+ /*
+ * Release the exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name);
+
+ return (status);
+}
+
+/* dict_sdbm_delete - delete one entry from the dictionary */
+
+static int dict_sdbm_delete(DICT *dict, const char *name)
+{
+ DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict;
+ datum dbm_key;
+ int status = 1;
+
+ dict->error = 0;
+
+ /*
+ * Sanity check.
+ */
+ if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
+ msg_panic("dict_sdbm_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, name);
+ name = lowercase(vstring_str(dict->fold_buf));
+ }
+
+ /*
+ * Acquire an exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name);
+
+ /*
+ * See if this DBM file was written with one null byte appended to key
+ * and value.
+ */
+ if (dict->flags & DICT_FLAG_TRY1NULL) {
+ dbm_key.dptr = (void *) name;
+ dbm_key.dsize = strlen(name) + 1;
+ sdbm_clearerr(dict_sdbm->dbm);
+ if ((status = sdbm_delete(dict_sdbm->dbm, dbm_key)) < 0) {
+ if (sdbm_error(dict_sdbm->dbm) != 0)/* fatal error */
+ msg_fatal("error deleting from %s: %m", dict_sdbm->dict.name);
+ status = 1; /* not found */
+ } else {
+ dict->flags &= ~DICT_FLAG_TRY0NULL; /* found */
+ }
+ }
+
+ /*
+ * See if this DBM file was written with no null byte appended to key and
+ * value.
+ */
+ if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
+ dbm_key.dptr = (void *) name;
+ dbm_key.dsize = strlen(name);
+ sdbm_clearerr(dict_sdbm->dbm);
+ if ((status = sdbm_delete(dict_sdbm->dbm, dbm_key)) < 0) {
+ if (sdbm_error(dict_sdbm->dbm) != 0)/* fatal error */
+ msg_fatal("error deleting from %s: %m", dict_sdbm->dict.name);
+ status = 1; /* not found */
+ } else {
+ dict->flags &= ~DICT_FLAG_TRY1NULL; /* found */
+ }
+ }
+
+ /*
+ * Release the exclusive lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name);
+
+ return (status);
+}
+
+/* traverse the dictionary */
+
+static int dict_sdbm_sequence(DICT *dict, const int function,
+ const char **key, const char **value)
+{
+ const char *myname = "dict_sdbm_sequence";
+ DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict;
+ datum dbm_key;
+ datum dbm_value;
+ int status;
+
+ dict->error = 0;
+
+ /*
+ * Acquire a shared lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
+ msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name);
+
+ /*
+ * Determine and execute the seek function. It returns the key.
+ */
+ sdbm_clearerr(dict_sdbm->dbm);
+ switch (function) {
+ case DICT_SEQ_FUN_FIRST:
+ dbm_key = sdbm_firstkey(dict_sdbm->dbm);
+ break;
+ case DICT_SEQ_FUN_NEXT:
+ dbm_key = sdbm_nextkey(dict_sdbm->dbm);
+ break;
+ default:
+ msg_panic("%s: invalid function: %d", myname, function);
+ }
+
+ if (dbm_key.dptr != 0 && dbm_key.dsize > 0) {
+
+ /*
+ * Copy the key so that it is guaranteed null terminated.
+ */
+ *key = SCOPY(dict_sdbm->key_buf, dbm_key.dptr, dbm_key.dsize);
+
+ /*
+ * Fetch the corresponding value.
+ */
+ dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key);
+
+ if (dbm_value.dptr != 0 && dbm_value.dsize > 0) {
+
+ /*
+ * Copy the value so that it is guaranteed null terminated.
+ */
+ *value = SCOPY(dict_sdbm->val_buf, dbm_value.dptr, dbm_value.dsize);
+ status = 0;
+ } else {
+
+ /*
+ * Determine if we have hit the last record or an error
+ * condition.
+ */
+ if (sdbm_error(dict_sdbm->dbm))
+ msg_fatal("error seeking %s: %m", dict_sdbm->dict.name);
+ status = 1; /* no error: eof/not found
+ * (should not happen!) */
+ }
+ } else {
+
+ /*
+ * Determine if we have hit the last record or an error condition.
+ */
+ if (sdbm_error(dict_sdbm->dbm))
+ msg_fatal("error seeking %s: %m", dict_sdbm->dict.name);
+ status = 1; /* no error: eof/not found */
+ }
+
+ /*
+ * Release the shared lock.
+ */
+ if ((dict->flags & DICT_FLAG_LOCK)
+ && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name);
+
+ return (status);
+}
+
+/* dict_sdbm_close - disassociate from data base */
+
+static void dict_sdbm_close(DICT *dict)
+{
+ DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict;
+
+ sdbm_close(dict_sdbm->dbm);
+ if (dict_sdbm->key_buf)
+ vstring_free(dict_sdbm->key_buf);
+ if (dict_sdbm->val_buf)
+ vstring_free(dict_sdbm->val_buf);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_sdbm_open - open SDBM data base */
+
+DICT *dict_sdbm_open(const char *path, int open_flags, int dict_flags)
+{
+ DICT_SDBM *dict_sdbm;
+ struct stat st;
+ SDBM *dbm;
+ char *dbm_path;
+ int lock_fd;
+
+ /*
+ * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in
+ * the time domain) locking while accessing individual database records.
+ *
+ * Programs such as postmap/postalias use their own large-grained (in the
+ * time domain) locks while rewriting the entire file.
+ */
+ if (dict_flags & DICT_FLAG_LOCK) {
+ dbm_path = concatenate(path, ".dir", (char *) 0);
+ if ((lock_fd = open(dbm_path, open_flags, 0644)) < 0)
+ msg_fatal("open database %s: %m", dbm_path);
+ if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
+ msg_fatal("shared-lock database %s for open: %m", dbm_path);
+ }
+
+ /*
+ * XXX sdbm_open() has no const in prototype.
+ */
+ if ((dbm = sdbm_open((char *) path, open_flags, 0644)) == 0)
+ msg_fatal("open database %s.{dir,pag}: %m", path);
+
+ if (dict_flags & DICT_FLAG_LOCK) {
+ if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
+ msg_fatal("unlock database %s for open: %m", dbm_path);
+ if (close(lock_fd) < 0)
+ msg_fatal("close database %s: %m", dbm_path);
+ }
+ dict_sdbm = (DICT_SDBM *) dict_alloc(DICT_TYPE_SDBM, path, sizeof(*dict_sdbm));
+ dict_sdbm->dict.lookup = dict_sdbm_lookup;
+ dict_sdbm->dict.update = dict_sdbm_update;
+ dict_sdbm->dict.delete = dict_sdbm_delete;
+ dict_sdbm->dict.sequence = dict_sdbm_sequence;
+ dict_sdbm->dict.close = dict_sdbm_close;
+ dict_sdbm->dict.lock_fd = sdbm_dirfno(dbm);
+ dict_sdbm->dict.stat_fd = sdbm_pagfno(dbm);
+ if (fstat(dict_sdbm->dict.stat_fd, &st) < 0)
+ msg_fatal("dict_sdbm_open: fstat: %m");
+ dict_sdbm->dict.mtime = st.st_mtime;
+ dict_sdbm->dict.owner.uid = st.st_uid;
+ dict_sdbm->dict.owner.status = (st.st_uid != 0);
+
+ /*
+ * Warn if the source file is newer than the indexed file, except when
+ * the source file changed only seconds ago.
+ */
+ if ((dict_flags & DICT_FLAG_LOCK) != 0
+ && stat(path, &st) == 0
+ && st.st_mtime > dict_sdbm->dict.mtime
+ && st.st_mtime < time((time_t *) 0) - 100)
+ msg_warn("database %s is older than source file %s", dbm_path, path);
+
+ close_on_exec(sdbm_pagfno(dbm), CLOSE_ON_EXEC);
+ close_on_exec(sdbm_dirfno(dbm), CLOSE_ON_EXEC);
+ dict_sdbm->dict.flags = dict_flags | DICT_FLAG_FIXED;
+ if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0)
+ dict_sdbm->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL);
+ if (dict_flags & DICT_FLAG_FOLD_FIX)
+ dict_sdbm->dict.fold_buf = vstring_alloc(10);
+ dict_sdbm->dbm = dbm;
+ dict_sdbm->key_buf = 0;
+ dict_sdbm->val_buf = 0;
+
+ if ((dict_flags & DICT_FLAG_LOCK))
+ myfree(dbm_path);
+
+ return (DICT_DEBUG (&dict_sdbm->dict));
+}
+
+#endif
diff --git a/src/util/dict_sdbm.h b/src/util/dict_sdbm.h
new file mode 100644
index 0000000..57b458c
--- /dev/null
+++ b/src/util/dict_sdbm.h
@@ -0,0 +1,44 @@
+#ifndef _DICT_SDBM_H_INCLUDED_
+#define _DICT_SDBM_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_dbm 3h
+/* SUMMARY
+/* dictionary manager interface to DBM files
+/* SYNOPSIS
+/* #include <dict_dbm.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+#include <mkmap.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_SDBM "sdbm"
+
+extern DICT *dict_sdbm_open(const char *, int, int);
+extern MKMAP *mkmap_sdbm_open(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/dict_seq.in b/src/util/dict_seq.in
new file mode 100644
index 0000000..998e7c4
--- /dev/null
+++ b/src/util/dict_seq.in
@@ -0,0 +1,11 @@
+put 5 5
+put 3 3
+put 4 4
+put 1 1
+put 2 2
+first
+next
+next
+next
+next
+next
diff --git a/src/util/dict_seq.ref b/src/util/dict_seq.ref
new file mode 100644
index 0000000..ce7d744
--- /dev/null
+++ b/src/util/dict_seq.ref
@@ -0,0 +1,22 @@
+> put 5 5
+5=5
+> put 3 3
+3=3
+> put 4 4
+4=4
+> put 1 1
+1=1
+> put 2 2
+2=2
+> first
+4=4
+> next
+2=2
+> next
+5=5
+> next
+3=3
+> next
+1=1
+> next
+not found
diff --git a/src/util/dict_sockmap.c b/src/util/dict_sockmap.c
new file mode 100644
index 0000000..becad1a
--- /dev/null
+++ b/src/util/dict_sockmap.c
@@ -0,0 +1,384 @@
+/*++
+/* NAME
+/* dict_sockmap 3
+/* SUMMARY
+/* dictionary manager interface to Sendmail-style socketmap server
+/* SYNOPSIS
+/* #include <dict_sockmap.h>
+/*
+/* DICT *dict_sockmap_open(map, open_flags, dict_flags)
+/* const char *map;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_sockmap_open() makes a Sendmail-style socketmap server
+/* accessible via the generic dictionary operations described
+/* in dict_open(3). The only implemented operation is dictionary
+/* lookup. This map type can be useful for simulating a dynamic
+/* lookup table.
+/*
+/* Postfix socketmap names have the form inet:host:port:socketmap-name
+/* or unix:pathname:socketmap-name, where socketmap-name
+/* specifies the socketmap name that the socketmap server uses.
+/*
+/* To test this module, build the netstring and dict_open test
+/* programs. Run "./netstring nc -l portnumber" as the server,
+/* and "./dict_open socketmap:127.0.0.1:portnumber:socketmapname"
+/* as the client.
+/* PROTOCOL
+/* .ad
+/* .fi
+/* The socketmap class implements a simple protocol: the client
+/* sends one request, and the server sends one reply.
+/* ENCODING
+/* .ad
+/* .fi
+/* Each request and reply are sent as one netstring object.
+/* REQUEST FORMAT
+/* .ad
+/* .fi
+/* .IP "<mapname> <space> <key>"
+/* Search the specified socketmap under the specified key.
+/* REPLY FORMAT
+/* .ad
+/* .fi
+/* Replies must be no longer than 100000 characters (not including
+/* the netstring encapsulation), and must have the following
+/* form:
+/* .IP "OK <space> <data>"
+/* The requested data was found.
+/* .IP "NOTFOUND <space>"
+/* The requested data was not found.
+/* .IP "TEMP <space> <reason>"
+/* .IP "TIMEOUT <space> <reason>"
+/* .IP "PERM <space> <reason>"
+/* The request failed. The reason, if non-empty, is descriptive
+/* text.
+/* SECURITY
+/* This map cannot be used for security-sensitive information,
+/* because neither the connection nor the server are authenticated.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* netstring(3) netstring stream I/O support
+/* DIAGNOSTICS
+/* Fatal errors: out of memory, unknown host or service name,
+/* attempt to update or iterate over map.
+/* BUGS
+/* The protocol limits are not yet configurable.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+
+ /*
+ * Utility library.
+ */
+#include <mymalloc.h>
+#include <msg.h>
+#include <vstream.h>
+#include <auto_clnt.h>
+#include <netstring.h>
+#include <split_at.h>
+#include <stringops.h>
+#include <htable.h>
+#include <dict_sockmap.h>
+
+ /*
+ * Socket map data structure.
+ */
+typedef struct {
+ DICT dict; /* parent class */
+ char *sockmap_name; /* on-the-wire socketmap name */
+ VSTRING *rdwr_buf; /* read/write buffer */
+ HTABLE_INFO *client_info; /* shared endpoint name and handle */
+} DICT_SOCKMAP;
+
+ /*
+ * Default limits.
+ */
+#define DICT_SOCKMAP_DEF_TIMEOUT 100 /* connect/read/write timeout */
+#define DICT_SOCKMAP_DEF_MAX_REPLY 100000 /* reply size limit */
+#define DICT_SOCKMAP_DEF_MAX_IDLE 10 /* close idle socket */
+#define DICT_SOCKMAP_DEF_MAX_TTL 100 /* close old socket */
+
+ /*
+ * Class variables.
+ */
+static int dict_sockmap_timeout = DICT_SOCKMAP_DEF_TIMEOUT;
+static int dict_sockmap_max_reply = DICT_SOCKMAP_DEF_MAX_REPLY;
+static int dict_sockmap_max_idle = DICT_SOCKMAP_DEF_MAX_IDLE;
+static int dict_sockmap_max_ttl = DICT_SOCKMAP_DEF_MAX_TTL;
+
+ /*
+ * The client handle is shared between socketmap instances that have the
+ * same inet:host:port or unix:pathame information. This could be factored
+ * out as a general module for reference-counted handles of any kind.
+ */
+static HTABLE *dict_sockmap_handles; /* shared handles */
+
+typedef struct {
+ AUTO_CLNT *client_handle; /* the client handle */
+ int refcount; /* the reference count */
+} DICT_SOCKMAP_REFC_HANDLE;
+
+#define DICT_SOCKMAP_RH_NAME(ht) (ht)->key
+#define DICT_SOCKMAP_RH_HANDLE(ht) \
+ ((DICT_SOCKMAP_REFC_HANDLE *) (ht)->value)->client_handle
+#define DICT_SOCKMAP_RH_REFCOUNT(ht) \
+ ((DICT_SOCKMAP_REFC_HANDLE *) (ht)->value)->refcount
+
+ /*
+ * Socketmap protocol elements.
+ */
+#define DICT_SOCKMAP_PROT_OK "OK"
+#define DICT_SOCKMAP_PROT_NOTFOUND "NOTFOUND"
+#define DICT_SOCKMAP_PROT_TEMP "TEMP"
+#define DICT_SOCKMAP_PROT_TIMEOUT "TIMEOUT"
+#define DICT_SOCKMAP_PROT_PERM "PERM"
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* dict_sockmap_lookup - socket map lookup */
+
+static const char *dict_sockmap_lookup(DICT *dict, const char *key)
+{
+ const char *myname = "dict_sockmap_lookup";
+ DICT_SOCKMAP *dp = (DICT_SOCKMAP *) dict;
+ AUTO_CLNT *sockmap_clnt = DICT_SOCKMAP_RH_HANDLE(dp->client_info);
+ VSTREAM *fp;
+ int netstring_err;
+ char *reply_payload;
+ int except_count;
+ const char *error_class;
+
+ if (msg_verbose)
+ msg_info("%s: key %s", myname, key);
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_MUL) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(100);
+ vstring_strcpy(dict->fold_buf, key);
+ key = lowercase(STR(dict->fold_buf));
+ }
+
+ /*
+ * We retry connection-level errors once, to make server restarts
+ * transparent.
+ */
+ for (except_count = 0; /* see below */ ; except_count++) {
+
+ /*
+ * Look up the stream.
+ */
+ if ((fp = auto_clnt_access(sockmap_clnt)) == 0) {
+ msg_warn("table %s:%s lookup error: %m", dict->type, dict->name);
+ dict->error = DICT_ERR_RETRY;
+ return (0);
+ }
+
+ /*
+ * Set up an exception handler.
+ */
+ netstring_setup(fp, dict_sockmap_timeout);
+ if ((netstring_err = vstream_setjmp(fp)) == 0) {
+
+ /*
+ * Send the query. This may raise an exception.
+ */
+ vstring_sprintf(dp->rdwr_buf, "%s %s", dp->sockmap_name, key);
+ NETSTRING_PUT_BUF(fp, dp->rdwr_buf);
+
+ /*
+ * Receive the response. This may raise an exception.
+ */
+ netstring_get(fp, dp->rdwr_buf, dict_sockmap_max_reply);
+
+ /*
+ * If we got here, then no exception was raised.
+ */
+ break;
+ }
+
+ /*
+ * Handle exceptions.
+ */
+ else {
+
+ /*
+ * We retry a broken connection only once.
+ */
+ if (except_count == 0 && netstring_err == NETSTRING_ERR_EOF
+ && errno != ETIMEDOUT) {
+ auto_clnt_recover(sockmap_clnt);
+ continue;
+ }
+
+ /*
+ * We do not retry other errors.
+ */
+ else {
+ msg_warn("table %s:%s lookup error: %s",
+ dict->type, dict->name,
+ netstring_strerror(netstring_err));
+ dict->error = DICT_ERR_RETRY;
+ return (0);
+ }
+ }
+ }
+
+ /*
+ * Parse the reply.
+ */
+ VSTRING_TERMINATE(dp->rdwr_buf);
+ reply_payload = split_at(STR(dp->rdwr_buf), ' ');
+ if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_OK) == 0) {
+ dict->error = 0;
+ return (reply_payload);
+ } else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_NOTFOUND) == 0) {
+ dict->error = 0;
+ return (0);
+ }
+ /* We got no definitive reply. */
+ if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_TEMP) == 0) {
+ error_class = "temporary";
+ dict->error = DICT_ERR_RETRY;
+ } else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_TIMEOUT) == 0) {
+ error_class = "timeout";
+ dict->error = DICT_ERR_RETRY;
+ } else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_PERM) == 0) {
+ error_class = "permanent";
+ dict->error = DICT_ERR_CONFIG;
+ } else {
+ error_class = "unknown";
+ dict->error = DICT_ERR_RETRY;
+ }
+ while (reply_payload && ISSPACE(*reply_payload))
+ reply_payload++;
+ msg_warn("%s:%s socketmap server %s error%s%.200s",
+ dict->type, dict->name, error_class,
+ reply_payload && *reply_payload ? ": " : "",
+ reply_payload && *reply_payload ?
+ printable(reply_payload, '?') : "");
+ return (0);
+}
+
+/* dict_sockmap_close - close socket map */
+
+static void dict_sockmap_close(DICT *dict)
+{
+ const char *myname = "dict_sockmap_close";
+ DICT_SOCKMAP *dp = (DICT_SOCKMAP *) dict;
+
+ if (dict_sockmap_handles == 0 || dict_sockmap_handles->used == 0)
+ msg_panic("%s: attempt to close a non-existent map", myname);
+ vstring_free(dp->rdwr_buf);
+ myfree(dp->sockmap_name);
+ if (--DICT_SOCKMAP_RH_REFCOUNT(dp->client_info) == 0) {
+ auto_clnt_free(DICT_SOCKMAP_RH_HANDLE(dp->client_info));
+ htable_delete(dict_sockmap_handles,
+ DICT_SOCKMAP_RH_NAME(dp->client_info), myfree);
+ }
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_sockmap_open - open socket map */
+
+DICT *dict_sockmap_open(const char *mapname, int open_flags, int dict_flags)
+{
+ DICT_SOCKMAP *dp;
+ char *saved_name = 0;
+ char *sockmap;
+ DICT_SOCKMAP_REFC_HANDLE *ref_handle;
+ HTABLE_INFO *client_info;
+
+ /*
+ * Let the optimizer worry about eliminating redundant code.
+ */
+#define DICT_SOCKMAP_OPEN_RETURN(d) do { \
+ DICT *__d = (d); \
+ if (saved_name != 0) \
+ myfree(saved_name); \
+ return (__d); \
+ } while (0)
+
+ /*
+ * Sanity checks.
+ */
+ if (open_flags != O_RDONLY)
+ DICT_SOCKMAP_OPEN_RETURN(dict_surrogate(DICT_TYPE_SOCKMAP, mapname,
+ open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_SOCKMAP, mapname));
+ if (dict_flags & DICT_FLAG_NO_UNAUTH)
+ DICT_SOCKMAP_OPEN_RETURN(dict_surrogate(DICT_TYPE_SOCKMAP, mapname,
+ open_flags, dict_flags,
+ "%s:%s map is not allowed for security-sensitive data",
+ DICT_TYPE_SOCKMAP, mapname));
+
+ /*
+ * Separate the socketmap name from the socketmap server name.
+ */
+ saved_name = mystrdup(mapname);
+ if ((sockmap = split_at_right(saved_name, ':')) == 0)
+ DICT_SOCKMAP_OPEN_RETURN(dict_surrogate(DICT_TYPE_SOCKMAP, mapname,
+ open_flags, dict_flags,
+ "%s requires server:socketmap argument",
+ DICT_TYPE_SOCKMAP));
+
+ /*
+ * Use one reference-counted client handle for all socketmaps with the
+ * same inet:host:port or unix:pathname information.
+ *
+ * XXX Todo: graceful degradation after endpoint syntax error.
+ */
+ if (dict_sockmap_handles == 0)
+ dict_sockmap_handles = htable_create(1);
+ if ((client_info = htable_locate(dict_sockmap_handles, saved_name)) == 0) {
+ ref_handle = (DICT_SOCKMAP_REFC_HANDLE *) mymalloc(sizeof(*ref_handle));
+ client_info = htable_enter(dict_sockmap_handles,
+ saved_name, (void *) ref_handle);
+ /* XXX Late initialization, so we can reuse macros for consistency. */
+ DICT_SOCKMAP_RH_REFCOUNT(client_info) = 1;
+ DICT_SOCKMAP_RH_HANDLE(client_info) =
+ auto_clnt_create(saved_name, dict_sockmap_timeout,
+ dict_sockmap_max_idle, dict_sockmap_max_ttl);
+ } else
+ DICT_SOCKMAP_RH_REFCOUNT(client_info) += 1;
+
+ /*
+ * Instantiate a socket map handle.
+ */
+ dp = (DICT_SOCKMAP *) dict_alloc(DICT_TYPE_SOCKMAP, mapname, sizeof(*dp));
+ dp->rdwr_buf = vstring_alloc(100);
+ dp->sockmap_name = mystrdup(sockmap);
+ dp->client_info = client_info;
+ dp->dict.lookup = dict_sockmap_lookup;
+ dp->dict.close = dict_sockmap_close;
+ /* Don't look up parent domains or network superblocks. */
+ dp->dict.flags = dict_flags | DICT_FLAG_PATTERN;
+
+ DICT_SOCKMAP_OPEN_RETURN(DICT_DEBUG (&dp->dict));
+}
diff --git a/src/util/dict_sockmap.h b/src/util/dict_sockmap.h
new file mode 100644
index 0000000..b81212a
--- /dev/null
+++ b/src/util/dict_sockmap.h
@@ -0,0 +1,37 @@
+#ifndef _DICT_SOCKMAP_H_INCLUDED_
+#define _DICT_SOCKMAP_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_sockmap 3h
+/* SUMMARY
+/* dictionary manager interface to Sendmail-stye socketmap.
+/* SYNOPSIS
+/* #include <dict_sockmap.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_SOCKMAP "socketmap"
+
+extern DICT *dict_sockmap_open(const char *, int, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/dict_static.c b/src/util/dict_static.c
new file mode 100644
index 0000000..448dde0
--- /dev/null
+++ b/src/util/dict_static.c
@@ -0,0 +1,151 @@
+/*++
+/* NAME
+/* dict_static 3
+/* SUMMARY
+/* dictionary manager interface to static variables
+/* SYNOPSIS
+/* #include <dict_static.h>
+/*
+/* DICT *dict_static_open(name, name, dict_flags)
+/* const char *name;
+/* int dummy;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_static_open() implements a dummy dictionary that returns
+/* as lookup result the dictionary name, regardless of the lookup
+/* key value.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* jeffm
+/* ghostgun.com
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <stdio.h> /* sprintf() prototype */
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include "mymalloc.h"
+#include "msg.h"
+#include "stringops.h"
+#include "dict.h"
+#include "dict_static.h"
+
+ /*
+ * Subclass of DICT.
+ */
+typedef struct {
+ DICT dict; /* parent class */
+ char *value;
+} DICT_STATIC;
+
+#define STR(x) vstring_str(x)
+
+/* dict_static_lookup - access static value*/
+
+static const char *dict_static_lookup(DICT *dict, const char *unused_name)
+{
+ DICT_STATIC *dict_static = (DICT_STATIC *) dict;
+
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, dict_static->value);
+}
+
+/* dict_static_close - close static dictionary */
+
+static void dict_static_close(DICT *dict)
+{
+ DICT_STATIC *dict_static = (DICT_STATIC *) dict;
+
+ if (dict_static->value)
+ myfree(dict_static->value);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_static_open - make association with static variable */
+
+DICT *dict_static_open(const char *name, int open_flags, int dict_flags)
+{
+ DICT_STATIC *dict_static;
+ char *err = 0;
+ char *cp, *saved_name = 0;
+ const char *value;
+ VSTRING *base64_buf;
+
+ /*
+ * Let the optimizer worry about eliminating redundant code.
+ */
+#define DICT_STATIC_OPEN_RETURN(d) do { \
+ DICT *__d = (d); \
+ if (saved_name != 0) \
+ myfree(saved_name); \
+ if (err != 0) \
+ myfree(err); \
+ return (__d); \
+ } while (0)
+
+ /*
+ * Optionally strip surrounding braces and whitespace.
+ */
+ if (name[0] == CHARS_BRACE[0]) {
+ saved_name = cp = mystrdup(name);
+ if ((err = extpar(&cp, CHARS_BRACE, EXTPAR_FLAG_STRIP)) != 0)
+ DICT_STATIC_OPEN_RETURN(dict_surrogate(DICT_TYPE_STATIC, name,
+ open_flags, dict_flags,
+ "bad %s:name syntax: %s",
+ DICT_TYPE_STATIC,
+ err));
+ value = cp;
+ } else {
+ value = name;
+ }
+
+ /*
+ * Bundle up the preliminary result.
+ */
+ dict_static = (DICT_STATIC *) dict_alloc(DICT_TYPE_STATIC, name,
+ sizeof(*dict_static));
+ dict_static->dict.lookup = dict_static_lookup;
+ dict_static->dict.close = dict_static_close;
+ dict_static->dict.flags = dict_flags | DICT_FLAG_FIXED;
+ dict_static->dict.owner.status = DICT_OWNER_TRUSTED;
+ dict_static->value = 0;
+
+ /*
+ * Optionally replace the value with file contents.
+ */
+ if (dict_flags & DICT_FLAG_SRC_RHS_IS_FILE) {
+ if ((base64_buf = dict_file_to_b64(&dict_static->dict, value)) == 0) {
+ err = dict_file_get_error(&dict_static->dict);
+ dict_close(&dict_static->dict);
+ DICT_STATIC_OPEN_RETURN(dict_surrogate(DICT_TYPE_STATIC, name,
+ open_flags, dict_flags,
+ "%s", err));
+ }
+ value = vstring_str(base64_buf);
+ }
+
+ /*
+ * Finalize the result.
+ */
+ dict_static->value = mystrdup(value);
+ dict_file_purge_buffers(&dict_static->dict);
+
+ DICT_STATIC_OPEN_RETURN(DICT_DEBUG (&(dict_static->dict)));
+}
diff --git a/src/util/dict_static.h b/src/util/dict_static.h
new file mode 100644
index 0000000..d4ad1cc
--- /dev/null
+++ b/src/util/dict_static.h
@@ -0,0 +1,35 @@
+#ifndef _DICT_STATIC_H_INCLUDED_
+#define _DICT_STATIC_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_static 3h
+/* SUMMARY
+/* dictionary manager interface to static settings
+/* SYNOPSIS
+/* #include <dict_static.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_STATIC "static"
+
+extern DICT *dict_static_open(const char *, int, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* jeffm
+/* ghostgun.com
+/*--*/
+
+#endif
diff --git a/src/util/dict_static.ref b/src/util/dict_static.ref
new file mode 100644
index 0000000..550264a
--- /dev/null
+++ b/src/util/dict_static.ref
@@ -0,0 +1,14 @@
+owner=trusted (uid=2147483647)
+> get foo
+foo=fooxx
+> get bar
+bar=fooxx
+./dict_open: error: bad static:name syntax: missing '}' in "{ foo xx "
+owner=trusted (uid=2147483647)
+./dict_open: error: bad static:name syntax: syntax error after '}' in "{ foo xx }x"
+owner=trusted (uid=2147483647)
+owner=trusted (uid=2147483647)
+> get foo
+foo=foo xx
+> get bar
+bar=foo xx
diff --git a/src/util/dict_static_file.ref b/src/util/dict_static_file.ref
new file mode 100644
index 0000000..259f9fb
--- /dev/null
+++ b/src/util/dict_static_file.ref
@@ -0,0 +1,10 @@
+./dict_open: error: open fooxx: No such file or directory
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: static:fooxx is unavailable. open fooxx: No such file or directory
+foo: error
+owner=trusted (uid=2147483647)
+> get file1
+file1=dGhpcy1pcy1maWxlMQo=
+> get file2
+file2=dGhpcy1pcy1maWxlMQo=
diff --git a/src/util/dict_stream.c b/src/util/dict_stream.c
new file mode 100644
index 0000000..e28ad71
--- /dev/null
+++ b/src/util/dict_stream.c
@@ -0,0 +1,274 @@
+/*++
+/* NAME
+/* dict_stream 3
+/* SUMMARY
+/*
+/* SYNOPSIS
+/* #include <dict.h>
+/*
+/* VSTREAM *dict_stream_open(
+/* const char *dict_type,
+/* const char *mapname,
+/* int open_flags,
+/* int dict_flags,
+/* struct stat * st,
+/* VSTRING **why)
+/* DESCRIPTION
+/* dict_stream_open() opens a dictionary, which can be specified
+/* as a file name, or as inline text enclosed with {}. If successful,
+/* dict_stream_open() returns a non-null VSTREAM pointer. Otherwise,
+/* it returns an error text through the why argument, allocating
+/* storage for the error text if the why argument points to a
+/* null pointer.
+/*
+/* When the dictionary file is specified inline, dict_stream_open()
+/* removes the outer {} from the mapname value, and removes leading
+/* or trailing comma or whitespace from the result. It then expects
+/* to find zero or more rules enclosed in {}, separated by comma
+/* and/or whitespace. dict_stream() writes each rule as one text
+/* line to an in-memory stream, without its enclosing {} and without
+/* leading or trailing whitespace. The result value is a VSTREAM
+/* pointer for the in-memory stream that can be read as a regular
+/* file.
+/* .sp
+/* inline-file = "{" 0*(0*wsp-comma rule-spec) 0*wsp-comma "}"
+/* .sp
+/* rule-spec = "{" 0*wsp rule-text 0*wsp "}"
+/* .sp
+/* rule-text = any text containing zero or more balanced {}
+/* .sp
+/* wsp-comma = wsp | ","
+/* .sp
+/* wsp = whitespace
+/*
+/* Arguments:
+/* .IP dict_type
+/* .IP open_flags
+/* .IP dict_flags
+/* The same as with dict_open(3).
+/* .IP mapname
+/* Pathname of a file with dictionary content, or inline dictionary
+/* content as specified above.
+/* .IP st
+/* File metadata with the file owner, or fake metadata with the
+/* real UID and GID of the dict_stream_open() caller. This is
+/* used for "taint" tracking (zero=trusted, non-zero=untrusted).
+/* IP why
+/* Pointer to pointer to error message storage. dict_stream_open()
+/* updates this storage when reporting an error, and allocates
+/* memory if why points to a null pointer.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <vstring.h>
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* dict_inline_to_multiline - convert inline map spec to multiline text */
+
+static char *dict_inline_to_multiline(VSTRING *vp, const char *mapname)
+{
+ char *saved_name = mystrdup(mapname);
+ char *bp = saved_name;
+ char *cp;
+ char *err = 0;
+
+ VSTRING_RESET(vp);
+ /* Strip the {} from the map "name". */
+ err = extpar(&bp, CHARS_BRACE, EXTPAR_FLAG_NONE);
+ /* Extract zero or more rules inside {}. */
+ while (err == 0 && (cp = mystrtokq(&bp, CHARS_COMMA_SP, CHARS_BRACE)) != 0)
+ if ((err = extpar(&cp, CHARS_BRACE, EXTPAR_FLAG_STRIP)) == 0)
+ /* Write rule to in-memory file. */
+ vstring_sprintf_append(vp, "%s\n", cp);
+ VSTRING_TERMINATE(vp);
+ myfree(saved_name);
+ return (err);
+}
+
+/* dict_stream_open - open inline configuration or configuration file */
+
+VSTREAM *dict_stream_open(const char *dict_type, const char *mapname,
+ int open_flags, int dict_flags,
+ struct stat * st, VSTRING **why)
+{
+ VSTRING *inline_buf = 0;
+ VSTREAM *map_fp;
+ char *err = 0;
+
+#define RETURN_0_WITH_REASON(...) do { \
+ if (*why == 0) \
+ *why = vstring_alloc(100); \
+ vstring_sprintf(*why, __VA_ARGS__); \
+ if (inline_buf != 0) \
+ vstring_free(inline_buf); \
+ if (err != 0) \
+ myfree(err); \
+ return (0); \
+ } while (0)
+
+ if (mapname[0] == CHARS_BRACE[0]) {
+ inline_buf = vstring_alloc(100);
+ if ((err = dict_inline_to_multiline(inline_buf, mapname)) != 0)
+ RETURN_0_WITH_REASON("%s map: %s", dict_type, err);
+ map_fp = vstream_memopen(inline_buf, O_RDONLY);
+ vstream_control(map_fp, VSTREAM_CTL_OWN_VSTRING, VSTREAM_CTL_END);
+ st->st_uid = getuid(); /* geteuid()? */
+ st->st_gid = getgid(); /* getegid()? */
+ return (map_fp);
+ } else {
+ if ((map_fp = vstream_fopen(mapname, open_flags, 0)) == 0)
+ RETURN_0_WITH_REASON("open %s: %m", mapname);
+ if (fstat(vstream_fileno(map_fp), st) < 0)
+ msg_fatal("fstat %s: %m", mapname);
+ return (map_fp);
+ }
+}
+
+#ifdef TEST
+
+#include <string.h>
+
+int main(int argc, char **argv)
+{
+ struct testcase {
+ const char *title;
+ const char *mapname; /* starts with brace */
+ const char *expect_err; /* null or message */
+ const char *expect_cont; /* null or content */
+ };
+
+#define EXP_NOERR 0
+#define EXP_NOCONT 0
+
+#define STRING_OR(s, text_if_null) ((s) ? (s) : (text_if_null))
+#define DICT_TYPE_TEST "test"
+
+ const char rule_spec_error[] = DICT_TYPE_TEST " map: "
+ "syntax error after '}' in \"{blah blah}x\"";
+ const char inline_config_error[] = DICT_TYPE_TEST " map: "
+ "syntax error after '}' in \"{{foo bar}, {blah blah}}x\"";
+ struct testcase testcases[] = {
+ {"normal",
+ "{{foo bar}, {blah blah}}", EXP_NOERR, "foo bar\nblah blah\n"
+ },
+ {"trims leading/trailing wsp around rule-text",
+ "{{ foo bar }, { blah blah }}", EXP_NOERR, "foo bar\nblah blah\n"
+ },
+ {"trims leading/trailing comma-wsp around rule-spec",
+ "{, ,{foo bar}, {blah blah}, ,}", EXP_NOERR, "foo bar\nblah blah\n"
+ },
+ {"empty inline-file",
+ "{, }", EXP_NOERR, ""
+ },
+ {"propagates extpar error for inline-file",
+ "{{foo bar}, {blah blah}}x", inline_config_error, EXP_NOCONT
+ },
+ {"propagates extpar error for rule-spec",
+ "{{foo bar}, {blah blah}x}", rule_spec_error, EXP_NOCONT
+ },
+ 0,
+ };
+ struct testcase *tp;
+ VSTRING *act_err = 0;
+ VSTRING *act_cont = vstring_alloc(100);
+ VSTREAM *fp;
+ struct stat st;
+ ssize_t exp_len;
+ ssize_t act_len;
+ int pass;
+ int fail;
+
+ for (pass = fail = 0, tp = testcases; tp->title; tp++) {
+ int test_passed = 0;
+
+ msg_info("RUN test case %ld %s", (long) (tp - testcases), tp->title);
+
+#if 0
+ msg_info("title=%s", tp->title);
+ msg_info("mapname=%s", tp->mapname);
+ msg_info("expect_err=%s", STRING_OR_NULL(tp->expect_err));
+ msg_info("expect_cont=%s", STRINGOR_NULL(tp->expect_cont));
+#endif
+
+ if (act_err)
+ VSTRING_RESET(act_err);
+ fp = dict_stream_open(DICT_TYPE_TEST, tp->mapname, O_RDONLY,
+ 0, &st, &act_err);
+ if (fp) {
+ if (tp->expect_err) {
+ msg_warn("test case %s: got stream, expected error", tp->title);
+ } else if (!tp->expect_err && act_err && LEN(act_err) > 0) {
+ msg_warn("test case %s: got error '%s', expected noerror",
+ tp->title, STR(act_err));
+ } else if (!tp->expect_cont) {
+ msg_warn("test case %s: got stream, expected nostream",
+ tp->title);
+ } else {
+ exp_len = strlen(tp->expect_cont);
+ if ((act_len = vstream_fread_buf(fp, act_cont, 2 * exp_len)) < 0) {
+ msg_warn("test case %s: content read error", tp->title);
+ } else {
+ VSTRING_TERMINATE(act_cont);
+ if (strcmp(tp->expect_cont, STR(act_cont)) != 0) {
+ msg_warn("test case %s: got content '%s', expected '%s'",
+ tp->title, STR(act_cont), tp->expect_cont);
+ } else {
+ test_passed = 1;
+ }
+ }
+ }
+ } else {
+ if (!tp->expect_err) {
+ msg_warn("test case %s: got nostream, expected noerror",
+ tp->title);
+ } else if (tp->expect_cont) {
+ msg_warn("test case %s: got nostream, expected stream",
+ tp->title);
+ } else if (strcmp(STR(act_err), tp->expect_err) != 0) {
+ msg_warn("test case %s: got error '%s', expected '%s'",
+ tp->title, STR(act_err), tp->expect_err);
+ } else {
+ test_passed = 1;
+ }
+
+ }
+ if (test_passed) {
+ msg_info("PASS test %ld", (long) (tp - testcases));
+ pass++;
+ } else {
+ msg_info("FAIL test %ld", (long) (tp - testcases));
+ fail++;
+ }
+ if (fp)
+ vstream_fclose(fp);
+ }
+ if (act_err)
+ vstring_free(act_err);
+ vstring_free(act_cont);
+ msg_info("PASS=%d FAIL=%d", pass, fail);
+ return (fail > 0);
+}
+
+#endif /* TEST */
diff --git a/src/util/dict_stream.ref b/src/util/dict_stream.ref
new file mode 100644
index 0000000..87c30e5
--- /dev/null
+++ b/src/util/dict_stream.ref
@@ -0,0 +1,13 @@
+unknown: RUN test case 0 normal
+unknown: PASS test 0
+unknown: RUN test case 1 trims leading/trailing wsp around rule-text
+unknown: PASS test 1
+unknown: RUN test case 2 trims leading/trailing comma-wsp around rule-spec
+unknown: PASS test 2
+unknown: RUN test case 3 empty inline-file
+unknown: PASS test 3
+unknown: RUN test case 4 propagates extpar error for inline-file
+unknown: PASS test 4
+unknown: RUN test case 5 propagates extpar error for rule-spec
+unknown: PASS test 5
+unknown: PASS=6 FAIL=0
diff --git a/src/util/dict_surrogate.c b/src/util/dict_surrogate.c
new file mode 100644
index 0000000..a23cba3
--- /dev/null
+++ b/src/util/dict_surrogate.c
@@ -0,0 +1,180 @@
+/*++
+/* NAME
+/* dict_surrogate 3
+/* SUMMARY
+/* surrogate table for graceful "open" failure
+/* SYNOPSIS
+/* #include <dict_surrogate.h>
+/*
+/* DICT *dict_surrogate(dict_type, dict_name,
+/* open_flags, dict_flags,
+/* format, ...)
+/* const char *dict_type;
+/* const char *dict_name;
+/* int open_flags;
+/* int dict_flags;
+/* const char *format;
+/*
+/* int dict_allow_surrogate;
+/* DESCRIPTION
+/* dict_surrogate() either terminates the program with a fatal
+/* error, or provides a dummy dictionary that fails all
+/* operations with an error message, allowing the program to
+/* continue with reduced functionality.
+/*
+/* The global dict_allow_surrogate variable controls the choice
+/* between fatal error or reduced functionality. The default
+/* value is zero (fatal error). This is appropriate for user
+/* commands; the non-default is more appropriate for daemons.
+/*
+/* Arguments:
+/* .IP dict_type
+/* .IP dict_name
+/* .IP open_flags
+/* .IP dict_flags
+/* The parameters to the failed dictionary open() request.
+/* .IP format, ...
+/* The reason why the table could not be opened. This text is
+/* logged immediately as an "error" class message, and is logged
+/* as a "warning" class message upon every attempt to access the
+/* surrogate dictionary, before returning a "failed" completion
+/* status.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <msg.h>
+#include <compat_va_copy.h>
+#include <dict.h>
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ char *reason; /* open failure reason */
+} DICT_SURROGATE;
+
+/* dict_surrogate_sequence - fail lookup */
+
+static int dict_surrogate_sequence(DICT *dict, int unused_func,
+ const char **key, const char **value)
+{
+ DICT_SURROGATE *dp = (DICT_SURROGATE *) dict;
+
+ msg_warn("%s:%s is unavailable. %s",
+ dict->type, dict->name, dp->reason);
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, DICT_STAT_ERROR);
+}
+
+/* dict_surrogate_update - fail lookup */
+
+static int dict_surrogate_update(DICT *dict, const char *unused_name,
+ const char *unused_value)
+{
+ DICT_SURROGATE *dp = (DICT_SURROGATE *) dict;
+
+ msg_warn("%s:%s is unavailable. %s",
+ dict->type, dict->name, dp->reason);
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, DICT_STAT_ERROR);
+}
+
+/* dict_surrogate_lookup - fail lookup */
+
+static const char *dict_surrogate_lookup(DICT *dict, const char *unused_name)
+{
+ DICT_SURROGATE *dp = (DICT_SURROGATE *) dict;
+
+ msg_warn("%s:%s is unavailable. %s",
+ dict->type, dict->name, dp->reason);
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, (char *) 0);
+}
+
+/* dict_surrogate_delete - fail delete */
+
+static int dict_surrogate_delete(DICT *dict, const char *unused_name)
+{
+ DICT_SURROGATE *dp = (DICT_SURROGATE *) dict;
+
+ msg_warn("%s:%s is unavailable. %s",
+ dict->type, dict->name, dp->reason);
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, DICT_STAT_ERROR);
+}
+
+/* dict_surrogate_close - close fail dictionary */
+
+static void dict_surrogate_close(DICT *dict)
+{
+ DICT_SURROGATE *dp = (DICT_SURROGATE *) dict;
+
+ myfree((void *) dp->reason);
+ dict_free(dict);
+}
+
+int dict_allow_surrogate = 0;
+
+/* dict_surrogate - terminate or provide surrogate dictionary */
+
+DICT *dict_surrogate(const char *dict_type, const char *dict_name,
+ int open_flags, int dict_flags,
+ const char *fmt,...)
+{
+ va_list ap;
+ va_list ap2;
+ DICT_SURROGATE *dp;
+ VSTRING *buf;
+ void (*log_fn) (const char *, va_list);
+ int saved_errno = errno;
+
+ /*
+ * Initialize argument lists.
+ */
+ va_start(ap, fmt);
+ VA_COPY(ap2, ap);
+
+ /*
+ * Log the problem immediately when it is detected. The table may not be
+ * accessed in every program execution (that is the whole point of
+ * continuing with reduced functionality) but we don't want the problem
+ * to remain unnoticed until long after a configuration mistake is made.
+ */
+ log_fn = dict_allow_surrogate ? vmsg_error : vmsg_fatal;
+ log_fn(fmt, ap);
+ va_end(ap);
+
+ /*
+ * Log the problem upon each access.
+ */
+ dp = (DICT_SURROGATE *) dict_alloc(dict_type, dict_name, sizeof(*dp));
+ dp->dict.lookup = dict_surrogate_lookup;
+ if (open_flags & O_RDWR) {
+ dp->dict.update = dict_surrogate_update;
+ dp->dict.delete = dict_surrogate_delete;
+ }
+ dp->dict.sequence = dict_surrogate_sequence;
+ dp->dict.close = dict_surrogate_close;
+ dp->dict.flags = dict_flags | DICT_FLAG_PATTERN;
+ dp->dict.owner.status = DICT_OWNER_TRUSTED;
+ buf = vstring_alloc(10);
+ errno = saved_errno;
+ vstring_vsprintf(buf, fmt, ap2);
+ va_end(ap2);
+ dp->reason = vstring_export(buf);
+ return (DICT_DEBUG (&dp->dict));
+}
diff --git a/src/util/dict_tcp.c b/src/util/dict_tcp.c
new file mode 100644
index 0000000..922f449
--- /dev/null
+++ b/src/util/dict_tcp.c
@@ -0,0 +1,315 @@
+/*++
+/* NAME
+/* dict_tcp 3
+/* SUMMARY
+/* dictionary manager interface to tcp-based lookup tables
+/* SYNOPSIS
+/* #include <dict_tcp.h>
+/*
+/* DICT *dict_tcp_open(map, open_flags, dict_flags)
+/* const char *map;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_tcp_open() makes a TCP server accessible via the generic
+/* dictionary operations described in dict_open(3).
+/* The only implemented operation is dictionary lookup. This map
+/* type can be useful for simulating a dynamic lookup table.
+/*
+/* Map names have the form host:port.
+/*
+/* The TCP map class implements a very simple protocol: the client
+/* sends a request, and the server sends one reply. Requests and
+/* replies are sent as one line of ASCII text, terminated by the
+/* ASCII newline character. Request and reply parameters (see below)
+/* are separated by whitespace.
+/* ENCODING
+/* .ad
+/* .fi
+/* In request and reply parameters, the character % and any non-printing
+/* and whitespace characters must be replaced by %XX, XX being the
+/* corresponding ASCII hexadecimal character value. The hexadecimal codes
+/* can be specified in any case (upper, lower, mixed).
+/* REQUEST FORMAT
+/* .ad
+/* .fi
+/* Requests are strings that serve as lookup key in the simulated
+/* table.
+/* .IP "get SPACE key NEWLINE"
+/* Look up data under the specified key.
+/* .IP "put SPACE key SPACE value NEWLINE"
+/* This request is currently not implemented.
+/* REPLY FORMAT
+/* .ad
+/* .fi
+/* Replies must be no longer than 4096 characters including the
+/* newline terminator, and must have the following form:
+/* .IP "500 SPACE text NEWLINE"
+/* In case of a lookup request, the requested data does not exist.
+/* In case of an update request, the request was rejected.
+/* The text gives the nature of the problem.
+/* .IP "400 SPACE text NEWLINE"
+/* This indicates an error condition. The text gives the nature of
+/* the problem. The client should retry the request later.
+/* .IP "200 SPACE text NEWLINE"
+/* The request was successful. In the case of a lookup request,
+/* the text contains an encoded version of the requested data.
+/* SECURITY
+/* This map must not be used for security sensitive information,
+/* because neither the connection nor the server are authenticated.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* hex_quote(3) http-style quoting
+/* DIAGNOSTICS
+/* Fatal errors: out of memory, unknown host or service name,
+/* attempt to update or iterate over map.
+/* BUGS
+/* Only the lookup method is currently implemented.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <connect.h>
+#include <hex_quote.h>
+#include <dict.h>
+#include <stringops.h>
+#include <dict_tcp.h>
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ VSTRING *raw_buf; /* raw I/O buffer */
+ VSTRING *hex_buf; /* quoted I/O buffer */
+ VSTREAM *fp; /* I/O stream */
+} DICT_TCP;
+
+#define DICT_TCP_MAXTRY 10 /* attempts before giving up */
+#define DICT_TCP_TMOUT 100 /* connect/read/write timeout */
+#define DICT_TCP_MAXLEN 4096 /* server reply size limit */
+
+#define STR(x) vstring_str(x)
+
+/* dict_tcp_connect - connect to TCP server */
+
+static int dict_tcp_connect(DICT_TCP *dict_tcp)
+{
+ int fd;
+
+ /*
+ * Connect to the server. Enforce a time limit on all operations so that
+ * we do not get stuck.
+ */
+ if ((fd = inet_connect(dict_tcp->dict.name, NON_BLOCKING, DICT_TCP_TMOUT)) < 0) {
+ msg_warn("connect to TCP map %s: %m", dict_tcp->dict.name);
+ return (-1);
+ }
+ dict_tcp->fp = vstream_fdopen(fd, O_RDWR);
+ vstream_control(dict_tcp->fp,
+ CA_VSTREAM_CTL_TIMEOUT(DICT_TCP_TMOUT),
+ CA_VSTREAM_CTL_END);
+
+ /*
+ * Allocate per-map I/O buffers on the fly.
+ */
+ if (dict_tcp->raw_buf == 0) {
+ dict_tcp->raw_buf = vstring_alloc(10);
+ dict_tcp->hex_buf = vstring_alloc(10);
+ }
+ return (0);
+}
+
+/* dict_tcp_disconnect - disconnect from TCP server */
+
+static void dict_tcp_disconnect(DICT_TCP *dict_tcp)
+{
+ (void) vstream_fclose(dict_tcp->fp);
+ dict_tcp->fp = 0;
+}
+
+/* dict_tcp_lookup - request TCP server */
+
+static const char *dict_tcp_lookup(DICT *dict, const char *key)
+{
+ DICT_TCP *dict_tcp = (DICT_TCP *) dict;
+ const char *myname = "dict_tcp_lookup";
+ int tries;
+ char *start;
+ int last_ch;
+
+#define RETURN(errval, result) { dict->error = errval; return (result); }
+
+ if (msg_verbose)
+ msg_info("%s: key %s", myname, key);
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_MUL) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, key);
+ key = lowercase(vstring_str(dict->fold_buf));
+ }
+ for (tries = 0; /* see below */ ; /* see below */ ) {
+
+ /*
+ * Connect to the server, or use an existing connection.
+ */
+ if (dict_tcp->fp != 0 || dict_tcp_connect(dict_tcp) == 0) {
+
+ /*
+ * Send request and receive response. Both are %XX quoted and
+ * both are terminated by newline. This encoding is convenient
+ * for data that is mostly text.
+ */
+ hex_quote(dict_tcp->hex_buf, key);
+ vstream_fprintf(dict_tcp->fp, "get %s\n", STR(dict_tcp->hex_buf));
+ if (msg_verbose)
+ msg_info("%s: send: get %s", myname, STR(dict_tcp->hex_buf));
+ last_ch = vstring_get_nonl_bound(dict_tcp->hex_buf, dict_tcp->fp,
+ DICT_TCP_MAXLEN);
+ if (last_ch == '\n')
+ break;
+
+ /*
+ * Disconnect from the server if it can't talk to us.
+ */
+ if (last_ch < 0)
+ msg_warn("read TCP map reply from %s: unexpected EOF (%m)",
+ dict_tcp->dict.name);
+ else
+ msg_warn("read TCP map reply from %s: text longer than %d",
+ dict_tcp->dict.name, DICT_TCP_MAXLEN);
+ dict_tcp_disconnect(dict_tcp);
+ }
+
+ /*
+ * Try to connect a limited number of times before giving up.
+ */
+ if (++tries >= DICT_TCP_MAXTRY)
+ RETURN(DICT_ERR_RETRY, 0);
+
+ /*
+ * Sleep between attempts, instead of hammering the server.
+ */
+ sleep(1);
+ }
+ if (msg_verbose)
+ msg_info("%s: recv: %s", myname, STR(dict_tcp->hex_buf));
+
+ /*
+ * Check the general reply syntax. If the reply is malformed, disconnect
+ * and try again later.
+ */
+ if (start = STR(dict_tcp->hex_buf),
+ !ISDIGIT(start[0]) || !ISDIGIT(start[1])
+ || !ISDIGIT(start[2]) || !ISSPACE(start[3])
+ || !hex_unquote(dict_tcp->raw_buf, start + 4)) {
+ msg_warn("read TCP map reply from %s: malformed reply: %.100s",
+ dict_tcp->dict.name, printable(STR(dict_tcp->hex_buf), '_'));
+ dict_tcp_disconnect(dict_tcp);
+ RETURN(DICT_ERR_RETRY, 0);
+ }
+
+ /*
+ * Examine the reply status code. If the reply is malformed, disconnect
+ * and try again later.
+ */
+ switch (start[0]) {
+ default:
+ msg_warn("read TCP map reply from %s: bad status code: %.100s",
+ dict_tcp->dict.name, printable(STR(dict_tcp->hex_buf), '_'));
+ dict_tcp_disconnect(dict_tcp);
+ RETURN(DICT_ERR_RETRY, 0);
+ case '4':
+ if (msg_verbose)
+ msg_info("%s: soft error: %s",
+ myname, printable(STR(dict_tcp->hex_buf), '_'));
+ dict_tcp_disconnect(dict_tcp);
+ RETURN(DICT_ERR_RETRY, 0);
+ case '5':
+ if (msg_verbose)
+ msg_info("%s: not found: %s",
+ myname, printable(STR(dict_tcp->hex_buf), '_'));
+ RETURN(DICT_ERR_NONE, 0);
+ case '2':
+ if (msg_verbose)
+ msg_info("%s: found: %s",
+ myname, printable(STR(dict_tcp->raw_buf), '_'));
+ RETURN(DICT_ERR_NONE, STR(dict_tcp->raw_buf));
+ }
+}
+
+/* dict_tcp_close - close TCP map */
+
+static void dict_tcp_close(DICT *dict)
+{
+ DICT_TCP *dict_tcp = (DICT_TCP *) dict;
+
+ if (dict_tcp->fp)
+ (void) vstream_fclose(dict_tcp->fp);
+ if (dict_tcp->raw_buf)
+ vstring_free(dict_tcp->raw_buf);
+ if (dict_tcp->hex_buf)
+ vstring_free(dict_tcp->hex_buf);
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_tcp_open - open TCP map */
+
+DICT *dict_tcp_open(const char *map, int open_flags, int dict_flags)
+{
+ DICT_TCP *dict_tcp;
+
+ /*
+ * Sanity checks.
+ */
+ if (dict_flags & DICT_FLAG_NO_UNAUTH)
+ return (dict_surrogate(DICT_TYPE_TCP, map, open_flags, dict_flags,
+ "%s:%s map is not allowed for security sensitive data",
+ DICT_TYPE_TCP, map));
+ if (open_flags != O_RDONLY)
+ return (dict_surrogate(DICT_TYPE_TCP, map, open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_TCP, map));
+
+ /*
+ * Create the dictionary handle. Do not open the connection until the
+ * first request is made.
+ */
+ dict_tcp = (DICT_TCP *) dict_alloc(DICT_TYPE_TCP, map, sizeof(*dict_tcp));
+ dict_tcp->fp = 0;
+ dict_tcp->raw_buf = dict_tcp->hex_buf = 0;
+ dict_tcp->dict.lookup = dict_tcp_lookup;
+ dict_tcp->dict.close = dict_tcp_close;
+ dict_tcp->dict.flags = dict_flags | DICT_FLAG_PATTERN;
+ if (dict_flags & DICT_FLAG_FOLD_MUL)
+ dict_tcp->dict.fold_buf = vstring_alloc(10);
+
+ return (DICT_DEBUG (&dict_tcp->dict));
+}
diff --git a/src/util/dict_tcp.h b/src/util/dict_tcp.h
new file mode 100644
index 0000000..9814c61
--- /dev/null
+++ b/src/util/dict_tcp.h
@@ -0,0 +1,37 @@
+#ifndef _DICT_TCP_H_INCLUDED_
+#define _DICT_TCP_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_tcp 3h
+/* SUMMARY
+/* dictionary manager interface to tcp-based lookup tables
+/* SYNOPSIS
+/* #include <dict_tcp.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_TCP "tcp"
+
+extern DICT *dict_tcp_open(const char *, int, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/dict_test.c b/src/util/dict_test.c
new file mode 100644
index 0000000..ead61b2
--- /dev/null
+++ b/src/util/dict_test.c
@@ -0,0 +1,166 @@
+ /*
+ * Proof-of-concept test program. Create, update or read a database. Type
+ * '?' for a list of commands.
+ */
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <stringops.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <vstring_vstream.h>
+#include <dict.h>
+#include <dict_lmdb.h>
+#include <dict_db.h>
+
+static NORETURN usage(char *myname)
+{
+ msg_fatal("usage: %s type:file read|write|create [flags...]", myname);
+}
+
+void dict_test(int argc, char **argv)
+{
+ VSTRING *keybuf = vstring_alloc(1);
+ VSTRING *inbuf = vstring_alloc(1);
+ DICT *dict;
+ char *dict_name;
+ int open_flags;
+ char *bufp;
+ char *cmd;
+ const char *key;
+ const char *value;
+ int ch;
+ int dict_flags = 0;
+ int n;
+ int rc;
+
+#define USAGE "verbose|del key|get key|put key=value|first|next|masks|flags"
+
+ signal(SIGPIPE, SIG_IGN);
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ while ((ch = GETOPT(argc, argv, "v")) > 0) {
+ switch (ch) {
+ default:
+ usage(argv[0]);
+ case 'v':
+ msg_verbose++;
+ break;
+ }
+ }
+ optind = OPTIND;
+ if (argc - optind < 2)
+ usage(argv[0]);
+ if (strcasecmp(argv[optind + 1], "create") == 0)
+ open_flags = O_CREAT | O_RDWR | O_TRUNC;
+ else if (strcasecmp(argv[optind + 1], "write") == 0)
+ open_flags = O_RDWR;
+ else if (strcasecmp(argv[optind + 1], "read") == 0)
+ open_flags = O_RDONLY;
+ else
+ msg_fatal("unknown access mode: %s", argv[2]);
+ for (n = 2; argv[optind + n]; n++)
+ dict_flags |= dict_flags_mask(argv[optind + 2]);
+ if ((dict_flags & DICT_FLAG_OPEN_LOCK) == 0)
+ dict_flags |= DICT_FLAG_LOCK;
+ if ((dict_flags & (DICT_FLAG_DUP_WARN | DICT_FLAG_DUP_IGNORE)) == 0)
+ dict_flags |= DICT_FLAG_DUP_REPLACE;
+ dict_flags |= DICT_FLAG_UTF8_REQUEST;
+ vstream_fflush(VSTREAM_OUT);
+ dict_name = argv[optind];
+ dict_allow_surrogate = 1;
+ util_utf8_enable = 1;
+ dict = dict_open(dict_name, open_flags, dict_flags);
+ dict_register(dict_name, dict);
+ vstream_printf("owner=%s (uid=%ld)\n",
+ dict->owner.status == DICT_OWNER_TRUSTED ? "trusted" :
+ dict->owner.status == DICT_OWNER_UNTRUSTED ? "untrusted" :
+ dict->owner.status == DICT_OWNER_UNKNOWN ? "unspecified" :
+ "error", (long) dict->owner.uid);
+ vstream_fflush(VSTREAM_OUT);
+
+ while (vstring_fgets_nonl(inbuf, VSTREAM_IN)) {
+ bufp = vstring_str(inbuf);
+ if (!isatty(0)) {
+ vstream_printf("> %s\n", bufp);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ if (*bufp == '#')
+ continue;
+ if ((cmd = mystrtok(&bufp, " ")) == 0) {
+ vstream_printf("usage: %s\n", USAGE);
+ vstream_fflush(VSTREAM_OUT);
+ continue;
+ }
+ if (dict_changed_name())
+ msg_warn("dictionary has changed");
+ key = *bufp ? vstring_str(unescape(keybuf, mystrtok(&bufp, " ="))) : 0;
+ value = mystrtok(&bufp, " =");
+ if (strcmp(cmd, "verbose") == 0 && !key) {
+ msg_verbose++;
+ } else if (strcmp(cmd, "del") == 0 && key && !value) {
+ if ((rc = dict_del(dict, key)) > 0)
+ vstream_printf("%s: not found\n", key);
+ else if (rc < 0)
+ vstream_printf("%s: error\n", key);
+ else
+ vstream_printf("%s: deleted\n", key);
+ } else if (strcmp(cmd, "get") == 0 && key && !value) {
+ if ((value = dict_get(dict, key)) == 0) {
+ vstream_printf("%s: %s\n", key, dict->error ?
+ "error" : "not found");
+ } else {
+ vstream_printf("%s=%s\n", key, value);
+ }
+ } else if (strcmp(cmd, "put") == 0 && key && value) {
+ if (dict_put(dict, key, value) != 0)
+ vstream_printf("%s: %s\n", key, dict->error ?
+ "error" : "not updated");
+ } else if (strcmp(cmd, "first") == 0 && !key && !value) {
+ if (dict_seq(dict, DICT_SEQ_FUN_FIRST, &key, &value) == 0)
+ vstream_printf("%s=%s\n", key, value);
+ else
+ vstream_printf("%s\n", dict->error ?
+ "error" : "not found");
+ } else if (strcmp(cmd, "next") == 0 && !key && !value) {
+ if (dict_seq(dict, DICT_SEQ_FUN_NEXT, &key, &value) == 0)
+ vstream_printf("%s=%s\n", key, value);
+ else
+ vstream_printf("%s\n", dict->error ?
+ "error" : "not found");
+ } else if (strcmp(cmd, "flags") == 0 && !key && !value) {
+ vstream_printf("dict flags %s\n",
+ dict_flags_str(dict->flags));
+ } else if (strcmp(cmd, "masks") == 0 && !key && !value) {
+ vstream_printf("DICT_FLAG_IMPL_MASK %s\n",
+ dict_flags_str(DICT_FLAG_IMPL_MASK));
+ vstream_printf("DICT_FLAG_PARANOID %s\n",
+ dict_flags_str(DICT_FLAG_PARANOID));
+ vstream_printf("DICT_FLAG_RQST_MASK %s\n",
+ dict_flags_str(DICT_FLAG_RQST_MASK));
+ vstream_printf("DICT_FLAG_INST_MASK %s\n",
+ dict_flags_str(DICT_FLAG_INST_MASK));
+ } else {
+ vstream_printf("usage: %s\n", USAGE);
+ }
+ vstream_fflush(VSTREAM_OUT);
+ }
+ vstring_free(keybuf);
+ vstring_free(inbuf);
+ dict_close(dict);
+}
diff --git a/src/util/dict_test.in b/src/util/dict_test.in
new file mode 100644
index 0000000..ef838a1
--- /dev/null
+++ b/src/util/dict_test.in
@@ -0,0 +1,10 @@
+del bar
+get bar
+get nonexist
+del nonexist
+get foo
+del foo
+put baz bazval
+get baz
+del baz
+get baz
diff --git a/src/util/dict_test.ref b/src/util/dict_test.ref
new file mode 100644
index 0000000..54e91f8
--- /dev/null
+++ b/src/util/dict_test.ref
@@ -0,0 +1,20 @@
+owner=untrusted (uid=USER)
+> del bar
+bar: deleted
+> get bar
+bar: not found
+> get nonexist
+nonexist: not found
+> del nonexist
+nonexist: not found
+> get foo
+foo=fooval
+> del foo
+foo: deleted
+> put baz bazval
+> get baz
+baz=bazval
+> del baz
+baz: deleted
+> get baz
+baz: not found
diff --git a/src/util/dict_thash.c b/src/util/dict_thash.c
new file mode 100644
index 0000000..69eb17b
--- /dev/null
+++ b/src/util/dict_thash.c
@@ -0,0 +1,255 @@
+/*++
+/* NAME
+/* dict_thash 3
+/* SUMMARY
+/* dictionary manager interface to hashed flat text files
+/* SYNOPSIS
+/* #include <dict_thash.h>
+/*
+/* DICT *dict_thash_open(path, open_flags, dict_flags)
+/* const char *name;
+/* const char *path;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_thash_open() opens the named flat text file, creates
+/* an in-memory hash table, and makes it available via the
+/* generic interface described in dict_open(3). The input
+/* format is as with postmap(1).
+/* DIAGNOSTICS
+/* Fatal errors: cannot open file, out of memory.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <iostuff.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <readlline.h>
+#include <dict.h>
+#include <dict_ht.h>
+#include <dict_thash.h>
+
+/* Application-specific. */
+
+#define STR vstring_str
+#define LEN VSTRING_LEN
+
+/* dict_thash_open - open flat text data base */
+
+DICT *dict_thash_open(const char *path, int open_flags, int dict_flags)
+{
+ DICT *dict;
+ VSTREAM *fp = 0; /* DICT_THASH_OPEN_RETURN() */
+ struct stat st;
+ time_t before;
+ time_t after;
+ VSTRING *line_buffer = 0; /* DICT_THASH_OPEN_RETURN() */
+ int lineno;
+ int last_line;
+ char *key;
+ char *value;
+
+ /*
+ * Let the optimizer worry about eliminating redundant code.
+ */
+#define DICT_THASH_OPEN_RETURN(d) do { \
+ DICT *__d = (d); \
+ if (fp != 0) \
+ vstream_fclose(fp); \
+ if (line_buffer != 0) \
+ vstring_free(line_buffer); \
+ return (__d); \
+ } while (0)
+
+ /*
+ * Sanity checks.
+ */
+ if (open_flags != O_RDONLY)
+ DICT_THASH_OPEN_RETURN(dict_surrogate(DICT_TYPE_THASH, path,
+ open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_THASH, path));
+
+ /*
+ * Read the flat text file into in-memory hash. Read the file again if it
+ * may have changed while we were reading.
+ */
+ for (before = time((time_t *) 0); /* see below */ ; before = after) {
+ if ((fp = vstream_fopen(path, open_flags, 0644)) == 0) {
+ DICT_THASH_OPEN_RETURN(dict_surrogate(DICT_TYPE_THASH, path,
+ open_flags, dict_flags,
+ "open database %s: %m", path));
+ }
+
+ /*
+ * Reuse the "internal" dictionary type.
+ */
+ dict = dict_open3(DICT_TYPE_HT, path, open_flags, dict_flags);
+ dict_type_override(dict, DICT_TYPE_THASH);
+
+ /*
+ * XXX This duplicates the parser in postmap.c.
+ */
+ if (line_buffer == 0)
+ line_buffer = vstring_alloc(100);
+ last_line = 0;
+ while (readllines(line_buffer, fp, &last_line, &lineno)) {
+ int in_quotes = 0;
+
+ /*
+ * First some UTF-8 checks sans casefolding.
+ */
+ if ((dict->flags & DICT_FLAG_UTF8_ACTIVE)
+ && allascii(STR(line_buffer)) == 0
+ && valid_utf8_string(STR(line_buffer), LEN(line_buffer)) == 0) {
+ msg_warn("%s, line %d: non-UTF-8 input \"%s\""
+ " -- ignoring this line",
+ VSTREAM_PATH(fp), lineno, STR(line_buffer));
+ continue;
+ }
+
+ /*
+ * Split on the first whitespace character, then trim leading and
+ * trailing whitespace from key and value.
+ */
+ for (value = STR(line_buffer); *value; value++) {
+ if (*value == '\\') {
+ if (*++value == 0)
+ break;
+ } else if (ISSPACE(*value)) {
+ if (!in_quotes)
+ break;
+ } else if (*value == '"') {
+ in_quotes = !in_quotes;
+ }
+ }
+ if (in_quotes) {
+ msg_warn("%s, line %d: unbalanced '\"' in '%s'"
+ " -- ignoring this line",
+ VSTREAM_PATH(fp), lineno, STR(line_buffer));
+ continue;
+ }
+ if (*value)
+ *value++ = 0;
+ while (ISSPACE(*value))
+ value++;
+ trimblanks(value, 0)[0] = 0;
+
+ /*
+ * Leave the key in quoted form, for consistency with postmap.c
+ * and dict_inline.c.
+ */
+ key = STR(line_buffer);
+
+ /*
+ * Enforce the "key whitespace value" format. Disallow missing
+ * keys or missing values.
+ */
+ if (*key == 0 || *value == 0) {
+ msg_warn("%s, line %d: expected format: key whitespace value"
+ " -- ignoring this line", path, lineno);
+ continue;
+ }
+ if (key[strlen(key) - 1] == ':')
+ msg_warn("%s, line %d: record is in \"key: value\" format;"
+ " is this an alias file?", path, lineno);
+
+ /*
+ * Optionally treat the value as a filename, and replace the value
+ * with the BASE64-encoded content of the named file.
+ */
+ if (dict_flags & DICT_FLAG_SRC_RHS_IS_FILE) {
+ VSTRING *base64_buf;
+ char *err;
+
+ if ((base64_buf = dict_file_to_b64(dict, value)) == 0) {
+ err = dict_file_get_error(dict);
+ msg_warn("%s, line %d: %s: skipping this entry",
+ VSTREAM_PATH(fp), lineno, err);
+ myfree(err);
+ continue;
+ }
+ value = vstring_str(base64_buf);
+ }
+
+ /*
+ * Store the value under the key. Handle duplicates
+ * appropriately. XXX Move this into dict_ht, but 1) that map
+ * ignores duplicates by default and we would have to check that
+ * we won't break existing code that depends on such behavior; 2)
+ * by inlining the checks here we can degrade gracefully instead
+ * of terminating with a fatal error. See comment in
+ * dict_inline.c.
+ */
+ if (dict->lookup(dict, key) != 0) {
+ if (dict_flags & DICT_FLAG_DUP_IGNORE) {
+ /* void */ ;
+ } else if (dict_flags & DICT_FLAG_DUP_REPLACE) {
+ dict->update(dict, key, value);
+ } else if (dict_flags & DICT_FLAG_DUP_WARN) {
+ msg_warn("%s, line %d: duplicate entry: \"%s\"",
+ path, lineno, key);
+ } else {
+ dict->close(dict);
+ DICT_THASH_OPEN_RETURN(dict_surrogate(DICT_TYPE_THASH, path,
+ open_flags, dict_flags,
+ "%s, line %d: duplicate entry: \"%s\"",
+ path, lineno, key));
+ }
+ } else {
+ dict->update(dict, key, value);
+ }
+ }
+
+ /*
+ * See if the source file is hot.
+ */
+ if (fstat(vstream_fileno(fp), &st) < 0)
+ msg_fatal("fstat %s: %m", path);
+ if (vstream_fclose(fp))
+ msg_fatal("read %s: %m", path);
+ fp = 0; /* DICT_THASH_OPEN_RETURN() */
+ after = time((time_t *) 0);
+ if (st.st_mtime < before - 1 || st.st_mtime > after)
+ break;
+
+ /*
+ * Yes, it is hot. Discard the result and read the file again.
+ */
+ dict->close(dict);
+ if (msg_verbose > 1)
+ msg_info("pausing to let file %s cool down", path);
+ doze(300000);
+ }
+
+ dict->owner.uid = st.st_uid;
+ dict->owner.status = (st.st_uid != 0);
+
+ DICT_THASH_OPEN_RETURN(DICT_DEBUG (dict));
+}
diff --git a/src/util/dict_thash.h b/src/util/dict_thash.h
new file mode 100644
index 0000000..878366a
--- /dev/null
+++ b/src/util/dict_thash.h
@@ -0,0 +1,37 @@
+#ifndef _DICT_THASH_H_INCLUDED_
+#define _DICT_THASH_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_thash 3h
+/* SUMMARY
+/* dictionary manager interface to flat text files
+/* SYNOPSIS
+/* #include <dict_thash.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_THASH "texthash"
+
+extern DICT *dict_thash_open(const char *, int, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/dict_thash.in b/src/util/dict_thash.in
new file mode 100644
index 0000000..89938f6
--- /dev/null
+++ b/src/util/dict_thash.in
@@ -0,0 +1,5 @@
+"the answer is 42
+xxx: yyy
+xxx
+aaa bbb
+aaa bbb
diff --git a/src/util/dict_thash.map b/src/util/dict_thash.map
new file mode 100644
index 0000000..cce1144
--- /dev/null
+++ b/src/util/dict_thash.map
@@ -0,0 +1,17 @@
+"the answer is" 42
+ABCDEF 012345
+allascii.c 915
+alldig.c 928
+allprint.c 943
+allspace.c 931
+argv.c 5271
+argv_split.c 2780
+attr_clnt.c 5813
+attr_print0.c 7243
+attr_print64.c 8104
+attr_print_plain.c 7086
+attr_scan0.c 15454
+attr_scan64.c 17256
+attr_scan_plain.c 16924
+auto_clnt.c 9819
+the answer is 42
diff --git a/src/util/dict_thash.ref b/src/util/dict_thash.ref
new file mode 100644
index 0000000..efdc207
--- /dev/null
+++ b/src/util/dict_thash.ref
@@ -0,0 +1,6 @@
+postmap: warning: dict_thash.in, line 1: unbalanced '"' in '"the answer is 42' -- ignoring this line
+postmap: warning: dict_thash.in, line 2: record is in "key: value" format; is this an alias file?
+postmap: warning: dict_thash.in, line 3: expected format: key whitespace value -- ignoring this line
+postmap: warning: dict_thash.in, line 5: duplicate entry: "aaa"
+aaa bbb
+xxx: yyy
diff --git a/src/util/dict_union.c b/src/util/dict_union.c
new file mode 100644
index 0000000..80df03b
--- /dev/null
+++ b/src/util/dict_union.c
@@ -0,0 +1,202 @@
+/*++
+/* NAME
+/* dict_union 3
+/* SUMMARY
+/* dictionary manager interface for union of tables
+/* SYNOPSIS
+/* #include <dict_union.h>
+/*
+/* DICT *dict_union_open(name, open_flags, dict_flags)
+/* const char *name;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_union_open() opens a sequence of one or more tables.
+/* Example: "\fBunionmap:{\fItype_1:name_1, ..., type_n:name_n\fR}".
+/*
+/* Each "unionmap:" query is given to each table in the specified
+/* order. All found results are concatenated, separated by
+/* comma. The unionmap table produces no result when all
+/* lookup tables return no result.
+/*
+/* The first and last characters of a "unionmap:" table name
+/* must be '{' and '}'. Within these, individual maps are
+/* separated with comma or whitespace.
+/*
+/* The open_flags and dict_flags arguments are passed on to
+/* the underlying dictionaries.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <htable.h>
+#include <dict.h>
+#include <dict_union.h>
+#include <stringops.h>
+#include <vstring.h>
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ ARGV *map_union; /* pipelined tables */
+ VSTRING *re_buf; /* reply buffer */
+} DICT_UNION;
+
+#define STR(x) vstring_str(x)
+
+/* dict_union_lookup - search a bunch of tables and combine the results */
+
+static const char *dict_union_lookup(DICT *dict, const char *query)
+{
+ static const char myname[] = "dict_union_lookup";
+ DICT_UNION *dict_union = (DICT_UNION *) dict;
+ DICT *map;
+ char **cpp;
+ char *dict_type_name;
+ const char *result = 0;
+
+ /*
+ * After Roel van Meer, postfix-users mailing list, Sept 2014.
+ */
+ VSTRING_RESET(dict_union->re_buf);
+ for (cpp = dict_union->map_union->argv; (dict_type_name = *cpp) != 0; cpp++) {
+ if ((map = dict_handle(dict_type_name)) == 0)
+ msg_panic("%s: dictionary \"%s\" not found", myname, dict_type_name);
+ if ((result = dict_get(map, query)) != 0) {
+ if (VSTRING_LEN(dict_union->re_buf) > 0)
+ VSTRING_ADDCH(dict_union->re_buf, ',');
+ vstring_strcat(dict_union->re_buf, result);
+ } else if (map->error != 0) {
+ DICT_ERR_VAL_RETURN(dict, map->error, 0);
+ }
+ }
+ DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE,
+ VSTRING_LEN(dict_union->re_buf) > 0 ?
+ STR(dict_union->re_buf) : 0);
+}
+
+/* dict_union_close - disassociate from a bunch of tables */
+
+static void dict_union_close(DICT *dict)
+{
+ DICT_UNION *dict_union = (DICT_UNION *) dict;
+ char **cpp;
+ char *dict_type_name;
+
+ for (cpp = dict_union->map_union->argv; (dict_type_name = *cpp) != 0; cpp++)
+ dict_unregister(dict_type_name);
+ argv_free(dict_union->map_union);
+ vstring_free(dict_union->re_buf);
+ dict_free(dict);
+}
+
+/* dict_union_open - open a bunch of tables */
+
+DICT *dict_union_open(const char *name, int open_flags, int dict_flags)
+{
+ static const char myname[] = "dict_union_open";
+ DICT_UNION *dict_union;
+ char *saved_name = 0;
+ char *dict_type_name;
+ ARGV *argv = 0;
+ char **cpp;
+ DICT *dict;
+ int match_flags = 0;
+ struct DICT_OWNER aggr_owner;
+ size_t len;
+
+ /*
+ * Clarity first. Let the optimizer worry about redundant code.
+ */
+#define DICT_UNION_RETURN(x) do { \
+ if (saved_name != 0) \
+ myfree(saved_name); \
+ if (argv != 0) \
+ argv_free(argv); \
+ return (x); \
+ } while (0)
+
+ /*
+ * Sanity checks.
+ */
+ if (open_flags != O_RDONLY)
+ DICT_UNION_RETURN(dict_surrogate(DICT_TYPE_UNION, name,
+ open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_UNION, name));
+
+ /*
+ * Split the table name into its constituent parts.
+ */
+ if ((len = balpar(name, CHARS_BRACE)) == 0 || name[len] != 0
+ || *(saved_name = mystrndup(name + 1, len - 2)) == 0
+ || ((argv = argv_splitq(saved_name, CHARS_COMMA_SP, CHARS_BRACE)),
+ (argv->argc == 0)))
+ DICT_UNION_RETURN(dict_surrogate(DICT_TYPE_UNION, name,
+ open_flags, dict_flags,
+ "bad syntax: \"%s:%s\"; "
+ "need \"%s:{type:name...}\"",
+ DICT_TYPE_UNION, name,
+ DICT_TYPE_UNION));
+
+ /*
+ * The least-trusted table in the set determines the over-all trust
+ * level. The first table determines the pattern-matching flags.
+ */
+ DICT_OWNER_AGGREGATE_INIT(aggr_owner);
+ for (cpp = argv->argv; (dict_type_name = *cpp) != 0; cpp++) {
+ if (msg_verbose)
+ msg_info("%s: %s", myname, dict_type_name);
+ if (strchr(dict_type_name, ':') == 0)
+ DICT_UNION_RETURN(dict_surrogate(DICT_TYPE_UNION, name,
+ open_flags, dict_flags,
+ "bad syntax: \"%s:%s\"; "
+ "need \"%s:{type:name...}\"",
+ DICT_TYPE_UNION, name,
+ DICT_TYPE_UNION));
+ if ((dict = dict_handle(dict_type_name)) == 0)
+ dict = dict_open(dict_type_name, open_flags, dict_flags);
+ dict_register(dict_type_name, dict);
+ DICT_OWNER_AGGREGATE_UPDATE(aggr_owner, dict->owner);
+ if (cpp == argv->argv)
+ match_flags = dict->flags & (DICT_FLAG_FIXED | DICT_FLAG_PATTERN);
+ }
+
+ /*
+ * Bundle up the result.
+ */
+ dict_union =
+ (DICT_UNION *) dict_alloc(DICT_TYPE_UNION, name, sizeof(*dict_union));
+ dict_union->dict.lookup = dict_union_lookup;
+ dict_union->dict.close = dict_union_close;
+ dict_union->dict.flags = dict_flags | match_flags;
+ dict_union->dict.owner = aggr_owner;
+ dict_union->re_buf = vstring_alloc(100);
+ dict_union->map_union = argv;
+ argv = 0;
+ DICT_UNION_RETURN(DICT_DEBUG (&dict_union->dict));
+}
diff --git a/src/util/dict_union.h b/src/util/dict_union.h
new file mode 100644
index 0000000..9554f84
--- /dev/null
+++ b/src/util/dict_union.h
@@ -0,0 +1,37 @@
+#ifndef _DICT_UNION_H_INCLUDED_
+#define _DICT_UNION_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_union 3h
+/* SUMMARY
+/* dictionary manager interface for union of tables
+/* SYNOPSIS
+/* #include <dict_union.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_UNION "unionmap"
+
+extern DICT *dict_union_open(const char *, int, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/dict_union_test.in b/src/util/dict_union_test.in
new file mode 100644
index 0000000..9d111d4
--- /dev/null
+++ b/src/util/dict_union_test.in
@@ -0,0 +1,7 @@
+${VALGRIND} ./dict_open 'unionmap:{static:one,static:two,inline:{foo=three}}' read <<EOF
+get foo
+get bar
+EOF
+${VALGRIND} ./dict_open 'unionmap:{static:one,fail:fail}' read <<EOF
+get foo
+EOF
diff --git a/src/util/dict_union_test.ref b/src/util/dict_union_test.ref
new file mode 100644
index 0000000..b609410
--- /dev/null
+++ b/src/util/dict_union_test.ref
@@ -0,0 +1,10 @@
++ ./dict_open unionmap:{static:one,static:two,inline:{foo=three}} read
+owner=trusted (uid=2147483647)
+> get foo
+foo=one,two,three
+> get bar
+bar=one,two
++ ./dict_open unionmap:{static:one,fail:fail} read
+owner=trusted (uid=2147483647)
+> get foo
+foo: error
diff --git a/src/util/dict_unix.c b/src/util/dict_unix.c
new file mode 100644
index 0000000..4635344
--- /dev/null
+++ b/src/util/dict_unix.c
@@ -0,0 +1,204 @@
+/*++
+/* NAME
+/* dict_unix 3
+/* SUMMARY
+/* dictionary manager interface to UNIX tables
+/* SYNOPSIS
+/* #include <dict_unix.h>
+/*
+/* DICT *dict_unix_open(map, dummy, dict_flags)
+/* const char *map;
+/* int dummy;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_unix_open() makes the specified UNIX table accessible via
+/* the generic dictionary operations described in dict_open(3).
+/* The \fIdummy\fR argument is not used.
+/*
+/* Known map names:
+/* .IP passwd.byname
+/* The table is the UNIX password database. The key is a login name.
+/* The result is a password file entry in passwd(5) format.
+/* .IP group.byname
+/* The table is the UNIX group database. The key is a group name.
+/* The result is a group file entry in group(5) format.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* DIAGNOSTICS
+/* Fatal errors: out of memory, unknown map name, attempt to update map.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <pwd.h>
+#include <grp.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "mymalloc.h"
+#include "vstring.h"
+#include "stringops.h"
+#include "dict.h"
+#include "dict_unix.h"
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+} DICT_UNIX;
+
+/* dict_unix_getpwnam - find password table entry */
+
+static const char *dict_unix_getpwnam(DICT *dict, const char *key)
+{
+ struct passwd *pwd;
+ static VSTRING *buf;
+ static int sanity_checked;
+
+ dict->error = 0;
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, key);
+ key = lowercase(vstring_str(dict->fold_buf));
+ }
+ if ((pwd = getpwnam(key)) == 0) {
+ if (sanity_checked == 0) {
+ sanity_checked = 1;
+ errno = 0;
+ if (getpwuid(0) == 0) {
+ msg_warn("cannot access UNIX password database: %m");
+ dict->error = DICT_ERR_RETRY;
+ }
+ }
+ return (0);
+ } else {
+ if (buf == 0)
+ buf = vstring_alloc(10);
+ sanity_checked = 1;
+ vstring_sprintf(buf, "%s:%s:%ld:%ld:%s:%s:%s",
+ pwd->pw_name, pwd->pw_passwd, (long) pwd->pw_uid,
+ (long) pwd->pw_gid, pwd->pw_gecos, pwd->pw_dir,
+ pwd->pw_shell);
+ return (vstring_str(buf));
+ }
+}
+
+/* dict_unix_getgrnam - find group table entry */
+
+static const char *dict_unix_getgrnam(DICT *dict, const char *key)
+{
+ struct group *grp;
+ static VSTRING *buf;
+ char **cpp;
+ static int sanity_checked;
+
+ dict->error = 0;
+
+ /*
+ * Optionally fold the key.
+ */
+ if (dict->flags & DICT_FLAG_FOLD_FIX) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ vstring_strcpy(dict->fold_buf, key);
+ key = lowercase(vstring_str(dict->fold_buf));
+ }
+ if ((grp = getgrnam(key)) == 0) {
+ if (sanity_checked == 0) {
+ sanity_checked = 1;
+ errno = 0;
+ if (getgrgid(0) == 0) {
+ msg_warn("cannot access UNIX group database: %m");
+ dict->error = DICT_ERR_RETRY;
+ }
+ }
+ return (0);
+ } else {
+ if (buf == 0)
+ buf = vstring_alloc(10);
+ sanity_checked = 1;
+ vstring_sprintf(buf, "%s:%s:%ld:",
+ grp->gr_name, grp->gr_passwd, (long) grp->gr_gid);
+ for (cpp = grp->gr_mem; *cpp; cpp++) {
+ vstring_strcat(buf, *cpp);
+ if (cpp[1])
+ VSTRING_ADDCH(buf, ',');
+ }
+ VSTRING_TERMINATE(buf);
+ return (vstring_str(buf));
+ }
+}
+
+/* dict_unix_close - close UNIX map */
+
+static void dict_unix_close(DICT *dict)
+{
+ if (dict->fold_buf)
+ vstring_free(dict->fold_buf);
+ dict_free(dict);
+}
+
+/* dict_unix_open - open UNIX map */
+
+DICT *dict_unix_open(const char *map, int open_flags, int dict_flags)
+{
+ DICT_UNIX *dict_unix;
+ struct dict_unix_lookup {
+ char *name;
+ const char *(*lookup) (DICT *, const char *);
+ };
+ static struct dict_unix_lookup dict_unix_lookup[] = {
+ "passwd.byname", dict_unix_getpwnam,
+ "group.byname", dict_unix_getgrnam,
+ 0,
+ };
+ struct dict_unix_lookup *lp;
+
+ /*
+ * Sanity checks.
+ */
+ if (open_flags != O_RDONLY)
+ return (dict_surrogate(DICT_TYPE_UNIX, map, open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_UNIX, map));
+
+ /*
+ * "Open" the database.
+ */
+ for (lp = dict_unix_lookup; /* void */ ; lp++) {
+ if (lp->name == 0)
+ return (dict_surrogate(DICT_TYPE_UNIX, map, open_flags, dict_flags,
+ "unknown table: %s:%s", DICT_TYPE_UNIX, map));
+ if (strcmp(map, lp->name) == 0)
+ break;
+ }
+ dict_unix = (DICT_UNIX *) dict_alloc(DICT_TYPE_UNIX, map,
+ sizeof(*dict_unix));
+ dict_unix->dict.lookup = lp->lookup;
+ dict_unix->dict.close = dict_unix_close;
+ dict_unix->dict.flags = dict_flags | DICT_FLAG_FIXED;
+ if (dict_flags & DICT_FLAG_FOLD_FIX)
+ dict_unix->dict.fold_buf = vstring_alloc(10);
+ dict_unix->dict.owner.status = DICT_OWNER_TRUSTED;
+
+ return (DICT_DEBUG (&dict_unix->dict));
+}
diff --git a/src/util/dict_unix.h b/src/util/dict_unix.h
new file mode 100644
index 0000000..b5674b2
--- /dev/null
+++ b/src/util/dict_unix.h
@@ -0,0 +1,37 @@
+#ifndef _DICT_UNIX_H_INCLUDED_
+#define _DICT_UNIX_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_unix 3h
+/* SUMMARY
+/* dictionary manager interface to UNIX maps
+/* SYNOPSIS
+/* #include <dict_unix.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+#define DICT_TYPE_UNIX "unix"
+
+extern DICT *dict_unix_open(const char *, int, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/dict_utf8.c b/src/util/dict_utf8.c
new file mode 100644
index 0000000..f1fc65a
--- /dev/null
+++ b/src/util/dict_utf8.c
@@ -0,0 +1,300 @@
+/*++
+/* NAME
+/* dict_utf8 3
+/* SUMMARY
+/* dictionary UTF-8 helpers
+/* SYNOPSIS
+/* #include <dict.h>
+/*
+/* DICT *dict_utf8_activate(
+/* DICT *dict)
+/* DESCRIPTION
+/* dict_utf8_activate() wraps a dictionary's lookup/update/delete
+/* methods with code that enforces UTF-8 checks on keys and
+/* values, and that logs a warning when incorrect UTF-8 is
+/* encountered. The original dictionary handle becomes invalid.
+/*
+/* The wrapper code enforces a policy that maximizes application
+/* robustness (it avoids the need for new error-handling code
+/* paths in application code). Attempts to store non-UTF-8
+/* keys or values are skipped while reporting a non-error
+/* status, attempts to look up or delete non-UTF-8 keys are
+/* skipped while reporting a non-error status, and lookup
+/* results that contain a non-UTF-8 value are blocked while
+/* reporting a configuration error.
+/* BUGS
+/* dict_utf8_activate() does not nest.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <string.h>
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <stringops.h>
+#include <dict.h>
+#include <mymalloc.h>
+#include <msg.h>
+
+ /*
+ * The goal is to maximize robustness: bad UTF-8 should not appear in keys,
+ * because those are derived from controlled inputs, and values should be
+ * printable before they are stored. But if we failed to check something
+ * then it should not result in fatal errors and thus open up the system for
+ * a denial-of-service attack.
+ *
+ * Proposed over-all policy: skip attempts to store invalid UTF-8 lookup keys
+ * or values. Rationale: some storage may not permit malformed UTF-8. This
+ * maximizes program robustness. If we get an invalid lookup result, report
+ * a configuration error.
+ *
+ * LOOKUP
+ *
+ * If the key is invalid, log a warning and skip the request. Rationale: the
+ * item cannot exist.
+ *
+ * If the lookup result is invalid, log a warning and return a configuration
+ * error.
+ *
+ * UPDATE
+ *
+ * If the key is invalid, then log a warning and skip the request. Rationale:
+ * the item cannot exist.
+ *
+ * If the value is invalid, log a warning and skip the request. Rationale:
+ * storage may not permit malformed UTF-8. This maximizes program
+ * robustness.
+ *
+ * DELETE
+ *
+ * If the key is invalid, then skip the request. Rationale: the item cannot
+ * exist.
+ */
+
+/* dict_utf8_check_fold - casefold or validate string */
+
+static char *dict_utf8_check_fold(DICT *dict, const char *string,
+ CONST_CHAR_STAR *err)
+{
+ int fold_flag = (dict->flags & DICT_FLAG_FOLD_ANY);
+
+ /*
+ * Validate UTF-8 without casefolding.
+ */
+ if (!allascii(string) && valid_utf8_string(string, strlen(string)) == 0) {
+ if (err)
+ *err = "malformed UTF-8 or invalid codepoint";
+ return (0);
+ }
+
+ /*
+ * Casefold UTF-8.
+ */
+ if (fold_flag != 0
+ && (fold_flag & ((dict->flags & DICT_FLAG_FIXED) ?
+ DICT_FLAG_FOLD_FIX : DICT_FLAG_FOLD_MUL))) {
+ if (dict->fold_buf == 0)
+ dict->fold_buf = vstring_alloc(10);
+ return (casefold(dict->fold_buf, string));
+ }
+ return ((char *) string);
+}
+
+/* dict_utf8_check validate UTF-8 string */
+
+static int dict_utf8_check(const char *string, CONST_CHAR_STAR *err)
+{
+ if (!allascii(string) && valid_utf8_string(string, strlen(string)) == 0) {
+ if (err)
+ *err = "malformed UTF-8 or invalid codepoint";
+ return (0);
+ }
+ return (1);
+}
+
+/* dict_utf8_lookup - UTF-8 lookup method wrapper */
+
+static const char *dict_utf8_lookup(DICT *dict, const char *key)
+{
+ DICT_UTF8_BACKUP *backup;
+ const char *utf8_err;
+ const char *fold_res;
+ const char *value;
+ int saved_flags;
+
+ /*
+ * Validate and optionally fold the key, and if invalid skip the request.
+ */
+ if ((fold_res = dict_utf8_check_fold(dict, key, &utf8_err)) == 0) {
+ msg_warn("%s:%s: non-UTF-8 key \"%s\": %s",
+ dict->type, dict->name, key, utf8_err);
+ dict->error = DICT_ERR_NONE;
+ return (0);
+ }
+
+ /*
+ * Proxy the request with casefolding turned off.
+ */
+ saved_flags = (dict->flags & DICT_FLAG_FOLD_ANY);
+ dict->flags &= ~DICT_FLAG_FOLD_ANY;
+ backup = dict->utf8_backup;
+ value = backup->lookup(dict, fold_res);
+ dict->flags |= saved_flags;
+
+ /*
+ * Validate the result, and if invalid fail the request.
+ */
+ if (value != 0 && dict_utf8_check(value, &utf8_err) == 0) {
+ msg_warn("%s:%s: key \"%s\": non-UTF-8 value \"%s\": %s",
+ dict->type, dict->name, key, value, utf8_err);
+ dict->error = DICT_ERR_CONFIG;
+ return (0);
+ } else {
+ return (value);
+ }
+}
+
+/* dict_utf8_update - UTF-8 update method wrapper */
+
+static int dict_utf8_update(DICT *dict, const char *key, const char *value)
+{
+ DICT_UTF8_BACKUP *backup;
+ const char *utf8_err;
+ const char *fold_res;
+ int saved_flags;
+ int status;
+
+ /*
+ * Validate or fold the key, and if invalid skip the request.
+ */
+ if ((fold_res = dict_utf8_check_fold(dict, key, &utf8_err)) == 0) {
+ msg_warn("%s:%s: non-UTF-8 key \"%s\": %s",
+ dict->type, dict->name, key, utf8_err);
+ dict->error = DICT_ERR_NONE;
+ return (DICT_STAT_SUCCESS);
+ }
+
+ /*
+ * Validate the value, and if invalid skip the request.
+ */
+ else if (dict_utf8_check(value, &utf8_err) == 0) {
+ msg_warn("%s:%s: key \"%s\": non-UTF-8 value \"%s\": %s",
+ dict->type, dict->name, key, value, utf8_err);
+ dict->error = DICT_ERR_NONE;
+ return (DICT_STAT_SUCCESS);
+ }
+
+ /*
+ * Proxy the request with casefolding turned off.
+ */
+ else {
+ saved_flags = (dict->flags & DICT_FLAG_FOLD_ANY);
+ dict->flags &= ~DICT_FLAG_FOLD_ANY;
+ backup = dict->utf8_backup;
+ status = backup->update(dict, fold_res, value);
+ dict->flags |= saved_flags;
+ return (status);
+ }
+}
+
+/* dict_utf8_delete - UTF-8 delete method wrapper */
+
+static int dict_utf8_delete(DICT *dict, const char *key)
+{
+ DICT_UTF8_BACKUP *backup;
+ const char *utf8_err;
+ const char *fold_res;
+ int saved_flags;
+ int status;
+
+ /*
+ * Validate and optionally fold the key, and if invalid skip the request.
+ */
+ if ((fold_res = dict_utf8_check_fold(dict, key, &utf8_err)) == 0) {
+ msg_warn("%s:%s: non-UTF-8 key \"%s\": %s",
+ dict->type, dict->name, key, utf8_err);
+ dict->error = DICT_ERR_NONE;
+ return (DICT_STAT_SUCCESS);
+ }
+
+ /*
+ * Proxy the request with casefolding turned off.
+ */
+ else {
+ saved_flags = (dict->flags & DICT_FLAG_FOLD_ANY);
+ dict->flags &= ~DICT_FLAG_FOLD_ANY;
+ backup = dict->utf8_backup;
+ status = backup->delete(dict, fold_res);
+ dict->flags |= saved_flags;
+ return (status);
+ }
+}
+
+/* dict_utf8_activate - wrap a legacy dict object for UTF-8 processing */
+
+DICT *dict_utf8_activate(DICT *dict)
+{
+ const char myname[] = "dict_utf8_activate";
+ DICT_UTF8_BACKUP *backup;
+
+ /*
+ * Sanity check.
+ */
+ if (util_utf8_enable == 0)
+ msg_panic("%s: Unicode support is not available", myname);
+ if ((dict->flags & DICT_FLAG_UTF8_REQUEST) == 0)
+ msg_panic("%s: %s:%s does not request Unicode support",
+ myname, dict->type, dict->name);
+ if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) || dict->utf8_backup != 0)
+ msg_panic("%s: %s:%s Unicode support is already activated",
+ myname, dict->type, dict->name);
+
+ /*
+ * Unlike dict_debug(3) we do not put a proxy dict object in front of the
+ * encapsulated object, because then we would have to bidirectionally
+ * propagate changes in the data members (errors, flags, jbuf, and so on)
+ * between proxy object and encapsulated object.
+ *
+ * Instead we attach ourselves behind the encapsulated dict object, and
+ * redirect some function pointers to ourselves.
+ */
+ backup = dict->utf8_backup = (DICT_UTF8_BACKUP *) mymalloc(sizeof(*backup));
+
+ /*
+ * Interpose on the lookup/update/delete methods. It is a conscious
+ * decision not to tinker with the iterator or destructor.
+ */
+ backup->lookup = dict->lookup;
+ backup->update = dict->update;
+ backup->delete = dict->delete;
+
+ dict->lookup = dict_utf8_lookup;
+ dict->update = dict_utf8_update;
+ dict->delete = dict_utf8_delete;
+
+ /*
+ * Leave our mark. See sanity check above.
+ */
+ dict->flags |= DICT_FLAG_UTF8_ACTIVE;
+
+ return (dict);
+}
diff --git a/src/util/dict_utf8_test.in b/src/util/dict_utf8_test.in
new file mode 100644
index 0000000..f8d4536
--- /dev/null
+++ b/src/util/dict_utf8_test.in
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+LC_ALL=C awk 'BEGIN {
+ print "flags"
+ print "verbose"
+ printf "get foo\n"
+ printf "put Δημοσθένους.example.com aaa\n"
+ printf "get Δημοσθένους.example.com\n"
+ printf "put %c%c%c xxx\n", 128, 128, 128
+ printf "get %c%c%c\n", 128, 128, 128
+ printf "put xxx %c%c%c\n", 128, 128, 128
+ printf "get xxx\n"
+ exit
+}' | ${VALGRIND} ./dict_open internal:whatever write utf8_request
diff --git a/src/util/dict_utf8_test.ref b/src/util/dict_utf8_test.ref
new file mode 100644
index 0000000..7b825f9
--- /dev/null
+++ b/src/util/dict_utf8_test.ref
@@ -0,0 +1,18 @@
+owner=trusted (uid=2147483647)
+> flags
+dict flags fixed|lock|replace|utf8_request|utf8_active
+> verbose
+> get foo
+foo: not found
+> put Δημοσθένους.example.com aaa
+> get Δημοσθένους.example.com
+Δημοσθένους.example.com=aaa
+> put €€€ xxx
+./dict_open: warning: internal:whatever: non-UTF-8 key "???": malformed UTF-8 or invalid codepoint
+> get €€€
+./dict_open: warning: internal:whatever: non-UTF-8 key "???": malformed UTF-8 or invalid codepoint
+€€€: not found
+> put xxx €€€
+./dict_open: warning: internal:whatever: key "xxx": non-UTF-8 value "???": malformed UTF-8 or invalid codepoint
+> get xxx
+xxx: not found
diff --git a/src/util/dir_forest.c b/src/util/dir_forest.c
new file mode 100644
index 0000000..0070177
--- /dev/null
+++ b/src/util/dir_forest.c
@@ -0,0 +1,110 @@
+/*++
+/* NAME
+/* dir_forest 3
+/* SUMMARY
+/* file name to directory forest
+/* SYNOPSIS
+/* #include <dir_forest.h>
+/*
+/* char *dir_forest(buf, path, depth)
+/* VSTRING *buf;
+/* const char *path;
+/* int depth;
+/* DESCRIPTION
+/* This module implements support for directory forests: a file
+/* organization that introduces one or more levels of intermediate
+/* subdirectories in order to reduce the number of files per directory.
+/*
+/* dir_forest() maps a file basename to a directory forest and
+/* returns the resulting string: file name "abcd" becomes "a/b/"
+/* and so on. The number of subdirectory levels is adjustable.
+/*
+/* Arguments:
+/* .IP buf
+/* A buffer that is overwritten with the result. The result
+/* ends in "/" and is null terminated. If a null pointer is
+/* specified, the result is written to a private buffer that
+/* is overwritten upon each call.
+/* .IP path
+/* A null-terminated string of printable characters. Characters
+/* special to the file system are not permitted.
+/* The first subdirectory is named after the first character
+/* in \fIpath\fR, and so on. When the path is shorter than the
+/* desired number of subdirectory levels, directory names
+/* of '_' (underscore) are used as replacement.
+/* .IP depth
+/* The desired number of subdirectory levels.
+/* DIAGNOSTICS
+/* Panic: interface violations. Fatal error: out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "dir_forest.h"
+
+/* dir_forest - translate base name to directory forest */
+
+char *dir_forest(VSTRING *buf, const char *path, int depth)
+{
+ const char *myname = "dir_forest";
+ static VSTRING *private_buf = 0;
+ int n;
+ const char *cp;
+ int ch;
+
+ /*
+ * Sanity checks.
+ */
+ if (*path == 0)
+ msg_panic("%s: empty path", myname);
+ if (depth < 1)
+ msg_panic("%s: depth %d", myname, depth);
+
+ /*
+ * Your buffer or mine?
+ */
+ if (buf == 0) {
+ if (private_buf == 0)
+ private_buf = vstring_alloc(1);
+ buf = private_buf;
+ }
+
+ /*
+ * Generate one or more subdirectory levels, depending on the pathname
+ * contents. When the pathname is short, use underscores instead.
+ * Disallow non-printable characters or characters that are special to
+ * the file system.
+ */
+ VSTRING_RESET(buf);
+ for (cp = path, n = 0; n < depth; n++) {
+ if ((ch = *cp) == 0) {
+ ch = '_';
+ } else {
+ if (!ISPRINT(ch) || ch == '.' || ch == '/')
+ msg_panic("%s: invalid pathname: %s", myname, path);
+ cp++;
+ }
+ VSTRING_ADDCH(buf, ch);
+ VSTRING_ADDCH(buf, '/');
+ }
+ VSTRING_TERMINATE(buf);
+
+ if (msg_verbose > 1)
+ msg_info("%s: %s -> %s", myname, path, vstring_str(buf));
+ return (vstring_str(buf));
+}
diff --git a/src/util/dir_forest.h b/src/util/dir_forest.h
new file mode 100644
index 0000000..2c4c363
--- /dev/null
+++ b/src/util/dir_forest.h
@@ -0,0 +1,35 @@
+#ifndef _DIR_FOREST_H_INCLUDED_
+#define _DIR_FOREST_H_INCLUDED_
+
+/*++
+/* NAME
+/* dir_forest 3h
+/* SUMMARY
+/* file name to directory forest
+/* SYNOPSIS
+/* #include <dir_forest.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern char *dir_forest(VSTRING *, const char *, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/doze.c b/src/util/doze.c
new file mode 100644
index 0000000..28d5669
--- /dev/null
+++ b/src/util/doze.c
@@ -0,0 +1,71 @@
+/*++
+/* NAME
+/* doze 3
+/* SUMMARY
+/* take a nap
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* void doze(microseconds)
+/* unsigned microseconds;
+/* DESCRIPTION
+/* doze() sleeps for the specified number of microseconds. It is
+/* a simple alternative for systems that lack usleep().
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <sys/time.h>
+#include <unistd.h>
+#include <errno.h>
+#ifdef USE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+/* doze - sleep a while */
+
+void doze(unsigned delay)
+{
+ struct timeval tv;
+
+#define MILLION 1000000
+
+ tv.tv_sec = delay / MILLION;
+ tv.tv_usec = delay % MILLION;
+ while (select(0, (fd_set *) 0, (fd_set *) 0, (fd_set *) 0, &tv) < 0)
+ if (errno != EINTR)
+ msg_fatal("doze: select: %m");
+}
+
+#ifdef TEST
+
+#include <stdlib.h>
+
+int main(int argc, char **argv)
+{
+ unsigned delay;
+
+ if (argc != 2 || (delay = atol(argv[1])) == 0)
+ msg_fatal("usage: %s microseconds", argv[0]);
+ doze(delay);
+ exit(0);
+}
+
+#endif
diff --git a/src/util/dummy_read.c b/src/util/dummy_read.c
new file mode 100644
index 0000000..639004f
--- /dev/null
+++ b/src/util/dummy_read.c
@@ -0,0 +1,61 @@
+/*++
+/* NAME
+/* dummy_read 3
+/* SUMMARY
+/* dummy read operation
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* ssize_t dummy_read(fd, buf, buf_len, timeout, context)
+/* int fd;
+/* void *buf;
+/* size_t len;
+/* int timeout;
+/* void *context;
+/* DESCRIPTION
+/* dummy_read() reports an EOF condition without side effects.
+/*
+/* Arguments:
+/* .IP fd
+/* File descriptor whose value is logged when verbose logging
+/* is turned on.
+/* .IP buf
+/* Read buffer pointer. Not used.
+/* .IP buf_len
+/* Read buffer size. Its value is logged when verbose logging is
+/* turned on.
+/* .IP timeout
+/* The deadline in seconds. Not used.
+/* .IP context
+/* Application context. Not used.
+/* DIAGNOSTICS
+/* None.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+/* dummy_read - dummy read operation */
+
+ssize_t dummy_read(int fd, void *unused_buf, size_t len,
+ int unused_timeout, void *unused_context)
+{
+ if (msg_verbose)
+ msg_info("dummy_read: fd %d, len %lu", fd, (unsigned long) len);
+ return (0);
+}
diff --git a/src/util/dummy_write.c b/src/util/dummy_write.c
new file mode 100644
index 0000000..943e2ad
--- /dev/null
+++ b/src/util/dummy_write.c
@@ -0,0 +1,61 @@
+/*++
+/* NAME
+/* dummy_write 3
+/* SUMMARY
+/* dummy write operation
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* ssize_t dummy_write(fd, buf, buf_len, timeout, context)
+/* int fd;
+/* void *buf;
+/* size_t len;
+/* int timeout;
+/* void *context;
+/* DESCRIPTION
+/* dummy_write() implements a data sink without side effects.
+/*
+/* Arguments:
+/* .IP fd
+/* File descriptor whose value is logged when verbose logging
+/* is turned on.
+/* .IP buf
+/* Write buffer pointer. Not used.
+/* .IP buf_len
+/* Write buffer size. Its value is logged when verbose logging is
+/* turned on.
+/* .IP timeout
+/* The deadline in seconds. Not used.
+/* .IP context
+/* Application context. Not used.
+/* DIAGNOSTICS
+/* None.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+/* dummy_write - dummy write operation */
+
+ssize_t dummy_write(int fd, void *unused_buf, size_t len,
+ int unused_timeout, void *unused_context)
+{
+ if (msg_verbose)
+ msg_info("dummy_write: fd %d, len %lu", fd, (unsigned long) len);
+ return (len);
+}
diff --git a/src/util/dup2_pass_on_exec.c b/src/util/dup2_pass_on_exec.c
new file mode 100644
index 0000000..5286e5b
--- /dev/null
+++ b/src/util/dup2_pass_on_exec.c
@@ -0,0 +1,64 @@
+/*++
+/* NAME
+/* dup2_pass_on_exec 1
+/* SUMMARY
+/* dup2 close-on-exec behavior test program
+/* SYNOPSIS
+/* dup2_pass_on_exec
+/* DESCRIPTION
+/* dup2_pass_on_exec sets the close-on-exec flag on its
+/* standard input and then dup2() to duplicate it.
+/* Posix-1003.1 specifies in section 6.2.1.2 that dup2(o,n) should behave
+/* as: close(n); n = fcntl(o, F_DUPFD, n); as long as o is a valid
+/* file-descriptor, n!=o, and 0<=n<=[OPEN_MAX].
+/* Section 6.5.2.2 states that the close-on-exec flag of the result of a
+/* successful fcntl(o, F_DUPFD, n) is cleared.
+/*
+/* At least Ultrix4.3a does not clear the close-on-exec flag of n on
+/* dup2(o, n).
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Christian von Roques <roques@pond.sub.org>
+/* Forststrasse 71
+/* 76131 Karlsruhe, GERMANY
+/*--*/
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#define DO(s) if (s < 0) { perror(#s); exit(1); }
+
+int main(int unused_argc, char **unused_argv)
+{
+ int res;
+
+ printf("Setting the close-on-exec flag of file-descriptor 0.\n");
+ DO(fcntl(0, F_SETFD, 1));
+
+ printf("Duplicating file-descriptor 0 to 3.\n");
+ DO(dup2(0, 3));
+
+ printf("Testing if the close-on-exec flag of file-descriptor 3 is set.\n");
+ DO((res = fcntl(3, F_GETFD, 0)));
+ if (res & 1)
+ printf(
+"Yes, a newly dup2()ed file-descriptor has the close-on-exec \
+flag cloned.\n\
+THIS VIOLATES Posix1003.1 section 6.2.1.2 or 6.5.2.2!\n\
+You should #define DUP2_DUPS_CLOSE_ON_EXEC in sys_defs.h \
+for your OS.\n");
+ else
+ printf(
+"No, a newly dup2()ed file-descriptor has the close-on-exec \
+flag cleared.\n\
+This complies with Posix1003.1 section 6.2.1.2 and 6.5.2.2!\n");
+
+ return 0;
+}
diff --git a/src/util/duplex_pipe.c b/src/util/duplex_pipe.c
new file mode 100644
index 0000000..04f23f6
--- /dev/null
+++ b/src/util/duplex_pipe.c
@@ -0,0 +1,49 @@
+/*++
+/* NAME
+/* duplex_pipe 3
+/* SUMMARY
+/* local IPD
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* int duplex_pipe(fds)
+/* int *fds;
+/* DESCRIPTION
+/* duplex_pipe() uses whatever local primitive it takes
+/* to get a two-way I/O channel.
+/* DIAGNOSTICS
+/* A null result means success. In case of error, the result
+/* is -1 and errno is set to the appropriate number.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System libraries */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include "iostuff.h"
+#include "sane_socketpair.h"
+
+/* duplex_pipe - give me a duplex pipe or bust */
+
+int duplex_pipe(int *fds)
+{
+#ifdef HAS_DUPLEX_PIPE
+ return (pipe(fds));
+#else
+ return (sane_socketpair(AF_UNIX, SOCK_STREAM, 0, fds));
+#endif
+}
+
diff --git a/src/util/edit_file.c b/src/util/edit_file.c
new file mode 100644
index 0000000..9d76b93
--- /dev/null
+++ b/src/util/edit_file.c
@@ -0,0 +1,353 @@
+/*++
+/* NAME
+/* edit_file 3
+/* SUMMARY
+/* simple cooperative file updating protocol
+/* SYNOPSIS
+/* #include <edit_file.h>
+/*
+/* typedef struct {
+/* .in +4
+/* char *tmp_path; /* temp. pathname */
+/* VSTREAM *tmp_fp; /* temp. stream */
+/* /* private members... */
+/* .in -4
+/* } EDIT_FILE;
+/*
+/* EDIT_FILE *edit_file_open(original_path, output_flags, output_mode)
+/* const char *original_path;
+/* int output_flags;
+/* mode_t output_mode;
+/*
+/* int edit_file_close(edit_file)
+/* EDIT_FILE *edit_file;
+/*
+/* void edit_file_cleanup(edit_file)
+/* EDIT_FILE *edit_file;
+/* DESCRIPTION
+/* This module implements a simple protocol for cooperative
+/* processes to update one file. The idea is to 1) create a
+/* new file under a deterministic temporary pathname, 2)
+/* populate the new file with updated information, and 3)
+/* rename the new file into the place of the original file.
+/* This module provides 1) and 3), and leaves 2) to the
+/* application. The temporary pathname is deterministic to
+/* avoid accumulation of thrash after program crashes.
+/*
+/* edit_file_open() implements the first phase of the protocol.
+/* It creates or opens an output file with a deterministic
+/* temporary pathname, obtained by appending the suffix defined
+/* with EDIT_FILE_SUFFIX to the specified original file pathname.
+/* The original file itself is not opened. edit_file_open()
+/* then locks the output file for exclusive access, and verifies
+/* that the file still exists under the temporary pathname.
+/* At this point in the protocol, the current process controls
+/* both the output file content and its temporary pathname.
+/*
+/* In the second phase, the application opens the original
+/* file if needed, and updates the output file via the
+/* \fBtmp_fp\fR member of the EDIT_FILE data structure. This
+/* phase is not implemented by the edit_file() module.
+/*
+/* edit_file_close() implements the third and final phase of
+/* the protocol. It flushes the output file to persistent
+/* storage, and renames the output file from its temporary
+/* pathname into the place of the original file. When any of
+/* these operations fails, edit_file_close() behaves as if
+/* edit_file_cleanup() was called. Regardless of whether these
+/* operations succeed, edit_file_close() releases the exclusive
+/* lock, closes the output file, and frees up memory that was
+/* allocated by edit_file_open().
+/*
+/* edit_file_cleanup() aborts the protocol. It discards the
+/* output file, releases the exclusive lock, closes the output
+/* file, and frees up memory that was allocated by edit_file_open().
+/*
+/* Arguments:
+/* .IP original_path
+/* The pathname of the original file that will be replaced by
+/* the output file. The temporary pathname for the output file
+/* is obtained by appending the suffix defined with EDIT_FILE_SUFFIX
+/* to a copy of the specified original file pathname, and is
+/* made available via the \fBtmp_path\fR member of the EDIT_FILE
+/* data structure.
+/* .IP output_flags
+/* Flags for opening the output file. These are as with open(2),
+/* except that the O_TRUNC flag is ignored. edit_file_open()
+/* always truncates the output file after it has obtained
+/* exclusive control over the output file content and temporary
+/* pathname.
+/* .IP output_mode
+/* Permissions for the output file. These are as with open(2),
+/* except that the output file is initially created with no
+/* group or other access permissions. The specified output
+/* file permissions are applied by edit_file_close().
+/* .IP edit_file
+/* Pointer to data structure that is returned upon successful
+/* completion by edit_file_open(), and that must be passed to
+/* edit_file_close() or edit_file_cleanup().
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation failure, fstat() failure,
+/* unlink() failure, lock failure, ftruncate() failure.
+/*
+/* edit_file_open() immediately returns a null pointer when
+/* it cannot open the output file.
+/*
+/* edit_file_close() returns zero on success, VSTREAM_EOF on
+/* failure.
+/*
+/* With both functions, the global errno variable indicates
+/* the nature of the problem. All errors are relative to the
+/* temporary output's pathname. With both functions, this
+/* pathname is not available via the EDIT_FILE data structure,
+/* because that structure was already destroyed, or not created.
+/* BUGS
+/* In the non-error case, edit_file_open() will not return
+/* until it obtains exclusive control over the output file
+/* content and temporary pathname. Applications that are
+/* concerned about deadlock should protect the edit_file_open()
+/* call with a watchdog timer.
+/*
+/* When interrupted, edit_file_close() may leave behind a
+/* world-readable output file under the temporary pathname.
+/* On some systems this can be used to inflict a shared-lock
+/* DOS on the protocol. Applications that are concerned about
+/* maximal safety should protect the edit_file_close() call
+/* with sigdelay() and sigresume() calls, but this introduces
+/* the risk that the program will get stuck forever.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Based on code originally by:
+/* Victor Duchovni
+/* Morgan Stanley
+/*
+/* Packaged into one module with minor improvements by:
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <stdio.h> /* rename(2) */
+#include <errno.h>
+
+ /*
+ * This mask selects all permission bits in the st_mode stat data. There is
+ * no portable definition (unlike S_IFMT, which is defined for the file type
+ * bits). For example, BSD / Linux have ALLPERMS, while Solaris has S_IAMB.
+ */
+#define FILE_PERM_MASK \
+ (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO)
+
+/* Utility Library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <myflock.h>
+#include <edit_file.h>
+#include <warn_stat.h>
+
+ /*
+ * Do we reuse and truncate an output file that persists after a crash, or
+ * do we unlink it and create a new file?
+ */
+#define EDIT_FILE_REUSE_AFTER_CRASH
+
+ /*
+ * Protocol internals: the temporary file permissions.
+ */
+#define EDIT_FILE_MODE (S_IRUSR | S_IWUSR) /* temp file mode */
+
+ /*
+ * Make complex operations more readable. We could use functions, instead.
+ * The main thing is that we keep the _alloc and _free code together.
+ */
+#define EDIT_FILE_ALLOC(ep, path, mode) do { \
+ (ep) = (EDIT_FILE *) mymalloc(sizeof(EDIT_FILE)); \
+ (ep)->final_path = mystrdup(path); \
+ (ep)->final_mode = (mode); \
+ (ep)->tmp_path = concatenate((path), EDIT_FILE_SUFFIX, (char *) 0); \
+ (ep)->tmp_fp = 0; \
+ } while (0)
+
+#define EDIT_FILE_FREE(ep) do { \
+ myfree((ep)->final_path); \
+ myfree((ep)->tmp_path); \
+ myfree((void *) (ep)); \
+ } while (0)
+
+/* edit_file_open - open and lock file with deterministic temporary pathname */
+
+EDIT_FILE *edit_file_open(const char *path, int flags, mode_t mode)
+{
+ struct stat before_lock;
+ struct stat after_lock;
+ int saved_errno;
+ EDIT_FILE *ep;
+
+ /*
+ * Initialize. Do not bother to optimize for the error case.
+ */
+ EDIT_FILE_ALLOC(ep, path, mode);
+
+ /*
+ * As long as the output file can be opened under the temporary pathname,
+ * this code can loop or block forever.
+ *
+ * Applications that are concerned about deadlock should protect the
+ * edit_file_open() call with a watchdog timer.
+ */
+ for ( /* void */ ; /* void */ ; (void) vstream_fclose(ep->tmp_fp)) {
+
+ /*
+ * Try to open the output file under the temporary pathname. This
+ * succeeds or fails immediately. To avoid creating a shared-lock DOS
+ * opportunity after we crash, we create the output file with no
+ * group or other permissions, and set the final permissions at the
+ * end (this is one reason why we try to get exclusive control over
+ * the output file instead of the original file). We postpone file
+ * truncation until we have obtained exclusive control over the file
+ * content and temporary pathname. If the open operation fails, we
+ * give up immediately. The caller can retry the call if desirable.
+ *
+ * XXX If we replace the vstream_fopen() call by safe_open(), then we
+ * should replace the stat() call below by lstat().
+ */
+ if ((ep->tmp_fp = vstream_fopen(ep->tmp_path, flags & ~(O_TRUNC),
+ EDIT_FILE_MODE)) == 0) {
+ saved_errno = errno;
+ EDIT_FILE_FREE(ep);
+ errno = saved_errno;
+ return (0);
+ }
+
+ /*
+ * At this point we may have opened an existing output file that was
+ * already locked. Try to lock the open file exclusively. This may
+ * take some time.
+ */
+ if (myflock(vstream_fileno(ep->tmp_fp), INTERNAL_LOCK,
+ MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("lock %s: %m", ep->tmp_path);
+
+ /*
+ * At this point we have an exclusive lock, but some other process
+ * may have renamed or removed the output file while we were waiting
+ * for the lock. If that is the case, back out and try again.
+ */
+ if (fstat(vstream_fileno(ep->tmp_fp), &before_lock) < 0)
+ msg_fatal("open %s: %m", ep->tmp_path);
+ if (stat(ep->tmp_path, &after_lock) < 0
+ || before_lock.st_dev != after_lock.st_dev
+ || before_lock.st_ino != after_lock.st_ino
+#ifdef HAS_ST_GEN
+ || before_lock.st_gen != after_lock.st_gen
+#endif
+ /* No need to compare st_rdev or st_nlink here. */
+ ) {
+ continue;
+ }
+
+ /*
+ * At this point we have exclusive control over the output file
+ * content and its temporary pathname (within the rules of the
+ * cooperative protocol). But wait, there is more.
+ *
+ * There are many opportunities for trouble when opening a pre-existing
+ * output file. Here are just a few.
+ *
+ * - Victor observes that a system crash in the middle of the
+ * final-phase rename() operation may result in the output file
+ * having both the temporary pathname and the final pathname. In that
+ * case we must not write to the output file.
+ *
+ * - Wietse observes that crashes may also leave the output file in
+ * other inconsistent states. To avoid permission-related trouble, we
+ * simply refuse to work with an output file that has the wrong
+ * temporary permissions. This won't stop the shared-lock DOS if we
+ * crash after changing the file permissions, though.
+ *
+ * To work around these crash-related problems, remove the temporary
+ * pathname, back out, and try again.
+ */
+ if (!S_ISREG(after_lock.st_mode)
+#ifndef EDIT_FILE_REUSE_AFTER_CRASH
+ || after_lock.st_size > 0
+#endif
+ || after_lock.st_nlink > 1
+ || (after_lock.st_mode & FILE_PERM_MASK) != EDIT_FILE_MODE) {
+ if (unlink(ep->tmp_path) < 0 && errno != ENOENT)
+ msg_fatal("unlink %s: %m", ep->tmp_path);
+ continue;
+ }
+
+ /*
+ * Settle the final details.
+ */
+#ifdef EDIT_FILE_REUSE_AFTER_CRASH
+ if (ftruncate(vstream_fileno(ep->tmp_fp), 0) < 0)
+ msg_fatal("truncate %s: %m", ep->tmp_path);
+#endif
+ return (ep);
+ }
+}
+
+/* edit_file_cleanup - clean up without completing the protocol */
+
+void edit_file_cleanup(EDIT_FILE *ep)
+{
+
+ /*
+ * Don't touch the file after we lose the exclusive lock!
+ */
+ if (unlink(ep->tmp_path) < 0 && errno != ENOENT)
+ msg_fatal("unlink %s: %m", ep->tmp_path);
+ (void) vstream_fclose(ep->tmp_fp);
+ EDIT_FILE_FREE(ep);
+}
+
+/* edit_file_close - rename the file into place and close the file */
+
+int edit_file_close(EDIT_FILE *ep)
+{
+ VSTREAM *fp = ep->tmp_fp;
+ int fd = vstream_fileno(fp);
+ int saved_errno;
+
+ /*
+ * The rename/unlock portion of the protocol is relatively simple. The
+ * only things that really matter here are that we change permissions as
+ * late as possible, and that we rename the file to its final pathname
+ * before we lose the exclusive lock.
+ *
+ * Applications that are concerned about maximal safety should protect the
+ * edit_file_close() call with sigdelay() and sigresume() calls. It is
+ * not safe for us to call these functions directly, because the calls do
+ * not nest. It is also not nice to force every caller to run with
+ * interrupts turned off.
+ */
+ if (vstream_fflush(fp) < 0
+ || fchmod(fd, ep->final_mode) < 0
+#ifdef HAS_FSYNC
+ || fsync(fd) < 0
+#endif
+ || rename(ep->tmp_path, ep->final_path) < 0) {
+ saved_errno = errno;
+ edit_file_cleanup(ep);
+ errno = saved_errno;
+ return (VSTREAM_EOF);
+ } else {
+ (void) vstream_fclose(ep->tmp_fp);
+ EDIT_FILE_FREE(ep);
+ return (0);
+ }
+}
diff --git a/src/util/edit_file.h b/src/util/edit_file.h
new file mode 100644
index 0000000..bd13a29
--- /dev/null
+++ b/src/util/edit_file.h
@@ -0,0 +1,53 @@
+#ifndef _EDIT_FILE_H_INCLUDED_
+#define _EDIT_FILE_H_INCLUDED_
+
+/*++
+/* NAME
+/* edit_file 3h
+/* SUMMARY
+/* simple cooperative file updating protocol
+/* SYNOPSIS
+/* #include <edit_file.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+
+ /*
+ * External interface.
+ */
+typedef struct {
+ /* Private. */
+ char *final_path;
+ mode_t final_mode;
+ /* Public. */
+ char *tmp_path;
+ VSTREAM *tmp_fp;
+} EDIT_FILE;
+
+#define EDIT_FILE_SUFFIX ".tmp"
+
+extern EDIT_FILE *edit_file_open(const char *, int, mode_t);
+extern int WARN_UNUSED_RESULT edit_file_close(EDIT_FILE *);
+extern void edit_file_cleanup(EDIT_FILE *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/environ.c b/src/util/environ.c
new file mode 100644
index 0000000..4b6c59e
--- /dev/null
+++ b/src/util/environ.c
@@ -0,0 +1,156 @@
+ /*
+ * From: TCP Wrapper.
+ *
+ * Many systems have putenv() but no setenv(). Other systems have setenv() but
+ * no putenv() (MIPS). Still other systems have neither (NeXT). This is a
+ * re-implementation that hopefully ends all problems.
+ *
+ * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
+ */
+#include "sys_defs.h"
+
+#ifdef MISSING_SETENV_PUTENV
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+extern char **environ;
+
+static int addenv(char *); /* append entry to environment */
+static int allocated = 0; /* environ is, or is not, allocated */
+
+#define DO_CLOBBER 1
+
+/* namelength - determine length of name in "name=whatever" */
+
+static ssize_t namelength(const char *name)
+{
+ char *equal;
+
+ equal = strchr(name, '=');
+ return ((equal == 0) ? strlen(name) : (equal - name));
+}
+
+/* findenv - given name, locate name=value */
+
+static char **findenv(const char *name, ssize_t len)
+{
+ char **envp;
+
+ for (envp = environ; envp && *envp; envp++)
+ if (strncmp(name, *envp, len) == 0 && (*envp)[len] == '=')
+ return (envp);
+ return (0);
+}
+
+#if 0
+
+/* getenv - given name, locate value */
+
+char *getenv(const char *name)
+{
+ ssize_t len = namelength(name);
+ char **envp = findenv(name, len);
+
+ return (envp ? *envp + len + 1 : 0);
+}
+
+/* putenv - update or append environment (name,value) pair */
+
+int putenv(const char *nameval)
+{
+ char *equal = strchr(nameval, '=');
+ char *value = (equal ? equal : "");
+
+ return (setenv(nameval, value, DO_CLOBBER));
+}
+
+/* unsetenv - remove variable from environment */
+
+void unsetenv(const char *name)
+{
+ char **envp;
+
+ while ((envp = findenv(name, namelength(name))) != 0)
+ while (envp[0] = envp[1])
+ envp++;
+}
+
+#endif
+
+/* setenv - update or append environment (name,value) pair */
+
+int setenv(const char *name, const char *value, int clobber)
+{
+ char *destination;
+ char **envp;
+ ssize_t l_name; /* length of name part */
+ unsigned int l_nameval; /* length of name=value */
+
+ /* Permit name= and =value. */
+
+ l_name = namelength(name);
+ envp = findenv(name, l_name);
+ if (envp != 0 && clobber == 0)
+ return (0);
+ if (*value == '=')
+ value++;
+ l_nameval = l_name + strlen(value) + 1;
+
+ /*
+ * Use available memory if the old value is long enough. Never free an
+ * old name=value entry because it may not be allocated.
+ */
+
+ destination = (envp != 0 && strlen(*envp) >= l_nameval) ?
+ *envp : malloc(l_nameval + 1);
+ if (destination == 0)
+ return (-1);
+ strncpy(destination, name, l_name);
+ destination[l_name] = '=';
+ strcpy(destination + l_name + 1, value);
+ return ((envp == 0) ? addenv(destination) : (*envp = destination, 0));
+}
+
+/* cmalloc - malloc and copy block of memory */
+
+static char *cmalloc(ssize_t new_len, char *old, ssize_t old_len)
+{
+ char *new = malloc(new_len);
+
+ if (new != 0)
+ memcpy(new, old, old_len);
+ return (new);
+}
+
+/* addenv - append environment entry */
+
+static int addenv(char *nameval)
+{
+ char **envp;
+ ssize_t n_used; /* number of environment entries */
+ ssize_t l_used; /* bytes used excl. terminator */
+ ssize_t l_need; /* bytes needed incl. terminator */
+
+ for (envp = environ; envp && *envp; envp++)
+ /* void */ ;
+ n_used = envp - environ;
+ l_used = n_used * sizeof(*envp);
+ l_need = l_used + 2 * sizeof(*envp);
+
+ envp = allocated ?
+ (char **) realloc((char *) environ, l_need) :
+ (char **) cmalloc(l_need, (char *) environ, l_used);
+ if (envp == 0) {
+ return (-1);
+ } else {
+ allocated = 1;
+ environ = envp;
+ environ[n_used++] = nameval; /* add new entry */
+ environ[n_used] = 0; /* terminate list */
+ return (0);
+ }
+}
+
+#endif
diff --git a/src/util/events.c b/src/util/events.c
new file mode 100644
index 0000000..c2157bb
--- /dev/null
+++ b/src/util/events.c
@@ -0,0 +1,1261 @@
+/*++
+/* NAME
+/* events 3
+/* SUMMARY
+/* event manager
+/* SYNOPSIS
+/* #include <events.h>
+/*
+/* time_t event_time()
+/*
+/* void event_loop(delay)
+/* int delay;
+/*
+/* time_t event_request_timer(callback, context, delay)
+/* void (*callback)(int event, void *context);
+/* void *context;
+/* int delay;
+/*
+/* int event_cancel_timer(callback, context)
+/* void (*callback)(int event, void *context);
+/* void *context;
+/*
+/* void event_enable_read(fd, callback, context)
+/* int fd;
+/* void (*callback)(int event, void *context);
+/* void *context;
+/*
+/* void event_enable_write(fd, callback, context)
+/* int fd;
+/* void (*callback)(int event, void *context);
+/* void *context;
+/*
+/* void event_disable_readwrite(fd)
+/* int fd;
+/*
+/* void event_drain(time_limit)
+/* int time_limit;
+/*
+/* void event_fork(void)
+/* DESCRIPTION
+/* This module delivers I/O and timer events.
+/* Multiple I/O streams and timers can be monitored simultaneously.
+/* Events are delivered via callback routines provided by the
+/* application. When requesting an event, the application can provide
+/* private context that is passed back when the callback routine is
+/* executed.
+/*
+/* event_time() returns a cached value of the current time.
+/*
+/* event_loop() monitors all I/O channels for which the application has
+/* expressed interest, and monitors the timer request queue.
+/* It notifies the application whenever events of interest happen.
+/* A negative delay value causes the function to pause until something
+/* happens; a positive delay value causes event_loop() to return when
+/* the next event happens or when the delay time in seconds is over,
+/* whatever happens first. A zero delay effectuates a poll.
+/*
+/* Note: in order to avoid race conditions, event_loop() cannot
+/* not be called recursively.
+/*
+/* event_request_timer() causes the specified callback function to
+/* be called with the specified context argument after \fIdelay\fR
+/* seconds, or as soon as possible thereafter. The delay should
+/* not be negative (the manifest EVENT_NULL_DELAY provides for
+/* convenient zero-delay notification).
+/* The event argument is equal to EVENT_TIME.
+/* Only one timer request can be active per (callback, context) pair.
+/* Calling event_request_timer() with an existing (callback, context)
+/* pair does not schedule a new event, but updates the time of event
+/* delivery. The result is the absolute time at which the timer is
+/* scheduled to go off.
+/*
+/* event_cancel_timer() cancels the specified (callback, context) request.
+/* The application is allowed to cancel non-existing requests. The result
+/* value is the amount of time left before the timer would have gone off,
+/* or -1 in case of no pending timer.
+/*
+/* event_enable_read() (event_enable_write()) enables read (write) events
+/* on the named I/O channel. It is up to the application to assemble
+/* partial reads or writes.
+/* An I/O channel cannot handle more than one request at the
+/* same time. The application is allowed to enable an event that
+/* is already enabled (same channel, same read or write operation,
+/* but perhaps a different callback or context). On systems with
+/* kernel-based event filters this is preferred usage, because
+/* each disable and enable request would cost a system call.
+/*
+/* The manifest constants EVENT_NULL_CONTEXT and EVENT_NULL_TYPE
+/* provide convenient null values.
+/*
+/* The callback routine has the following arguments:
+/* .IP fd
+/* The stream on which the event happened.
+/* .IP event
+/* An indication of the event type:
+/* .RS
+/* .IP EVENT_READ
+/* read event,
+/* .IP EVENT_WRITE
+/* write event,
+/* .IP EVENT_XCPT
+/* exception (actually, any event other than read or write).
+/* .RE
+/* .IP context
+/* Application context given to event_enable_read() (event_enable_write()).
+/* .PP
+/* event_disable_readwrite() disables further I/O events on the specified
+/* I/O channel. The application is allowed to cancel non-existing
+/* I/O event requests.
+/*
+/* event_drain() repeatedly calls event_loop() until no more timer
+/* events or I/O events are pending or until the time limit is reached.
+/* This routine must not be called from an event_whatever() callback
+/* routine. Note: this function assumes that no new I/O events
+/* will be registered.
+/*
+/* event_fork() must be called by a child process after it is
+/* created with fork(), to re-initialize event processing.
+/* DIAGNOSTICS
+/* Panics: interface violations. Fatal errors: out of memory,
+/* system call failure. Warnings: the number of available
+/* file descriptors is much less than FD_SETSIZE.
+/* BUGS
+/* This module is based on event selection. It assumes that the
+/* event_loop() routine is called frequently. This approach is
+/* not suitable for applications with compute-bound loops that
+/* take a significant amount of time.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System libraries. */
+
+#include "sys_defs.h"
+#include <sys/time.h> /* XXX: 44BSD uses bzero() */
+#include <time.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stddef.h> /* offsetof() */
+#include <string.h> /* bzero() prototype for 44BSD */
+#include <limits.h> /* INT_MAX */
+
+#ifdef USE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+
+/* Application-specific. */
+
+#include "mymalloc.h"
+#include "msg.h"
+#include "iostuff.h"
+#include "ring.h"
+#include "events.h"
+
+#if !defined(EVENTS_STYLE)
+#error "must define EVENTS_STYLE"
+#endif
+
+ /*
+ * Traditional BSD-style select(2). Works everywhere, but has a built-in
+ * upper bound on the number of file descriptors, and that limit is hard to
+ * change on Linux. Is sometimes emulated with SYSV-style poll(2) which
+ * doesn't have the file descriptor limit, but unfortunately does not help
+ * to improve the performance of servers with lots of connections.
+ */
+#define EVENT_ALLOC_INCR 10
+
+#if (EVENTS_STYLE == EVENTS_STYLE_SELECT)
+typedef fd_set EVENT_MASK;
+
+#define EVENT_MASK_BYTE_COUNT(mask) sizeof(*(mask))
+#define EVENT_MASK_ZERO(mask) FD_ZERO(mask)
+#define EVENT_MASK_SET(fd, mask) FD_SET((fd), (mask))
+#define EVENT_MASK_ISSET(fd, mask) FD_ISSET((fd), (mask))
+#define EVENT_MASK_CLR(fd, mask) FD_CLR((fd), (mask))
+#define EVENT_MASK_CMP(m1, m2) memcmp((m1), (m2), EVENT_MASK_BYTE_COUNT(m1))
+#else
+
+ /*
+ * Kernel-based event filters (kqueue, /dev/poll, epoll). We use the
+ * following file descriptor mask structure which is expanded on the fly.
+ */
+typedef struct {
+ char *data; /* bit mask */
+ size_t data_len; /* data byte count */
+} EVENT_MASK;
+
+ /* Bits per byte, byte in vector, bit offset in byte, bytes per set. */
+#define EVENT_MASK_NBBY (8)
+#define EVENT_MASK_FD_BYTE(fd, mask) \
+ (((unsigned char *) (mask)->data)[(fd) / EVENT_MASK_NBBY])
+#define EVENT_MASK_FD_BIT(fd) (1 << ((fd) % EVENT_MASK_NBBY))
+#define EVENT_MASK_BYTES_NEEDED(len) \
+ (((len) + (EVENT_MASK_NBBY -1)) / EVENT_MASK_NBBY)
+#define EVENT_MASK_BYTE_COUNT(mask) ((mask)->data_len)
+
+ /* Memory management. */
+#define EVENT_MASK_ALLOC(mask, bit_len) do { \
+ size_t _byte_len = EVENT_MASK_BYTES_NEEDED(bit_len); \
+ (mask)->data = mymalloc(_byte_len); \
+ memset((mask)->data, 0, _byte_len); \
+ (mask)->data_len = _byte_len; \
+ } while (0)
+#define EVENT_MASK_REALLOC(mask, bit_len) do { \
+ size_t _byte_len = EVENT_MASK_BYTES_NEEDED(bit_len); \
+ size_t _old_len = (mask)->data_len; \
+ (mask)->data = myrealloc((mask)->data, _byte_len); \
+ if (_byte_len > _old_len) \
+ memset((mask)->data + _old_len, 0, _byte_len - _old_len); \
+ (mask)->data_len = _byte_len; \
+ } while (0)
+#define EVENT_MASK_FREE(mask) myfree((mask)->data)
+
+ /* Set operations, modeled after FD_ZERO/SET/ISSET/CLR. */
+#define EVENT_MASK_ZERO(mask) \
+ memset((mask)->data, 0, (mask)->data_len)
+#define EVENT_MASK_SET(fd, mask) \
+ (EVENT_MASK_FD_BYTE((fd), (mask)) |= EVENT_MASK_FD_BIT(fd))
+#define EVENT_MASK_ISSET(fd, mask) \
+ (EVENT_MASK_FD_BYTE((fd), (mask)) & EVENT_MASK_FD_BIT(fd))
+#define EVENT_MASK_CLR(fd, mask) \
+ (EVENT_MASK_FD_BYTE((fd), (mask)) &= ~EVENT_MASK_FD_BIT(fd))
+#define EVENT_MASK_CMP(m1, m2) \
+ memcmp((m1)->data, (m2)->data, EVENT_MASK_BYTE_COUNT(m1))
+#endif
+
+ /*
+ * I/O events.
+ */
+typedef struct EVENT_FDTABLE EVENT_FDTABLE;
+
+struct EVENT_FDTABLE {
+ EVENT_NOTIFY_RDWR_FN callback;
+ char *context;
+};
+static EVENT_MASK event_rmask; /* enabled read events */
+static EVENT_MASK event_wmask; /* enabled write events */
+static EVENT_MASK event_xmask; /* for bad news mostly */
+static int event_fdlimit; /* per-process open file limit */
+static EVENT_FDTABLE *event_fdtable; /* one slot per file descriptor */
+static int event_fdslots; /* number of file descriptor slots */
+static int event_max_fd = -1; /* highest fd number seen */
+
+ /*
+ * FreeBSD kqueue supports no system call to find out what descriptors are
+ * registered in the kernel-based filter. To implement our own sanity checks
+ * we maintain our own descriptor bitmask.
+ *
+ * FreeBSD kqueue does support application context pointers. Unfortunately,
+ * changing that information would cost a system call, and some of the
+ * competitors don't support application context. To keep the implementation
+ * simple we maintain our own table with call-back information.
+ *
+ * FreeBSD kqueue silently unregisters a descriptor from its filter when the
+ * descriptor is closed, so our information could get out of sync with the
+ * kernel. But that will never happen, because we have to meticulously
+ * unregister a file descriptor before it is closed, to avoid errors on
+ * systems that are built with EVENTS_STYLE == EVENTS_STYLE_SELECT.
+ */
+#if (EVENTS_STYLE == EVENTS_STYLE_KQUEUE)
+#include <sys/event.h>
+
+ /*
+ * Some early FreeBSD implementations don't have the EV_SET macro.
+ */
+#ifndef EV_SET
+#define EV_SET(kp, id, fi, fl, ffl, da, ud) do { \
+ (kp)->ident = (id); \
+ (kp)->filter = (fi); \
+ (kp)->flags = (fl); \
+ (kp)->fflags = (ffl); \
+ (kp)->data = (da); \
+ (kp)->udata = (ud); \
+ } while(0)
+#endif
+
+ /*
+ * Macros to initialize the kernel-based filter; see event_init().
+ */
+static int event_kq; /* handle to event filter */
+
+#define EVENT_REG_INIT_HANDLE(er, n) do { \
+ er = event_kq = kqueue(); \
+ } while (0)
+#define EVENT_REG_INIT_TEXT "kqueue"
+
+#define EVENT_REG_FORK_HANDLE(er, n) do { \
+ (void) close(event_kq); \
+ EVENT_REG_INIT_HANDLE(er, (n)); \
+ } while (0)
+
+ /*
+ * Macros to update the kernel-based filter; see event_enable_read(),
+ * event_enable_write() and event_disable_readwrite().
+ */
+#define EVENT_REG_FD_OP(er, fh, ev, op) do { \
+ struct kevent dummy; \
+ EV_SET(&dummy, (fh), (ev), (op), 0, 0, 0); \
+ (er) = kevent(event_kq, &dummy, 1, 0, 0, 0); \
+ } while (0)
+
+#define EVENT_REG_ADD_OP(e, f, ev) EVENT_REG_FD_OP((e), (f), (ev), EV_ADD)
+#define EVENT_REG_ADD_READ(e, f) EVENT_REG_ADD_OP((e), (f), EVFILT_READ)
+#define EVENT_REG_ADD_WRITE(e, f) EVENT_REG_ADD_OP((e), (f), EVFILT_WRITE)
+#define EVENT_REG_ADD_TEXT "kevent EV_ADD"
+
+#define EVENT_REG_DEL_OP(e, f, ev) EVENT_REG_FD_OP((e), (f), (ev), EV_DELETE)
+#define EVENT_REG_DEL_READ(e, f) EVENT_REG_DEL_OP((e), (f), EVFILT_READ)
+#define EVENT_REG_DEL_WRITE(e, f) EVENT_REG_DEL_OP((e), (f), EVFILT_WRITE)
+#define EVENT_REG_DEL_TEXT "kevent EV_DELETE"
+
+ /*
+ * Macros to retrieve event buffers from the kernel; see event_loop().
+ */
+typedef struct kevent EVENT_BUFFER;
+
+#define EVENT_BUFFER_READ(event_count, event_buf, buflen, delay) do { \
+ struct timespec ts; \
+ struct timespec *tsp; \
+ if ((delay) < 0) { \
+ tsp = 0; \
+ } else { \
+ tsp = &ts; \
+ ts.tv_nsec = 0; \
+ ts.tv_sec = (delay); \
+ } \
+ (event_count) = kevent(event_kq, (struct kevent *) 0, 0, (event_buf), \
+ (buflen), (tsp)); \
+ } while (0)
+#define EVENT_BUFFER_READ_TEXT "kevent"
+
+ /*
+ * Macros to process event buffers from the kernel; see event_loop().
+ */
+#define EVENT_GET_FD(bp) ((bp)->ident)
+#define EVENT_GET_TYPE(bp) ((bp)->filter)
+#define EVENT_TEST_READ(bp) (EVENT_GET_TYPE(bp) == EVFILT_READ)
+#define EVENT_TEST_WRITE(bp) (EVENT_GET_TYPE(bp) == EVFILT_WRITE)
+
+#endif
+
+ /*
+ * Solaris /dev/poll does not support application context, so we have to
+ * maintain our own. This has the benefit of avoiding an expensive system
+ * call just to change a call-back function or argument.
+ *
+ * Solaris /dev/poll does have a way to query if a specific descriptor is
+ * registered. However, we maintain a descriptor mask anyway because a) it
+ * avoids having to make an expensive system call to find out if something
+ * is registered, b) some EVENTS_STYLE_MUMBLE implementations need a
+ * descriptor bitmask anyway and c) we use the bitmask already to implement
+ * sanity checks.
+ */
+#if (EVENTS_STYLE == EVENTS_STYLE_DEVPOLL)
+#include <sys/devpoll.h>
+#include <fcntl.h>
+
+ /*
+ * Macros to initialize the kernel-based filter; see event_init().
+ */
+static int event_pollfd; /* handle to file descriptor set */
+
+#define EVENT_REG_INIT_HANDLE(er, n) do { \
+ er = event_pollfd = open("/dev/poll", O_RDWR); \
+ if (event_pollfd >= 0) close_on_exec(event_pollfd, CLOSE_ON_EXEC); \
+ } while (0)
+#define EVENT_REG_INIT_TEXT "open /dev/poll"
+
+#define EVENT_REG_FORK_HANDLE(er, n) do { \
+ (void) close(event_pollfd); \
+ EVENT_REG_INIT_HANDLE(er, (n)); \
+ } while (0)
+
+ /*
+ * Macros to update the kernel-based filter; see event_enable_read(),
+ * event_enable_write() and event_disable_readwrite().
+ */
+#define EVENT_REG_FD_OP(er, fh, ev) do { \
+ struct pollfd dummy; \
+ dummy.fd = (fh); \
+ dummy.events = (ev); \
+ (er) = write(event_pollfd, (void *) &dummy, \
+ sizeof(dummy)) != sizeof(dummy) ? -1 : 0; \
+ } while (0)
+
+#define EVENT_REG_ADD_READ(e, f) EVENT_REG_FD_OP((e), (f), POLLIN)
+#define EVENT_REG_ADD_WRITE(e, f) EVENT_REG_FD_OP((e), (f), POLLOUT)
+#define EVENT_REG_ADD_TEXT "write /dev/poll"
+
+#define EVENT_REG_DEL_BOTH(e, f) EVENT_REG_FD_OP((e), (f), POLLREMOVE)
+#define EVENT_REG_DEL_TEXT "write /dev/poll"
+
+ /*
+ * Macros to retrieve event buffers from the kernel; see event_loop().
+ */
+typedef struct pollfd EVENT_BUFFER;
+
+#define EVENT_BUFFER_READ(event_count, event_buf, buflen, delay) do { \
+ struct dvpoll dvpoll; \
+ dvpoll.dp_fds = (event_buf); \
+ dvpoll.dp_nfds = (buflen); \
+ dvpoll.dp_timeout = (delay) < 0 ? -1 : (delay) * 1000; \
+ (event_count) = ioctl(event_pollfd, DP_POLL, &dvpoll); \
+ } while (0)
+#define EVENT_BUFFER_READ_TEXT "ioctl DP_POLL"
+
+ /*
+ * Macros to process event buffers from the kernel; see event_loop().
+ */
+#define EVENT_GET_FD(bp) ((bp)->fd)
+#define EVENT_GET_TYPE(bp) ((bp)->revents)
+#define EVENT_TEST_READ(bp) (EVENT_GET_TYPE(bp) & POLLIN)
+#define EVENT_TEST_WRITE(bp) (EVENT_GET_TYPE(bp) & POLLOUT)
+
+#endif
+
+ /*
+ * Linux epoll supports no system call to find out what descriptors are
+ * registered in the kernel-based filter. To implement our own sanity checks
+ * we maintain our own descriptor bitmask.
+ *
+ * Linux epoll does support application context pointers. Unfortunately,
+ * changing that information would cost a system call, and some of the
+ * competitors don't support application context. To keep the implementation
+ * simple we maintain our own table with call-back information.
+ *
+ * Linux epoll silently unregisters a descriptor from its filter when the
+ * descriptor is closed, so our information could get out of sync with the
+ * kernel. But that will never happen, because we have to meticulously
+ * unregister a file descriptor before it is closed, to avoid errors on
+ * systems that are built with EVENTS_STYLE == EVENTS_STYLE_SELECT.
+ */
+#if (EVENTS_STYLE == EVENTS_STYLE_EPOLL)
+#include <sys/epoll.h>
+
+ /*
+ * Macros to initialize the kernel-based filter; see event_init().
+ */
+static int event_epollfd; /* epoll handle */
+
+#define EVENT_REG_INIT_HANDLE(er, n) do { \
+ er = event_epollfd = epoll_create(n); \
+ if (event_epollfd >= 0) close_on_exec(event_epollfd, CLOSE_ON_EXEC); \
+ } while (0)
+#define EVENT_REG_INIT_TEXT "epoll_create"
+
+#define EVENT_REG_FORK_HANDLE(er, n) do { \
+ (void) close(event_epollfd); \
+ EVENT_REG_INIT_HANDLE(er, (n)); \
+ } while (0)
+
+ /*
+ * Macros to update the kernel-based filter; see event_enable_read(),
+ * event_enable_write() and event_disable_readwrite().
+ */
+#define EVENT_REG_FD_OP(er, fh, ev, op) do { \
+ struct epoll_event dummy; \
+ dummy.events = (ev); \
+ dummy.data.fd = (fh); \
+ (er) = epoll_ctl(event_epollfd, (op), (fh), &dummy); \
+ } while (0)
+
+#define EVENT_REG_ADD_OP(e, f, ev) EVENT_REG_FD_OP((e), (f), (ev), EPOLL_CTL_ADD)
+#define EVENT_REG_ADD_READ(e, f) EVENT_REG_ADD_OP((e), (f), EPOLLIN)
+#define EVENT_REG_ADD_WRITE(e, f) EVENT_REG_ADD_OP((e), (f), EPOLLOUT)
+#define EVENT_REG_ADD_TEXT "epoll_ctl EPOLL_CTL_ADD"
+
+#define EVENT_REG_DEL_OP(e, f, ev) EVENT_REG_FD_OP((e), (f), (ev), EPOLL_CTL_DEL)
+#define EVENT_REG_DEL_READ(e, f) EVENT_REG_DEL_OP((e), (f), EPOLLIN)
+#define EVENT_REG_DEL_WRITE(e, f) EVENT_REG_DEL_OP((e), (f), EPOLLOUT)
+#define EVENT_REG_DEL_TEXT "epoll_ctl EPOLL_CTL_DEL"
+
+ /*
+ * Macros to retrieve event buffers from the kernel; see event_loop().
+ */
+typedef struct epoll_event EVENT_BUFFER;
+
+#define EVENT_BUFFER_READ(event_count, event_buf, buflen, delay) do { \
+ (event_count) = epoll_wait(event_epollfd, (event_buf), (buflen), \
+ (delay) < 0 ? -1 : (delay) * 1000); \
+ } while (0)
+#define EVENT_BUFFER_READ_TEXT "epoll_wait"
+
+ /*
+ * Macros to process event buffers from the kernel; see event_loop().
+ */
+#define EVENT_GET_FD(bp) ((bp)->data.fd)
+#define EVENT_GET_TYPE(bp) ((bp)->events)
+#define EVENT_TEST_READ(bp) (EVENT_GET_TYPE(bp) & EPOLLIN)
+#define EVENT_TEST_WRITE(bp) (EVENT_GET_TYPE(bp) & EPOLLOUT)
+
+#endif
+
+ /*
+ * Timer events. Timer requests are kept sorted, in a circular list. We use
+ * the RING abstraction, so we get to use a couple ugly macros.
+ *
+ * When a call-back function adds a timer request, we label the request with
+ * the event_loop() call instance that invoked the call-back. We use this to
+ * prevent zero-delay timer requests from running in a tight loop and
+ * starving I/O events.
+ */
+typedef struct EVENT_TIMER EVENT_TIMER;
+
+struct EVENT_TIMER {
+ time_t when; /* when event is wanted */
+ EVENT_NOTIFY_TIME_FN callback; /* callback function */
+ char *context; /* callback context */
+ long loop_instance; /* event_loop() call instance */
+ RING ring; /* linkage */
+};
+
+static RING event_timer_head; /* timer queue head */
+static long event_loop_instance; /* event_loop() call instance */
+
+#define RING_TO_TIMER(r) \
+ ((EVENT_TIMER *) ((void *) (r) - offsetof(EVENT_TIMER, ring)))
+
+#define FOREACH_QUEUE_ENTRY(entry, head) \
+ for (entry = ring_succ(head); entry != (head); entry = ring_succ(entry))
+
+#define FIRST_TIMER(head) \
+ (ring_succ(head) != (head) ? RING_TO_TIMER(ring_succ(head)) : 0)
+
+ /*
+ * Other private data structures.
+ */
+static time_t event_present; /* cached time of day */
+
+#define EVENT_INIT_NEEDED() (event_present == 0)
+
+/* event_init - set up tables and such */
+
+static void event_init(void)
+{
+ EVENT_FDTABLE *fdp;
+ int err;
+
+ if (!EVENT_INIT_NEEDED())
+ msg_panic("event_init: repeated call");
+
+ /*
+ * Initialize the file descriptor masks and the call-back table. Where
+ * possible we extend these data structures on the fly. With select(2)
+ * based implementations we can only handle FD_SETSIZE open files.
+ */
+#if (EVENTS_STYLE == EVENTS_STYLE_SELECT)
+ if ((event_fdlimit = open_limit(FD_SETSIZE)) < 0)
+ msg_fatal("unable to determine open file limit");
+#else
+ if ((event_fdlimit = open_limit(INT_MAX)) < 0)
+ msg_fatal("unable to determine open file limit");
+#endif
+ if (event_fdlimit < FD_SETSIZE / 2 && event_fdlimit < 256)
+ msg_warn("could allocate space for only %d open files", event_fdlimit);
+ event_fdslots = EVENT_ALLOC_INCR;
+ event_fdtable = (EVENT_FDTABLE *)
+ mymalloc(sizeof(EVENT_FDTABLE) * event_fdslots);
+ for (fdp = event_fdtable; fdp < event_fdtable + event_fdslots; fdp++) {
+ fdp->callback = 0;
+ fdp->context = 0;
+ }
+
+ /*
+ * Initialize the I/O event request masks.
+ */
+#if (EVENTS_STYLE == EVENTS_STYLE_SELECT)
+ EVENT_MASK_ZERO(&event_rmask);
+ EVENT_MASK_ZERO(&event_wmask);
+ EVENT_MASK_ZERO(&event_xmask);
+#else
+ EVENT_MASK_ALLOC(&event_rmask, event_fdslots);
+ EVENT_MASK_ALLOC(&event_wmask, event_fdslots);
+ EVENT_MASK_ALLOC(&event_xmask, event_fdslots);
+
+ /*
+ * Initialize the kernel-based filter.
+ */
+ EVENT_REG_INIT_HANDLE(err, event_fdslots);
+ if (err < 0)
+ msg_fatal("%s: %m", EVENT_REG_INIT_TEXT);
+#endif
+
+ /*
+ * Initialize timer stuff.
+ */
+ ring_init(&event_timer_head);
+ (void) time(&event_present);
+
+ /*
+ * Avoid an infinite initialization loop.
+ */
+ if (EVENT_INIT_NEEDED())
+ msg_panic("event_init: unable to initialize");
+}
+
+/* event_extend - make room for more descriptor slots */
+
+static void event_extend(int fd)
+{
+ const char *myname = "event_extend";
+ int old_slots = event_fdslots;
+ int new_slots = (event_fdslots > fd / 2 ?
+ 2 * old_slots : fd + EVENT_ALLOC_INCR);
+ EVENT_FDTABLE *fdp;
+
+#ifdef EVENT_REG_UPD_HANDLE
+ int err;
+
+#endif
+
+ if (msg_verbose > 2)
+ msg_info("%s: fd %d", myname, fd);
+ event_fdtable = (EVENT_FDTABLE *)
+ myrealloc((void *) event_fdtable, sizeof(EVENT_FDTABLE) * new_slots);
+ event_fdslots = new_slots;
+ for (fdp = event_fdtable + old_slots;
+ fdp < event_fdtable + new_slots; fdp++) {
+ fdp->callback = 0;
+ fdp->context = 0;
+ }
+
+ /*
+ * Initialize the I/O event request masks.
+ */
+#if (EVENTS_STYLE != EVENTS_STYLE_SELECT)
+ EVENT_MASK_REALLOC(&event_rmask, new_slots);
+ EVENT_MASK_REALLOC(&event_wmask, new_slots);
+ EVENT_MASK_REALLOC(&event_xmask, new_slots);
+#endif
+#ifdef EVENT_REG_UPD_HANDLE
+ EVENT_REG_UPD_HANDLE(err, new_slots);
+ if (err < 0)
+ msg_fatal("%s: %s: %m", myname, EVENT_REG_UPD_TEXT);
+#endif
+}
+
+/* event_time - look up cached time of day */
+
+time_t event_time(void)
+{
+ if (EVENT_INIT_NEEDED())
+ event_init();
+
+ return (event_present);
+}
+
+/* event_drain - loop until all pending events are done */
+
+void event_drain(int time_limit)
+{
+ EVENT_MASK zero_mask;
+ time_t max_time;
+
+ if (EVENT_INIT_NEEDED())
+ return;
+
+#if (EVENTS_STYLE == EVENTS_STYLE_SELECT)
+ EVENT_MASK_ZERO(&zero_mask);
+#else
+ EVENT_MASK_ALLOC(&zero_mask, event_fdslots);
+#endif
+ (void) time(&event_present);
+ max_time = event_present + time_limit;
+ while (event_present < max_time
+ && (event_timer_head.pred != &event_timer_head
+ || EVENT_MASK_CMP(&zero_mask, &event_xmask) != 0)) {
+ event_loop(1);
+#if (EVENTS_STYLE != EVENTS_STYLE_SELECT)
+ if (EVENT_MASK_BYTE_COUNT(&zero_mask)
+ != EVENT_MASK_BYTES_NEEDED(event_fdslots))
+ EVENT_MASK_REALLOC(&zero_mask, event_fdslots);
+#endif
+ }
+#if (EVENTS_STYLE != EVENTS_STYLE_SELECT)
+ EVENT_MASK_FREE(&zero_mask);
+#endif
+}
+
+/* event_fork - resume event processing after fork() */
+
+void event_fork(void)
+{
+#if (EVENTS_STYLE != EVENTS_STYLE_SELECT)
+ EVENT_FDTABLE *fdp;
+ int err;
+ int fd;
+
+ /*
+ * No event was ever registered, so there's nothing to be done.
+ */
+ if (EVENT_INIT_NEEDED())
+ return;
+
+ /*
+ * Close the existing filter handle and open a new kernel-based filter.
+ */
+ EVENT_REG_FORK_HANDLE(err, event_fdslots);
+ if (err < 0)
+ msg_fatal("%s: %m", EVENT_REG_INIT_TEXT);
+
+ /*
+ * Populate the new kernel-based filter with events that were registered
+ * in the parent process.
+ */
+ for (fd = 0; fd <= event_max_fd; fd++) {
+ if (EVENT_MASK_ISSET(fd, &event_wmask)) {
+ EVENT_MASK_CLR(fd, &event_wmask);
+ fdp = event_fdtable + fd;
+ event_enable_write(fd, fdp->callback, fdp->context);
+ } else if (EVENT_MASK_ISSET(fd, &event_rmask)) {
+ EVENT_MASK_CLR(fd, &event_rmask);
+ fdp = event_fdtable + fd;
+ event_enable_read(fd, fdp->callback, fdp->context);
+ }
+ }
+#endif
+}
+
+/* event_enable_read - enable read events */
+
+void event_enable_read(int fd, EVENT_NOTIFY_RDWR_FN callback, void *context)
+{
+ const char *myname = "event_enable_read";
+ EVENT_FDTABLE *fdp;
+ int err;
+
+ if (EVENT_INIT_NEEDED())
+ event_init();
+
+ /*
+ * Sanity checks.
+ */
+ if (fd < 0 || fd >= event_fdlimit)
+ msg_panic("%s: bad file descriptor: %d", myname, fd);
+
+ if (msg_verbose > 2)
+ msg_info("%s: fd %d", myname, fd);
+
+ if (fd >= event_fdslots)
+ event_extend(fd);
+
+ /*
+ * Disallow mixed (i.e. read and write) requests on the same descriptor.
+ */
+ if (EVENT_MASK_ISSET(fd, &event_wmask))
+ msg_panic("%s: fd %d: read/write I/O request", myname, fd);
+
+ /*
+ * Postfix 2.4 allows multiple event_enable_read() calls on the same
+ * descriptor without requiring event_disable_readwrite() calls between
+ * them. With kernel-based filters (kqueue, /dev/poll, epoll) it's
+ * wasteful to make system calls when we change only application
+ * call-back information. It has a noticeable effect on smtp-source
+ * performance.
+ */
+ if (EVENT_MASK_ISSET(fd, &event_rmask) == 0) {
+ EVENT_MASK_SET(fd, &event_xmask);
+ EVENT_MASK_SET(fd, &event_rmask);
+ if (event_max_fd < fd)
+ event_max_fd = fd;
+#if (EVENTS_STYLE != EVENTS_STYLE_SELECT)
+ EVENT_REG_ADD_READ(err, fd);
+ if (err < 0)
+ msg_fatal("%s: %s: %m", myname, EVENT_REG_ADD_TEXT);
+#endif
+ }
+ fdp = event_fdtable + fd;
+ if (fdp->callback != callback || fdp->context != context) {
+ fdp->callback = callback;
+ fdp->context = context;
+ }
+}
+
+/* event_enable_write - enable write events */
+
+void event_enable_write(int fd, EVENT_NOTIFY_RDWR_FN callback, void *context)
+{
+ const char *myname = "event_enable_write";
+ EVENT_FDTABLE *fdp;
+ int err;
+
+ if (EVENT_INIT_NEEDED())
+ event_init();
+
+ /*
+ * Sanity checks.
+ */
+ if (fd < 0 || fd >= event_fdlimit)
+ msg_panic("%s: bad file descriptor: %d", myname, fd);
+
+ if (msg_verbose > 2)
+ msg_info("%s: fd %d", myname, fd);
+
+ if (fd >= event_fdslots)
+ event_extend(fd);
+
+ /*
+ * Disallow mixed (i.e. read and write) requests on the same descriptor.
+ */
+ if (EVENT_MASK_ISSET(fd, &event_rmask))
+ msg_panic("%s: fd %d: read/write I/O request", myname, fd);
+
+ /*
+ * Postfix 2.4 allows multiple event_enable_write() calls on the same
+ * descriptor without requiring event_disable_readwrite() calls between
+ * them. With kernel-based filters (kqueue, /dev/poll, epoll) it's
+ * incredibly wasteful to make unregister and register system calls when
+ * we change only application call-back information. It has a noticeable
+ * effect on smtp-source performance.
+ */
+ if (EVENT_MASK_ISSET(fd, &event_wmask) == 0) {
+ EVENT_MASK_SET(fd, &event_xmask);
+ EVENT_MASK_SET(fd, &event_wmask);
+ if (event_max_fd < fd)
+ event_max_fd = fd;
+#if (EVENTS_STYLE != EVENTS_STYLE_SELECT)
+ EVENT_REG_ADD_WRITE(err, fd);
+ if (err < 0)
+ msg_fatal("%s: %s: %m", myname, EVENT_REG_ADD_TEXT);
+#endif
+ }
+ fdp = event_fdtable + fd;
+ if (fdp->callback != callback || fdp->context != context) {
+ fdp->callback = callback;
+ fdp->context = context;
+ }
+}
+
+/* event_disable_readwrite - disable request for read or write events */
+
+void event_disable_readwrite(int fd)
+{
+ const char *myname = "event_disable_readwrite";
+ EVENT_FDTABLE *fdp;
+ int err;
+
+ if (EVENT_INIT_NEEDED())
+ event_init();
+
+ /*
+ * Sanity checks.
+ */
+ if (fd < 0 || fd >= event_fdlimit)
+ msg_panic("%s: bad file descriptor: %d", myname, fd);
+
+ if (msg_verbose > 2)
+ msg_info("%s: fd %d", myname, fd);
+
+ /*
+ * Don't complain when there is nothing to cancel. The request may have
+ * been canceled from another thread.
+ */
+ if (fd >= event_fdslots)
+ return;
+#if (EVENTS_STYLE != EVENTS_STYLE_SELECT)
+#ifdef EVENT_REG_DEL_BOTH
+ /* XXX Can't seem to disable READ and WRITE events selectively. */
+ if (EVENT_MASK_ISSET(fd, &event_rmask)
+ || EVENT_MASK_ISSET(fd, &event_wmask)) {
+ EVENT_REG_DEL_BOTH(err, fd);
+ if (err < 0)
+ msg_fatal("%s: %s: %m", myname, EVENT_REG_DEL_TEXT);
+ }
+#else
+ if (EVENT_MASK_ISSET(fd, &event_rmask)) {
+ EVENT_REG_DEL_READ(err, fd);
+ if (err < 0)
+ msg_fatal("%s: %s: %m", myname, EVENT_REG_DEL_TEXT);
+ } else if (EVENT_MASK_ISSET(fd, &event_wmask)) {
+ EVENT_REG_DEL_WRITE(err, fd);
+ if (err < 0)
+ msg_fatal("%s: %s: %m", myname, EVENT_REG_DEL_TEXT);
+ }
+#endif /* EVENT_REG_DEL_BOTH */
+#endif /* != EVENTS_STYLE_SELECT */
+ EVENT_MASK_CLR(fd, &event_xmask);
+ EVENT_MASK_CLR(fd, &event_rmask);
+ EVENT_MASK_CLR(fd, &event_wmask);
+ fdp = event_fdtable + fd;
+ fdp->callback = 0;
+ fdp->context = 0;
+}
+
+/* event_request_timer - (re)set timer */
+
+time_t event_request_timer(EVENT_NOTIFY_TIME_FN callback, void *context, int delay)
+{
+ const char *myname = "event_request_timer";
+ RING *ring;
+ EVENT_TIMER *timer;
+
+ if (EVENT_INIT_NEEDED())
+ event_init();
+
+ /*
+ * Sanity checks.
+ */
+ if (delay < 0)
+ msg_panic("%s: invalid delay: %d", myname, delay);
+
+ /*
+ * Make sure we schedule this event at the right time.
+ */
+ time(&event_present);
+
+ /*
+ * See if they are resetting an existing timer request. If so, take the
+ * request away from the timer queue so that it can be inserted at the
+ * right place.
+ */
+ FOREACH_QUEUE_ENTRY(ring, &event_timer_head) {
+ timer = RING_TO_TIMER(ring);
+ if (timer->callback == callback && timer->context == context) {
+ timer->when = event_present + delay;
+ timer->loop_instance = event_loop_instance;
+ ring_detach(ring);
+ if (msg_verbose > 2)
+ msg_info("%s: reset 0x%lx 0x%lx %d", myname,
+ (long) callback, (long) context, delay);
+ break;
+ }
+ }
+
+ /*
+ * If not found, schedule a new timer request.
+ */
+ if (ring == &event_timer_head) {
+ timer = (EVENT_TIMER *) mymalloc(sizeof(EVENT_TIMER));
+ timer->when = event_present + delay;
+ timer->callback = callback;
+ timer->context = context;
+ timer->loop_instance = event_loop_instance;
+ if (msg_verbose > 2)
+ msg_info("%s: set 0x%lx 0x%lx %d", myname,
+ (long) callback, (long) context, delay);
+ }
+
+ /*
+ * Timer requests are kept sorted to reduce lookup overhead in the event
+ * loop.
+ *
+ * XXX Append the new request after existing requests for the same time
+ * slot. The event_loop() routine depends on this to avoid starving I/O
+ * events when a call-back function schedules a zero-delay timer request.
+ */
+ FOREACH_QUEUE_ENTRY(ring, &event_timer_head) {
+ if (timer->when < RING_TO_TIMER(ring)->when)
+ break;
+ }
+ ring_prepend(ring, &timer->ring);
+
+ return (timer->when);
+}
+
+/* event_cancel_timer - cancel timer */
+
+int event_cancel_timer(EVENT_NOTIFY_TIME_FN callback, void *context)
+{
+ const char *myname = "event_cancel_timer";
+ RING *ring;
+ EVENT_TIMER *timer;
+ int time_left = -1;
+
+ if (EVENT_INIT_NEEDED())
+ event_init();
+
+ /*
+ * See if they are canceling an existing timer request. Do not complain
+ * when the request is not found. It might have been canceled from some
+ * other thread.
+ */
+ FOREACH_QUEUE_ENTRY(ring, &event_timer_head) {
+ timer = RING_TO_TIMER(ring);
+ if (timer->callback == callback && timer->context == context) {
+ if ((time_left = timer->when - event_present) < 0)
+ time_left = 0;
+ ring_detach(ring);
+ myfree((void *) timer);
+ break;
+ }
+ }
+ if (msg_verbose > 2)
+ msg_info("%s: 0x%lx 0x%lx %d", myname,
+ (long) callback, (long) context, time_left);
+ return (time_left);
+}
+
+/* event_loop - wait for the next event */
+
+void event_loop(int delay)
+{
+ const char *myname = "event_loop";
+ static int nested;
+
+#if (EVENTS_STYLE == EVENTS_STYLE_SELECT)
+ fd_set rmask;
+ fd_set wmask;
+ fd_set xmask;
+ struct timeval tv;
+ struct timeval *tvp;
+ int new_max_fd;
+
+#else
+ EVENT_BUFFER event_buf[100];
+ EVENT_BUFFER *bp;
+
+#endif
+ int event_count;
+ EVENT_TIMER *timer;
+ int fd;
+ EVENT_FDTABLE *fdp;
+ int select_delay;
+
+ if (EVENT_INIT_NEEDED())
+ event_init();
+
+ /*
+ * XXX Also print the select() masks?
+ */
+ if (msg_verbose > 2) {
+ RING *ring;
+
+ FOREACH_QUEUE_ENTRY(ring, &event_timer_head) {
+ timer = RING_TO_TIMER(ring);
+ msg_info("%s: time left %3d for 0x%lx 0x%lx", myname,
+ (int) (timer->when - event_present),
+ (long) timer->callback, (long) timer->context);
+ }
+ }
+
+ /*
+ * Find out when the next timer would go off. Timer requests are sorted.
+ * If any timer is scheduled, adjust the delay appropriately.
+ */
+ if ((timer = FIRST_TIMER(&event_timer_head)) != 0) {
+ event_present = time((time_t *) 0);
+ if ((select_delay = timer->when - event_present) < 0) {
+ select_delay = 0;
+ } else if (delay >= 0 && select_delay > delay) {
+ select_delay = delay;
+ }
+ } else {
+ select_delay = delay;
+ }
+ if (msg_verbose > 2)
+ msg_info("event_loop: select_delay %d", select_delay);
+
+ /*
+ * Negative delay means: wait until something happens. Zero delay means:
+ * poll. Positive delay means: wait at most this long.
+ */
+#if (EVENTS_STYLE == EVENTS_STYLE_SELECT)
+ if (select_delay < 0) {
+ tvp = 0;
+ } else {
+ tvp = &tv;
+ tv.tv_usec = 0;
+ tv.tv_sec = select_delay;
+ }
+
+ /*
+ * Pause until the next event happens. When select() has a problem, don't
+ * go into a tight loop. Allow select() to be interrupted due to the
+ * arrival of a signal.
+ */
+ rmask = event_rmask;
+ wmask = event_wmask;
+ xmask = event_xmask;
+
+ event_count = select(event_max_fd + 1, &rmask, &wmask, &xmask, tvp);
+ if (event_count < 0) {
+ if (errno != EINTR)
+ msg_fatal("event_loop: select: %m");
+ return;
+ }
+#else
+ EVENT_BUFFER_READ(event_count, event_buf,
+ sizeof(event_buf) / sizeof(event_buf[0]),
+ select_delay);
+ if (event_count < 0) {
+ if (errno != EINTR)
+ msg_fatal("event_loop: " EVENT_BUFFER_READ_TEXT ": %m");
+ return;
+ }
+#endif
+
+ /*
+ * Before entering the application call-back routines, make sure we
+ * aren't being called from a call-back routine. Doing so would make us
+ * vulnerable to all kinds of race conditions.
+ */
+ if (nested++ > 0)
+ msg_panic("event_loop: recursive call");
+
+ /*
+ * Deliver timer events. Allow the application to add/delete timer queue
+ * requests while it is being called back. Requests are sorted: we keep
+ * running over the timer request queue from the start, and stop when we
+ * reach the future or the list end. We also stop when we reach a timer
+ * request that was added by a call-back that was invoked from this
+ * event_loop() call instance, for reasons that are explained below.
+ *
+ * To avoid dangling pointer problems 1) we must remove a request from the
+ * timer queue before delivering its event to the application and 2) we
+ * must look up the next timer request *after* calling the application.
+ * The latter complicates the handling of zero-delay timer requests that
+ * are added by event_loop() call-back functions.
+ *
+ * XXX When a timer event call-back function adds a new timer request,
+ * event_request_timer() labels the request with the event_loop() call
+ * instance that invoked the timer event call-back. We use this instance
+ * label here to prevent zero-delay timer requests from running in a
+ * tight loop and starving I/O events. To make this solution work,
+ * event_request_timer() appends a new request after existing requests
+ * for the same time slot.
+ */
+ event_present = time((time_t *) 0);
+ event_loop_instance += 1;
+
+ while ((timer = FIRST_TIMER(&event_timer_head)) != 0) {
+ if (timer->when > event_present)
+ break;
+ if (timer->loop_instance == event_loop_instance)
+ break;
+ ring_detach(&timer->ring); /* first this */
+ if (msg_verbose > 2)
+ msg_info("%s: timer 0x%lx 0x%lx", myname,
+ (long) timer->callback, (long) timer->context);
+ timer->callback(EVENT_TIME, timer->context); /* then this */
+ myfree((void *) timer);
+ }
+
+ /*
+ * Deliver I/O events. Allow the application to cancel event requests
+ * while it is being called back. To this end, we keep an eye on the
+ * contents of event_xmask, so that we deliver only events that are still
+ * wanted. We do not change the event request masks. It is up to the
+ * application to determine when a read or write is complete.
+ */
+#if (EVENTS_STYLE == EVENTS_STYLE_SELECT)
+ if (event_count > 0) {
+ for (new_max_fd = 0, fd = 0; fd <= event_max_fd; fd++) {
+ if (FD_ISSET(fd, &event_xmask)) {
+ new_max_fd = fd;
+ /* In case event_fdtable is updated. */
+ fdp = event_fdtable + fd;
+ if (FD_ISSET(fd, &xmask)) {
+ if (msg_verbose > 2)
+ msg_info("%s: exception fd=%d act=0x%lx 0x%lx", myname,
+ fd, (long) fdp->callback, (long) fdp->context);
+ fdp->callback(EVENT_XCPT, fdp->context);
+ } else if (FD_ISSET(fd, &wmask)) {
+ if (msg_verbose > 2)
+ msg_info("%s: write fd=%d act=0x%lx 0x%lx", myname,
+ fd, (long) fdp->callback, (long) fdp->context);
+ fdp->callback(EVENT_WRITE, fdp->context);
+ } else if (FD_ISSET(fd, &rmask)) {
+ if (msg_verbose > 2)
+ msg_info("%s: read fd=%d act=0x%lx 0x%lx", myname,
+ fd, (long) fdp->callback, (long) fdp->context);
+ fdp->callback(EVENT_READ, fdp->context);
+ }
+ }
+ }
+ event_max_fd = new_max_fd;
+ }
+#else
+ for (bp = event_buf; bp < event_buf + event_count; bp++) {
+ fd = EVENT_GET_FD(bp);
+ if (fd < 0 || fd > event_max_fd)
+ msg_panic("%s: bad file descriptor: %d", myname, fd);
+ if (EVENT_MASK_ISSET(fd, &event_xmask)) {
+ fdp = event_fdtable + fd;
+ if (EVENT_TEST_READ(bp)) {
+ if (msg_verbose > 2)
+ msg_info("%s: read fd=%d act=0x%lx 0x%lx", myname,
+ fd, (long) fdp->callback, (long) fdp->context);
+ fdp->callback(EVENT_READ, fdp->context);
+ } else if (EVENT_TEST_WRITE(bp)) {
+ if (msg_verbose > 2)
+ msg_info("%s: write fd=%d act=0x%lx 0x%lx", myname,
+ fd, (long) fdp->callback,
+ (long) fdp->context);
+ fdp->callback(EVENT_WRITE, fdp->context);
+ } else {
+ if (msg_verbose > 2)
+ msg_info("%s: other fd=%d act=0x%lx 0x%lx", myname,
+ fd, (long) fdp->callback, (long) fdp->context);
+ fdp->callback(EVENT_XCPT, fdp->context);
+ }
+ }
+ }
+#endif
+ nested--;
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program for the event manager. Schedule a series of
+ * events at one-second intervals and let them happen, while echoing any
+ * lines read from stdin.
+ */
+#include <stdio.h>
+#include <ctype.h>
+#include <stdlib.h>
+
+/* timer_event - display event */
+
+static void timer_event(int unused_event, void *context)
+{
+ printf("%ld: %s\n", (long) event_present, context);
+ fflush(stdout);
+}
+
+/* echo - echo text received on stdin */
+
+static void echo(int unused_event, void *unused_context)
+{
+ char buf[BUFSIZ];
+
+ if (fgets(buf, sizeof(buf), stdin) == 0)
+ exit(0);
+ printf("Result: %s", buf);
+}
+
+/* request - request a bunch of timer events */
+
+static void request(int unused_event, void *unused_context)
+{
+ event_request_timer(timer_event, "3 first", 3);
+ event_request_timer(timer_event, "3 second", 3);
+ event_request_timer(timer_event, "4 first", 4);
+ event_request_timer(timer_event, "4 second", 4);
+ event_request_timer(timer_event, "2 first", 2);
+ event_request_timer(timer_event, "2 second", 2);
+ event_request_timer(timer_event, "1 first", 1);
+ event_request_timer(timer_event, "1 second", 1);
+ event_request_timer(timer_event, "0 first", 0);
+ event_request_timer(timer_event, "0 second", 0);
+}
+
+int main(int argc, void **argv)
+{
+ if (argv[1])
+ msg_verbose = atoi(argv[1]);
+ event_request_timer(request, (void *) 0, 0);
+ event_enable_read(fileno(stdin), echo, (void *) 0);
+ event_drain(10);
+ exit(0);
+}
+
+#endif
diff --git a/src/util/events.h b/src/util/events.h
new file mode 100644
index 0000000..b7f2047
--- /dev/null
+++ b/src/util/events.h
@@ -0,0 +1,67 @@
+#ifndef _EVENTS_H_INCLUDED_
+#define _EVENTS_H_INCLUDED_
+
+/*++
+/* NAME
+/* events 3h
+/* SUMMARY
+/* event manager
+/* SYNOPSIS
+/* #include <events.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <time.h>
+
+ /*
+ * External interface.
+ */
+typedef void (*EVENT_NOTIFY_FN) (int, void *);
+
+#define EVENT_NOTIFY_TIME_FN EVENT_NOTIFY_FN /* legacy */
+#define EVENT_NOTIFY_RDWR_FN EVENT_NOTIFY_FN /* legacy */
+
+extern time_t event_time(void);
+extern void event_enable_read(int, EVENT_NOTIFY_RDWR_FN, void *);
+extern void event_enable_write(int, EVENT_NOTIFY_RDWR_FN, void *);
+extern void event_disable_readwrite(int);
+extern time_t event_request_timer(EVENT_NOTIFY_TIME_FN, void *, int);
+extern int event_cancel_timer(EVENT_NOTIFY_TIME_FN, void *);
+extern void event_loop(int);
+extern void event_drain(int);
+extern void event_fork(void);
+
+ /*
+ * Event codes.
+ */
+#define EVENT_READ (1<<0) /* read event */
+#define EVENT_WRITE (1<<1) /* write event */
+#define EVENT_XCPT (1<<2) /* exception */
+#define EVENT_TIME (1<<3) /* timer event */
+
+#define EVENT_ERROR EVENT_XCPT
+
+ /*
+ * Dummies.
+ */
+#define EVENT_NULL_TYPE (0)
+#define EVENT_NULL_CONTEXT ((void *) 0)
+#define EVENT_NULL_DELAY (0)
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/* CREATION DATE
+/* Wed Jan 29 17:00:03 EST 1997
+/*--*/
+
+#endif
diff --git a/src/util/exec_command.c b/src/util/exec_command.c
new file mode 100644
index 0000000..8629b0c
--- /dev/null
+++ b/src/util/exec_command.c
@@ -0,0 +1,112 @@
+/*++
+/* NAME
+/* exec_command 3
+/* SUMMARY
+/* execute command
+/* SYNOPSIS
+/* #include <exec_command.h>
+/*
+/* NORETURN exec_command(command)
+/* const char *command;
+/* DESCRIPTION
+/* \fIexec_command\fR() replaces the current process by an instance
+/* of \fIcommand\fR. This routine uses a simple heuristic to avoid
+/* the overhead of running a command shell interpreter.
+/* DIAGNOSTICS
+/* This routine never returns. All errors are fatal.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <unistd.h>
+#include <string.h>
+#ifdef USE_PATHS_H
+#include <paths.h>
+#endif
+#include <errno.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <argv.h>
+#include <exec_command.h>
+
+/* Application-specific. */
+
+#define SPACE_TAB " \t"
+
+/* exec_command - exec command */
+
+NORETURN exec_command(const char *command)
+{
+ ARGV *argv;
+
+ /*
+ * Character filter. In this particular case, we allow space and tab in
+ * addition to the regular character set.
+ */
+ static char ok_chars[] = "1234567890!@%-_=+:,./\
+abcdefghijklmnopqrstuvwxyz\
+ABCDEFGHIJKLMNOPQRSTUVWXYZ" SPACE_TAB;
+
+ /*
+ * See if this command contains any shell magic characters.
+ */
+ if (command[strspn(command, ok_chars)] == 0
+ && command[strspn(command, SPACE_TAB)] != 0) {
+
+ /*
+ * No shell meta characters found, so we can try to avoid the overhead
+ * of running a shell. Just split the command on whitespace and exec
+ * the result directly.
+ */
+ argv = argv_split(command, SPACE_TAB);
+ (void) execvp(argv->argv[0], argv->argv);
+
+ /*
+ * Auch. Perhaps they're using some shell built-in command.
+ */
+ if (errno != ENOENT || strchr(argv->argv[0], '/') != 0)
+ msg_fatal("execvp %s: %m", argv->argv[0]);
+
+ /*
+ * Not really necessary, but...
+ */
+ argv_free(argv);
+ }
+
+ /*
+ * Pass the command to a shell.
+ */
+ (void) execl(_PATH_BSHELL, "sh", "-c", command, (char *) 0);
+ msg_fatal("execl %s: %m", _PATH_BSHELL);
+}
+
+#ifdef TEST
+
+ /*
+ * Yet another proof-of-concept test program.
+ */
+#include <vstream.h>
+#include <msg_vstream.h>
+
+int main(int argc, char **argv)
+{
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ if (argc != 2)
+ msg_fatal("usage: %s 'command'", argv[0]);
+ exec_command(argv[1]);
+}
+
+#endif
diff --git a/src/util/exec_command.h b/src/util/exec_command.h
new file mode 100644
index 0000000..4e77211
--- /dev/null
+++ b/src/util/exec_command.h
@@ -0,0 +1,30 @@
+#ifndef _EXEC_COMMAND_H_INCLUDED_
+#define _EXEC_COMMAND_H_INCLUDED_
+
+/*++
+/* NAME
+/* exec_command 3h
+/* SUMMARY
+/* execute command
+/* SYNOPSIS
+/* #include <exec_command.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern NORETURN exec_command(const char *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/extpar.c b/src/util/extpar.c
new file mode 100644
index 0000000..0b106ba
--- /dev/null
+++ b/src/util/extpar.c
@@ -0,0 +1,109 @@
+/*++
+/* NAME
+/* extpar 3
+/* SUMMARY
+/* extract text from parentheses
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* char *extpar(bp, parens, flags)
+/* char **bp;
+/* const char *parens;
+/* int flags;
+/* DESCRIPTION
+/* extpar() extracts text from an input string that is enclosed
+/* in the specified parentheses, and updates the buffer pointer
+/* to point to that text.
+/*
+/* Arguments:
+/* .IP bp
+/* Pointer to buffer pointer. Both the buffer and the buffer
+/* pointer are modified.
+/* .IP parens
+/* One matching pair of parentheses, opening parenthesis first.
+/* .IP flags
+/* EXTPAR_FLAG_NONE, or the bitwise OR of one or more flags:
+/* .RS
+/* .IP EXTPAR_FLAG_EXTRACT
+/* This flag is intended to instruct extpar() callers that
+/* extpar() should be invoked. It has no effect on expar()
+/* itself.
+/* .IP EXTPAR_FLAG_STRIP
+/* Skip whitespace after the opening parenthesis, and trim
+/* whitespace before the closing parenthesis.
+/* .RE
+/* DIAGNOSTICS
+/* In case of error the result value is a dynamically-allocated
+/* string with a description of the problem that includes a
+/* copy of the offending input. A non-null result value should
+/* be destroyed with myfree(). The following describes the errors
+/* and the state of the buffer and buffer pointer.
+/* .IP "no opening parenthesis at start of text"
+/* The buffer pointer points to the input text.
+/* .IP "missing closing parenthesis"
+/* The buffer pointer points to text as if a closing parenthesis
+/* were present at the end of the input.
+/* .IP "text after closing parenthesis"
+/* The buffer pointer points to text as if the offending text
+/* were not present.
+/* SEE ALSO
+/* balpar(3) determine length of string in parentheses
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <ctype.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+#include <stringops.h>
+
+/* extpar - extract text from parentheses */
+
+char *extpar(char **bp, const char *parens, int flags)
+{
+ char *cp = *bp;
+ char *err = 0;
+ size_t len;
+
+ if (cp[0] != parens[0]) {
+ err = vstring_export(vstring_sprintf(vstring_alloc(100),
+ "no '%c' at start of text in \"%s\"", parens[0], cp));
+ len = 0;
+ } else if ((len = balpar(cp, parens)) == 0) {
+ err = concatenate("missing '", parens + 1, "' in \"",
+ cp, "\"", (char *) 0);
+ cp += 1;
+ } else {
+ if (cp[len] != 0)
+ err = concatenate("syntax error after '", parens + 1, "' in \"",
+ cp, "\"", (char *) 0);
+ cp += 1;
+ cp[len -= 2] = 0;
+ }
+ if (flags & EXTPAR_FLAG_STRIP) {
+ trimblanks(cp, len)[0] = 0;
+ while (ISSPACE(*cp))
+ cp++;
+ }
+ *bp = cp;
+ return (err);
+}
diff --git a/src/util/fifo_listen.c b/src/util/fifo_listen.c
new file mode 100644
index 0000000..9ad3440
--- /dev/null
+++ b/src/util/fifo_listen.c
@@ -0,0 +1,111 @@
+/*++
+/* NAME
+/* fifo_listen 3
+/* SUMMARY
+/* start fifo listener
+/* SYNOPSIS
+/* #include <listen.h>
+/*
+/* int fifo_listen(path, permissions, block_mode)
+/* const char *path;
+/* int permissions;
+/* int block_mode;
+/* DESCRIPTION
+/* The \fBfifo_listen\fR routine creates the specified named pipe with
+/* the specified permissions, opens the FIFO read-write or read-only,
+/* depending on the host operating system, and returns the resulting
+/* file descriptor.
+/* The \fIblock_mode\fR argument is either NON_BLOCKING for
+/* a non-blocking socket, or BLOCKING for blocking mode.
+/* DIAGNOSTICS
+/* Fatal errors: all system call failures.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System interfaces. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "iostuff.h"
+#include "listen.h"
+#include "warn_stat.h"
+
+#define BUF_LEN 100
+
+/* fifo_listen - create fifo listener */
+
+int fifo_listen(const char *path, int permissions, int block_mode)
+{
+ char buf[BUF_LEN];
+ static int open_mode = 0;
+ const char *myname = "fifo_listen";
+ struct stat st;
+ int fd;
+ int count;
+
+ /*
+ * Create a named pipe (fifo). Do whatever we can so we don't run into
+ * trouble when this process is restarted after crash. Make sure that we
+ * open a fifo and not something else, then change permissions to what we
+ * wanted them to be, because mkfifo() is subject to umask settings.
+ * Instead we could zero the umask temporarily before creating the FIFO,
+ * but that would cost even more system calls. Figure out if the fifo
+ * needs to be opened O_RDWR or O_RDONLY. Some systems need one, some
+ * need the other. If we choose the wrong mode, the fifo will stay
+ * readable, causing the program to go into a loop.
+ */
+ if (unlink(path) && errno != ENOENT)
+ msg_fatal("%s: remove %s: %m", myname, path);
+ if (mkfifo(path, permissions) < 0)
+ msg_fatal("%s: create fifo %s: %m", myname, path);
+ switch (open_mode) {
+ case 0:
+ if ((fd = open(path, O_RDWR | O_NONBLOCK, 0)) < 0)
+ msg_fatal("%s: open %s: %m", myname, path);
+ if (readable(fd) == 0) {
+ open_mode = O_RDWR | O_NONBLOCK;
+ break;
+ } else {
+ open_mode = O_RDONLY | O_NONBLOCK;
+ if (msg_verbose)
+ msg_info("open O_RDWR makes fifo readable - trying O_RDONLY");
+ (void) close(fd);
+ /* FALLTRHOUGH */
+ }
+ default:
+ if ((fd = open(path, open_mode, 0)) < 0)
+ msg_fatal("%s: open %s: %m", myname, path);
+ break;
+ }
+
+ /*
+ * Make sure we opened a FIFO and skip any cruft that might have
+ * accumulated before we opened it.
+ */
+ if (fstat(fd, &st) < 0)
+ msg_fatal("%s: fstat %s: %m", myname, path);
+ if (S_ISFIFO(st.st_mode) == 0)
+ msg_fatal("%s: not a fifo: %s", myname, path);
+ if (fchmod(fd, permissions) < 0)
+ msg_fatal("%s: fchmod %s: %m", myname, path);
+ non_blocking(fd, block_mode);
+ while ((count = peekfd(fd)) > 0
+ && read(fd, buf, BUF_LEN < count ? BUF_LEN : count) > 0)
+ /* void */ ;
+ return (fd);
+}
diff --git a/src/util/fifo_open.c b/src/util/fifo_open.c
new file mode 100644
index 0000000..1587779
--- /dev/null
+++ b/src/util/fifo_open.c
@@ -0,0 +1,67 @@
+/*++
+/* NAME
+/* fifo_open 1
+/* SUMMARY
+/* fifo client test program
+/* SYNOPSIS
+/* fifo_open
+/* DESCRIPTION
+/* fifo_open creates a FIFO, then attempts to open it for writing
+/* with non-blocking mode enabled. According to the POSIX standard
+/* the open should succeed.
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#include <sys/stat.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#define FIFO_PATH "test-fifo"
+#define perrorexit(s) { perror(s); exit(1); }
+
+static void cleanup(void)
+{
+ printf("Removing fifo %s...\n", FIFO_PATH);
+ if (unlink(FIFO_PATH))
+ perrorexit("unlink");
+ printf("Done.\n");
+}
+
+static void stuck(int unused_sig)
+{
+ printf("Non-blocking, write-only open of FIFO blocked\n");
+ cleanup();
+ exit(1);
+}
+
+int main(int unused_argc, char **unused_argv)
+{
+ (void) unlink(FIFO_PATH);
+ printf("Creating fifo %s...\n", FIFO_PATH);
+ if (mkfifo(FIFO_PATH, 0600) < 0)
+ perrorexit("mkfifo");
+ signal(SIGALRM, stuck);
+ alarm(5);
+ printf("Opening fifo %s, non-blocking, write-only mode...\n", FIFO_PATH);
+ if (open(FIFO_PATH, O_WRONLY | O_NONBLOCK, 0) < 0) {
+ perror("open");
+ cleanup();
+ exit(1);
+ }
+ printf("Non-blocking, write-only open of FIFO succeeded\n");
+ cleanup();
+ exit(0);
+}
diff --git a/src/util/fifo_rdonly_bug.c b/src/util/fifo_rdonly_bug.c
new file mode 100644
index 0000000..bf93467
--- /dev/null
+++ b/src/util/fifo_rdonly_bug.c
@@ -0,0 +1,127 @@
+/*++
+/* NAME
+/* fifo_rdonly_bug 1
+/* SUMMARY
+/* fifo server test program
+/* SYNOPSIS
+/* fifo_rdonly_bug
+/* DESCRIPTION
+/* fifo_rdonly_bug creates a FIFO and opens it read only. It
+/* then opens the FIFO for writing, writes one byte, and closes
+/* the writing end. On Linux Redhat 4.2 and 5.0, and HP-UX 9.05
+/* and 10.20, select() will report that the FIFO remains readable
+/* even after multiple read operations.
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <string.h>
+
+#define FIFO_PATH "test-fifo"
+#define TRIGGER_DELAY 5
+
+#define perrorexit(s) { perror(s); exit(1); }
+
+static void cleanup(void)
+{
+ printf("Removing fifo %s...\n", FIFO_PATH);
+ if (unlink(FIFO_PATH))
+ perrorexit("unlink");
+ printf("Done.\n");
+}
+
+static void perrorcleanup(char *str)
+{
+ perror(str);
+ cleanup();
+ exit(0);
+}
+
+static void readable_event(int fd)
+{
+ char ch;
+ static int count = 0;
+
+ if (read(fd, &ch, 1) < 0) {
+ perror("read");
+ sleep(1);
+ }
+ if (count++ > 5) {
+ printf("FIFO remains readable after multiple reads.\n");
+ cleanup();
+ exit(1);
+ }
+}
+
+int main(int unused_argc, char **unused_argv)
+{
+ struct timeval tv;
+ fd_set read_fds;
+ fd_set except_fds;
+ int fd;
+ int fd2;
+
+ (void) unlink(FIFO_PATH);
+
+ printf("Create fifo %s...\n", FIFO_PATH);
+ if (mkfifo(FIFO_PATH, 0600) < 0)
+ perrorexit("mkfifo");
+
+ printf("Open fifo %s, read-only mode...\n", FIFO_PATH);
+ if ((fd = open(FIFO_PATH, O_RDONLY | O_NONBLOCK, 0)) < 0)
+ perrorcleanup("open");
+
+ printf("Write one byte to the fifo, then close it...\n");
+ if ((fd2 = open(FIFO_PATH, O_WRONLY, 0)) < 0)
+ perrorcleanup("open fifo O_WRONLY");
+ if (write(fd2, "", 1) < 1)
+ perrorcleanup("write one byte to fifo");
+ if (close(fd2) < 0)
+ perrorcleanup("close fifo");
+
+ printf("Selecting the fifo for readability...\n");
+
+ for (;;) {
+ FD_ZERO(&read_fds);
+ FD_SET(fd, &read_fds);
+ FD_ZERO(&except_fds);
+ FD_SET(fd, &except_fds);
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+
+ switch (select(fd + 1, &read_fds, (fd_set *) 0, &except_fds, &tv)) {
+ case -1:
+ perrorexit("select");
+ default:
+ if (FD_ISSET(fd, &except_fds)) {
+ printf("Exceptional fifo condition! You are not normal!\n");
+ readable_event(fd);
+ } else if (FD_ISSET(fd, &read_fds)) {
+ printf("Readable fifo condition\n");
+ readable_event(fd);
+ }
+ break;
+ case 0:
+ printf("The fifo is not readable. You're normal.\n");
+ cleanup();
+ exit(0);
+ break;
+ }
+ }
+}
diff --git a/src/util/fifo_rdwr_bug.c b/src/util/fifo_rdwr_bug.c
new file mode 100644
index 0000000..2486876
--- /dev/null
+++ b/src/util/fifo_rdwr_bug.c
@@ -0,0 +1,89 @@
+/*++
+/* NAME
+/* fifo_rdwr_bug 1
+/* SUMMARY
+/* fifo server test program
+/* SYNOPSIS
+/* fifo_rdwr_bug
+/* DESCRIPTION
+/* fifo_rdwr_bug creates a FIFO and opens it read-write mode.
+/* On BSD/OS 3.1 select() will report that the FIFO is readable
+/* even before any data is written to it. Doing an actual read
+/* causes the read to block; a non-blocking read fails.
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#include <sys_defs.h>
+#include <sys/time.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define FIFO_PATH "test-fifo"
+#define perrorexit(s) { perror(s); exit(1); }
+
+static void cleanup(void)
+{
+ printf("Removing fifo %s...\n", FIFO_PATH);
+ if (unlink(FIFO_PATH))
+ perrorexit("unlink");
+ printf("Done.\n");
+}
+
+int main(int unused_argc, char **unused_argv)
+{
+ struct timeval tv;
+ fd_set read_fds;
+ fd_set except_fds;
+ int fd;
+
+ (void) unlink(FIFO_PATH);
+
+ printf("Creating fifo %s...\n", FIFO_PATH);
+ if (mkfifo(FIFO_PATH, 0600) < 0)
+ perrorexit("mkfifo");
+
+ printf("Opening fifo %s, read-write mode...\n", FIFO_PATH);
+ if ((fd = open(FIFO_PATH, O_RDWR, 0)) < 0) {
+ perror("open");
+ cleanup();
+ exit(1);
+ }
+ printf("Selecting the fifo for readability...\n");
+ FD_ZERO(&read_fds);
+ FD_SET(fd, &read_fds);
+ FD_ZERO(&except_fds);
+ FD_SET(fd, &except_fds);
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+
+ switch (select(fd + 1, &read_fds, (fd_set *) 0, &except_fds, &tv)) {
+ case -1:
+ perrorexit("select");
+ default:
+ if (FD_ISSET(fd, &read_fds)) {
+ printf("Opening a fifo read-write makes it readable!!\n");
+ break;
+ }
+ case 0:
+ printf("The fifo is not readable, as it should be.\n");
+ break;
+ }
+ cleanup();
+ exit(0);
+}
diff --git a/src/util/fifo_trigger.c b/src/util/fifo_trigger.c
new file mode 100644
index 0000000..e5c5604
--- /dev/null
+++ b/src/util/fifo_trigger.c
@@ -0,0 +1,168 @@
+/*++
+/* NAME
+/* fifo_trigger 3
+/* SUMMARY
+/* wakeup fifo server
+/* SYNOPSIS
+/* #include <trigger.h>
+/*
+/* int fifo_trigger(service, buf, len, timeout)
+/* const char *service;
+/* const char *buf;
+/* ssize_t len;
+/* int timeout;
+/* DESCRIPTION
+/* fifo_trigger() wakes up the named fifo server by writing
+/* the contents of the specified buffer to the fifo. There is
+/* no guarantee that the written data will actually be received.
+/*
+/* Arguments:
+/* .IP service
+/* Name of the communication endpoint.
+/* .IP buf
+/* Address of data to be written.
+/* .IP len
+/* Amount of data to be written.
+/* .IP timeout
+/* Deadline in seconds. Specify a value <= 0 to disable
+/* the time limit.
+/* DIAGNOSTICS
+/* The result is zero when the fifo could be opened, -1 otherwise.
+/* BUGS
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+#include <safe_open.h>
+#include <trigger.h>
+
+/* fifo_trigger - wakeup fifo server */
+
+int fifo_trigger(const char *service, const char *buf, ssize_t len, int timeout)
+{
+ static VSTRING *why;
+ const char *myname = "fifo_trigger";
+ VSTREAM *fp;
+ int fd;
+
+ if (why == 0)
+ why = vstring_alloc(1);
+
+ /*
+ * Write the request to the service fifo. According to POSIX, the open
+ * shall always return immediately, and shall return an error when no
+ * process is reading from the FIFO.
+ *
+ * Use safe_open() so that we don't follow symlinks, and so that we don't
+ * open files with multiple hard links. We're not (yet) going to bother
+ * the caller with safe_open() specific quirks such as the why argument.
+ */
+ if ((fp = safe_open(service, O_WRONLY | O_NONBLOCK, 0,
+ (struct stat *) 0, -1, -1, why)) == 0) {
+ if (msg_verbose)
+ msg_info("%s: open %s: %s", myname, service, vstring_str(why));
+ return (-1);
+ }
+ fd = vstream_fileno(fp);
+
+ /*
+ * Write the request...
+ */
+ non_blocking(fd, timeout > 0 ? NON_BLOCKING : BLOCKING);
+ if (write_buf(fd, buf, len, timeout) < 0)
+ if (msg_verbose)
+ msg_warn("%s: write %s: %m", myname, service);
+
+ /*
+ * Disconnect.
+ */
+ if (vstream_fclose(fp))
+ if (msg_verbose)
+ msg_warn("%s: close %s: %m", myname, service);
+ return (0);
+}
+
+#ifdef TEST
+
+ /*
+ * Set up a FIFO listener, and keep triggering until the listener becomes
+ * idle, which should never happen.
+ */
+#include <signal.h>
+#include <stdlib.h>
+
+#include "events.h"
+#include "listen.h"
+
+#define TEST_FIFO "test-fifo"
+
+int trig_count;
+int wakeup_count;
+
+static void cleanup(void)
+{
+ unlink(TEST_FIFO);
+ exit(1);
+}
+
+static void handler(int sig)
+{
+ msg_fatal("got signal %d after %d triggers %d wakeups",
+ sig, trig_count, wakeup_count);
+}
+
+static void read_event(int unused_event, char *context)
+{
+ int fd = CAST_ANY_PTR_TO_INT(context);
+ char ch;
+
+ wakeup_count++;
+
+ if (read(fd, &ch, 1) != 1)
+ msg_fatal("read %s: %m", TEST_FIFO);
+}
+
+int main(int unused_argc, char **unused_argv)
+{
+ int listen_fd;
+
+ listen_fd = fifo_listen(TEST_FIFO, 0600, NON_BLOCKING);
+ msg_cleanup(cleanup);
+ event_enable_read(listen_fd, read_event, CAST_INT_TO_VOID_PTR(listen_fd));
+ signal(SIGINT, handler);
+ signal(SIGALRM, handler);
+ for (;;) {
+ alarm(10);
+ if (fifo_trigger(TEST_FIFO, "", 1, 0) < 0)
+ msg_fatal("trigger %s: %m", TEST_FIFO);
+ trig_count++;
+ if (fifo_trigger(TEST_FIFO, "", 1, 0) < 0)
+ msg_fatal("trigger %s: %m", TEST_FIFO);
+ trig_count++;
+ if (fifo_trigger(TEST_FIFO, "", 1, 0) < 0)
+ msg_fatal("trigger %s: %m", TEST_FIFO);
+ trig_count++;
+ event_loop(-1);
+ event_loop(-1);
+ event_loop(-1);
+ }
+}
+
+#endif
diff --git a/src/util/file_limit.c b/src/util/file_limit.c
new file mode 100644
index 0000000..5cfae4e
--- /dev/null
+++ b/src/util/file_limit.c
@@ -0,0 +1,96 @@
+/*++
+/* NAME
+/* file_limit 3
+/* SUMMARY
+/* limit the file size
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* off_t get_file_limit()
+/*
+/* void set_file_limit(limit)
+/* off_t limit;
+/* DESCRIPTION
+/* This module manipulates the process-wide file size limit.
+/* The limit is specified in bytes.
+/*
+/* get_file_limit() looks up the process-wide file size limit.
+/*
+/* set_file_limit() sets the process-wide file size limit to
+/* \fIlimit\fR.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* SEE ALSO
+/* setrlimit(2)
+/* ulimit(2)
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#ifdef USE_ULIMIT
+#include <ulimit.h>
+#else
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <signal.h>
+#endif
+#include <limits.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+#define ULIMIT_BLOCK_SIZE 512
+
+/* get_file_limit - get process-wide file size limit */
+
+off_t get_file_limit(void)
+{
+ off_t limit;
+
+#ifdef USE_ULIMIT
+ if ((limit = ulimit(UL_GETFSIZE, 0)) < 0)
+ msg_fatal("ulimit: %m");
+ if (limit > OFF_T_MAX / ULIMIT_BLOCK_SIZE)
+ limit = OFF_T_MAX / ULIMIT_BLOCK_SIZE;
+ return (limit * ULIMIT_BLOCK_SIZE);
+#else
+ struct rlimit rlim;
+
+ if (getrlimit(RLIMIT_FSIZE, &rlim) < 0)
+ msg_fatal("getrlimit: %m");
+ limit = rlim.rlim_cur;
+ return (limit < 0 ? OFF_T_MAX : rlim.rlim_cur);
+#endif /* USE_ULIMIT */
+}
+
+/* set_file_limit - process-wide file size limit */
+
+void set_file_limit(off_t limit)
+{
+#ifdef USE_ULIMIT
+ if (ulimit(UL_SETFSIZE, limit / ULIMIT_BLOCK_SIZE) < 0)
+ msg_fatal("ulimit: %m");
+#else
+ struct rlimit rlim;
+
+ rlim.rlim_cur = rlim.rlim_max = limit;
+ if (setrlimit(RLIMIT_FSIZE, &rlim) < 0)
+ msg_fatal("setrlimit: %m");
+#ifdef SIGXFSZ
+ if (signal(SIGXFSZ, SIG_IGN) == SIG_ERR)
+ msg_fatal("signal(SIGXFSZ,SIG_IGN): %m");
+#endif
+#endif /* USE_ULIMIT */
+}
diff --git a/src/util/find_inet.c b/src/util/find_inet.c
new file mode 100644
index 0000000..294634c
--- /dev/null
+++ b/src/util/find_inet.c
@@ -0,0 +1,253 @@
+/*++
+/* NAME
+/* find_inet 3
+/* SUMMARY
+/* inet-domain name services
+/* SYNOPSIS
+/* #include <find_inet.h>
+/*
+/* unsigned find_inet_addr(host)
+/* const char *host;
+/*
+/* int find_inet_port(port, proto)
+/* const char *port;
+/* const char *proto;
+/* DESCRIPTION
+/* These functions translate network address information from
+/* between printable form to the internal the form used by the
+/* BSD TCP/IP network software.
+/*
+/* find_inet_addr() translates a symbolic or numerical hostname.
+/* This function is deprecated. Use hostname_to_hostaddr() instead.
+/*
+/* find_inet_port() translates a symbolic or numerical port name.
+/* BUGS
+/* find_inet_addr() ignores all but the first address listed for
+/* a symbolic hostname.
+/* DIAGNOSTICS
+/* Lookup and conversion errors are fatal.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System libraries. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Application-specific. */
+
+#include "msg.h"
+#include "stringops.h"
+#include "find_inet.h"
+#include "known_tcp_ports.h"
+
+#ifndef INADDR_NONE
+#define INADDR_NONE 0xffffffff
+#endif
+
+#ifdef TEST
+extern NORETURN PRINTFLIKE(1, 2) test_msg_fatal(const char *,...);
+
+#define msg_fatal test_msg_fatal
+#endif
+
+/* find_inet_addr - translate numerical or symbolic host name */
+
+unsigned find_inet_addr(const char *host)
+{
+ struct in_addr addr;
+ struct hostent *hp;
+
+ addr.s_addr = inet_addr(host);
+ if ((addr.s_addr == INADDR_NONE) || (addr.s_addr == 0)) {
+ if ((hp = gethostbyname(host)) == 0)
+ msg_fatal("host not found: %s", host);
+ if (hp->h_addrtype != AF_INET)
+ msg_fatal("unexpected address family: %d", hp->h_addrtype);
+ if (hp->h_length != sizeof(addr))
+ msg_fatal("unexpected address length %d", hp->h_length);
+ memcpy((void *) &addr, hp->h_addr, hp->h_length);
+ }
+ return (addr.s_addr);
+}
+
+/* find_inet_port - translate numerical or symbolic service name */
+
+int find_inet_port(const char *service, const char *protocol)
+{
+ struct servent *sp;
+ int port;
+
+ service = filter_known_tcp_port(service);
+ if (alldig(service) && (port = atoi(service)) != 0) {
+ if (port < 0 || port > 65535)
+ msg_fatal("bad port number: %s", service);
+ return (htons(port));
+ } else {
+ if ((sp = getservbyname(service, protocol)) == 0)
+ msg_fatal("unknown service: %s/%s", service, protocol);
+ return (sp->s_port);
+ }
+}
+
+#ifdef TEST
+
+#include <stdlib.h>
+#include <setjmp.h>
+#include <string.h>
+
+#include <vstream.h>
+#include <vstring.h>
+#include <msg_vstream.h>
+
+#define STR(x) vstring_str(x)
+
+ /* TODO(wietse) make this a proper VSTREAM interface */
+
+/* vstream_swap - kludge to capture output for testing */
+
+static void vstream_swap(VSTREAM *one, VSTREAM *two)
+{
+ VSTREAM save;
+
+ save = *one;
+ *one = *two;
+ *two = save;
+}
+
+jmp_buf test_fatal_jbuf;
+
+#undef msg_fatal
+
+/* test_msg_fatal - does not return, and does not terminate */
+
+void test_msg_fatal(const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vmsg_warn(fmt, ap);
+ va_end(ap);
+ longjmp(test_fatal_jbuf, 1);
+}
+
+struct association {
+ const char *lhs; /* service name */
+ const char *rhs; /* service port */
+};
+
+struct test_case {
+ const char *label; /* identifies test case */
+ struct association associations[10];
+ const char *service;
+ const char *proto;
+ const char *exp_warning; /* expected error */
+ int exp_hport; /* expected port, host byte order */
+};
+
+struct test_case test_cases[] = {
+ {"good-symbolic",
+ /* association */ {{"foobar", "25252"}, 0},
+ /* service */ "foobar",
+ /* proto */ "tcp",
+ /* exp_warning */ "",
+ /* exp_hport */ 25252,
+ },
+ {"good-numeric",
+ /* association */ {{"foobar", "25252"}, 0},
+ /* service */ "25252",
+ /* proto */ "tcp",
+ /* exp_warning */ "",
+ /* exp_hport */ 25252,
+ },
+ {"bad-symbolic",
+ /* association */ {{"foobar", "25252"}, 0},
+ /* service */ "an-impossible-name",
+ /* proto */ "tcp",
+ /* exp_warning */ "find_inet: warning: unknown service: an-impossible-name/tcp\n",
+ },
+ {"bad-numeric",
+ /* association */ {{"foobar", "25252"}, 0},
+ /* service */ "123456",
+ /* proto */ "tcp",
+ /* exp_warning */ "find_inet: warning: bad port number: 123456\n",
+ },
+};
+
+int main(int argc, char **argv) {
+ struct test_case *tp;
+ struct association *ap;
+ int pass = 0;
+ int fail = 0;
+ const char *err;
+ int test_failed;
+ int nport;
+ VSTRING *msg_buf;
+ VSTREAM *memory_stream;
+
+ msg_vstream_init("find_inet", VSTREAM_ERR);
+ msg_buf = vstring_alloc(100);
+
+ for (tp = test_cases; tp->label != 0; tp++) {
+ test_failed = 0;
+ VSTRING_RESET(msg_buf);
+ VSTRING_TERMINATE(msg_buf);
+ clear_known_tcp_ports();
+ for (err = 0, ap = tp->associations; err == 0 && ap->lhs != 0; ap++)
+ err = add_known_tcp_port(ap->lhs, ap->rhs);
+ if (err != 0) {
+ msg_warn("test case %s: got err: \"%s\"", tp->label, err);
+ test_failed = 1;
+ } else {
+ if ((memory_stream = vstream_memopen(msg_buf, O_WRONLY)) == 0)
+ msg_fatal("open memory stream: %m");
+ vstream_swap(VSTREAM_ERR, memory_stream);
+ if (setjmp(test_fatal_jbuf) == 0)
+ nport = find_inet_port(tp->service, tp->proto);
+ vstream_swap(memory_stream, VSTREAM_ERR);
+ if (vstream_fclose(memory_stream))
+ msg_fatal("close memory stream: %m");
+ if (strcmp(STR(msg_buf), tp->exp_warning) != 0) {
+ msg_warn("test case %s: got error: \"%s\", want: \"%s\"",
+ tp->label, STR(msg_buf), tp->exp_warning);
+ test_failed = 1;
+ } else if (tp->exp_warning[0] == 0) {
+ if (ntohs(nport) != tp->exp_hport) {
+ msg_warn("test case %s: got port \"%d\", want: \"%d\"",
+ tp->label, ntohs(nport), tp->exp_hport);
+ test_failed = 1;
+ }
+ }
+ }
+ if (test_failed) {
+ msg_info("%s: FAIL", tp->label);
+ fail++;
+ } else {
+ msg_info("%s: PASS", tp->label);
+ pass++;
+ }
+ }
+ msg_info("PASS=%d FAIL=%d", pass, fail);
+ vstring_free(msg_buf);
+ exit(fail != 0);
+}
+
+#endif
diff --git a/src/util/find_inet.h b/src/util/find_inet.h
new file mode 100644
index 0000000..0e5ce72
--- /dev/null
+++ b/src/util/find_inet.h
@@ -0,0 +1,33 @@
+#ifndef _FIND_INET_H_INCLUDED_
+#define _FIND_INET_H_INCLUDED_
+
+/*++
+/* NAME
+/* find_inet 3h
+/* SUMMARY
+/* inet-domain name services
+/* SYNOPSIS
+/* #include <find_inet.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern unsigned find_inet_addr(const char *);
+extern int find_inet_port(const char *, const char *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/* LAST MODIFICATION
+/* Thu Feb 6 12:46:36 EST 1997
+/*--*/
+
+#endif
diff --git a/src/util/find_inet.ref b/src/util/find_inet.ref
new file mode 100644
index 0000000..a6f9cac
--- /dev/null
+++ b/src/util/find_inet.ref
@@ -0,0 +1,5 @@
+find_inet: good-symbolic: PASS
+find_inet: good-numeric: PASS
+find_inet: bad-symbolic: PASS
+find_inet: bad-numeric: PASS
+find_inet: PASS=4 FAIL=0
diff --git a/src/util/format_tv.c b/src/util/format_tv.c
new file mode 100644
index 0000000..a932bdb
--- /dev/null
+++ b/src/util/format_tv.c
@@ -0,0 +1,160 @@
+/*++
+/* NAME
+/* format_tv 3
+/* SUMMARY
+/* format time value with sane precision
+/* SYNOPSIS
+/* #include <format_tv.h>
+/*
+/* VSTRING *format_tv(buffer, sec, usec, sig_dig, max_dig)
+/* VSTRING *buffer;
+/* long sec;
+/* long usec;
+/* int sig_dig;
+/* int max_dig;
+/* DESCRIPTION
+/* format_tv() formats the specified time as a floating-point
+/* number while suppressing irrelevant digits in the output.
+/* Large numbers are always rounded up to an integral number
+/* of seconds. Small numbers are produced with a limited number
+/* of significant digits, provided that the result does not
+/* exceed the limit on the total number of digits after the
+/* decimal point. Trailing zeros are always omitted from the
+/* output.
+/*
+/* Arguments:
+/* .IP buffer
+/* The buffer to which the result is appended.
+/* .IP sec
+/* The seconds portion of the time value.
+/* .IP usec
+/* The microseconds portion of the time value.
+/* .IP sig_dig
+/* The maximal number of significant digits when formatting
+/* small numbers. Leading nulls don't count as significant,
+/* and trailing nulls are not included in the output. Specify
+/* a number in the range 1..6.
+/* .IP max_dig
+/* The maximal number of all digits after the decimal point.
+/* Specify a number in the range 0..6.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this
+/* software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <format_tv.h>
+
+/* Application-specific. */
+
+#define MILLION 1000000
+
+/* format_tv - print time with limited precision */
+
+VSTRING *format_tv(VSTRING *buf, long sec, long usec,
+ int sig_dig, int max_dig)
+{
+ static int pow10[] = {1, 10, 100, 1000, 10000, 100000, 1000000};
+ int n;
+ int rem;
+ int wid;
+ int ures;
+
+ /*
+ * Sanity check.
+ */
+ if (max_dig < 0 || max_dig > 6)
+ msg_panic("format_tv: bad maximum decimal count %d", max_dig);
+ if (sec < 0 || usec < 0 || usec > MILLION)
+ msg_panic("format_tv: bad time %lds %ldus", sec, usec);
+ if (sig_dig < 1 || sig_dig > 6)
+ msg_panic("format_tv: bad significant decimal count %d", sig_dig);
+ ures = MILLION / pow10[max_dig];
+ wid = pow10[sig_dig];
+
+ /*
+ * Adjust the resolution to suppress irrelevant digits.
+ */
+ if (ures < MILLION) {
+ if (sec > 0) {
+ for (n = 1; sec >= n && n <= wid / 10; n *= 10)
+ /* void */ ;
+ ures = (MILLION / wid) * n;
+ } else {
+ while (usec >= wid * ures)
+ ures *= 10;
+ }
+ }
+
+ /*
+ * Round up the number if necessary. Leave thrash below the resolution.
+ */
+ if (ures > 1) {
+ usec += ures / 2;
+ if (usec >= MILLION) {
+ sec += 1;
+ usec -= MILLION;
+ }
+ }
+
+ /*
+ * Format the number. Truncate trailing null and thrash below resolution.
+ */
+ vstring_sprintf_append(buf, "%ld", sec);
+ if (usec >= ures) {
+ VSTRING_ADDCH(buf, '.');
+ for (rem = usec, n = MILLION / 10; rem >= ures && n > 0; n /= 10) {
+ VSTRING_ADDCH(buf, "0123456789"[rem / n]);
+ rem %= n;
+ }
+ }
+ VSTRING_TERMINATE(buf);
+ return (buf);
+}
+
+#ifdef TEST
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <vstring_vstream.h>
+
+int main(int argc, char **argv)
+{
+ VSTRING *in = vstring_alloc(10);
+ VSTRING *out = vstring_alloc(10);
+ double tval;
+ int sec;
+ int usec;
+ int sig_dig;
+ int max_dig;
+
+ while (vstring_get_nonl(in, VSTREAM_IN) > 0) {
+ vstream_printf(">> %s\n", vstring_str(in));
+ if (vstring_str(in)[0] == 0 || vstring_str(in)[0] == '#')
+ continue;
+ if (sscanf(vstring_str(in), "%lf %d %d", &tval, &sig_dig, &max_dig) != 3)
+ msg_fatal("bad input: %s", vstring_str(in));
+ sec = (int) tval; /* raw seconds */
+ usec = (tval - sec) * MILLION; /* raw microseconds */
+ VSTRING_RESET(out);
+ format_tv(out, sec, usec, sig_dig, max_dig);
+ vstream_printf("%s\n", vstring_str(out));
+ vstream_fflush(VSTREAM_OUT);
+ }
+ vstring_free(in);
+ vstring_free(out);
+ return (0);
+}
+
+#endif
diff --git a/src/util/format_tv.h b/src/util/format_tv.h
new file mode 100644
index 0000000..bef787a
--- /dev/null
+++ b/src/util/format_tv.h
@@ -0,0 +1,35 @@
+#ifndef _FORMAT_TV_H_INCLUDED_
+#define _FORMAT_TV_H_INCLUDED_
+
+/*++
+/* NAME
+/* format_tv 3h
+/* SUMMARY
+/* format time with limited precision
+/* SYNOPSIS
+/* #include <format_tv.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern VSTRING *format_tv(VSTRING *, long, long, int, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/format_tv.in b/src/util/format_tv.in
new file mode 100644
index 0000000..a6b5848
--- /dev/null
+++ b/src/util/format_tv.in
@@ -0,0 +1,68 @@
+# Three digits in, 2/6 digits out, rounding down.
+1110 2 6
+111 2 6
+11.1 2 6
+1.11 2 6
+0.111 2 6
+0.0111 2 6
+0.00111 2 6
+0.000111 2 6
+0.0000111 2 6
+
+# One digit in. Must not produce spurious digits or trailing nulls.
+
+1000 2 6
+100 2 6
+10 2 6
+1 2 6
+0.1 2 6
+0.01 2 6
+0.001 2 6
+0.0001 2 6
+0.00001 2 6
+0.0000011 2 6
+
+# Three digits in, 2/6 digits out, rounding up.
+
+996 2 6
+99.6 2 6
+9.96 2 6
+.996 2 6
+.0996 2 6
+.00996 2 6
+.000996 2 6
+
+# Three digits in, 1/6 digits out, rounding down.
+
+1110 1 6
+111 1 6
+11.1 1 6
+1.11 1 6
+0.111 1 6
+0.0111 1 6
+0.00111 1 6
+0.000111 1 6
+0.000011 1 6
+
+# One digit in. Must not produce trailing nulls.
+
+1000 1 6
+100 1 6
+10 1 6
+1 1 6
+0.1 1 6
+0.01 1 6
+0.001 1 6
+0.0001 1 6
+0.00001 1 6
+0.0000011 1 6
+
+# Three digits in, 1/6 digits out, rounding up.
+
+996 1 6
+99.6 1 6
+9.96 1 6
+.996 1 6
+.0996 1 6
+.00996 1 6
+.000996 1 6
diff --git a/src/util/format_tv.ref b/src/util/format_tv.ref
new file mode 100644
index 0000000..718c3c7
--- /dev/null
+++ b/src/util/format_tv.ref
@@ -0,0 +1,120 @@
+>> # Three digits in, 2/6 digits out, rounding down.
+>> 1110 2 6
+1110
+>> 111 2 6
+111
+>> 11.1 2 6
+11
+>> 1.11 2 6
+1.1
+>> 0.111 2 6
+0.11
+>> 0.0111 2 6
+0.011
+>> 0.00111 2 6
+0.0011
+>> 0.000111 2 6
+0.00011
+>> 0.0000111 2 6
+0.000011
+>>
+>> # One digit in. Must not produce spurious digits or trailing nulls.
+>>
+>> 1000 2 6
+1000
+>> 100 2 6
+100
+>> 10 2 6
+10
+>> 1 2 6
+1
+>> 0.1 2 6
+0.1
+>> 0.01 2 6
+0.01
+>> 0.001 2 6
+0.001
+>> 0.0001 2 6
+0.0001
+>> 0.00001 2 6
+0.00001
+>> 0.0000011 2 6
+0.000001
+>>
+>> # Three digits in, 2/6 digits out, rounding up.
+>>
+>> 996 2 6
+996
+>> 99.6 2 6
+100
+>> 9.96 2 6
+10
+>> .996 2 6
+1
+>> .0996 2 6
+0.1
+>> .00996 2 6
+0.01
+>> .000996 2 6
+0.001
+>>
+>> # Three digits in, 1/6 digits out, rounding down.
+>>
+>> 1110 1 6
+1110
+>> 111 1 6
+111
+>> 11.1 1 6
+11
+>> 1.11 1 6
+1
+>> 0.111 1 6
+0.1
+>> 0.0111 1 6
+0.01
+>> 0.00111 1 6
+0.001
+>> 0.000111 1 6
+0.0001
+>> 0.000011 1 6
+0.00001
+>>
+>> # One digit in. Must not produce trailing nulls.
+>>
+>> 1000 1 6
+1000
+>> 100 1 6
+100
+>> 10 1 6
+10
+>> 1 1 6
+1
+>> 0.1 1 6
+0.1
+>> 0.01 1 6
+0.01
+>> 0.001 1 6
+0.001
+>> 0.0001 1 6
+0.0001
+>> 0.00001 1 6
+0.00001
+>> 0.0000011 1 6
+0.000001
+>>
+>> # Three digits in, 1/6 digits out, rounding up.
+>>
+>> 996 1 6
+996
+>> 99.6 1 6
+100
+>> 9.96 1 6
+10
+>> .996 1 6
+1
+>> .0996 1 6
+0.1
+>> .00996 1 6
+0.01
+>> .000996 1 6
+0.001
diff --git a/src/util/fsspace.c b/src/util/fsspace.c
new file mode 100644
index 0000000..50a4aa7
--- /dev/null
+++ b/src/util/fsspace.c
@@ -0,0 +1,127 @@
+/*++
+/* NAME
+/* fsspace 3
+/* SUMMARY
+/* determine available file system space
+/* SYNOPSIS
+/* #include <fsspace.h>
+/*
+/* struct fsspace {
+/* .in +4
+/* unsigned long block_size;
+/* unsigned long block_free;
+/* .in -4
+/* };
+/*
+/* void fsspace(path, sp)
+/* const char *path;
+/* struct fsspace *sp;
+/* DESCRIPTION
+/* fsspace() returns the amount of available space in the file
+/* system specified in \fIpath\fR, in terms of the block size and
+/* of the number of available blocks.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* BUGS
+/* Use caution when doing computations with the result from fsspace().
+/* It is easy to cause overflow (by multiplying large numbers) or to
+/* cause underflow (by subtracting unsigned numbers).
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+#if defined(STATFS_IN_SYS_MOUNT_H)
+#include <sys/param.h>
+#include <sys/mount.h>
+#elif defined(STATFS_IN_SYS_VFS_H)
+#include <sys/vfs.h>
+#elif defined(STATVFS_IN_SYS_STATVFS_H)
+#include <sys/statvfs.h>
+#elif defined(STATFS_IN_SYS_STATFS_H)
+#include <sys/statfs.h>
+#else
+#ifdef USE_STATFS
+#error "please specify the include file with `struct statfs'"
+#else
+#error "please specify the include file with `struct statvfs'"
+#endif
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <fsspace.h>
+
+/* fsspace - find amount of available file system space */
+
+void fsspace(const char *path, struct fsspace * sp)
+{
+ const char *myname = "fsspace";
+
+#ifdef USE_STATFS
+#ifdef USE_STRUCT_FS_DATA /* Ultrix */
+ struct fs_data fsbuf;
+
+ if (statfs(path, &fsbuf) < 0)
+ msg_fatal("statfs %s: %m", path);
+ sp->block_size = 1024;
+ sp->block_free = fsbuf.fd_bfreen;
+#else
+ struct statfs fsbuf;
+
+ if (statfs(path, &fsbuf) < 0)
+ msg_fatal("statfs %s: %m", path);
+ sp->block_size = fsbuf.f_bsize;
+ sp->block_free = fsbuf.f_bavail;
+#endif
+#endif
+#ifdef USE_STATVFS
+ struct statvfs fsbuf;
+
+ if (statvfs(path, &fsbuf) < 0)
+ msg_fatal("statvfs %s: %m", path);
+ sp->block_size = fsbuf.f_frsize;
+ sp->block_free = fsbuf.f_bavail;
+#endif
+ if (msg_verbose)
+ msg_info("%s: %s: block size %lu, blocks free %lu",
+ myname, path, sp->block_size, sp->block_free);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program: print free space unit and count for all
+ * listed file systems.
+ */
+
+#include <vstream.h>
+
+int main(int argc, char **argv)
+{
+ struct fsspace sp;
+
+ if (argc == 1)
+ msg_fatal("usage: %s filesystem...", argv[0]);
+
+ while (--argc && *++argv) {
+ fsspace(*argv, &sp);
+ vstream_printf("%10s: block size %lu, blocks free %lu\n",
+ *argv, sp.block_size, sp.block_free);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ return (0);
+}
+
+#endif
diff --git a/src/util/fsspace.h b/src/util/fsspace.h
new file mode 100644
index 0000000..c118e50
--- /dev/null
+++ b/src/util/fsspace.h
@@ -0,0 +1,33 @@
+#ifndef _FSSPACE_H_INCLUDED_
+#define _FSSPACE_H_INCLUDED_
+
+/*++
+/* NAME
+/* fsspace 3h
+/* SUMMARY
+/* determine available file system space
+/* SYNOPSIS
+/* #include <fsspace.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+struct fsspace {
+ unsigned long block_size; /* block size */
+ unsigned long block_free; /* free space */
+};
+
+extern void fsspace(const char *, struct fsspace *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/fullname.c b/src/util/fullname.c
new file mode 100644
index 0000000..a9d4b32
--- /dev/null
+++ b/src/util/fullname.c
@@ -0,0 +1,111 @@
+/*++
+/* NAME
+/* fullname 3
+/* SUMMARY
+/* lookup personal name of invoking user
+/* SYNOPSIS
+/* #include <fullname.h>
+/*
+/* const char *fullname()
+/* DESCRIPTION
+/* fullname() looks up the personal name of the invoking user.
+/* The result is volatile. Make a copy if it is to be used for
+/* an appreciable amount of time.
+/*
+/* On UNIX systems, fullname() first tries to use the NAME environment
+/* variable, provided that the environment can be trusted.
+/* If that fails, fullname() extracts the username from the GECOS
+/* field of the user's password-file entry, replacing any occurrence
+/* of "&" by the login name, first letter capitalized.
+/*
+/* A null result means that no full name information was found.
+/* SEE ALSO
+/* safe_getenv(3) safe getenv() interface
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include "vstring.h"
+#include "safe.h"
+#include "fullname.h"
+
+/* fullname - get name of user */
+
+const char *fullname(void)
+{
+ static VSTRING *result;
+ char *cp;
+ int ch;
+ uid_t uid;
+ struct passwd *pwd;
+
+ if (result == 0)
+ result = vstring_alloc(10);
+
+ /*
+ * Try the environment.
+ */
+ if ((cp = safe_getenv("NAME")) != 0)
+ return (vstring_str(vstring_strcpy(result, cp)));
+
+ /*
+ * Try the password file database.
+ */
+ uid = getuid();
+ if ((pwd = getpwuid(uid)) == 0)
+ return (0);
+
+ /*
+ * Replace all `&' characters by the login name of this user, first
+ * letter capitalized. Although the full name comes from the protected
+ * password file, the actual data is specified by the user so we should
+ * not trust its sanity.
+ */
+ VSTRING_RESET(result);
+ for (cp = pwd->pw_gecos; (ch = *(unsigned char *) cp) != 0; cp++) {
+ if (ch == ',' || ch == ';' || ch == '%')
+ break;
+ if (ch == '&') {
+ if (pwd->pw_name[0]) {
+ VSTRING_ADDCH(result, TOUPPER(pwd->pw_name[0]));
+ vstring_strcat(result, pwd->pw_name + 1);
+ }
+ } else {
+ VSTRING_ADDCH(result, ch);
+ }
+ }
+ VSTRING_TERMINATE(result);
+ return (vstring_str(result));
+}
+
+#ifdef TEST
+
+#include <stdio.h>
+
+int main(int unused_argc, char **unused_argv)
+{
+ const char *cp = fullname();
+
+ printf("%s\n", cp ? cp : "null!");
+ return (0);
+}
+
+#endif
diff --git a/src/util/fullname.h b/src/util/fullname.h
new file mode 100644
index 0000000..f24492a
--- /dev/null
+++ b/src/util/fullname.h
@@ -0,0 +1,29 @@
+#ifndef _FULLNAME_H_INCLUDED_
+#define _FULLNAME_H_INCLUDED_
+
+/*++
+/* NAME
+/* fullname 3h
+/* SUMMARY
+/* lookup personal name of invoking user
+/* SYNOPSIS
+/* #include <fullname.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern const char *fullname(void);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/gccw.c b/src/util/gccw.c
new file mode 100644
index 0000000..d3fd985
--- /dev/null
+++ b/src/util/gccw.c
@@ -0,0 +1,58 @@
+ /*
+ * This is a regression test for all the things that gcc is meant to warn
+ * about.
+ *
+ * gcc version 3 breaks several tests:
+ *
+ * -W does not report missing return value
+ *
+ * -Wunused does not report unused parameter
+ */
+
+#include <stdio.h>
+#include <setjmp.h>
+
+jmp_buf jbuf;
+
+ /* -Wmissing-prototypes: no previous prototype for 'test1' */
+ /* -Wimplicit: return type defaults to `int' */
+test1(void)
+{
+ /* -Wunused: unused variable `foo' */
+ int foo;
+
+ /* -Wparentheses: suggest parentheses around && within || */
+ printf("%d\n", 1 && 2 || 3 && 4);
+ /* -W: statement with no effect */
+ 0;
+ /* BROKEN in gcc 3 */
+ /* -W: control reaches end of non-void function */
+}
+
+
+ /* -W??????: unused parameter `foo' */
+void test2(int foo)
+{
+ enum {
+ a = 10, b = 15} moe;
+ int bar;
+
+ /* -Wuninitialized: 'bar' might be used uninitialized in this function */
+ /* -Wformat: format argument is not a pointer (arg 2) */
+ printf("%s\n", bar);
+ /* -Wformat: too few arguments for format */
+ printf("%s%s\n", "bar");
+ /* -Wformat: too many arguments for format */
+ printf("%s\n", "bar", "bar");
+
+ /* -Wswitch: enumeration value `b' not handled in switch */
+ switch (moe) {
+ case a:
+ return;
+ }
+}
+
+ /* -Wstrict-prototypes: function declaration isn't a prototype */
+void test3()
+{
+}
diff --git a/src/util/gccw.ref b/src/util/gccw.ref
new file mode 100644
index 0000000..7616c29
--- /dev/null
+++ b/src/util/gccw.ref
@@ -0,0 +1,18 @@
+gcc -Wall -Wno-comment -Wformat -Wimplicit -Wmissing-prototypes -Wparentheses -Wstrict-prototypes -Wswitch -Wuninitialized -Wunused -DUSE_TLS -DHAS_PCRE -I/usr/local/include -DSNAPSHOT -g -O -I. -DFREEBSD5 -c gccw.c
+gccw.c: At top level:
+gccw.c: At top level:
+gccw.c: In function 'test1':
+gccw.c: In function 'test2':
+gccw.c:20: warning: no previous prototype for 'test1'
+gccw.c:20: warning: return type defaults to 'int'
+gccw.c:22: warning: unused variable 'foo'
+gccw.c:25: warning: suggest parentheses around && within ||
+gccw.c:27: warning: statement with no effect
+gccw.c:30: warning: control reaches end of non-void function
+gccw.c:35: warning: no previous prototype for 'test2'
+gccw.c:38: warning: 'bar' might be used uninitialized in this function
+gccw.c:42: warning: format argument is not a pointer (arg 2)
+gccw.c:44: warning: too few arguments for format
+gccw.c:46: warning: too many arguments for format
+gccw.c:52: warning: enumeration value 'b' not handled in switch
+gccw.c:57: warning: function declaration isn't a prototype
diff --git a/src/util/get_domainname.c b/src/util/get_domainname.c
new file mode 100644
index 0000000..0897f52
--- /dev/null
+++ b/src/util/get_domainname.c
@@ -0,0 +1,66 @@
+/*++
+/* NAME
+/* get_domainname 3
+/* SUMMARY
+/* network domain name lookup
+/* SYNOPSIS
+/* #include <get_domainname.h>
+/*
+/* const char *get_domainname()
+/* DESCRIPTION
+/* get_domainname() returns the local domain name as obtained
+/* by stripping the hostname component from the result from
+/* get_hostname(). The result is the hostname when get_hostname()
+/* does not return a FQDN form ("foo"), or its result has only two
+/* components ("foo.com").
+/* DIAGNOSTICS
+/* Fatal errors: no hostname, invalid hostname.
+/* SEE ALSO
+/* get_hostname(3)
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include "mymalloc.h"
+#include "get_hostname.h"
+#include "get_domainname.h"
+
+/* Local stuff. */
+
+static char *my_domain_name;
+
+/* get_domainname - look up my domain name */
+
+const char *get_domainname(void)
+{
+ const char *host;
+ const char *dot;
+
+ /*
+ * Use the hostname when it is not a FQDN ("foo"), or when the hostname
+ * actually is a domain name ("foo.com").
+ */
+ if (my_domain_name == 0) {
+ host = get_hostname();
+ if ((dot = strchr(host, '.')) == 0 || strchr(dot + 1, '.') == 0) {
+ my_domain_name = mystrdup(host);
+ } else {
+ my_domain_name = mystrdup(dot + 1);
+ }
+ }
+ return (my_domain_name);
+}
diff --git a/src/util/get_domainname.h b/src/util/get_domainname.h
new file mode 100644
index 0000000..177e3fd
--- /dev/null
+++ b/src/util/get_domainname.h
@@ -0,0 +1,29 @@
+#ifndef _GET_DOMAINNAME_H_INCLUDED_
+#define _GET_DOMAINNAME_H_INCLUDED_
+
+/*++
+/* NAME
+/* get_domainname 3h
+/* SUMMARY
+/* network domain name lookup
+/* SYNOPSIS
+/* #include <get_domainname.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface */
+
+extern const char *get_domainname(void);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/get_hostname.c b/src/util/get_hostname.c
new file mode 100644
index 0000000..eb2cfea
--- /dev/null
+++ b/src/util/get_hostname.c
@@ -0,0 +1,84 @@
+/*++
+/* NAME
+/* get_hostname 3
+/* SUMMARY
+/* network name lookup
+/* SYNOPSIS
+/* #include <get_hostname.h>
+/*
+/* const char *get_hostname()
+/* DESCRIPTION
+/* get_hostname() returns the local hostname as obtained
+/* via gethostname() or its moral equivalent. This routine
+/* goes to great length to avoid dependencies on any network
+/* services.
+/* DIAGNOSTICS
+/* Fatal errors: no hostname, invalid hostname.
+/* SEE ALSO
+/* valid_hostname(3)
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/param.h>
+#include <string.h>
+#include <unistd.h>
+
+#if (MAXHOSTNAMELEN < 256)
+#undef MAXHOSTNAMELEN
+#define MAXHOSTNAMELEN 256
+#endif
+
+/* Utility library. */
+
+#include "mymalloc.h"
+#include "msg.h"
+#include "valid_hostname.h"
+#include "get_hostname.h"
+
+/* Local stuff. */
+
+static char *my_host_name;
+
+/* get_hostname - look up my host name */
+
+const char *get_hostname(void)
+{
+ char namebuf[MAXHOSTNAMELEN + 1];
+
+ /*
+ * The gethostname() call is not (or not yet) in ANSI or POSIX, but it is
+ * part of the socket interface library. We avoid the more politically-
+ * correct uname() routine because that has no portable way of dealing
+ * with long (FQDN) hostnames.
+ *
+ * DO NOT CALL GETHOSTBYNAME FROM THIS FUNCTION. IT BREAKS MAILDIR DELIVERY
+ * AND OTHER THINGS WHEN THE MACHINE NAME IS NOT FOUND IN /ETC/HOSTS OR
+ * CAUSES PROCESSES TO HANG WHEN THE NETWORK IS DISCONNECTED.
+ *
+ * POSTFIX NO LONGER NEEDS A FULLY QUALIFIED HOSTNAME. INSTEAD POSTFIX WILL
+ * USE A DEFAULT DOMAIN NAME "LOCALDOMAIN".
+ */
+ if (my_host_name == 0) {
+ /* DO NOT CALL GETHOSTBYNAME FROM THIS FUNCTION */
+ if (gethostname(namebuf, sizeof(namebuf)) < 0)
+ msg_fatal("gethostname: %m");
+ namebuf[MAXHOSTNAMELEN] = 0;
+ /* DO NOT CALL GETHOSTBYNAME FROM THIS FUNCTION */
+ if (valid_hostname(namebuf, DO_GRIPE) == 0)
+ msg_fatal("unable to use my own hostname");
+ /* DO NOT CALL GETHOSTBYNAME FROM THIS FUNCTION */
+ my_host_name = mystrdup(namebuf);
+ }
+ return (my_host_name);
+}
diff --git a/src/util/get_hostname.h b/src/util/get_hostname.h
new file mode 100644
index 0000000..32d2ab2
--- /dev/null
+++ b/src/util/get_hostname.h
@@ -0,0 +1,29 @@
+#ifndef _GET_HOSTNAME_H_INCLUDED_
+#define _GET_HOSTNAME_H_INCLUDED_
+
+/*++
+/* NAME
+/* get_hostname 3h
+/* SUMMARY
+/* network name lookup
+/* SYNOPSIS
+/* #include <get_hostname.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface */
+
+extern const char *get_hostname(void);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/hash_fnv.c b/src/util/hash_fnv.c
new file mode 100644
index 0000000..b4d7b30
--- /dev/null
+++ b/src/util/hash_fnv.c
@@ -0,0 +1,237 @@
+/*++
+/* NAME
+/* hash_fnv 3
+/* SUMMARY
+/* Fowler/Noll/Vo hash function
+/* SYNOPSIS
+/* #include <hash_fnv.h>
+/*
+/* HASH_FNV_T hash_fnv(
+/* const void *src,
+/* size_t len)
+/*
+/* HASH_FNV_T hash_fnvz(
+/* const char *src)
+/* DESCRIPTION
+/* hash_fnv() implements a modified FNV type 1a hash function.
+/*
+/* hash_fnvz() provides the same functionality for null-terminated
+/* strings, avoiding an unnecessary strlen() call.
+/*
+/* To thwart collision attacks, the hash function is seeded
+/* once with ldseed(). To disable seeding (typically, to make
+/* tests predictable), specify the NORANDOMIZE environment
+/* variable; the value does not matter.
+/*
+/* This implementation works around a "sticky state" problem
+/* with FNV hash functions: when an input produces a zero hash
+/* state, and the next input byte is zero, then the hash state
+/* would not change. To avoid this, hash_fnv() adds 1 to each
+/* input value. Compile with -DSTRICT_FNV1A to get the standard
+/* behavior.
+/*
+/* The default HASH_FNV_T result type is uint64_t. When compiled
+/* with -DUSE_FNV_32BIT, the result type is uint32_t. On ancient
+/* systems without <stdint.h>, define HASH_FNV_T on the compiler
+/* command line as an unsigned 32-bit or 64-bit integer type,
+/* and specify -DUSE_FNV_32BIT when HASH_FNV_T is a 32-bit type.
+/* SEE ALSO
+/* http://www.isthe.com/chongo/tech/comp/fnv/index.html
+/* https://softwareengineering.stackexchange.com/questions/49550/
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library
+ */
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <ldseed.h>
+#include <hash_fnv.h>
+
+ /*
+ * Application-specific.
+ */
+#ifdef USE_FNV_32BIT
+#define FNV_prime 0x01000193UL
+#define FNV_offset_basis 0x811c9dc5UL
+#else
+#define FNV_prime 0x00000100000001B3ULL
+#define FNV_offset_basis 0xcbf29ce484222325ULL
+#endif
+
+ /*
+ * Workaround for the sticky all-zero hash state: when the next input byte
+ * is zero, then the operations "hash ^= 0" and "hash *= FNV_prime" would
+ * not change the hash state. To avoid that, add 1 to the every input value.
+ */
+#ifdef STRICT_FNV1A
+#define HASH_FNV_NEW_BITS(new_bits) (new_bits)
+#else
+#define HASH_FNV_NEW_BITS(new_bits) (1 + (new_bits))
+#endif
+
+static HASH_FNV_T hash_fnv_basis = FNV_offset_basis;
+static int hash_fnv_must_init = 1;
+
+/* hash_fnv_init - seed the hash */
+
+static void hash_fnv_init(void)
+{
+ HASH_FNV_T seed;
+
+ if (!getenv("NORANDOMIZE")) {
+ ldseed(&seed, sizeof(seed));
+ hash_fnv_basis ^= seed;
+ }
+ hash_fnv_must_init = 0;
+}
+
+/* hash_fnv - modified FNV 1a hash */
+
+HASH_FNV_T hash_fnv(const void *src, size_t len)
+{
+ HASH_FNV_T hash;
+ HASH_FNV_T new_bits;
+
+ if (hash_fnv_must_init)
+ hash_fnv_init();
+
+ hash = hash_fnv_basis;
+ while (len-- > 0) {
+ new_bits = *(unsigned char *) src++;
+ hash ^= HASH_FNV_NEW_BITS(new_bits);
+ hash *= FNV_prime;
+ }
+ return (hash);
+}
+
+/* hash_fnvz - modified FNV 1a hash for null-terminated strings */
+
+HASH_FNV_T hash_fnvz(const char *src)
+{
+ HASH_FNV_T hash;
+ HASH_FNV_T new_bits;
+
+ if (hash_fnv_must_init)
+ hash_fnv_init();
+
+ hash = hash_fnv_basis;
+ while ((new_bits = *(unsigned char *) src++) != 0) {
+ hash ^= HASH_FNV_NEW_BITS(new_bits);
+ hash *= FNV_prime;
+ }
+ return (hash);
+}
+
+#ifdef TEST
+#include <stdlib.h>
+#include <string.h>
+#include <msg.h>
+
+int main(void)
+{
+ int pass = 0;
+ int fail = 0;
+
+ /*
+ * Sanity check.
+ */
+#ifdef STRICT_FNV1A
+ msg_fatal("This test requires no STRICT_FNV1A");
+#endif
+
+ /*
+ * Force unseeded hash, to make tests predictable.
+ */
+ if (putenv("NORANDOMIZE=") != 0)
+ msg_fatal("putenv(\"NORANDOMIZE=\"): %m");
+
+ /*
+ * Test: hashing produces the expected results.
+ */
+ {
+ struct testcase {
+ HASH_FNV_T hval;
+ const char *str;
+ };
+ static struct testcase testcases[] =
+ {
+#ifdef USE_FNV_32BIT
+ 0x1c00fc06UL, "overdeeply",
+ 0x1c00fc06UL, "undescript",
+ 0x1e1e52a4UL, "fanfold",
+ 0x1e1e52a4UL, "phrensied",
+#else
+ 0xda19999ec0bda706ULL, "overdeeply",
+ 0xd7b9e43f26396a66ULL, "undescript",
+ 0xa50c585d385a2604ULL, "fanfold",
+ 0x1ec3ef9bb2b734a4ULL, "phrensied",
+#endif
+ 0,
+ };
+ struct testcase *tp;
+ HASH_FNV_T hval;
+ int test_failed;
+
+ for (tp = testcases; tp->str; tp++) {
+ test_failed = 0;
+ if ((hval = hash_fnvz(tp->str)) != tp->hval) {
+ msg_warn("hash_fnv(\"%s\") want %lu, got: %lu",
+ tp->str, (unsigned long) tp->hval,
+ (unsigned long) hval);
+ test_failed = 1;
+ }
+ if (test_failed) {
+ fail += 1;
+ msg_info("FAIL: %s", tp->str);
+ } else {
+ pass += 1;
+ msg_info("PASS: %s", tp->str);
+ }
+ }
+ }
+
+ /*
+ * Test: hash_fnvz(s) is equivalent to hash_fnv(s, strlen(s)). No need to
+ * verify the actual result; we already did that above.
+ */
+ {
+ const char *strval = "foobar";
+ HASH_FNV_T h1 = hash_fnv(strval, strlen(strval));
+ HASH_FNV_T h2 = hash_fnvz(strval);
+
+ if (h1 == h2) {
+ pass += 1;
+ msg_info("PASS: hash_fnvz(\"%s\") == hash_fnv(\"%s\", %ld)",
+ strval, strval, (long) strlen(strval));
+ } else {
+ fail += 1;
+ msg_info("FAIL: hash_fnvz(\"%s\") == hash_fnv(\"%s\", %ld)",
+ strval, strval, (long) strlen(strval));
+ }
+ }
+
+
+ /*
+ * Wrap up.
+ */
+ msg_info("PASS=%d FAIL=%d", pass, fail);
+ return (fail != 0);
+}
+
+#endif
diff --git a/src/util/hash_fnv.h b/src/util/hash_fnv.h
new file mode 100644
index 0000000..0bf1771
--- /dev/null
+++ b/src/util/hash_fnv.h
@@ -0,0 +1,40 @@
+#ifndef _HASH_FNV_H_INCLUDED_
+#define _HASH_FNV_H_INCLUDED_
+
+/*++
+/* NAME
+/* hash_fnv 3h
+/* SUMMARY
+/* Fowler/Noll/Vo hash function
+/* SYNOPSIS
+/* #include <hash_fnv.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+#ifndef HASH_FNV_T
+#include <stdint.h>
+#ifdef USE_FNV_32BIT
+#define HASH_FNV_T uint32_t
+#else /* USE_FNV_32BIT */
+#define HASH_FNV_T uint64_t
+#endif /* USE_FNV_32BIT */
+#endif /* HASH_FNV_T */
+
+extern HASH_FNV_T hash_fnv(const void *, size_t);
+extern HASH_FNV_T hash_fnvz(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/util/hex_code.c b/src/util/hex_code.c
new file mode 100644
index 0000000..3dfcb98
--- /dev/null
+++ b/src/util/hex_code.c
@@ -0,0 +1,243 @@
+/*++
+/* NAME
+/* hex_code 3
+/* SUMMARY
+/* encode/decode data, hexadecimal style
+/* SYNOPSIS
+/* #include <hex_code.h>
+/*
+/* VSTRING *hex_encode(result, in, len)
+/* VSTRING *result;
+/* const char *in;
+/* ssize_t len;
+/*
+/* VSTRING *hex_decode(result, in, len)
+/* VSTRING *result;
+/* const char *in;
+/* ssize_t len;
+/*
+/* VSTRING *hex_encode_opt(result, in, len, flags)
+/* VSTRING *result;
+/* const char *in;
+/* ssize_t len;
+/* int flags;
+/*
+/* VSTRING *hex_decode_opt(result, in, len, flags)
+/* VSTRING *result;
+/* const char *in;
+/* ssize_t len;
+/* int flags;
+/* DESCRIPTION
+/* hex_encode() takes a block of len bytes and encodes it as one
+/* upper-case null-terminated string. The result value is
+/* the result argument.
+/*
+/* hex_decode() performs the opposite transformation on
+/* lower-case, upper-case or mixed-case input. The result
+/* value is the result argument. The result is null terminated,
+/* whether or not that makes sense.
+/*
+/* hex_encode_opt() enables extended functionality as controlled
+/* with \fIflags\fR.
+/* .IP HEX_ENCODE_FLAG_NONE
+/* The default: a self-documenting flag that enables no
+/* functionality.
+/* .IP HEX_ENCODE_FLAG_USE_COLON
+/* Inserts one ":" between bytes.
+/* .PP
+/* hex_decode_opt() enables extended functionality as controlled
+/* with \fIflags\fR.
+/* .IP HEX_DECODE_FLAG_NONE
+/* The default: a self-documenting flag that enables no
+/* functionality.
+/* .IP HEX_DECODE_FLAG_ALLOW_COLON
+/* Allows, but does not require, one ":" between bytes.
+/* DIAGNOSTICS
+/* hex_decode() returns a null pointer when the input contains
+/* characters not in the hexadecimal alphabet.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <ctype.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <hex_code.h>
+
+/* Application-specific. */
+
+static const unsigned char hex_chars[] = "0123456789ABCDEF";
+
+#define UCHAR_PTR(x) ((const unsigned char *)(x))
+
+/* hex_encode - ABI compatibility */
+
+#undef hex_encode
+
+VSTRING *hex_encode(VSTRING *result, const char *in, ssize_t len)
+{
+ return (hex_encode_opt(result, in, len, HEX_ENCODE_FLAG_NONE));
+}
+
+/* hex_encode_opt - raw data to encoded */
+
+VSTRING *hex_encode_opt(VSTRING *result, const char *in, ssize_t len, int flags)
+{
+ const unsigned char *cp;
+ int ch;
+ ssize_t count;
+
+ VSTRING_RESET(result);
+ for (cp = UCHAR_PTR(in), count = len; count > 0; count--, cp++) {
+ ch = *cp;
+ VSTRING_ADDCH(result, hex_chars[(ch >> 4) & 0xf]);
+ VSTRING_ADDCH(result, hex_chars[ch & 0xf]);
+ if ((flags & HEX_ENCODE_FLAG_USE_COLON) && count > 1)
+ VSTRING_ADDCH(result, ':');
+ }
+ VSTRING_TERMINATE(result);
+ return (result);
+}
+
+/* hex_decode - ABI compatibility wrapper */
+
+#undef hex_decode
+
+VSTRING *hex_decode(VSTRING *result, const char *in, ssize_t len)
+{
+ return (hex_decode_opt(result, in, len, HEX_DECODE_FLAG_NONE));
+}
+
+/* hex_decode_opt - encoded data to raw */
+
+VSTRING *hex_decode_opt(VSTRING *result, const char *in, ssize_t len, int flags)
+{
+ const unsigned char *cp;
+ ssize_t count;
+ unsigned int hex;
+ unsigned int bin;
+
+ VSTRING_RESET(result);
+ for (cp = UCHAR_PTR(in), count = len; count > 0; cp += 2, count -= 2) {
+ if (count < 2)
+ return (0);
+ hex = cp[0];
+ if (hex >= '0' && hex <= '9')
+ bin = (hex - '0') << 4;
+ else if (hex >= 'A' && hex <= 'F')
+ bin = (hex - 'A' + 10) << 4;
+ else if (hex >= 'a' && hex <= 'f')
+ bin = (hex - 'a' + 10) << 4;
+ else
+ return (0);
+ hex = cp[1];
+ if (hex >= '0' && hex <= '9')
+ bin |= (hex - '0');
+ else if (hex >= 'A' && hex <= 'F')
+ bin |= (hex - 'A' + 10);
+ else if (hex >= 'a' && hex <= 'f')
+ bin |= (hex - 'a' + 10);
+ else
+ return (0);
+ VSTRING_ADDCH(result, bin);
+
+ /*
+ * Support *colon-separated* input (no leading or trailing colons).
+ * After decoding "xx", skip a possible ':' preceding "yy" in
+ * "xx:yy".
+ */
+ if ((flags & HEX_DECODE_FLAG_ALLOW_COLON)
+ && count > 4 && cp[2] == ':') {
+ ++cp;
+ --count;
+ }
+ }
+ VSTRING_TERMINATE(result);
+ return (result);
+}
+
+#ifdef TEST
+#include <argv.h>
+
+ /*
+ * Proof-of-concept test program: convert to hexadecimal and back.
+ */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *b1 = vstring_alloc(1);
+ VSTRING *b2 = vstring_alloc(1);
+ char *test = "this is a test";
+ ARGV *argv;
+
+#define DECODE(b,x,l) { \
+ if (hex_decode((b),(x),(l)) == 0) \
+ msg_panic("bad hex: %s", (x)); \
+ }
+#define VERIFY(b,t) { \
+ if (strcmp((b), (t)) != 0) \
+ msg_panic("bad test: %s", (b)); \
+ }
+
+ hex_encode(b1, test, strlen(test));
+ DECODE(b2, STR(b1), LEN(b1));
+ VERIFY(STR(b2), test);
+
+ hex_encode(b1, test, strlen(test));
+ hex_encode(b2, STR(b1), LEN(b1));
+ hex_encode(b1, STR(b2), LEN(b2));
+ DECODE(b2, STR(b1), LEN(b1));
+ DECODE(b1, STR(b2), LEN(b2));
+ DECODE(b2, STR(b1), LEN(b1));
+ VERIFY(STR(b2), test);
+
+ hex_encode(b1, test, strlen(test));
+ hex_encode(b2, STR(b1), LEN(b1));
+ hex_encode(b1, STR(b2), LEN(b2));
+ hex_encode(b2, STR(b1), LEN(b1));
+ hex_encode(b1, STR(b2), LEN(b2));
+ DECODE(b2, STR(b1), LEN(b1));
+ DECODE(b1, STR(b2), LEN(b2));
+ DECODE(b2, STR(b1), LEN(b1));
+ DECODE(b1, STR(b2), LEN(b2));
+ DECODE(b2, STR(b1), LEN(b1));
+ VERIFY(STR(b2), test);
+
+ hex_encode_opt(b1, test, strlen(test), HEX_ENCODE_FLAG_USE_COLON);
+ argv = argv_split(STR(b1), ":");
+ if (argv->argc != strlen(test))
+ msg_panic("HEX_ENCODE_FLAG_USE_COLON");
+ if (hex_decode_opt(b2, STR(b1), LEN(b1), HEX_DECODE_FLAG_ALLOW_COLON) == 0)
+ msg_panic("HEX_DECODE_FLAG_ALLOW_COLON");
+ VERIFY(STR(b2), test);
+ argv_free(argv);
+
+ vstring_free(b1);
+ vstring_free(b2);
+ return (0);
+}
+
+#endif
diff --git a/src/util/hex_code.h b/src/util/hex_code.h
new file mode 100644
index 0000000..720977a
--- /dev/null
+++ b/src/util/hex_code.h
@@ -0,0 +1,54 @@
+#ifndef _HEX_CODE_H_INCLUDED_
+#define _HEX_CODE_H_INCLUDED_
+
+/*++
+/* NAME
+/* hex_code 3h
+/* SUMMARY
+/* encode/decode data, hexadecimal style
+/* SYNOPSIS
+/* #include <hex_code.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+#define HEX_ENCODE_FLAG_NONE (0)
+#define HEX_ENCODE_FLAG_USE_COLON (1<<0)
+
+#define HEX_DECODE_FLAG_NONE (0)
+#define HEX_DECODE_FLAG_ALLOW_COLON (1<<0)
+
+extern VSTRING *hex_encode(VSTRING *, const char *, ssize_t);
+extern VSTRING *WARN_UNUSED_RESULT hex_decode(VSTRING *, const char *, ssize_t);
+extern VSTRING *hex_encode_opt(VSTRING *, const char *, ssize_t, int);
+extern VSTRING *WARN_UNUSED_RESULT hex_decode_opt(VSTRING *, const char *, ssize_t, int);
+
+#define hex_encode(res, in, len) \
+ hex_encode_opt((res), (in), (len), HEX_ENCODE_FLAG_NONE)
+#define hex_decode(res, in, len) \
+ hex_decode_opt((res), (in), (len), HEX_DECODE_FLAG_NONE)
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/hex_quote.c b/src/util/hex_quote.c
new file mode 100644
index 0000000..7089385
--- /dev/null
+++ b/src/util/hex_quote.c
@@ -0,0 +1,153 @@
+/*++
+/* NAME
+/* hex_quote 3
+/* SUMMARY
+/* quote/unquote text, HTTP style.
+/* SYNOPSIS
+/* #include <hex_quote.h>
+/*
+/* VSTRING *hex_quote(hex, raw)
+/* VSTRING *hex;
+/* const char *raw;
+/*
+/* VSTRING *hex_unquote(raw, hex)
+/* VSTRING *raw;
+/* const char *hex;
+/* DESCRIPTION
+/* hex_quote() takes a null-terminated string and replaces non-printable
+/* and whitespace characters and the % by %XX, XX being the two-digit
+/* hexadecimal equivalent.
+/* The hexadecimal codes are produced as upper-case characters. The result
+/* value is the hex argument.
+/*
+/* hex_unquote() performs the opposite transformation. This function
+/* understands lowercase, uppercase, and mixed case %XX sequences. The
+/* result value is the raw argument in case of success, a null pointer
+/* otherwise.
+/* BUGS
+/* hex_quote() cannot process null characters in data.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <ctype.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "vstring.h"
+#include "hex_quote.h"
+
+/* Application-specific. */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* hex_quote - raw data to quoted */
+
+VSTRING *hex_quote(VSTRING *hex, const char *raw)
+{
+ const char *cp;
+ int ch;
+
+ VSTRING_RESET(hex);
+ for (cp = raw; (ch = *(unsigned const char *) cp) != 0; cp++) {
+ if (ch != '%' && !ISSPACE(ch) && ISPRINT(ch)) {
+ VSTRING_ADDCH(hex, ch);
+ } else {
+ vstring_sprintf_append(hex, "%%%02X", ch);
+ }
+ }
+ VSTRING_TERMINATE(hex);
+ return (hex);
+}
+
+/* hex_unquote - quoted data to raw */
+
+VSTRING *hex_unquote(VSTRING *raw, const char *hex)
+{
+ const char *cp;
+ int ch;
+
+ VSTRING_RESET(raw);
+ for (cp = hex; (ch = *cp) != 0; cp++) {
+ if (ch == '%') {
+ if (ISDIGIT(cp[1]))
+ ch = (cp[1] - '0') << 4;
+ else if (cp[1] >= 'a' && cp[1] <= 'f')
+ ch = (cp[1] - 'a' + 10) << 4;
+ else if (cp[1] >= 'A' && cp[1] <= 'F')
+ ch = (cp[1] - 'A' + 10) << 4;
+ else
+ return (0);
+ if (ISDIGIT(cp[2]))
+ ch |= (cp[2] - '0');
+ else if (cp[2] >= 'a' && cp[2] <= 'f')
+ ch |= (cp[2] - 'a' + 10);
+ else if (cp[2] >= 'A' && cp[2] <= 'F')
+ ch |= (cp[2] - 'A' + 10);
+ else
+ return (0);
+ cp += 2;
+ }
+ VSTRING_ADDCH(raw, ch);
+ }
+ VSTRING_TERMINATE(raw);
+ return (raw);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program: convert to hex and back.
+ */
+#include <vstream.h>
+
+#define BUFLEN 1024
+
+static ssize_t read_buf(VSTREAM *fp, VSTRING *buf)
+{
+ ssize_t len;
+
+ len = vstream_fread_buf(fp, buf, BUFLEN);
+ VSTRING_TERMINATE(buf);
+ return (len);
+}
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *raw = vstring_alloc(BUFLEN);
+ VSTRING *hex = vstring_alloc(100);
+ ssize_t len;
+
+ while ((len = read_buf(VSTREAM_IN, raw)) > 0) {
+ hex_quote(hex, STR(raw));
+ if (hex_unquote(raw, STR(hex)) == 0)
+ msg_fatal("bad input: %.100s", STR(hex));
+ if (LEN(raw) != len)
+ msg_fatal("len %ld != raw len %ld", (long) len, (long) LEN(raw));
+ if (vstream_fwrite(VSTREAM_OUT, STR(raw), LEN(raw)) != LEN(raw))
+ msg_fatal("write error: %m");
+ }
+ vstream_fflush(VSTREAM_OUT);
+ vstring_free(raw);
+ vstring_free(hex);
+ return (0);
+}
+
+#endif
diff --git a/src/util/hex_quote.h b/src/util/hex_quote.h
new file mode 100644
index 0000000..d72ec57
--- /dev/null
+++ b/src/util/hex_quote.h
@@ -0,0 +1,36 @@
+#ifndef _HEX_QUOTE_H_INCLUDED_
+#define _HEX_QUOTE_H_INCLUDED_
+
+/*++
+/* NAME
+/* hex_quote 3h
+/* SUMMARY
+/* quote/unquote text, HTTP style.
+/* SYNOPSIS
+/* #include <hex_quote.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern VSTRING *hex_quote(VSTRING *, const char *);
+extern VSTRING *hex_unquote(VSTRING *, const char *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/host_port.c b/src/util/host_port.c
new file mode 100644
index 0000000..c4e8616
--- /dev/null
+++ b/src/util/host_port.c
@@ -0,0 +1,203 @@
+/*++
+/* NAME
+/* host_port 3
+/* SUMMARY
+/* split string into host and port, destroy string
+/* SYNOPSIS
+/* #include <host_port.h>
+/*
+/* const char *host_port(string, host, def_host, port, def_service)
+/* char *string;
+/* char **host;
+/* char *def_host;
+/* char **port;
+/* char *def_service;
+/* DESCRIPTION
+/* host_port() splits a string into substrings with the host
+/* name or address, and the service name or port number.
+/* The input string is modified.
+/*
+/* Host/domain names are validated with valid_utf8_hostname(),
+/* and host addresses are validated with valid_hostaddr().
+/*
+/* The following input formats are understood (null means
+/* a null pointer argument):
+/*
+/* When def_service is not null, and def_host is null:
+/*
+/* [host]:port, [host]:, [host]
+/*
+/* host:port, host:, host
+/*
+/* When def_host is not null, and def_service is null:
+/*
+/* :port, port
+/*
+/* Other combinations of def_service and def_host are
+/* not supported and produce undefined results.
+/* DIAGNOSTICS
+/* The result is a null pointer in case of success.
+/* In case of problems the result is a string pointer with
+/* the problem type.
+/* CLIENT EXAMPLE
+/* .ad
+/* .fi
+/* Typical client usage allows the user to omit the service port,
+/* in which case the client connects to a pre-determined default
+/* port:
+/* .nf
+/* .na
+/*
+/* buf = mystrdup(endpoint);
+/* if ((parse_error = host_port(buf, &host, NULL, &port, defport)) != 0)
+/* msg_fatal("%s in \"%s\"", parse_error, endpoint);
+/* if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0)
+/* msg_fatal("%s: %s", endpoint, MAI_STRERROR(aierr));
+/* myfree(buf);
+/* SERVER EXAMPLE
+/* .ad
+/* .fi
+/* Typical server usage allows the user to omit the host, meaning
+/* listen on all available network addresses:
+/* .nf
+/* .na
+/*
+/* buf = mystrdup(endpoint);
+/* if ((parse_error = host_port(buf, &host, "", &port, NULL)) != 0)
+/* msg_fatal("%s in \"%s\"", parse_error, endpoint);
+/* if (*host == 0)
+/* host = 0;
+/* if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0)
+/* msg_fatal("%s: %s", endpoint, MAI_STRERROR(aierr));
+/* myfree(buf);
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <split_at.h>
+#include <stringops.h> /* XXX util_utf8_enable */
+#include <valid_utf8_hostname.h>
+
+/* Global library. */
+
+#include <host_port.h>
+
+ /*
+ * Point-fix workaround. The libutil library should be email agnostic, but
+ * we can't rip up the library APIs in the stable releases.
+ */
+#include <string.h>
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+#define IPV6_COL "IPv6:" /* RFC 2821 */
+#define IPV6_COL_LEN (sizeof(IPV6_COL) - 1)
+#define HAS_IPV6_COL(str) (strncasecmp((str), IPV6_COL, IPV6_COL_LEN) == 0)
+
+/* host_port - parse string into host and port, destroy string */
+
+const char *host_port(char *buf, char **host, char *def_host,
+ char **port, char *def_service)
+{
+ char *cp = buf;
+ int ipv6 = 0;
+
+ /*-
+ * [host]:port, [host]:, [host].
+ * [ipv6:ipv6addr]:port, [ipv6:ipv6addr]:, [ipv6:ipv6addr].
+ */
+ if (*cp == '[') {
+ ++cp;
+ if ((ipv6 = HAS_IPV6_COL(cp)) != 0)
+ cp += IPV6_COL_LEN;
+ *host = cp;
+ if ((cp = split_at(cp, ']')) == 0)
+ return ("missing \"]\"");
+ if (*cp && *cp++ != ':')
+ return ("garbage after \"]\"");
+ if (ipv6 && !valid_ipv6_hostaddr(*host, DONT_GRIPE))
+ return ("malformed IPv6 address");
+ *port = *cp ? cp : def_service;
+ }
+
+ /*
+ * host:port, host:, host, :port, port.
+ */
+ else {
+ if ((cp = split_at_right(buf, ':')) != 0) {
+ *host = *buf ? buf : def_host;
+ *port = *cp ? cp : def_service;
+ } else {
+ *host = def_host ? def_host : (*buf ? buf : 0);
+ *port = def_service ? def_service : (*buf ? buf : 0);
+ }
+ }
+ if (*host == 0)
+ return ("missing host information");
+ if (*port == 0)
+ return ("missing service information");
+
+ /*
+ * Final sanity checks. We're still sloppy, allowing bare numerical
+ * network addresses instead of requiring proper [ipaddress] forms.
+ */
+ if (*host != def_host
+ && !valid_utf8_hostname(util_utf8_enable, *host, DONT_GRIPE)
+ && !valid_hostaddr(*host, DONT_GRIPE))
+ return ("valid hostname or network address required");
+ if (*port != def_service && ISDIGIT(**port) && !alldig(*port))
+ return ("garbage after numerical service");
+ return (0);
+}
+
+#ifdef TEST
+
+#include <vstream.h>
+#include <vstring.h>
+#include <vstring_vstream.h>
+
+#define STR(x) vstring_str(x)
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *in_buf = vstring_alloc(10);
+ VSTRING *parse_buf = vstring_alloc(10);
+ char *host;
+ char *port;
+ const char *err;
+
+ while (vstring_fgets_nonl(in_buf, VSTREAM_IN)) {
+ vstream_printf(">> %s\n", STR(in_buf));
+ vstream_fflush(VSTREAM_OUT);
+ if (*STR(in_buf) == '#')
+ continue;
+ vstring_strcpy(parse_buf, STR(in_buf));
+ if ((err = host_port(STR(parse_buf), &host, (char *) 0, &port, "default-service")) != 0) {
+ msg_warn("%s in %s", err, STR(in_buf));
+ } else {
+ vstream_printf("host %s port %s\n", host, port);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ }
+ vstring_free(in_buf);
+ vstring_free(parse_buf);
+ return (0);
+}
+
+#endif
diff --git a/src/util/host_port.h b/src/util/host_port.h
new file mode 100644
index 0000000..f2fecbb
--- /dev/null
+++ b/src/util/host_port.h
@@ -0,0 +1,35 @@
+#ifndef _HOST_PORT_H_INCLUDED_
+#define _HOST_PORT_H_INCLUDED_
+
+/*++
+/* NAME
+/* host_port 3h
+/* SUMMARY
+/* split string into host and port, destroy string
+/* SYNOPSIS
+/* #include <host_port.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern const char *WARN_UNUSED_RESULT host_port(char *, char **, char *,
+ char **, char *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/host_port.in b/src/util/host_port.in
new file mode 100644
index 0000000..1608222
--- /dev/null
+++ b/src/util/host_port.in
@@ -0,0 +1,16 @@
+hhh:ppp
+hhh:
+hhh
+[hhh]:ppp
+[hhh]:
+[hhh]
+#[hhh:ppp]
+#[hhh:]
+hhh:1pp
+[hh.]
+hh.
+999
+[::1]
+[ipv6:::1]
+[ipv6:127.0.0.1]
+[ipv6:example.com]
diff --git a/src/util/host_port.ref b/src/util/host_port.ref
new file mode 100644
index 0000000..1d79745
--- /dev/null
+++ b/src/util/host_port.ref
@@ -0,0 +1,30 @@
+>> hhh:ppp
+host hhh port ppp
+>> hhh:
+host hhh port default-service
+>> hhh
+host hhh port default-service
+>> [hhh]:ppp
+host hhh port ppp
+>> [hhh]:
+host hhh port default-service
+>> [hhh]
+host hhh port default-service
+>> #[hhh:ppp]
+>> #[hhh:]
+>> hhh:1pp
+unknown: warning: garbage after numerical service in hhh:1pp
+>> [hh.]
+unknown: warning: valid hostname or network address required in [hh.]
+>> hh.
+unknown: warning: valid hostname or network address required in hh.
+>> 999
+unknown: warning: valid hostname or network address required in 999
+>> [::1]
+host ::1 port default-service
+>> [ipv6:::1]
+host ::1 port default-service
+>> [ipv6:127.0.0.1]
+unknown: warning: malformed IPv6 address in [ipv6:127.0.0.1]
+>> [ipv6:example.com]
+unknown: warning: malformed IPv6 address in [ipv6:example.com]
diff --git a/src/util/htable.c b/src/util/htable.c
new file mode 100644
index 0000000..5d659aa
--- /dev/null
+++ b/src/util/htable.c
@@ -0,0 +1,439 @@
+/*++
+/* NAME
+/* htable 3
+/* SUMMARY
+/* hash table manager
+/* SYNOPSIS
+/* #include <htable.h>
+/*
+/* typedef struct {
+/* .in +4
+/* char *key;
+/* void *value;
+/* /* private fields... */
+/* .in -4
+/* } HTABLE_INFO;
+/*
+/* HTABLE *htable_create(size)
+/* int size;
+/*
+/* HTABLE_INFO *htable_enter(table, key, value)
+/* HTABLE *table;
+/* const char *key;
+/* void *value;
+/*
+/* char *htable_find(table, key)
+/* HTABLE *table;
+/* const char *key;
+/*
+/* HTABLE_INFO *htable_locate(table, key)
+/* HTABLE *table;
+/* const char *key;
+/*
+/* void htable_delete(table, key, free_fn)
+/* HTABLE *table;
+/* const char *key;
+/* void (*free_fn)(void *);
+/*
+/* void htable_free(table, free_fn)
+/* HTABLE *table;
+/* void (*free_fn)(void *);
+/*
+/* void htable_walk(table, action, ptr)
+/* HTABLE *table;
+/* void (*action)(HTABLE_INFO *, void *ptr);
+/* void *ptr;
+/*
+/* HTABLE_INFO **htable_list(table)
+/* HTABLE *table;
+/*
+/* HTABLE_INFO *htable_sequence(table, how)
+/* HTABLE *table;
+/* int how;
+/* DESCRIPTION
+/* This module maintains one or more hash tables. Each table entry
+/* consists of a unique string-valued lookup key and a generic
+/* character-pointer value.
+/* The tables are automatically resized when they fill up. When the
+/* values to be remembered are not character pointers, proper casts
+/* should be used or the code will not be portable.
+/*
+/* htable_create() creates a table of the specified size and returns a
+/* pointer to the result. The lookup keys are saved with mystrdup().
+/* htable_enter() stores a (key, value) pair into the specified table
+/* and returns a pointer to the resulting entry. The code does not
+/* check if an entry with that key already exists: use htable_locate()
+/* for updating an existing entry.
+/*
+/* htable_find() returns the value that was stored under the given key,
+/* or a null pointer if it was not found. In order to distinguish
+/* a null value from a non-existent value, use htable_locate().
+/*
+/* htable_locate() returns a pointer to the entry that was stored
+/* for the given key, or a null pointer if it was not found.
+/*
+/* htable_delete() removes one entry that was stored under the given key.
+/* If the free_fn argument is not a null pointer, the corresponding
+/* function is called with as argument the non-zero value stored under
+/* the key.
+/*
+/* htable_free() destroys a hash table, including contents. If the free_fn
+/* argument is not a null pointer, the corresponding function is called
+/* for each table entry, with as argument the non-zero value stored
+/* with the entry.
+/*
+/* htable_walk() invokes the action function for each table entry, with
+/* a pointer to the entry as its argument. The ptr argument is passed
+/* on to the action function.
+/*
+/* htable_list() returns a null-terminated list of pointers to
+/* all elements in the named table. The list should be passed to
+/* myfree().
+/*
+/* htable_sequence() returns the first or next element depending
+/* on the value of the "how" argument. Specify HTABLE_SEQ_FIRST
+/* to start a new sequence, HTABLE_SEQ_NEXT to continue, and
+/* HTABLE_SEQ_STOP to terminate a sequence early. The caller
+/* must not delete an element before it is visited.
+/* RESTRICTIONS
+/* A callback function should not modify the hash table that is
+/* specified to its caller.
+/* DIAGNOSTICS
+/* The following conditions are reported and cause the program to
+/* terminate immediately: memory allocation failure; an attempt
+/* to delete a non-existent entry.
+/* SEE ALSO
+/* mymalloc(3) memory management wrapper
+/* hash_fnv(3) Fowler/Noll/Vo hash function
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* C library */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Local stuff */
+
+#include "mymalloc.h"
+#include "msg.h"
+#include "htable.h"
+
+/* htable_hash - hash a string */
+
+#ifndef NO_HASH_FNV
+#include "hash_fnv.h"
+
+#define htable_hash(s, size) (hash_fnvz(s) % (size))
+
+#else
+
+static size_t htable_hash(const char *s, size_t size)
+{
+ size_t h = 0;
+ size_t g;
+
+ /*
+ * From the "Dragon" book by Aho, Sethi and Ullman.
+ */
+
+ while (*s) {
+ h = (h << 4U) + *(unsigned const char *) s++;
+ if ((g = (h & 0xf0000000)) != 0) {
+ h ^= (g >> 24U);
+ h ^= g;
+ }
+ }
+ return (h % size);
+}
+
+#endif
+
+/* htable_link - insert element into table */
+
+#define htable_link(table, element) { \
+ HTABLE_INFO **_h = table->data + htable_hash(element->key, table->size);\
+ element->prev = 0; \
+ if ((element->next = *_h) != 0) \
+ (*_h)->prev = element; \
+ *_h = element; \
+ table->used++; \
+}
+
+/* htable_size - allocate and initialize hash table */
+
+static void htable_size(HTABLE *table, size_t size)
+{
+ HTABLE_INFO **h;
+
+ size |= 1;
+
+ table->data = h = (HTABLE_INFO **) mymalloc(size * sizeof(HTABLE_INFO *));
+ table->size = size;
+ table->used = 0;
+
+ while (size-- > 0)
+ *h++ = 0;
+}
+
+/* htable_create - create initial hash table */
+
+HTABLE *htable_create(ssize_t size)
+{
+ HTABLE *table;
+
+ table = (HTABLE *) mymalloc(sizeof(HTABLE));
+ htable_size(table, size < 13 ? 13 : size);
+ table->seq_bucket = table->seq_element = 0;
+ return (table);
+}
+
+/* htable_grow - extend existing table */
+
+static void htable_grow(HTABLE *table)
+{
+ HTABLE_INFO *ht;
+ HTABLE_INFO *next;
+ size_t old_size = table->size;
+ HTABLE_INFO **h = table->data;
+ HTABLE_INFO **old_entries = h;
+
+ htable_size(table, 2 * old_size);
+
+ while (old_size-- > 0) {
+ for (ht = *h++; ht; ht = next) {
+ next = ht->next;
+ htable_link(table, ht);
+ }
+ }
+ myfree((void *) old_entries);
+}
+
+/* htable_enter - enter (key, value) pair */
+
+HTABLE_INFO *htable_enter(HTABLE *table, const char *key, void *value)
+{
+ HTABLE_INFO *ht;
+
+ if (table->used >= table->size)
+ htable_grow(table);
+ ht = (HTABLE_INFO *) mymalloc(sizeof(HTABLE_INFO));
+ ht->key = mystrdup(key);
+ ht->value = value;
+ htable_link(table, ht);
+ return (ht);
+}
+
+/* htable_find - lookup value */
+
+void *htable_find(HTABLE *table, const char *key)
+{
+ HTABLE_INFO *ht;
+
+#define STREQ(x,y) (x == y || (x[0] == y[0] && strcmp(x,y) == 0))
+
+ if (table)
+ for (ht = table->data[htable_hash(key, table->size)]; ht; ht = ht->next)
+ if (STREQ(key, ht->key))
+ return (ht->value);
+ return (0);
+}
+
+/* htable_locate - lookup entry */
+
+HTABLE_INFO *htable_locate(HTABLE *table, const char *key)
+{
+ HTABLE_INFO *ht;
+
+#define STREQ(x,y) (x == y || (x[0] == y[0] && strcmp(x,y) == 0))
+
+ if (table)
+ for (ht = table->data[htable_hash(key, table->size)]; ht; ht = ht->next)
+ if (STREQ(key, ht->key))
+ return (ht);
+ return (0);
+}
+
+/* htable_delete - delete one entry */
+
+void htable_delete(HTABLE *table, const char *key, void (*free_fn) (void *))
+{
+ if (table) {
+ HTABLE_INFO *ht;
+ HTABLE_INFO **h = table->data + htable_hash(key, table->size);
+
+#define STREQ(x,y) (x == y || (x[0] == y[0] && strcmp(x,y) == 0))
+
+ for (ht = *h; ht; ht = ht->next) {
+ if (STREQ(key, ht->key)) {
+ if (ht->next)
+ ht->next->prev = ht->prev;
+ if (ht->prev)
+ ht->prev->next = ht->next;
+ else
+ *h = ht->next;
+ table->used--;
+ myfree(ht->key);
+ if (free_fn && ht->value)
+ (*free_fn) (ht->value);
+ myfree((void *) ht);
+ return;
+ }
+ }
+ msg_panic("htable_delete: unknown_key: \"%s\"", key);
+ }
+}
+
+/* htable_free - destroy hash table */
+
+void htable_free(HTABLE *table, void (*free_fn) (void *))
+{
+ if (table) {
+ ssize_t i = table->size;
+ HTABLE_INFO *ht;
+ HTABLE_INFO *next;
+ HTABLE_INFO **h = table->data;
+
+ while (i-- > 0) {
+ for (ht = *h++; ht; ht = next) {
+ next = ht->next;
+ myfree(ht->key);
+ if (free_fn && ht->value)
+ (*free_fn) (ht->value);
+ myfree((void *) ht);
+ }
+ }
+ myfree((void *) table->data);
+ table->data = 0;
+ if (table->seq_bucket)
+ myfree((void *) table->seq_bucket);
+ table->seq_bucket = 0;
+ myfree((void *) table);
+ }
+}
+
+/* htable_walk - iterate over hash table */
+
+void htable_walk(HTABLE *table, void (*action) (HTABLE_INFO *, void *),
+ void *ptr) {
+ if (table) {
+ ssize_t i = table->size;
+ HTABLE_INFO **h = table->data;
+ HTABLE_INFO *ht;
+
+ while (i-- > 0)
+ for (ht = *h++; ht; ht = ht->next)
+ (*action) (ht, ptr);
+ }
+}
+
+/* htable_list - list all table members */
+
+HTABLE_INFO **htable_list(HTABLE *table)
+{
+ HTABLE_INFO **list;
+ HTABLE_INFO *member;
+ ssize_t count = 0;
+ ssize_t i;
+
+ if (table != 0) {
+ list = (HTABLE_INFO **) mymalloc(sizeof(*list) * (table->used + 1));
+ for (i = 0; i < table->size; i++)
+ for (member = table->data[i]; member != 0; member = member->next)
+ list[count++] = member;
+ } else {
+ list = (HTABLE_INFO **) mymalloc(sizeof(*list));
+ }
+ list[count] = 0;
+ return (list);
+}
+
+/* htable_sequence - dict(3) compatibility iterator */
+
+HTABLE_INFO *htable_sequence(HTABLE *table, int how)
+{
+ if (table == 0)
+ return (0);
+
+ switch (how) {
+ case HTABLE_SEQ_FIRST: /* start new sequence */
+ if (table->seq_bucket)
+ myfree((void *) table->seq_bucket);
+ table->seq_bucket = htable_list(table);
+ table->seq_element = table->seq_bucket;
+ return (*(table->seq_element)++);
+ case HTABLE_SEQ_NEXT: /* next element */
+ if (table->seq_element && *table->seq_element)
+ return (*(table->seq_element)++);
+ /* FALLTHROUGH */
+ default: /* terminate sequence */
+ if (table->seq_bucket) {
+ myfree((void *) table->seq_bucket);
+ table->seq_bucket = table->seq_element = 0;
+ }
+ return (0);
+ }
+}
+
+#ifdef TEST
+#include <vstring_vstream.h>
+#include <myrand.h>
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *buf = vstring_alloc(10);
+ ssize_t count = 0;
+ HTABLE *hash;
+ HTABLE_INFO **ht_info;
+ HTABLE_INFO **ht;
+ HTABLE_INFO *info;
+ ssize_t i;
+ ssize_t r;
+ int op;
+
+ /*
+ * Load a large number of strings and delete them in a random order.
+ */
+ hash = htable_create(10);
+ while (vstring_get(buf, VSTREAM_IN) != VSTREAM_EOF)
+ htable_enter(hash, vstring_str(buf), CAST_INT_TO_VOID_PTR(count++));
+ if (count != hash->used)
+ msg_panic("%ld entries stored, but %lu entries exist",
+ (long) count, (unsigned long) hash->used);
+ for (i = 0, op = HTABLE_SEQ_FIRST; htable_sequence(hash, op) != 0;
+ i++, op = HTABLE_SEQ_NEXT)
+ /* void */ ;
+ if (i != hash->used)
+ msg_panic("%ld entries found, but %lu entries exist",
+ (long) i, (unsigned long) hash->used);
+ ht_info = htable_list(hash);
+ for (i = 0; i < hash->used; i++) {
+ r = myrand() % hash->used;
+ info = ht_info[i];
+ ht_info[i] = ht_info[r];
+ ht_info[r] = info;
+ }
+ for (ht = ht_info; *ht; ht++)
+ htable_delete(hash, ht[0]->key, (void (*) (void *)) 0);
+ if (hash->used > 0)
+ msg_panic("%ld entries not deleted", (long) hash->used);
+ myfree((void *) ht_info);
+ htable_free(hash, (void (*) (void *)) 0);
+ vstring_free(buf);
+ return (0);
+}
+
+#endif
diff --git a/src/util/htable.h b/src/util/htable.h
new file mode 100644
index 0000000..bba43e8
--- /dev/null
+++ b/src/util/htable.h
@@ -0,0 +1,70 @@
+#ifndef _HTABLE_H_INCLUDED_
+#define _HTABLE_H_INCLUDED_
+
+/*++
+/* NAME
+/* htable 3h
+/* SUMMARY
+/* hash table manager
+/* SYNOPSIS
+/* #include <htable.h>
+/* DESCRIPTION
+/* .nf
+
+ /* Structure of one hash table entry. */
+
+typedef struct HTABLE_INFO {
+ char *key; /* lookup key */
+ void *value; /* associated value */
+ struct HTABLE_INFO *next; /* colliding entry */
+ struct HTABLE_INFO *prev; /* colliding entry */
+} HTABLE_INFO;
+
+ /* Structure of one hash table. */
+
+typedef struct HTABLE {
+ ssize_t size; /* length of entries array */
+ ssize_t used; /* number of entries in table */
+ HTABLE_INFO **data; /* entries array, auto-resized */
+ HTABLE_INFO **seq_bucket; /* current sequence hash bucket */
+ HTABLE_INFO **seq_element; /* current sequence element */
+} HTABLE;
+
+extern HTABLE *htable_create(ssize_t);
+extern HTABLE_INFO *htable_enter(HTABLE *, const char *, void *);
+extern HTABLE_INFO *htable_locate(HTABLE *, const char *);
+extern void *htable_find(HTABLE *, const char *);
+extern void htable_delete(HTABLE *, const char *, void (*) (void *));
+extern void htable_free(HTABLE *, void (*) (void *));
+extern void htable_walk(HTABLE *, void (*) (HTABLE_INFO *, void *), void *);
+extern HTABLE_INFO **htable_list(HTABLE *);
+extern HTABLE_INFO *htable_sequence(HTABLE *, int);
+
+#define HTABLE_SEQ_FIRST 0
+#define HTABLE_SEQ_NEXT 1
+#define HTABLE_SEQ_STOP (-1)
+
+ /*
+ * Correct only when casting (char *) to (void *).
+ */
+#define HTABLE_ACTION_FN_CAST(f) ((void *)(HTABLE_INFO *, void *)) (f)
+#define HTABLE_FREE_FN_CAST(f) ((void *)(void *)) (f)
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/* CREATION DATE
+/* Fri Feb 14 13:43:19 EST 1997
+/* LAST MODIFICATION
+/* %E% %U%
+/* VERSION/RELEASE
+/* %I%
+/*--*/
+
+#endif
diff --git a/src/util/inet_addr_host.c b/src/util/inet_addr_host.c
new file mode 100644
index 0000000..d2c9d84
--- /dev/null
+++ b/src/util/inet_addr_host.c
@@ -0,0 +1,173 @@
+/*++
+/* NAME
+/* inet_addr_host 3
+/* SUMMARY
+/* determine all host internet interface addresses
+/* SYNOPSIS
+/* #include <inet_addr_host.h>
+/*
+/* int inet_addr_host(addr_list, hostname)
+/* INET_ADDR_LIST *addr_list;
+/* const char *hostname;
+/* DESCRIPTION
+/* inet_addr_host() determines all interface addresses of the
+/* named host. The host may be specified as a symbolic name,
+/* or as a numerical address. An empty host expands as the
+/* wild-card address. Address results are appended to
+/* the specified address list. The result value is the number
+/* of addresses appended to the list.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory.
+/* BUGS
+/* This code uses the name service, so it talks to the network,
+/* and that may not be desirable.
+/* SEE ALSO
+/* inet_addr_list(3) address list management
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <inet_addr_list.h>
+#include <inet_addr_host.h>
+#include <myaddrinfo.h>
+#include <sock_addr.h>
+#include <inet_proto.h>
+#include <msg.h>
+
+/* inet_addr_host - look up address list for host */
+
+int inet_addr_host(INET_ADDR_LIST *addr_list, const char *hostname)
+{
+ const char *myname = "inet_addr_host";
+ int sock;
+ struct addrinfo *res0;
+ struct addrinfo *res;
+ int aierr;
+ ssize_t hostnamelen;
+ const char *hname;
+ const char *serv;
+ int initial_count = addr_list->used;
+ const INET_PROTO_INFO *proto_info;
+
+ /*
+ * The use of square brackets around an IPv6 addresses is required, even
+ * though we don't enforce it as it'd make the code unnecessarily
+ * complicated.
+ *
+ * XXX AIX 5.1 getaddrinfo() does not allow "0" as service, regardless of
+ * whether or not a host is specified.
+ */
+ if (*hostname == 0) {
+ hname = 0;
+ serv = "1";
+ } else if (*hostname == '['
+ && hostname[(hostnamelen = strlen(hostname)) - 1] == ']') {
+ hname = mystrndup(hostname + 1, hostnamelen - 2);
+ serv = 0;
+ } else {
+ hname = hostname;
+ serv = 0;
+ }
+
+ proto_info = inet_proto_info();
+ if ((aierr = hostname_to_sockaddr(hname, serv, SOCK_STREAM, &res0)) == 0) {
+ for (res = res0; res; res = res->ai_next) {
+
+ /*
+ * Safety net.
+ */
+ if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) {
+ msg_info("%s: skipping address family %d for host \"%s\"",
+ myname, res->ai_family, hostname);
+ continue;
+ }
+
+ /*
+ * On Linux systems it is not unusual for user-land to be out of
+ * sync with kernel-land. When this is the case we try to be
+ * helpful and filter out address families that the library
+ * claims to understand but that are not supported by the kernel.
+ */
+ if ((sock = socket(res->ai_family, SOCK_STREAM, 0)) < 0) {
+ msg_warn("%s: skipping address family %d: %m",
+ myname, res->ai_family);
+ continue;
+ }
+ if (close(sock))
+ msg_warn("%s: close socket: %m", myname);
+
+ inet_addr_list_append(addr_list, res->ai_addr);
+ }
+ freeaddrinfo(res0);
+ }
+ if (hname && hname != hostname)
+ myfree((void *) hname);
+
+ return (addr_list->used - initial_count);
+}
+
+#ifdef TEST
+
+#include <msg.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <sock_addr.h>
+
+int main(int argc, char **argv)
+{
+ INET_ADDR_LIST list;
+ struct sockaddr_storage *sa;
+ MAI_HOSTADDR_STR hostaddr;
+ INET_PROTO_INFO *proto_info;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ if (argc < 3)
+ msg_fatal("usage: %s protocols hostname...", argv[0]);
+
+ proto_info = inet_proto_init(argv[0], argv[1]);
+ argv += 1;
+
+ while (--argc && *++argv) {
+ inet_addr_list_init(&list);
+ if (inet_addr_host(&list, *argv) == 0)
+ msg_fatal("not found: %s", *argv);
+
+ for (sa = list.addrs; sa < list.addrs + list.used; sa++) {
+ SOCKADDR_TO_HOSTADDR(SOCK_ADDR_PTR(sa), SOCK_ADDR_LEN(sa),
+ &hostaddr, (MAI_SERVPORT_STR *) 0, 0);
+ vstream_printf("%s\t%s\n", *argv, hostaddr.buf);
+ }
+ vstream_fflush(VSTREAM_OUT);
+ inet_addr_list_free(&list);
+ }
+ return (0);
+}
+
+#endif
diff --git a/src/util/inet_addr_host.h b/src/util/inet_addr_host.h
new file mode 100644
index 0000000..39d150a
--- /dev/null
+++ b/src/util/inet_addr_host.h
@@ -0,0 +1,35 @@
+#ifndef INET_ADDR_HOST_H_INCLUDED_
+#define INET_ADDR_HOST_H_INCLUDED_
+
+/*++
+/* NAME
+/* inet_addr_host 3h
+/* SUMMARY
+/* determine all host internet interface addresses
+/* SYNOPSIS
+/* #include <inet_addr_host.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <inet_addr_list.h>
+
+ /*
+ * External interface.
+ */
+extern int inet_addr_host(INET_ADDR_LIST *, const char *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/inet_addr_list.c b/src/util/inet_addr_list.c
new file mode 100644
index 0000000..e579b17
--- /dev/null
+++ b/src/util/inet_addr_list.c
@@ -0,0 +1,186 @@
+/*++
+/* NAME
+/* inet_addr_list 3
+/* SUMMARY
+/* internet address list manager
+/* SYNOPSIS
+/* #include <inet_addr_list.h>
+/*
+/* void inet_addr_list_init(list)
+/* INET_ADDR_LIST *list;
+/*
+/* void inet_addr_list_append(list,addr)
+/* INET_ADDR_LIST *list;
+/* struct sockaddr *addr;
+/*
+/* void inet_addr_list_uniq(list)
+/* INET_ADDR_LIST *list;
+/*
+/* void inet_addr_list_free(list)
+/* INET_ADDR_LIST *list;
+/* DESCRIPTION
+/* This module maintains simple lists of internet addresses.
+/*
+/* inet_addr_list_init() initializes a user-provided structure
+/* so that it can be used by inet_addr_list_append() and by
+/* inet_addr_list_free().
+/*
+/* inet_addr_list_append() appends the specified address to
+/* the specified list, extending the list on the fly.
+/*
+/* inet_addr_list_uniq() sorts the specified address list and
+/* eliminates duplicates.
+/*
+/* inet_addr_list_free() reclaims memory used for the
+/* specified address list.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdlib.h>
+#include <netdb.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <myaddrinfo.h>
+#include <sock_addr.h>
+#include <inet_addr_list.h>
+
+/* inet_addr_list_init - initialize internet address list */
+
+void inet_addr_list_init(INET_ADDR_LIST *list)
+{
+ int init_size;
+
+ list->used = 0;
+ list->size = 0;
+ init_size = 2;
+ list->addrs = (struct sockaddr_storage *)
+ mymalloc(sizeof(*list->addrs) * init_size);
+ list->size = init_size;
+}
+
+/* inet_addr_list_append - append address to internet address list */
+
+void inet_addr_list_append(INET_ADDR_LIST *list,
+ struct sockaddr *addr)
+{
+ const char *myname = "inet_addr_list_append";
+ MAI_HOSTADDR_STR hostaddr;
+ int new_size;
+
+ if (msg_verbose > 1) {
+ SOCKADDR_TO_HOSTADDR(addr, SOCK_ADDR_LEN(addr),
+ &hostaddr, (MAI_SERVPORT_STR *) 0, 0);
+ msg_info("%s: %s", myname, hostaddr.buf);
+ }
+ if (list->used >= list->size) {
+ new_size = list->size * 2;
+ list->addrs = (struct sockaddr_storage *)
+ myrealloc((void *) list->addrs, sizeof(*list->addrs) * new_size);
+ list->size = new_size;
+ }
+ memcpy(list->addrs + list->used++, addr, SOCK_ADDR_LEN(addr));
+}
+
+/* inet_addr_list_comp - compare addresses */
+
+static int inet_addr_list_comp(const void *a, const void *b)
+{
+
+ /*
+ * In case (struct *) != (void *).
+ */
+ return (sock_addr_cmp_addr(SOCK_ADDR_PTR(a), SOCK_ADDR_PTR(b)));
+}
+
+/* inet_addr_list_uniq - weed out duplicates */
+
+void inet_addr_list_uniq(INET_ADDR_LIST *list)
+{
+ int n;
+ int m;
+
+ /*
+ * Put the identical members right next to each other.
+ */
+ qsort((void *) list->addrs, list->used,
+ sizeof(list->addrs[0]), inet_addr_list_comp);
+
+ /*
+ * Nuke the duplicates. Postcondition after while loop: m is the largest
+ * index for which list->addrs[n] == list->addrs[m].
+ */
+ for (m = n = 0; m < list->used; m++, n++) {
+ if (m != n)
+ list->addrs[n] = list->addrs[m];
+ while (m + 1 < list->used
+ && inet_addr_list_comp((void *) &(list->addrs[n]),
+ (void *) &(list->addrs[m + 1])) == 0)
+ m += 1;
+ }
+ list->used = n;
+}
+
+/* inet_addr_list_free - destroy internet address list */
+
+void inet_addr_list_free(INET_ADDR_LIST *list)
+{
+ myfree((void *) list->addrs);
+}
+
+#ifdef TEST
+#include <inet_proto.h>
+
+ /*
+ * Duplicate elimination needs to be tested.
+ */
+#include <inet_addr_host.h>
+
+static void inet_addr_list_print(INET_ADDR_LIST *list)
+{
+ MAI_HOSTADDR_STR hostaddr;
+ struct sockaddr_storage *sa;
+
+ for (sa = list->addrs; sa < list->addrs + list->used; sa++) {
+ SOCKADDR_TO_HOSTADDR(SOCK_ADDR_PTR(sa), SOCK_ADDR_LEN(sa),
+ &hostaddr, (MAI_SERVPORT_STR *) 0, 0);
+ msg_info("%s", hostaddr.buf);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ INET_ADDR_LIST list;
+ INET_PROTO_INFO *proto_info;
+
+ proto_info = inet_proto_init(argv[0], INET_PROTO_NAME_ALL);
+ inet_addr_list_init(&list);
+ while (--argc && *++argv)
+ if (inet_addr_host(&list, *argv) == 0)
+ msg_fatal("host not found: %s", *argv);
+ msg_info("list before sort/uniq");
+ inet_addr_list_print(&list);
+ inet_addr_list_uniq(&list);
+ msg_info("list after sort/uniq");
+ inet_addr_list_print(&list);
+ inet_addr_list_free(&list);
+ return (0);
+}
+
+#endif
diff --git a/src/util/inet_addr_list.h b/src/util/inet_addr_list.h
new file mode 100644
index 0000000..8a109c1
--- /dev/null
+++ b/src/util/inet_addr_list.h
@@ -0,0 +1,44 @@
+#ifndef _INET_ADDR_LIST_H_INCLUDED_
+#define _INET_ADDR_LIST_H_INCLUDED_
+
+/*++
+/* NAME
+/* inet_addr_list 3h
+/* SUMMARY
+/* internet address list manager
+/* SYNOPSIS
+/* #include <inet_addr_list.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <myaddrinfo.h> /* generic name/addr API */
+
+ /*
+ * External interface.
+ */
+typedef struct INET_ADDR_LIST {
+ int used; /* nr of elements in use */
+ int size; /* actual list size */
+ struct sockaddr_storage *addrs; /* payload */
+} INET_ADDR_LIST;
+
+extern void inet_addr_list_init(INET_ADDR_LIST *);
+extern void inet_addr_list_free(INET_ADDR_LIST *);
+extern void inet_addr_list_uniq(INET_ADDR_LIST *);
+extern void inet_addr_list_append(INET_ADDR_LIST *, struct sockaddr *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/inet_addr_list.in b/src/util/inet_addr_list.in
new file mode 100644
index 0000000..5c72a1b
--- /dev/null
+++ b/src/util/inet_addr_list.in
@@ -0,0 +1,9 @@
+168.100.3.2
+168.100.3.2
+168.100.3.1
+168.100.3.3
+168.100.3.3
+168.100.3.3
+168.100.3.4
+168.100.3.1
+168.100.3.4
diff --git a/src/util/inet_addr_list.ref b/src/util/inet_addr_list.ref
new file mode 100644
index 0000000..9fbf48e
--- /dev/null
+++ b/src/util/inet_addr_list.ref
@@ -0,0 +1,15 @@
+unknown: list before sort/uniq
+unknown: 168.100.3.2
+unknown: 168.100.3.2
+unknown: 168.100.3.1
+unknown: 168.100.3.3
+unknown: 168.100.3.3
+unknown: 168.100.3.3
+unknown: 168.100.3.4
+unknown: 168.100.3.1
+unknown: 168.100.3.4
+unknown: list after sort/uniq
+unknown: 168.100.3.1
+unknown: 168.100.3.2
+unknown: 168.100.3.3
+unknown: 168.100.3.4
diff --git a/src/util/inet_addr_local.c b/src/util/inet_addr_local.c
new file mode 100644
index 0000000..e48803a
--- /dev/null
+++ b/src/util/inet_addr_local.c
@@ -0,0 +1,621 @@
+/*++
+/* NAME
+/* inet_addr_local 3
+/* SUMMARY
+/* determine if IP address is local
+/* SYNOPSIS
+/* #include <inet_addr_local.h>
+/*
+/* int inet_addr_local(addr_list, mask_list, addr_family_list)
+/* INET_ADDR_LIST *addr_list;
+/* INET_ADDR_LIST *mask_list;
+/* unsigned *addr_family;
+/* DESCRIPTION
+/* inet_addr_local() determines all active IP interface addresses
+/* of the local system. Any address found is appended to the
+/* specified address list. The result value is the number of
+/* active interfaces found.
+/*
+/* The mask_list is either a null pointer, or it is a list that
+/* receives the netmasks of the interface addresses that were found.
+/*
+/* The addr_family_list specifies one or more of AF_INET or AF_INET6.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory.
+/* SEE ALSO
+/* inet_addr_list(3) address list management
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Dean C. Strik
+/* Department ICT
+/* Eindhoven University of Technology
+/* P.O. Box 513
+/* 5600 MB Eindhoven, Netherlands
+/* E-mail: <dean@ipnet6.org>
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#ifdef USE_SYS_SOCKIO_H
+#include <sys/sockio.h>
+#endif
+#include <errno.h>
+#include <string.h>
+#ifdef HAS_IPV6 /* Linux only? */
+#include <netdb.h>
+#include <stdio.h>
+#endif
+#ifdef HAVE_GETIFADDRS
+#include <ifaddrs.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <inet_addr_list.h>
+#include <inet_addr_local.h>
+#include <myaddrinfo.h>
+#include <sock_addr.h>
+#include <mask_addr.h>
+#include <hex_code.h>
+
+ /*
+ * Postfix needs its own interface address information to determine whether
+ * or not it is an MX host for some destination; without this information,
+ * mail would loop between MX hosts. Postfix also needs its interface
+ * addresses to figure out whether or not it is final destination for
+ * addresses of the form username@[ipaddress].
+ *
+ * Postfix needs its own interface netmask information when no explicit
+ * mynetworks setting is given in main.cf, and "mynetworks_style = subnet".
+ * The mynetworks parameter controls, among others, what mail clients are
+ * allowed to relay mail through Postfix.
+ *
+ * Different systems have different ways to find out this information. We will
+ * therefore use OS dependent methods. An overview:
+ *
+ * - Use getifaddrs() when available. This supports both IPv4/IPv6 addresses.
+ * The implementation however is not present in all major operating systems.
+ *
+ * - Use SIOCGLIFCONF when available. This supports both IPv4/IPv6 addresses.
+ * With SIOCGLIFNETMASK we can obtain the netmask for either address family.
+ * Again, this is not present in all major operating systems.
+ *
+ * - On Linux, glibc's getifaddrs(3) has returned IPv4 information for some
+ * time, but IPv6 information was not returned until 2.3.3. With older Linux
+ * versions we get IPv4 interface information with SIOCGIFCONF, and read
+ * IPv6 address/prefix information from a file in the /proc filesystem.
+ *
+ * - On other systems we expect SIOCGIFCONF to return IPv6 addresses. Since
+ * SIOCGIFNETMASK does not work reliably for IPv6 addresses, we always set
+ * the prefix length to /128 (host), and expect the user to configure a more
+ * appropriate mynetworks setting if needed.
+ *
+ * XXX: Each lookup method is implemented by its own function, so we duplicate
+ * some code. In this case, I think this is better than really drowning in
+ * the #ifdefs...
+ *
+ * -- Dean Strik (dcs)
+ */
+
+#ifndef HAVE_GETIFADDRS
+
+/* ial_socket - make socket for ioctl() operations */
+
+static int ial_socket(int af)
+{
+ const char *myname = "inet_addr_local[socket]";
+ int sock;
+
+ /*
+ * The host may not be actually configured with IPv6. When IPv6 support
+ * is not actually in the kernel, don't consider failure to create an
+ * IPv6 socket as fatal. This could be tuned better though. For other
+ * families, the error is fatal.
+ *
+ * XXX Now that Postfix controls protocol support centrally with the
+ * inet_proto(3) module, this workaround should no longer be needed.
+ */
+ if ((sock = socket(af, SOCK_DGRAM, 0)) < 0) {
+#ifdef HAS_IPV6
+ if (af == AF_INET6) {
+ if (msg_verbose)
+ msg_warn("%s: socket: %m", myname);
+ return (-1);
+ }
+#endif
+ msg_fatal("%s: socket: %m", myname);
+ }
+ return (sock);
+}
+
+#endif
+
+#ifdef HAVE_GETIFADDRS
+
+/*
+ * The getifaddrs(3) function, introduced by BSD/OS, provides a
+ * platform-independent way of requesting interface addresses,
+ * including IPv6 addresses. The implementation however is not
+ * present in all major operating systems.
+ */
+
+/* ial_getifaddrs - determine IP addresses using getifaddrs(3) */
+
+static int ial_getifaddrs(INET_ADDR_LIST *addr_list,
+ INET_ADDR_LIST *mask_list,
+ int af)
+{
+ const char *myname = "inet_addr_local[getifaddrs]";
+ struct ifaddrs *ifap, *ifa;
+ struct sockaddr *sa, *sam;
+
+ if (getifaddrs(&ifap) < 0)
+ msg_fatal("%s: getifaddrs: %m", myname);
+
+ /*
+ * Get the address of each IP network interface. According to BIND we
+ * must include interfaces that are down because the machine may still
+ * receive packets for that address (yes, via some other interface).
+ * Having no way to verify this claim on every machine, I will give them
+ * the benefit of the doubt.
+ *
+ * FIX 200501: The IPv6 patch did not report NetBSD loopback interfaces;
+ * fixed by replacing IFF_RUNNING by IFF_UP.
+ *
+ * FIX 200501: The IPV6 patch did not skip wild-card interface addresses
+ * (tested on FreeBSD).
+ */
+ for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+ if (!(ifa->ifa_flags & IFF_UP) || ifa->ifa_addr == 0)
+ continue;
+ sa = ifa->ifa_addr;
+ if (af != AF_UNSPEC && sa->sa_family != af)
+ continue;
+ sam = ifa->ifa_netmask;
+ if (sam == 0) {
+ /* XXX In mynetworks, a null netmask would match everyone. */
+ msg_warn("ignoring interface with null netmask, address family %d",
+ sa->sa_family);
+ continue;
+ }
+ switch (sa->sa_family) {
+ case AF_INET:
+ if (SOCK_ADDR_IN_ADDR(sa).s_addr == INADDR_ANY)
+ continue;
+ break;
+#ifdef HAS_IPV6
+ case AF_INET6:
+ if (IN6_IS_ADDR_UNSPECIFIED(&SOCK_ADDR_IN6_ADDR(sa)))
+ continue;
+ break;
+#endif
+ default:
+ continue;
+ }
+
+ inet_addr_list_append(addr_list, sa);
+ if (mask_list != 0) {
+
+ /*
+ * Unfortunately, sa_len/sa_family may be broken in the netmask
+ * sockaddr structure. We must fix this manually to have correct
+ * addresses. --dcs
+ */
+#ifdef HAS_SA_LEN
+ sam->sa_len = sa->sa_family == AF_INET6 ?
+ sizeof(struct sockaddr_in6) :
+ sizeof(struct sockaddr_in);
+#endif
+ sam->sa_family = sa->sa_family;
+ inet_addr_list_append(mask_list, sam);
+ }
+ }
+ freeifaddrs(ifap);
+ return (0);
+}
+
+#elif defined(HAS_SIOCGLIF) /* HAVE_GETIFADDRS */
+
+/*
+ * The SIOCLIF* ioctls are the successors of SIOCGIF* on the Solaris
+ * and HP/UX operating systems. The data is stored in sockaddr_storage
+ * structure. Both IPv4 and IPv6 addresses are returned though these
+ * calls.
+ */
+#define NEXT_INTERFACE(lifr) (lifr + 1)
+#define LIFREQ_SIZE(lifr) sizeof(lifr[0])
+
+/* ial_siocglif - determine IP addresses using ioctl(SIOCGLIF*) */
+
+static int ial_siocglif(INET_ADDR_LIST *addr_list,
+ INET_ADDR_LIST *mask_list,
+ int af)
+{
+ const char *myname = "inet_addr_local[siocglif]";
+ struct lifconf lifc;
+ struct lifreq *lifr;
+ struct lifreq *lifr_mask;
+ struct lifreq *the_end;
+ struct sockaddr *sa;
+ int sock;
+ VSTRING *buf;
+
+ /*
+ * See also comments in ial_siocgif()
+ */
+ if (af != AF_INET && af != AF_INET6)
+ msg_fatal("%s: address family was %d, must be AF_INET (%d) or "
+ "AF_INET6 (%d)", myname, af, AF_INET, AF_INET6);
+ sock = ial_socket(af);
+ if (sock < 0)
+ return (0);
+ buf = vstring_alloc(1024);
+ for (;;) {
+ memset(&lifc, 0, sizeof(lifc));
+ lifc.lifc_family = AF_UNSPEC; /* XXX Why??? */
+ lifc.lifc_len = vstring_avail(buf);
+ lifc.lifc_buf = vstring_str(buf);
+ if (ioctl(sock, SIOCGLIFCONF, (char *) &lifc) < 0) {
+ if (errno != EINVAL)
+ msg_fatal("%s: ioctl SIOCGLIFCONF: %m", myname);
+ } else if (lifc.lifc_len < vstring_avail(buf) / 2)
+ break;
+ VSTRING_SPACE(buf, vstring_avail(buf) * 2);
+ }
+
+ the_end = (struct lifreq *) (lifc.lifc_buf + lifc.lifc_len);
+ for (lifr = lifc.lifc_req; lifr < the_end;) {
+ sa = (struct sockaddr *) &lifr->lifr_addr;
+ if (sa->sa_family != af) {
+ lifr = NEXT_INTERFACE(lifr);
+ continue;
+ }
+ if (af == AF_INET) {
+ if (SOCK_ADDR_IN_ADDR(sa).s_addr == INADDR_ANY) {
+ lifr = NEXT_INTERFACE(lifr);
+ continue;
+ }
+#ifdef HAS_IPV6
+ } else if (af == AF_INET6) {
+ if (IN6_IS_ADDR_UNSPECIFIED(&SOCK_ADDR_IN6_ADDR(sa))) {
+ lifr = NEXT_INTERFACE(lifr);
+ continue;
+ }
+ }
+#endif
+ inet_addr_list_append(addr_list, sa);
+ if (mask_list) {
+ lifr_mask = (struct lifreq *) mymalloc(sizeof(struct lifreq));
+ memcpy((void *) lifr_mask, (void *) lifr, sizeof(struct lifreq));
+ if (ioctl(sock, SIOCGLIFNETMASK, lifr_mask) < 0)
+ msg_fatal("%s: ioctl(SIOCGLIFNETMASK): %m", myname);
+ /* XXX: Check whether sa_len/family are honoured --dcs */
+ inet_addr_list_append(mask_list,
+ (struct sockaddr *) &lifr_mask->lifr_addr);
+ myfree((void *) lifr_mask);
+ }
+ lifr = NEXT_INTERFACE(lifr);
+ }
+ vstring_free(buf);
+ (void) close(sock);
+ return (0);
+}
+
+#else /* HAVE_SIOCGLIF */
+
+/*
+ * The classic SIOCGIF* ioctls. Modern BSD operating systems will
+ * also return IPv6 addresses through these structure. Note however
+ * that recent versions of these operating systems have getifaddrs.
+ */
+#if defined(_SIZEOF_ADDR_IFREQ)
+#define NEXT_INTERFACE(ifr) ((struct ifreq *) \
+ ((char *) ifr + _SIZEOF_ADDR_IFREQ(*ifr)))
+#define IFREQ_SIZE(ifr) _SIZEOF_ADDR_IFREQ(*ifr)
+#elif defined(HAS_SA_LEN)
+#define NEXT_INTERFACE(ifr) ((struct ifreq *) \
+ ((char *) ifr + sizeof(ifr->ifr_name) + ifr->ifr_addr.sa_len))
+#define IFREQ_SIZE(ifr) (sizeof(ifr->ifr_name) + ifr->ifr_addr.sa_len)
+#else
+#define NEXT_INTERFACE(ifr) (ifr + 1)
+#define IFREQ_SIZE(ifr) sizeof(ifr[0])
+#endif
+
+/* ial_siocgif - determine IP addresses using ioctl(SIOCGIF*) */
+
+static int ial_siocgif(INET_ADDR_LIST *addr_list,
+ INET_ADDR_LIST *mask_list,
+ int af)
+{
+ const char *myname = "inet_addr_local[siocgif]";
+ struct in_addr addr;
+ struct ifconf ifc;
+ struct ifreq *ifr;
+ struct ifreq *ifr_mask;
+ struct ifreq *the_end;
+ int sock;
+ VSTRING *buf;
+
+ /*
+ * Get the network interface list. XXX The socket API appears to have no
+ * function that returns the number of network interfaces, so we have to
+ * guess how much space is needed to store the result.
+ *
+ * On BSD-derived systems, ioctl SIOCGIFCONF returns as much information as
+ * possible, leaving it up to the application to repeat the request with
+ * a larger buffer if the result caused a tight fit.
+ *
+ * Other systems, such as Solaris 2.5, generate an EINVAL error when the
+ * buffer is too small for the entire result. Workaround: ignore EINVAL
+ * errors and repeat the request with a larger buffer. The downside is
+ * that the program can run out of memory due to a non-memory problem,
+ * making it more difficult than necessary to diagnose the real problem.
+ */
+ sock = ial_socket(af);
+ if (sock < 0)
+ return (0);
+ buf = vstring_alloc(1024);
+ for (;;) {
+ ifc.ifc_len = vstring_avail(buf);
+ ifc.ifc_buf = vstring_str(buf);
+ if (ioctl(sock, SIOCGIFCONF, (char *) &ifc) < 0) {
+ if (errno != EINVAL)
+ msg_fatal("%s: ioctl SIOCGIFCONF: %m", myname);
+ } else if (ifc.ifc_len < vstring_avail(buf) / 2)
+ break;
+ VSTRING_SPACE(buf, vstring_avail(buf) * 2);
+ }
+
+ the_end = (struct ifreq *) (ifc.ifc_buf + ifc.ifc_len);
+ for (ifr = ifc.ifc_req; ifr < the_end;) {
+ if (ifr->ifr_addr.sa_family != af) {
+ ifr = NEXT_INTERFACE(ifr);
+ continue;
+ }
+ if (af == AF_INET) {
+ addr = ((struct sockaddr_in *) & ifr->ifr_addr)->sin_addr;
+ if (addr.s_addr != INADDR_ANY) {
+ inet_addr_list_append(addr_list, &ifr->ifr_addr);
+ if (mask_list) {
+ ifr_mask = (struct ifreq *) mymalloc(IFREQ_SIZE(ifr));
+ memcpy((void *) ifr_mask, (void *) ifr, IFREQ_SIZE(ifr));
+ if (ioctl(sock, SIOCGIFNETMASK, ifr_mask) < 0)
+ msg_fatal("%s: ioctl SIOCGIFNETMASK: %m", myname);
+
+ /*
+ * Note that this SIOCGIFNETMASK has truly screwed up the
+ * contents of sa_len/sa_family. We must fix this
+ * manually to have correct addresses. --dcs
+ */
+ ifr_mask->ifr_addr.sa_family = af;
+#ifdef HAS_SA_LEN
+ ifr_mask->ifr_addr.sa_len = sizeof(struct sockaddr_in);
+#endif
+ inet_addr_list_append(mask_list, &ifr_mask->ifr_addr);
+ myfree((void *) ifr_mask);
+ }
+ }
+ }
+#ifdef HAS_IPV6
+ else if (af == AF_INET6) {
+ struct sockaddr *sa;
+
+ sa = SOCK_ADDR_PTR(&ifr->ifr_addr);
+ if (!(IN6_IS_ADDR_UNSPECIFIED(&SOCK_ADDR_IN6_ADDR(sa)))) {
+ inet_addr_list_append(addr_list, sa);
+ if (mask_list) {
+ /* XXX Assume /128 for everything */
+ struct sockaddr_in6 mask6;
+
+ mask6 = *SOCK_ADDR_IN6_PTR(sa);
+ memset((void *) &mask6.sin6_addr, ~0,
+ sizeof(mask6.sin6_addr));
+ inet_addr_list_append(mask_list, SOCK_ADDR_PTR(&mask6));
+ }
+ }
+ }
+#endif
+ ifr = NEXT_INTERFACE(ifr);
+ }
+ vstring_free(buf);
+ (void) close(sock);
+ return (0);
+}
+
+#endif /* HAVE_SIOCGLIF */
+
+#ifdef HAS_PROCNET_IFINET6
+
+/*
+ * Older Linux versions lack proper calls to retrieve IPv6 interface
+ * addresses. Instead, the addresses can be read from a file in the
+ * /proc tree. The most important issue with this approach however
+ * is that the /proc tree may not always be available, for example
+ * in a chrooted environment or in "hardened" (sic) installations.
+ */
+
+/* ial_procnet_ifinet6 - determine IPv6 addresses using /proc/net/if_inet6 */
+
+static int ial_procnet_ifinet6(INET_ADDR_LIST *addr_list,
+ INET_ADDR_LIST *mask_list)
+{
+ const char *myname = "inet_addr_local[procnet_ifinet6]";
+ FILE *fp;
+ char buf[BUFSIZ];
+ unsigned plen;
+ VSTRING *addrbuf;
+ struct sockaddr_in6 addr;
+ struct sockaddr_in6 mask;
+
+ /*
+ * Example: 00000000000000000000000000000001 01 80 10 80 lo
+ *
+ * Fields: address, interface index, prefix length, scope value
+ * (net/ipv6.h), interface flags (linux/rtnetlink.h), device name.
+ *
+ * FIX 200501 The IPv6 patch used fscanf(), which will hang on unexpected
+ * input. Use fgets() + sscanf() instead.
+ */
+ if ((fp = fopen(_PATH_PROCNET_IFINET6, "r")) != 0) {
+ addrbuf = vstring_alloc(MAI_V6ADDR_BYTES + 1);
+ memset((void *) &addr, 0, sizeof(addr));
+ addr.sin6_family = AF_INET6;
+#ifdef HAS_SA_LEN
+ addr.sin6_len = sizeof(addr);
+#endif
+ mask = addr;
+ while (fgets(buf, sizeof(buf), fp) != 0) {
+ /* 200501 hex_decode() is light-weight compared to getaddrinfo(). */
+ if (hex_decode(addrbuf, buf, MAI_V6ADDR_BYTES * 2) == 0
+ || sscanf(buf + MAI_V6ADDR_BYTES * 2, " %*x %x", &plen) != 1
+ || plen > MAI_V6ADDR_BITS) {
+ msg_warn("unexpected data in %s - skipping IPv6 configuration",
+ _PATH_PROCNET_IFINET6);
+ break;
+ }
+ /* vstring_str(addrbuf) has worst-case alignment. */
+ addr.sin6_addr = *(struct in6_addr *) vstring_str(addrbuf);
+ inet_addr_list_append(addr_list, SOCK_ADDR_PTR(&addr));
+
+ memset((void *) &mask.sin6_addr, ~0, sizeof(mask.sin6_addr));
+ mask_addr((unsigned char *) &mask.sin6_addr,
+ sizeof(mask.sin6_addr), plen);
+ inet_addr_list_append(mask_list, SOCK_ADDR_PTR(&mask));
+ }
+ vstring_free(addrbuf);
+ fclose(fp); /* FIX 200501 */
+ } else {
+ msg_warn("can't open %s (%m) - skipping IPv6 configuration",
+ _PATH_PROCNET_IFINET6);
+ }
+ return (0);
+}
+
+#endif /* HAS_PROCNET_IFINET6 */
+
+/* inet_addr_local - find all IP addresses for this host */
+
+int inet_addr_local(INET_ADDR_LIST *addr_list, INET_ADDR_LIST *mask_list,
+ unsigned *addr_family_list)
+{
+ const char *myname = "inet_addr_local";
+ int initial_count = addr_list->used;
+ unsigned family;
+ int count;
+
+ while ((family = *addr_family_list++) != 0) {
+
+ /*
+ * IP Version 4
+ */
+ if (family == AF_INET) {
+ count = addr_list->used;
+#if defined(HAVE_GETIFADDRS)
+ ial_getifaddrs(addr_list, mask_list, AF_INET);
+#elif defined (HAS_SIOCGLIF)
+ ial_siocglif(addr_list, mask_list, AF_INET);
+#else
+ ial_siocgif(addr_list, mask_list, AF_INET);
+#endif
+ if (msg_verbose)
+ msg_info("%s: configured %d IPv4 addresses",
+ myname, addr_list->used - count);
+ }
+
+ /*
+ * IP Version 6
+ */
+#ifdef HAS_IPV6
+ else if (family == AF_INET6) {
+ count = addr_list->used;
+#if defined(HAVE_GETIFADDRS)
+ ial_getifaddrs(addr_list, mask_list, AF_INET6);
+#elif defined(HAS_PROCNET_IFINET6)
+ ial_procnet_ifinet6(addr_list, mask_list);
+#elif defined(HAS_SIOCGLIF)
+ ial_siocglif(addr_list, mask_list, AF_INET6);
+#else
+ ial_siocgif(addr_list, mask_list, AF_INET6);
+#endif
+ if (msg_verbose)
+ msg_info("%s: configured %d IPv6 addresses", myname,
+ addr_list->used - count);
+ }
+#endif
+
+ /*
+ * Something's not right.
+ */
+ else
+ msg_panic("%s: unknown address family %d", myname, family);
+ }
+ return (addr_list->used - initial_count);
+}
+
+#ifdef TEST
+
+#include <string.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <inet_proto.h>
+
+int main(int unused_argc, char **argv)
+{
+ INET_ADDR_LIST addr_list;
+ INET_ADDR_LIST mask_list;
+ MAI_HOSTADDR_STR hostaddr;
+ MAI_HOSTADDR_STR hostmask;
+ struct sockaddr *sa;
+ int i;
+ INET_PROTO_INFO *proto_info;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ msg_verbose = 1;
+
+ proto_info = inet_proto_init(argv[0],
+ argv[1] ? argv[1] : INET_PROTO_NAME_ALL);
+ inet_addr_list_init(&addr_list);
+ inet_addr_list_init(&mask_list);
+ inet_addr_local(&addr_list, &mask_list, proto_info->ai_family_list);
+
+ if (addr_list.used == 0)
+ msg_fatal("cannot find any active network interfaces");
+
+ if (addr_list.used == 1)
+ msg_warn("found only one active network interface");
+
+ for (i = 0; i < addr_list.used; i++) {
+ sa = SOCK_ADDR_PTR(addr_list.addrs + i);
+ SOCKADDR_TO_HOSTADDR(SOCK_ADDR_PTR(sa), SOCK_ADDR_LEN(sa),
+ &hostaddr, (MAI_SERVPORT_STR *) 0, 0);
+ sa = SOCK_ADDR_PTR(mask_list.addrs + i);
+ SOCKADDR_TO_HOSTADDR(SOCK_ADDR_PTR(sa), SOCK_ADDR_LEN(sa),
+ &hostmask, (MAI_SERVPORT_STR *) 0, 0);
+ vstream_printf("%s/%s\n", hostaddr.buf, hostmask.buf);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ inet_addr_list_free(&addr_list);
+ inet_addr_list_free(&mask_list);
+ return (0);
+}
+
+#endif
diff --git a/src/util/inet_addr_local.h b/src/util/inet_addr_local.h
new file mode 100644
index 0000000..cb6b3f2
--- /dev/null
+++ b/src/util/inet_addr_local.h
@@ -0,0 +1,35 @@
+#ifndef _INET_ADDR_LOCAL_H_INCLUDED_
+#define _INET_ADDR_LOCAL_H_INCLUDED_
+
+/*++
+/* NAME
+/* inet_addr_local 3h
+/* SUMMARY
+/* determine if IP address is local
+/* SYNOPSIS
+/* #include <inet_addr_local.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <inet_addr_list.h>
+
+ /*
+ * External interface.
+ */
+extern int inet_addr_local(INET_ADDR_LIST *, INET_ADDR_LIST *, unsigned *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/inet_addr_sizes.c b/src/util/inet_addr_sizes.c
new file mode 100644
index 0000000..e788c22
--- /dev/null
+++ b/src/util/inet_addr_sizes.c
@@ -0,0 +1,77 @@
+/*++
+/* NAME
+/* inet_addr_sizes 3
+/* SUMMARY
+/* get network address size metrics
+/* SYNOPSIS
+/* #include <inet_addr_sizes.h>
+/*
+/* typedef struct {
+/* .in +4
+/* int af; /* network address family (binary) */
+/* char *ipproto_str; /* IP protocol version (string) */
+/* int addr_bitcount; /* bits per address */
+/* int addr_bytecount; /* bytes per address */
+/* int addr_strlen; /* address string length */
+/* int addr_bitcount_strlen;/* addr_bitcount string length */
+/* .in -4
+/* } INET_ADDR_SIZES;
+/*
+/* const INET_ADDR_SIZES *inet_addr_sizes(int family)
+/* DESCRIPTION
+/* inet_addr_sizes() returns address size metrics for the
+/* specified network address family, AF_INET or AF_INET6.
+/* DIAGNOSTICS
+/* inet_addr_sizes() returns a null pointer when the argument
+/* specifies an unexpected address family.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <string.h>
+
+ /*
+ * Utility library.
+ */
+#include <inet_addr_sizes.h>
+#include <msg.h>
+#include <myaddrinfo.h>
+
+ /*
+ * Stringify a numeric constant and use sizeof() to determine the resulting
+ * string length at compile time. Note that sizeof() includes a null
+ * terminator; the -1 corrects for that.
+ */
+#define _STRINGIFY(x) #x
+#define _STRLEN(x) (sizeof(_STRINGIFY(x)) - 1)
+
+static const INET_ADDR_SIZES table[] = {
+ {AF_INET, "IPv4", MAI_V4ADDR_BITS, MAI_V4ADDR_BYTES, INET_ADDRSTRLEN,
+ _STRLEN(MAI_V4ADDR_BITS)},
+#ifdef HAS_IPV6
+ {AF_INET6, "IPv6", MAI_V6ADDR_BITS, MAI_V6ADDR_BYTES, INET6_ADDRSTRLEN,
+ _STRLEN(MAI_V6ADDR_BITS)},
+#endif
+};
+
+/* inet_addr_sizes - get address size metrics for address family */
+
+const INET_ADDR_SIZES *inet_addr_sizes(int af)
+{
+ const INET_ADDR_SIZES *sp;
+
+ for (sp = table; /* see below */ ; sp++) {
+ if (sp >= table + sizeof(table) / sizeof(*table))
+ return (0);
+ if (sp->af == af)
+ return (sp);
+ }
+}
diff --git a/src/util/inet_addr_sizes.h b/src/util/inet_addr_sizes.h
new file mode 100644
index 0000000..c937c78
--- /dev/null
+++ b/src/util/inet_addr_sizes.h
@@ -0,0 +1,36 @@
+#ifndef _INET_ADDR_SIZES_H_INCLUDED_
+#define _INET_ADDR_SIZES_H_INCLUDED_
+
+/*++
+/* NAME
+/* inet_addr_sizes 3h
+/* SUMMARY
+/* get network address size metrics
+/* SYNOPSIS
+/* #include <inet_addr_sizes.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+typedef struct {
+ int af; /* network address family (binary) */
+ char *ipproto_str; /* IP protocol version (string) */
+ int addr_bitcount; /* bits per address */
+ int addr_bytecount; /* bytes per address */
+ int addr_strlen; /* address string length */
+ int addr_bitcount_strlen; /* addr_bitcount string length */
+} INET_ADDR_SIZES;
+
+extern const INET_ADDR_SIZES *inet_addr_sizes(int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/*--*/
+
+#endif
diff --git a/src/util/inet_connect.c b/src/util/inet_connect.c
new file mode 100644
index 0000000..dded7e4
--- /dev/null
+++ b/src/util/inet_connect.c
@@ -0,0 +1,190 @@
+/*++
+/* NAME
+/* inet_connect 3
+/* SUMMARY
+/* connect to TCP listener
+/* SYNOPSIS
+/* #include <connect.h>
+/*
+/* int inet_windowsize;
+/*
+/* int inet_connect(addr, block_mode, timeout)
+/* const char *addr;
+/* int block_mode;
+/* int timeout;
+/* DESCRIPTION
+/* inet_connect connects to a TCP listener at
+/* the specified address, and returns the resulting file descriptor.
+/*
+/* Specify an inet_windowsize value > 0 to override the TCP
+/* window size that the client advertises to the server.
+/*
+/* Arguments:
+/* .IP addr
+/* The destination to connect to. The format is host:port. If no
+/* host is specified, a port on the local host is assumed.
+/* Host and port information may be given in numerical form
+/* or as symbolical names.
+/* .IP block_mode
+/* Either NON_BLOCKING for a non-blocking socket, or BLOCKING for
+/* blocking mode.
+/* .IP timeout
+/* Bounds the number of seconds that the operation may take. Specify
+/* a value <= 0 to disable the time limit.
+/* DIAGNOSTICS
+/* The result is -1 when the connection could not be made.
+/* The nature of the error is available via the global \fIerrno\fR
+/* variable.
+/* Fatal errors: other system call failures.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System interfaces. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <netdb.h>
+
+/* Utility library. */
+
+#include "mymalloc.h"
+#include "msg.h"
+#include "iostuff.h"
+#include "host_port.h"
+#include "sane_connect.h"
+#include "connect.h"
+#include "timed_connect.h"
+#include "myaddrinfo.h"
+#include "sock_addr.h"
+#include "inet_proto.h"
+
+static int inet_connect_one(struct addrinfo *, int, int);
+
+/* inet_connect - connect to TCP listener */
+
+int inet_connect(const char *addr, int block_mode, int timeout)
+{
+ char *buf;
+ char *host;
+ char *port;
+ const char *parse_err;
+ struct addrinfo *res;
+ struct addrinfo *res0;
+ int aierr;
+ int sock;
+ MAI_HOSTADDR_STR hostaddr;
+ const INET_PROTO_INFO *proto_info;
+ int found;
+
+ /*
+ * Translate address information to internal form. No host defaults to
+ * the local host.
+ */
+ buf = mystrdup(addr);
+ if ((parse_err = host_port(buf, &host, "localhost", &port, (char *) 0)) != 0)
+ msg_fatal("%s: %s", addr, parse_err);
+ if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res0)) != 0)
+ msg_warn("host or service %s not found: %s",
+ addr, MAI_STRERROR(aierr));
+ myfree(buf);
+ if (aierr) {
+ errno = EADDRNOTAVAIL; /* for up-stream "%m" */
+ return (-1);
+ }
+
+ proto_info = inet_proto_info();
+ for (sock = -1, found = 0, res = res0; res != 0; res = res->ai_next) {
+
+ /*
+ * Safety net.
+ */
+ if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) {
+ msg_info("skipping address family %d for host %s",
+ res->ai_family, host);
+ continue;
+ }
+ found++;
+
+ /*
+ * In case of multiple addresses, show what address we're trying now.
+ */
+ if (msg_verbose) {
+ SOCKADDR_TO_HOSTADDR(res->ai_addr, res->ai_addrlen,
+ &hostaddr, (MAI_SERVPORT_STR *) 0, 0);
+ msg_info("trying... [%s]", hostaddr.buf);
+ }
+ if ((sock = inet_connect_one(res, block_mode, timeout)) < 0) {
+ if (msg_verbose)
+ msg_info("%m");
+ } else
+ break;
+ }
+ if (found == 0)
+ msg_fatal("host not found: %s", addr);
+ freeaddrinfo(res0);
+ return (sock);
+}
+
+/* inet_connect_one - try to connect to one address */
+
+static int inet_connect_one(struct addrinfo * res, int block_mode, int timeout)
+{
+ int sock;
+
+ /*
+ * Create a client socket.
+ */
+ sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (sock < 0)
+ return (-1);
+
+ /*
+ * Window scaling workaround.
+ */
+ if (inet_windowsize > 0)
+ set_inet_windowsize(sock, inet_windowsize);
+
+ /*
+ * Timed connect.
+ */
+ if (timeout > 0) {
+ non_blocking(sock, NON_BLOCKING);
+ if (timed_connect(sock, res->ai_addr, res->ai_addrlen, timeout) < 0) {
+ close(sock);
+ return (-1);
+ }
+ if (block_mode != NON_BLOCKING)
+ non_blocking(sock, block_mode);
+ return (sock);
+ }
+
+ /*
+ * Maybe block until connected.
+ */
+ else {
+ non_blocking(sock, block_mode);
+ if (sane_connect(sock, res->ai_addr, res->ai_addrlen) < 0
+ && errno != EINPROGRESS) {
+ close(sock);
+ return (-1);
+ }
+ return (sock);
+ }
+}
diff --git a/src/util/inet_listen.c b/src/util/inet_listen.c
new file mode 100644
index 0000000..31800cd
--- /dev/null
+++ b/src/util/inet_listen.c
@@ -0,0 +1,189 @@
+/*++
+/* NAME
+/* inet_listen 3
+/* SUMMARY
+/* start TCP listener
+/* SYNOPSIS
+/* #include <listen.h>
+/*
+/* int inet_windowsize;
+/*
+/* int inet_listen(addr, backlog, block_mode)
+/* const char *addr;
+/* int backlog;
+/* int block_mode;
+/*
+/* int inet_accept(fd)
+/* int fd;
+/* DESCRIPTION
+/* The \fBinet_listen\fR routine starts a TCP listener
+/* on the specified address, with the specified backlog, and returns
+/* the resulting file descriptor.
+/*
+/* inet_accept() accepts a connection and sanitizes error results.
+/*
+/* Specify an inet_windowsize value > 0 to override the TCP
+/* window size that the server advertises to the client.
+/*
+/* Arguments:
+/* .IP addr
+/* The communication endpoint to listen on. The syntax is "host:port".
+/* Host and port may be specified in symbolic form or numerically.
+/* A null host field means listen on all network interfaces.
+/* .IP backlog
+/* This argument is passed on to the \fIlisten(2)\fR routine.
+/* .IP block_mode
+/* Either NON_BLOCKING for a non-blocking socket, or BLOCKING for
+/* blocking mode.
+/* .IP fd
+/* File descriptor returned by inet_listen().
+/* DIAGNOSTICS
+/* Fatal errors: inet_listen() aborts upon any system call failure.
+/* inet_accept() leaves all error handling up to the caller.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System libraries. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#ifndef MAXHOSTNAMELEN
+#include <sys/param.h>
+#endif
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include "mymalloc.h"
+#include "msg.h"
+#include "host_port.h"
+#include "iostuff.h"
+#include "listen.h"
+#include "sane_accept.h"
+#include "myaddrinfo.h"
+#include "sock_addr.h"
+#include "inet_proto.h"
+
+/* inet_listen - create TCP listener */
+
+int inet_listen(const char *addr, int backlog, int block_mode)
+{
+ struct addrinfo *res;
+ struct addrinfo *res0;
+ int aierr;
+ int sock;
+ int on = 1;
+ char *buf;
+ char *host;
+ char *port;
+ const char *parse_err;
+ MAI_HOSTADDR_STR hostaddr;
+ MAI_SERVPORT_STR portnum;
+ const INET_PROTO_INFO *proto_info;
+
+ /*
+ * Translate address information to internal form.
+ */
+ buf = mystrdup(addr);
+ if ((parse_err = host_port(buf, &host, "", &port, (char *) 0)) != 0)
+ msg_fatal("%s: %s", addr, parse_err);
+ if (*host == 0)
+ host = 0;
+ if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res0)) != 0)
+ msg_fatal("%s: %s", addr, MAI_STRERROR(aierr));
+ myfree(buf);
+ /* No early returns or res0 leaks. */
+
+ proto_info = inet_proto_info();
+ for (res = res0; /* see below */ ; res = res->ai_next) {
+
+ /*
+ * No usable address found.
+ */
+ if (res == 0)
+ msg_fatal("%s: host found but no usable address", addr);
+
+ /*
+ * Safety net.
+ */
+ if (strchr((char *) proto_info->sa_family_list, res->ai_family) != 0)
+ break;
+
+ msg_info("skipping address family %d for %s", res->ai_family, addr);
+ }
+
+ /*
+ * Show what address we're trying.
+ */
+ if (msg_verbose) {
+ SOCKADDR_TO_HOSTADDR(res->ai_addr, res->ai_addrlen,
+ &hostaddr, &portnum, 0);
+ msg_info("trying... [%s]:%s", hostaddr.buf, portnum.buf);
+ }
+
+ /*
+ * Create a listener socket.
+ */
+ if ((sock = socket(res->ai_family, res->ai_socktype, 0)) < 0)
+ msg_fatal("socket: %m");
+#ifdef HAS_IPV6
+#if defined(IPV6_V6ONLY) && !defined(BROKEN_AI_PASSIVE_NULL_HOST)
+ if (res->ai_family == AF_INET6
+ && setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
+ (void *) &on, sizeof(on)) < 0)
+ msg_fatal("setsockopt(IPV6_V6ONLY): %m");
+#endif
+#endif
+ if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
+ (void *) &on, sizeof(on)) < 0)
+ msg_fatal("setsockopt(SO_REUSEADDR): %m");
+#if defined(SO_REUSEPORT_LB)
+ if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT_LB,
+ (void *) &on, sizeof(on)) < 0)
+ msg_fatal("setsockopt(SO_REUSEPORT_LB): %m");
+#elif defined(SO_REUSEPORT)
+ if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,
+ (void *) &on, sizeof(on)) < 0)
+ msg_fatal("setsockopt(SO_REUSEPORT): %m");
+#endif
+ if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) {
+ SOCKADDR_TO_HOSTADDR(res->ai_addr, res->ai_addrlen,
+ &hostaddr, &portnum, 0);
+ msg_fatal("bind %s port %s: %m", hostaddr.buf, portnum.buf);
+ }
+ freeaddrinfo(res0);
+ non_blocking(sock, block_mode);
+ if (inet_windowsize > 0)
+ set_inet_windowsize(sock, inet_windowsize);
+ if (listen(sock, backlog) < 0)
+ msg_fatal("listen: %m");
+ return (sock);
+}
+
+/* inet_accept - accept connection */
+
+int inet_accept(int fd)
+{
+ struct sockaddr_storage ss;
+ SOCKADDR_SIZE ss_len = sizeof(ss);
+
+ return (sane_accept(fd, (struct sockaddr *) &ss, &ss_len));
+}
diff --git a/src/util/inet_prefix_top.c b/src/util/inet_prefix_top.c
new file mode 100644
index 0000000..8d5af00
--- /dev/null
+++ b/src/util/inet_prefix_top.c
@@ -0,0 +1,172 @@
+/*++
+/* NAME
+/* inet_prefix_top 3
+/* SUMMARY
+/* convert net/mask to printable string
+/* SYNOPSIS
+/* #include <inet_prefix_top.h>
+/*
+/* char *inet_prefix_top(
+/* int family,
+/* const void *src,
+/* int prefix_len)
+/* DESCRIPTION
+/* inet_prefix_top() prints the network portion of the specified
+/* IPv4 or IPv6 address, null bits for the host portion, and
+/* the prefix length if it is shorter than the address.
+/* The result should be passed to myfree(). The code can
+/* handle addresses of any length, and bytes of any width.
+/*
+/* Arguments:
+/* .IP af
+/* The address family, as with inet_ntop().
+/* .IP src
+/* Pointer to storage for an IPv4 or IPv6 address, as with
+/* inet_ntop().
+/* .IP prefix_len
+/* The number of most-significant bits in \fBsrc\fR that should
+/* not be cleared.
+/* DIAGNOSTICS
+/* Panic: unexpected protocol family, bad prefix length. Fatal
+/* errors: address conversion error.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+ /*
+ * Utility library.
+ */
+#include <mask_addr.h>
+#include <msg.h>
+#include <inet_addr_sizes.h>
+#include <inet_prefix_top.h>
+#include <vstring.h>
+
+/* inet_prefix_top - printable net/mask pattern */
+
+char *inet_prefix_top(int af, const void *src, int prefix_len)
+{
+ const char myname[] = "inet_prefix_top";
+ union {
+ struct in_addr in_addr;
+ struct in6_addr in6_addr;
+ } u;
+ VSTRING *buf;
+ const INET_ADDR_SIZES *sp;
+
+ /*
+ * Sanity checks. XXX We use msg_fatal() because mail_conf_int() does not
+ * (yet) support non-negative integers.
+ */
+ if ((sp = inet_addr_sizes(af)) == 0)
+ msg_panic("%s: unexpected address family: %d", myname, af);
+ if (prefix_len > sp->addr_bitcount || prefix_len < 0)
+ msg_fatal("%s: bad %s address prefix length: %d",
+ myname, sp->ipproto_str, prefix_len);
+
+ /*
+ * Strip a copy of the input address. When allocating the result memory,
+ * add 1 for the string terminator from inet_ntop(), or 1 for the '/'
+ * before the prefix. We should not rely on vstring(3)'s safety byte.
+ */
+ memcpy((void *) &u, src, sp->addr_bytecount);
+ if (prefix_len < sp->addr_bitcount) {
+ mask_addr((unsigned char *) &u, sp->addr_bytecount, prefix_len);
+ buf = vstring_alloc(sp->addr_strlen + sp->addr_bitcount_strlen + 1);
+ } else {
+ buf = vstring_alloc(sp->addr_strlen + 1);
+ }
+
+ /*
+ * Convert the result to string, and append the optional /prefix.
+ */
+ if (inet_ntop(af, &u, vstring_str(buf), vstring_avail(buf)) == 0)
+ msg_fatal("%s: inet_ntop: %m", myname);
+ vstring_set_payload_size(buf, strlen(vstring_str(buf)));
+ if (prefix_len < sp->addr_bitcount)
+ vstring_sprintf_append(buf, "/%d", prefix_len);
+ return (vstring_export(buf));
+}
+
+#ifdef TEST
+
+#include <stdlib.h>
+#include <msg_vstream.h>
+#include <name_code.h>
+
+ /*
+ * TODO: add test cases for fatal and panic errors, intercept msg_fatal()
+ * and msg_panic(), and verify the expected error messages.
+ */
+typedef struct TEST_CASE {
+ int in_af;
+ int in_prefix_len;
+ const char *exp_prefix;
+} TEST_CASE;
+
+static TEST_CASE test_cases[] = {
+ AF_INET, 32, "255.255.255.255",
+ AF_INET, 28, "255.255.255.240/28",
+ AF_INET, 4, "240.0.0.0/4",
+ AF_INET, 0, "0.0.0.0/0",
+ AF_INET6, 128, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
+ AF_INET6, 124, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff0/124",
+ AF_INET6, 4, "f000::/4",
+ AF_INET6, 0, "::/0",
+};
+
+const NAME_CODE af_map[] = {
+ "AF_INET", AF_INET,
+ "AF_INET6", AF_INET6,
+ 0,
+};
+
+#define TEST_CASE_COUNT (sizeof(test_cases) / sizeof(test_cases[0]))
+
+int main(int argc, char **argv)
+{
+ TEST_CASE *tp;
+ union {
+ struct in_addr in_addr;
+ struct in6_addr in6_addr;
+ } u;
+ char *act_prefix;
+ int pass = 0;
+ int fail = 0;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ memset(&u, ~0, sizeof(u));
+
+ for (tp = test_cases; tp < test_cases + TEST_CASE_COUNT; tp++) {
+ msg_info("RUN %s/%d", str_name_code(af_map, tp->in_af),
+ tp->in_prefix_len);
+ act_prefix = inet_prefix_top(tp->in_af, &u, tp->in_prefix_len);
+ if (strcmp(act_prefix, tp->exp_prefix) != 0) {
+ msg_warn("got \"%s\", want \"%s\"", act_prefix, tp->exp_prefix);
+ fail += 1;
+ msg_info("FAIL %s/%d", str_name_code(af_map, tp->in_af),
+ tp->in_prefix_len);
+ } else {
+ pass += 1;
+ msg_info("PASS %s/%d", str_name_code(af_map, tp->in_af),
+ tp->in_prefix_len);
+ }
+ }
+ msg_info("PASS=%d FAIL=%d", pass, fail);
+ return (fail > 0);
+}
+
+#endif /* TEST */
diff --git a/src/util/inet_prefix_top.h b/src/util/inet_prefix_top.h
new file mode 100644
index 0000000..f604ee3
--- /dev/null
+++ b/src/util/inet_prefix_top.h
@@ -0,0 +1,27 @@
+#ifndef _INET_MASK_TOP_H_INCLUDED_
+#define _INET_MASK_TOP_H_INCLUDED_
+
+/*++
+/* NAME
+/* inet_prefix_top 3h
+/* SUMMARY
+/* convert net/mask to printable string
+/* SYNOPSIS
+/* #include <inet_prefix_top.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern char *inet_prefix_top(int, const void *, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/*--*/
+
+#endif
diff --git a/src/util/inet_proto.c b/src/util/inet_proto.c
new file mode 100644
index 0000000..fedf761
--- /dev/null
+++ b/src/util/inet_proto.c
@@ -0,0 +1,328 @@
+/*++
+/* NAME
+/* inet_proto 3
+/* SUMMARY
+/* convert protocol names to assorted constants
+/* SYNOPSIS
+/* #include <inet_proto.h>
+/*
+/* typedef struct {
+/* .in +4
+/* unsigned ai_family; /* PF_UNSPEC, PF_INET, or PF_INET6 */
+/* unsigned *ai_family_list; /* PF_INET and/or PF_INET6 */
+/* unsigned *dns_atype_list;/* TAAAA and/or TA */
+/* unsigned char *sa_family_list;/* AF_INET6 and/or AF_INET */
+/* .in -4
+/* } INET_PROTO_INFO;
+/*
+/* const INET_PROTO_INFO *inet_proto_init(context, protocols)
+/*
+/* const INET_PROTO_INFO *inet_proto_info()
+/* DESCRIPTION
+/* inet_proto_init() converts a string with protocol names
+/* into null-terminated lists of appropriate constants used
+/* by Postfix library routines. The idea is that one should
+/* be able to configure an MTA for IPv4 only, without having
+/* to recompile code (what a concept).
+/*
+/* Unfortunately, some compilers won't link initialized data
+/* without a function call into the same source module, so
+/* we invoke inet_proto_info() in order to access the result
+/* from inet_proto_init() from within library routines.
+/* inet_proto_info() also conveniently initializes the data
+/* to built-in defaults.
+/*
+/* Arguments:
+/* .IP context
+/* Typically, a configuration parameter name.
+/* .IP protocols
+/* Null-terminated string with protocol names separated by
+/* whitespace and/or commas:
+/* .RS
+/* .IP INET_PROTO_NAME_ALL
+/* Enable all available IP protocols.
+/* .IP INET_PROTO_NAME_IPV4
+/* Enable IP version 4 support.
+/* .IP INET_PROTO_NAME_IPV6
+/* Enable IP version 6 support.
+/* .RS
+/* .PP
+/* Results:
+/* .IP ai_family
+/* Only one of PF_UNSPEC, PF_INET, or PF_INET6. This can be
+/* used as input for the getaddrinfo() and getnameinfo()
+/* routines.
+/* .IP ai_family_list
+/* One or more of PF_INET or PF_INET6. This can be used as
+/* input for the inet_addr_local() routine.
+/* .IP dns_atype_list
+/* One or more of T_AAAA or T_A. This can be used as input for
+/* the dns_lookup_v() and dns_lookup_l() routines.
+/* .IP sa_family_list
+/* One or more of AF_INET6 or AF_INET. This can be used as an
+/* output filter for the results from the getaddrinfo() and
+/* getnameinfo() routines.
+/* SEE ALSO
+/* msg(3) diagnostics interface
+/* DIAGNOSTICS
+/* This module will warn and turn off support for any protocol
+/* that is requested but unavailable.
+/*
+/* Fatal errors: memory allocation problem.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#ifdef RESOLVE_H_NEEDS_STDIO_H
+#include <stdio.h>
+#endif
+#include <resolv.h>
+#include <stdarg.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <msg.h>
+#include <myaddrinfo.h>
+#include <name_mask.h>
+#include <inet_proto.h>
+
+ /*
+ * Application-specific.
+ */
+
+ /*
+ * Run-time initialization, so we can work around LINUX where IPv6 falls
+ * flat on its face because it is not turned on in the kernel.
+ */
+INET_PROTO_INFO *inet_proto_table = 0;
+
+ /*
+ * Infrastructure: lookup table with the protocol names that we support.
+ */
+#define INET_PROTO_MASK_IPV4 (1<<0)
+#define INET_PROTO_MASK_IPV6 (1<<1)
+
+static const NAME_MASK proto_table[] = {
+#ifdef HAS_IPV6
+ INET_PROTO_NAME_ALL, INET_PROTO_MASK_IPV4 | INET_PROTO_MASK_IPV6,
+ INET_PROTO_NAME_IPV6, INET_PROTO_MASK_IPV6,
+#else
+ INET_PROTO_NAME_ALL, INET_PROTO_MASK_IPV4,
+#endif
+ INET_PROTO_NAME_IPV4, INET_PROTO_MASK_IPV4,
+ 0,
+};
+
+/* make_uchar_vector - create and initialize uchar vector */
+
+static unsigned char *make_uchar_vector(int len,...)
+{
+ const char *myname = "make_uchar_vector";
+ va_list ap;
+ int count;
+ unsigned char *vp;
+
+ va_start(ap, len);
+ if (len <= 0)
+ msg_panic("%s: bad vector length: %d", myname, len);
+ vp = (unsigned char *) mymalloc(sizeof(*vp) * len);
+ for (count = 0; count < len; count++)
+ vp[count] = va_arg(ap, unsigned);
+ va_end(ap);
+ return (vp);
+}
+
+/* make_unsigned_vector - create and initialize integer vector */
+
+static unsigned *make_unsigned_vector(int len,...)
+{
+ const char *myname = "make_unsigned_vector";
+ va_list ap;
+ int count;
+ unsigned *vp;
+
+ va_start(ap, len);
+ if (len <= 0)
+ msg_panic("%s: bad vector length: %d", myname, len);
+ vp = (unsigned *) mymalloc(sizeof(*vp) * len);
+ for (count = 0; count < len; count++)
+ vp[count] = va_arg(ap, unsigned);
+ va_end(ap);
+ return (vp);
+}
+
+/* inet_proto_free - destroy data */
+
+static void inet_proto_free(INET_PROTO_INFO *pf)
+{
+ myfree((void *) pf->ai_family_list);
+ myfree((void *) pf->dns_atype_list);
+ myfree((void *) pf->sa_family_list);
+ myfree((void *) pf);
+}
+
+/* inet_proto_init - convert protocol names to library inputs */
+
+const INET_PROTO_INFO *inet_proto_init(const char *context, const char *protocols)
+{
+ const char *myname = "inet_proto";
+ INET_PROTO_INFO *pf;
+ int inet_proto_mask;
+ int sock;
+
+ /*
+ * Avoid run-time errors when all network protocols are disabled. We
+ * can't look up interface information, and we can't convert explicit
+ * names or addresses.
+ */
+ inet_proto_mask = name_mask(context, proto_table, protocols);
+#ifdef HAS_IPV6
+ if (inet_proto_mask & INET_PROTO_MASK_IPV6) {
+ if ((sock = socket(PF_INET6, SOCK_STREAM, 0)) >= 0) {
+ close(sock);
+ } else if (errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT) {
+ msg_warn("%s: disabling IPv6 name/address support: %m", context);
+ inet_proto_mask &= ~INET_PROTO_MASK_IPV6;
+ } else {
+ msg_fatal("socket: %m");
+ }
+ }
+#endif
+ if (inet_proto_mask & INET_PROTO_MASK_IPV4) {
+ if ((sock = socket(PF_INET, SOCK_STREAM, 0)) >= 0) {
+ close(sock);
+ } else if (errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT) {
+ msg_warn("%s: disabling IPv4 name/address support: %m", context);
+ inet_proto_mask &= ~INET_PROTO_MASK_IPV4;
+ } else {
+ msg_fatal("socket: %m");
+ }
+ }
+
+ /*
+ * Store address family etc. info as null-terminated vectors. If that
+ * breaks because we must be able to store nulls, we'll deal with the
+ * additional complexity.
+ *
+ * XXX Use compile-time initialized data templates instead of building the
+ * reply on the fly.
+ */
+ switch (inet_proto_mask) {
+#ifdef HAS_IPV6
+ case INET_PROTO_MASK_IPV6:
+ pf = (INET_PROTO_INFO *) mymalloc(sizeof(*pf));
+ pf->ai_family = PF_INET6;
+ pf->ai_family_list = make_unsigned_vector(2, PF_INET6, 0);
+ pf->dns_atype_list = make_unsigned_vector(2, T_AAAA, 0);
+ pf->sa_family_list = make_uchar_vector(2, AF_INET6, 0);
+ break;
+ case (INET_PROTO_MASK_IPV6 | INET_PROTO_MASK_IPV4):
+ pf = (INET_PROTO_INFO *) mymalloc(sizeof(*pf));
+ pf->ai_family = PF_UNSPEC;
+ pf->ai_family_list = make_unsigned_vector(3, PF_INET, PF_INET6, 0);
+ pf->dns_atype_list = make_unsigned_vector(3, T_A, T_AAAA, 0);
+ pf->sa_family_list = make_uchar_vector(3, AF_INET, AF_INET6, 0);
+ break;
+#endif
+ case INET_PROTO_MASK_IPV4:
+ pf = (INET_PROTO_INFO *) mymalloc(sizeof(*pf));
+ pf->ai_family = PF_INET;
+ pf->ai_family_list = make_unsigned_vector(2, PF_INET, 0);
+ pf->dns_atype_list = make_unsigned_vector(2, T_A, 0);
+ pf->sa_family_list = make_uchar_vector(2, AF_INET, 0);
+ break;
+ case 0:
+ pf = (INET_PROTO_INFO *) mymalloc(sizeof(*pf));
+ pf->ai_family = PF_UNSPEC;
+ pf->ai_family_list = make_unsigned_vector(1, 0);
+ pf->dns_atype_list = make_unsigned_vector(1, 0);
+ pf->sa_family_list = make_uchar_vector(1, 0);
+ break;
+ default:
+ msg_panic("%s: bad inet_proto_mask 0x%x", myname, inet_proto_mask);
+ }
+ if (inet_proto_table)
+ inet_proto_free(inet_proto_table);
+ return (inet_proto_table = pf);
+}
+
+#ifdef TEST
+
+ /*
+ * Small driver for unit tests.
+ */
+
+static char *print_unsigned_vector(VSTRING *buf, unsigned *vector)
+{
+ unsigned *p;
+
+ VSTRING_RESET(buf);
+ for (p = vector; *p; p++) {
+ vstring_sprintf_append(buf, "%u", *p);
+ if (p[1])
+ VSTRING_ADDCH(buf, ' ');
+ }
+ VSTRING_TERMINATE(buf);
+ return (vstring_str(buf));
+}
+
+static char *print_uchar_vector(VSTRING *buf, unsigned char *vector)
+{
+ unsigned char *p;
+
+ VSTRING_RESET(buf);
+ for (p = vector; *p; p++) {
+ vstring_sprintf_append(buf, "%u", *p);
+ if (p[1])
+ VSTRING_ADDCH(buf, ' ');
+ }
+ VSTRING_TERMINATE(buf);
+ return (vstring_str(buf));
+}
+
+int main(int argc, char **argv)
+{
+ const char *myname = argv[0];
+ INET_PROTO_INFO *pf;
+ VSTRING *buf;
+
+ if (argc < 2)
+ msg_fatal("usage: %s protocol(s)...", myname);
+
+ buf = vstring_alloc(10);
+ while (*++argv) {
+ msg_info("=== %s ===", *argv);
+ inet_proto_init(myname, *argv);
+ pf = inet_proto_table;
+ msg_info("ai_family = %u", pf->ai_family);
+ msg_info("ai_family_list = %s",
+ print_unsigned_vector(buf, pf->ai_family_list));
+ msg_info("dns_atype_list = %s",
+ print_unsigned_vector(buf, pf->dns_atype_list));
+ msg_info("sa_family_list = %s",
+ print_uchar_vector(buf, pf->sa_family_list));
+ }
+ vstring_free(buf);
+ return (0);
+}
+
+#endif
diff --git a/src/util/inet_proto.h b/src/util/inet_proto.h
new file mode 100644
index 0000000..9175eae
--- /dev/null
+++ b/src/util/inet_proto.h
@@ -0,0 +1,56 @@
+#ifndef _INET_PROTO_INFO_H_INCLUDED_
+#define _INET_PROTO_INFO_H_INCLUDED_
+
+/*++
+/* NAME
+/* inet_proto_info 3h
+/* SUMMARY
+/* convert protocol names to assorted constants
+/* SYNOPSIS
+/* #include <inet_proto_info.h>
+ DESCRIPTION
+ .nf
+
+ /*
+ * External interface.
+ */
+typedef struct {
+ unsigned int ai_family; /* PF_UNSPEC, PF_INET, or PF_INET6 */
+ unsigned int *ai_family_list; /* PF_INET and/or PF_INET6 */
+ unsigned int *dns_atype_list; /* TAAAA and/or TA */
+ unsigned char *sa_family_list; /* AF_INET6 and/or AF_INET */
+} INET_PROTO_INFO;
+
+ /*
+ * Some compilers won't link initialized data unless we call a function in
+ * the same source file. Therefore, inet_proto_info() is a function instead
+ * of a global variable.
+ */
+#define inet_proto_info() \
+ (inet_proto_table ? (const INET_PROTO_INFO*) inet_proto_table : \
+ inet_proto_init("default protocol setting", DEF_INET_PROTOCOLS))
+
+extern const INET_PROTO_INFO *inet_proto_init(const char *, const char *);
+extern INET_PROTO_INFO *inet_proto_table;
+
+#define INET_PROTO_NAME_IPV6 "ipv6"
+#define INET_PROTO_NAME_IPV4 "ipv4"
+#define INET_PROTO_NAME_ALL "all"
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/inet_trigger.c b/src/util/inet_trigger.c
new file mode 100644
index 0000000..b1734ee
--- /dev/null
+++ b/src/util/inet_trigger.c
@@ -0,0 +1,130 @@
+/*++
+/* NAME
+/* inet_trigger 3
+/* SUMMARY
+/* wakeup INET-domain server
+/* SYNOPSIS
+/* #include <trigger.h>
+/*
+/* int inet_trigger(service, buf, len, timeout)
+/* char *service;
+/* const char *buf;
+/* ssize_t len;
+/* int timeout;
+/* DESCRIPTION
+/* inet_trigger() wakes up the named INET-domain server by making
+/* a brief connection to it and by writing the contents of the
+/* named buffer.
+/*
+/* The connection is closed by a background thread. Some kernels
+/* cannot handle client-side disconnect before the server has
+/* received the message.
+/*
+/* Arguments:
+/* .IP service
+/* Name of the communication endpoint.
+/* .IP buf
+/* Address of data to be written.
+/* .IP len
+/* Amount of data to be written.
+/* .IP timeout
+/* Deadline in seconds. Specify a value <= 0 to disable
+/* the time limit.
+/* DIAGNOSTICS
+/* The result is zero in case of success, -1 in case of problems.
+/* BUGS
+/* SEE ALSO
+/* inet_connect(3), INET-domain client
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <connect.h>
+#include <iostuff.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <trigger.h>
+
+struct inet_trigger {
+ int fd;
+ char *service;
+};
+
+/* inet_trigger_event - disconnect from peer */
+
+static void inet_trigger_event(int event, void *context)
+{
+ struct inet_trigger *ip = (struct inet_trigger *) context;
+ static const char *myname = "inet_trigger_event";
+
+ /*
+ * Disconnect.
+ */
+ if (event == EVENT_TIME)
+ msg_warn("%s: read timeout for service %s", myname, ip->service);
+ event_disable_readwrite(ip->fd);
+ event_cancel_timer(inet_trigger_event, context);
+ if (close(ip->fd) < 0)
+ msg_warn("%s: close %s: %m", myname, ip->service);
+ myfree(ip->service);
+ myfree((void *) ip);
+}
+
+
+/* inet_trigger - wakeup INET-domain server */
+
+int inet_trigger(const char *service, const char *buf, ssize_t len, int timeout)
+{
+ const char *myname = "inet_trigger";
+ struct inet_trigger *ip;
+ int fd;
+
+ if (msg_verbose > 1)
+ msg_info("%s: service %s", myname, service);
+
+ /*
+ * Connect...
+ */
+ if ((fd = inet_connect(service, BLOCKING, timeout)) < 0) {
+ if (msg_verbose)
+ msg_warn("%s: connect to %s: %m", myname, service);
+ return (-1);
+ }
+ close_on_exec(fd, CLOSE_ON_EXEC);
+ ip = (struct inet_trigger *) mymalloc(sizeof(*ip));
+ ip->fd = fd;
+ ip->service = mystrdup(service);
+
+ /*
+ * Write the request...
+ */
+ if (write_buf(fd, buf, len, timeout) < 0
+ || write_buf(fd, "", 1, timeout) < 0)
+ if (msg_verbose)
+ msg_warn("%s: write to %s: %m", myname, service);
+
+ /*
+ * Wakeup when the peer disconnects, or when we lose patience.
+ */
+ if (timeout > 0)
+ event_request_timer(inet_trigger_event, (void *) ip, timeout + 100);
+ event_enable_read(fd, inet_trigger_event, (void *) ip);
+ return (0);
+}
diff --git a/src/util/inet_windowsize.c b/src/util/inet_windowsize.c
new file mode 100644
index 0000000..e982149
--- /dev/null
+++ b/src/util/inet_windowsize.c
@@ -0,0 +1,82 @@
+/*++
+/* NAME
+/* inet_windowsize 3
+/* SUMMARY
+/* TCP window scaling control
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* int inet_windowsize;
+/*
+/* void set_inet_windowsize(sock, windowsize)
+/* int sock;
+/* int windowsize;
+/* DESCRIPTION
+/* set_inet_windowsize() overrides the default TCP window size
+/* with the specified value. When called before listen() or
+/* accept(), this works around broken infrastructure that
+/* mis-handles TCP window scaling options.
+/*
+/* The global inet_windowsize variable is available for other
+/* routines to remember that they wish to override the default
+/* TCP window size. The variable is not accessed by the
+/* set_inet_windowsize() function itself.
+/*
+/* Arguments:
+/* .IP sock
+/* TCP communication endpoint, before the connect(2) or listen(2) call.
+/* .IP windowsize
+/* The preferred TCP window size. This must be > 0.
+/* DIAGNOSTICS
+/* Panic: interface violation.
+/* Warnings: some error return from setsockopt().
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System libraries. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+/* Application storage. */
+
+ /*
+ * Tunable to work around broken routers.
+ */
+int inet_windowsize = 0;
+
+/* set_inet_windowsize - set TCP send/receive window size */
+
+void set_inet_windowsize(int sock, int windowsize)
+{
+
+ /*
+ * Sanity check.
+ */
+ if (windowsize <= 0)
+ msg_panic("inet_windowsize: bad window size %d", windowsize);
+
+ /*
+ * Generic implementation: set the send and receive buffer size before
+ * listen() or connect().
+ */
+ if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void *) &windowsize,
+ sizeof(windowsize)) < 0)
+ msg_warn("setsockopt SO_SNDBUF %d: %m", windowsize);
+ if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void *) &windowsize,
+ sizeof(windowsize)) < 0)
+ msg_warn("setsockopt SO_RCVBUF %d: %m", windowsize);
+}
diff --git a/src/util/iostuff.h b/src/util/iostuff.h
new file mode 100644
index 0000000..0c54d03
--- /dev/null
+++ b/src/util/iostuff.h
@@ -0,0 +1,73 @@
+#ifndef _IOSTUFF_H_INCLUDED_
+#define _IOSTUFF_H_INCLUDED_
+
+/*++
+/* NAME
+/* iostuff 3h
+/* SUMMARY
+/* miscellaneous I/O primitives
+/* SYNOPSIS
+/* #include <iostuff.h>
+/* DESCRIPTION
+
+ /*
+ * External interface.
+ */
+extern int non_blocking(int, int);
+extern int close_on_exec(int, int);
+extern int open_limit(int);
+extern int poll_fd(int, int, int, int, int);
+extern off_t get_file_limit(void);
+extern void set_file_limit(off_t);
+extern ssize_t peekfd(int);
+extern ssize_t write_buf(int, const char *, ssize_t, int);
+extern ssize_t timed_read(int, void *, size_t, int, void *);
+extern ssize_t timed_write(int, const void *, size_t, int, void *);
+extern void doze(unsigned);
+extern void rand_sleep(unsigned, unsigned);
+extern int duplex_pipe(int *);
+extern int stream_recv_fd(int);
+extern int stream_send_fd(int, int);
+extern int unix_recv_fd(int);
+extern int unix_send_fd(int, int);
+extern ssize_t dummy_read(int, void *, size_t, int, void *);
+extern ssize_t dummy_write(int, void *, size_t, int, void *);
+
+#define readable(fd) poll_fd((fd), POLL_FD_READ, 0, 1, 0)
+#define writable(fd) poll_fd((fd), POLL_FD_WRITE, 0, 1, 0)
+
+#define read_wait(fd, timeout) poll_fd((fd), POLL_FD_READ, (timeout), 0, -1)
+#define write_wait(fd, timeout) poll_fd((fd), POLL_FD_WRITE, (timeout), 0, -1)
+
+extern int inet_windowsize;
+extern void set_inet_windowsize(int, int);
+
+#define POLL_FD_READ 0
+#define POLL_FD_WRITE 1
+
+#define BLOCKING 0
+#define NON_BLOCKING 1
+
+#define CLOSE_ON_EXEC 1
+#define PASS_ON_EXEC 0
+
+extern int unix_pass_fd_fix;
+extern void set_unix_pass_fd_fix(const char *);
+
+#define UNIX_PASS_FD_FIX_NONE (0)
+#define UNIX_PASS_FD_FIX_CMSG_LEN (1<<0)
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/* CREATION DATE
+/* Sat Jan 25 16:54:13 EST 1997
+/*--*/
+
+#endif
diff --git a/src/util/ip_match.c b/src/util/ip_match.c
new file mode 100644
index 0000000..aeea799
--- /dev/null
+++ b/src/util/ip_match.c
@@ -0,0 +1,676 @@
+/*++
+/* NAME
+/* ip_match 3
+/* SUMMARY
+/* IP address pattern matching
+/* SYNOPSIS
+/* #include <ip_match.h>
+/*
+/* char *ip_match_parse(byte_codes, pattern)
+/* VSTRING *byte_codes;
+/* char *pattern;
+/*
+/* char *ip_match_save(byte_codes)
+/* const VSTRING *byte_codes;
+/*
+/* int ip_match_execute(byte_codes, addr_bytes)
+/* cost char *byte_codes;
+/* const char *addr_bytes;
+/*
+/* char *ip_match_dump(printable, byte_codes)
+/* VSTRING *printable;
+/* const char *byte_codes;
+/* DESCRIPTION
+/* This module supports IP address pattern matching. See below
+/* for a description of the supported address pattern syntax.
+/*
+/* This implementation aims to minimize the cost of encoding
+/* the pattern in internal form, while still providing good
+/* matching performance in the typical case. The first byte
+/* of an encoded pattern specifies the expected address family
+/* (for example, AF_INET); other details of the encoding are
+/* private and are subject to change.
+/*
+/* ip_match_parse() converts the user-specified pattern to
+/* internal form. The result value is a null pointer in case
+/* of success, or a pointer into the byte_codes buffer with a
+/* detailed problem description.
+/*
+/* ip_match_save() saves the result from ip_match_parse() for
+/* longer-term usage. The result should be passed to myfree().
+/*
+/* ip_match_execute() matches a binary network in addr_bytes
+/* against a byte-code array in byte_codes. It is an error to
+/* use different address families for the byte_codes and addr_bytes
+/* arguments (the first byte-code value contains the expected
+/* address family). The result is non-zero in case of success.
+/*
+/* ip_match_dump() produces an ASCII dump of a byte-code array.
+/* The dump is supposed to be identical to the input pattern
+/* modulo upper/lower case or leading nulls with IPv6). This
+/* function is primarily a debugging aid.
+/*
+/* Arguments
+/* .IP addr_bytes
+/* Binary network address in network-byte order.
+/* .IP byte_codes
+/* Byte-code array produced by ip_match_parse().
+/* .IP pattern
+/* Human-readable address pattern.
+/* .IP printable
+/* storage for ASCII dump of a byte-code array.
+/* IPV4 PATTERN SYNTAX
+/* .ad
+/* .fi
+/* An IPv4 address pattern has four fields separated by ".".
+/* Each field is either a decimal number, or a sequence inside
+/* "[]" that contains one or more ";"-separated decimal
+/* numbers or number..number ranges.
+/*
+/* Examples of patterns are 1.2.3.4 (matches itself, as one
+/* would expect) and 1.2.3.[2,4,6..8] (matches 1.2.3.2, 1.2.3.4,
+/* 1.2.3.6, 1.2.3.7, 1.2.3.8).
+/*
+/* Thus, any pattern field can be a sequence inside "[]", but
+/* a "[]" sequence cannot span multiple address fields, and
+/* a pattern field cannot contain both a number and a "[]"
+/* sequence at the same time.
+/*
+/* This means that the pattern 1.2.[3.4] is not valid (the
+/* sequence [3.4] cannot span two address fields) and the
+/* pattern 1.2.3.3[6..9] is also not valid (the last field
+/* cannot be both number 3 and sequence [6..9] at the same
+/* time).
+/*
+/* The syntax for IPv4 patterns is as follows:
+/*
+/* .in +5
+/* v4pattern = v4field "." v4field "." v4field "." v4field
+/* .br
+/* v4field = v4octet | "[" v4sequence "]"
+/* .br
+/* v4octet = any decimal number in the range 0 through 255
+/* .br
+/* v4sequence = v4seq_member | v4sequence ";" v4seq_member
+/* .br
+/* v4seq_member = v4octet | v4octet ".." v4octet
+/* .in
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this
+/* software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <ctype.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <ip_match.h>
+
+ /*
+ * Token values. The in-band values are also used as byte-code values.
+ */
+#define IP_MATCH_CODE_OPEN '[' /* in-band */
+#define IP_MATCH_CODE_CLOSE ']' /* in-band */
+#define IP_MATCH_CODE_OVAL 'N' /* in-band */
+#define IP_MATCH_CODE_RANGE 'R' /* in-band */
+#define IP_MATCH_CODE_EOF '\0' /* in-band */
+#define IP_MATCH_CODE_ERR 256 /* out-of-band */
+
+ /*
+ * SLMs.
+ */
+#define STR vstring_str
+#define LEN VSTRING_LEN
+
+/* ip_match_save - make longer-term copy of byte code */
+
+char *ip_match_save(const VSTRING *byte_codes)
+{
+ char *dst;
+
+ dst = mymalloc(LEN(byte_codes));
+ return (memcpy(dst, STR(byte_codes), LEN(byte_codes)));
+}
+
+/* ip_match_dump - byte-code pretty printer */
+
+char *ip_match_dump(VSTRING *printable, const char *byte_codes)
+{
+ const char *myname = "ip_match_dump";
+ const unsigned char *bp;
+ int octet_count = 0;
+ int ch;
+
+ /*
+ * Sanity check. Use different dumping loops for AF_INET and AF_INET6.
+ */
+ if (*byte_codes != AF_INET)
+ msg_panic("%s: malformed byte-code header", myname);
+
+ /*
+ * Pretty-print and sanity-check the byte codes. Note: the loops in this
+ * code have no auto-increment at the end of the iteration. Instead, each
+ * byte-code handler bumps the byte-code pointer appropriately.
+ */
+ VSTRING_RESET(printable);
+ bp = (const unsigned char *) byte_codes + 1;
+ for (;;) {
+
+ /*
+ * Simple numeric field.
+ */
+ if ((ch = *bp++) == IP_MATCH_CODE_OVAL) {
+ vstring_sprintf_append(printable, "%d", *bp);
+ bp += 1;
+ }
+
+ /*
+ * Wild-card numeric field.
+ */
+ else if (ch == IP_MATCH_CODE_OPEN) {
+ vstring_sprintf_append(printable, "[");
+ for (;;) {
+ /* Numeric range. */
+ if ((ch = *bp++) == IP_MATCH_CODE_RANGE) {
+ vstring_sprintf_append(printable, "%d..%d", bp[0], bp[1]);
+ bp += 2;
+ }
+ /* Number. */
+ else if (ch == IP_MATCH_CODE_OVAL) {
+ vstring_sprintf_append(printable, "%d", *bp);
+ bp += 1;
+ }
+ /* End-of-wildcard. */
+ else if (ch == IP_MATCH_CODE_CLOSE) {
+ break;
+ }
+ /* Corruption. */
+ else {
+ msg_panic("%s: unexpected byte code (decimal %d) "
+ "after \"%s\"", myname, ch, STR(printable));
+ }
+ /* Output the wild-card field separator and repeat the loop. */
+ if (*bp != IP_MATCH_CODE_CLOSE)
+ vstring_sprintf_append(printable, ";");
+ }
+ vstring_sprintf_append(printable, "]");
+ }
+
+ /*
+ * Corruption.
+ */
+ else {
+ msg_panic("%s: unexpected byte code (decimal %d) after \"%s\"",
+ myname, ch, STR(printable));
+ }
+
+ /*
+ * Require four octets, not one more, not one less.
+ */
+ if (++octet_count == 4) {
+ if (*bp != 0)
+ msg_panic("%s: unexpected byte code (decimal %d) after \"%s\"",
+ myname, ch, STR(printable));
+ return (STR(printable));
+ }
+ if (*bp == 0)
+ msg_panic("%s: truncated byte code after \"%s\"",
+ myname, STR(printable));
+
+ /*
+ * Output the address field separator and repeat the loop.
+ */
+ vstring_sprintf_append(printable, ".");
+ }
+}
+
+/* ip_match_print_code_prefix - printable byte-code prefix */
+
+static char *ip_match_print_code_prefix(const char *byte_codes, size_t len)
+{
+ static VSTRING *printable = 0;
+ const char *fmt;
+ const char *bp;
+
+ /*
+ * This is primarily for emergency debugging so we don't care about
+ * non-reentrancy.
+ */
+ if (printable == 0)
+ printable = vstring_alloc(100);
+ else
+ VSTRING_RESET(printable);
+
+ /*
+ * Use decimal for IPv4 and hexadecimal otherwise, so that address octet
+ * values are easy to recognize.
+ */
+ fmt = (*byte_codes == AF_INET ? "%d " : "%02x ");
+ for (bp = byte_codes; bp < byte_codes + len; bp++)
+ vstring_sprintf_append(printable, fmt, *(const unsigned char *) bp);
+
+ return (STR(printable));
+}
+
+/* ip_match_execute - byte-code matching engine */
+
+int ip_match_execute(const char *byte_codes, const char *addr_bytes)
+{
+ const char *myname = "ip_match_execute";
+ const unsigned char *bp;
+ const unsigned char *ap;
+ int octet_count = 0;
+ int ch;
+ int matched;
+
+ /*
+ * Sanity check. Use different execute loops for AF_INET and AF_INET6.
+ */
+ if (*byte_codes != AF_INET)
+ msg_panic("%s: malformed byte-code header (decimal %d)",
+ myname, *(const unsigned char *) byte_codes);
+
+ /*
+ * Match the address bytes against the byte codes. Avoid problems with
+ * (char -> int) sign extension on architectures with signed characters.
+ */
+ bp = (const unsigned char *) byte_codes + 1;
+ ap = (const unsigned char *) addr_bytes;
+
+ for (octet_count = 0; octet_count < 4; octet_count++, ap++) {
+
+ /*
+ * Simple numeric field.
+ */
+ if ((ch = *bp++) == IP_MATCH_CODE_OVAL) {
+ if (*ap == *bp)
+ bp += 1;
+ else
+ return (0);
+ }
+
+ /*
+ * Wild-card numeric field.
+ */
+ else if (ch == IP_MATCH_CODE_OPEN) {
+ matched = 0;
+ for (;;) {
+ /* Numeric range. */
+ if ((ch = *bp++) == IP_MATCH_CODE_RANGE) {
+ if (!matched)
+ matched = (*ap >= bp[0] && *ap <= bp[1]);
+ bp += 2;
+ }
+ /* Number. */
+ else if (ch == IP_MATCH_CODE_OVAL) {
+ if (!matched)
+ matched = (*ap == *bp);
+ bp += 1;
+ }
+ /* End-of-wildcard. */
+ else if (ch == IP_MATCH_CODE_CLOSE) {
+ break;
+ }
+ /* Corruption. */
+ else {
+ size_t len = (const char *) bp - byte_codes - 1;
+
+ msg_panic("%s: unexpected byte code (decimal %d) "
+ "after \"%s\"", myname, ch,
+ ip_match_print_code_prefix(byte_codes, len));
+ }
+ }
+ if (matched == 0)
+ return (0);
+ }
+
+ /*
+ * Corruption.
+ */
+ else {
+ size_t len = (const char *) bp - byte_codes - 1;
+
+ msg_panic("%s: unexpected byte code (decimal %d) after \"%s\"",
+ myname, ch, ip_match_print_code_prefix(byte_codes, len));
+ }
+ }
+ return (1);
+}
+
+/* ip_match_next_token - carve out the next token from input pattern */
+
+static int ip_match_next_token(char **pstart, char **psaved_start, int *poval)
+{
+ unsigned char *cp;
+ int oval; /* octet value */
+ int type; /* token value */
+
+ /*
+ * Return a literal, error, or EOF token. Update the read pointer to the
+ * start of the next token or leave it at the string terminator.
+ */
+#define IP_MATCH_RETURN_TOK(next, type) \
+ do { *pstart = (char *) (next); return (type); } while (0)
+
+ /*
+ * Return a token that contains an IPv4 address octet value.
+ */
+#define IP_MATCH_RETURN_TOK_VAL(next, type, oval) do { \
+ *poval = (oval); IP_MATCH_RETURN_TOK((next), type); \
+ } while (0)
+
+ /*
+ * Light-weight tokenizer. Each result is an IPv4 address octet value, a
+ * literal character value, error, or EOF.
+ */
+ *psaved_start = *pstart;
+ cp = (unsigned char *) *pstart;
+ if (ISDIGIT(*cp)) {
+ oval = *cp - '0';
+ type = IP_MATCH_CODE_OVAL;
+ for (cp += 1; ISDIGIT(*cp); cp++) {
+ oval *= 10;
+ oval += *cp - '0';
+ if (oval > 255)
+ type = IP_MATCH_CODE_ERR;
+ }
+ IP_MATCH_RETURN_TOK_VAL(cp, type, oval);
+ } else {
+ IP_MATCH_RETURN_TOK(*cp ? cp + 1 : cp, *cp);
+ }
+}
+
+/* ipmatch_print_parse_error - formatted parsing error, with context */
+
+static void PRINTFLIKE(5, 6) ipmatch_print_parse_error(VSTRING *reply,
+ char *start,
+ char *here,
+ char *next,
+ const char *fmt,...)
+{
+ va_list ap;
+ int start_width;
+ int here_width;
+
+ /*
+ * Format the error type.
+ */
+ va_start(ap, fmt);
+ vstring_vsprintf(reply, fmt, ap);
+ va_end(ap);
+
+ /*
+ * Format the error context. The syntax is complex enough that it is
+ * worth the effort to precisely indicate what input is in error.
+ *
+ * XXX Workaround for %.*s to avoid output when a zero width is specified.
+ */
+ if (start != 0) {
+ start_width = here - start;
+ here_width = next - here;
+ vstring_sprintf_append(reply, " at \"%.*s>%.*s<%s\"",
+ start_width, start_width == 0 ? "" : start,
+ here_width, here_width == 0 ? "" : here, next);
+ }
+}
+
+/* ip_match_parse - parse an entire wild-card address pattern */
+
+char *ip_match_parse(VSTRING *byte_codes, char *pattern)
+{
+ int octet_count;
+ char *saved_cp;
+ char *cp;
+ int token_type;
+ int look_ahead;
+ int oval;
+ int saved_oval;
+
+ /*
+ * Simplify this if we change to {} for wildcard notation.
+ */
+#define FIND_TERMINATOR(start, cp) do { \
+ int _level = 0; \
+ for (cp = (start) ; *cp; cp++) { \
+ if (*cp == '[') _level++; \
+ if (*cp != ']') continue; \
+ if (--_level == 0) break; \
+ } \
+ } while (0)
+
+ /*
+ * Strip [] around the entire pattern.
+ */
+ if (*pattern == '[') {
+ FIND_TERMINATOR(pattern, cp);
+ if (cp[0] == 0) {
+ vstring_sprintf(byte_codes, "missing \"]\" character");
+ return (STR(byte_codes));
+ }
+ if (cp[1] == 0) {
+ *cp = 0;
+ pattern += 1;
+ }
+ }
+
+ /*
+ * Sanity check. In this case we can't show any error context.
+ */
+ if (*pattern == 0) {
+ vstring_sprintf(byte_codes, "empty address pattern");
+ return (STR(byte_codes));
+ }
+
+ /*
+ * Simple parser with on-the-fly encoding. For now, IPv4 support only.
+ * Use different parser loops for IPv4 and IPv6.
+ */
+ VSTRING_RESET(byte_codes);
+ VSTRING_ADDCH(byte_codes, AF_INET);
+ octet_count = 0;
+ cp = pattern;
+
+ /*
+ * Require four address fields separated by ".", each field containing a
+ * numeric octet value or a sequence inside []. The loop head has no test
+ * and does not step the loop variable. The tokenizer advances the loop
+ * variable, and the loop termination logic is inside the loop.
+ */
+ for (;;) {
+ switch (token_type = ip_match_next_token(&cp, &saved_cp, &oval)) {
+
+ /*
+ * Numeric address field.
+ */
+ case IP_MATCH_CODE_OVAL:
+ VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_OVAL);
+ VSTRING_ADDCH(byte_codes, oval);
+ break;
+
+ /*
+ * Wild-card address field.
+ */
+ case IP_MATCH_CODE_OPEN:
+ VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_OPEN);
+ /* Require ";"-separated numbers or numeric ranges. */
+ for (;;) {
+ token_type = ip_match_next_token(&cp, &saved_cp, &oval);
+ if (token_type == IP_MATCH_CODE_OVAL) {
+ saved_oval = oval;
+ look_ahead = ip_match_next_token(&cp, &saved_cp, &oval);
+ /* Numeric range. */
+ if (look_ahead == '.') {
+ /* Brute-force parsing. */
+ if (ip_match_next_token(&cp, &saved_cp, &oval) == '.'
+ && ip_match_next_token(&cp, &saved_cp, &oval)
+ == IP_MATCH_CODE_OVAL
+ && saved_oval <= oval) {
+ VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_RANGE);
+ VSTRING_ADDCH(byte_codes, saved_oval);
+ VSTRING_ADDCH(byte_codes, oval);
+ look_ahead =
+ ip_match_next_token(&cp, &saved_cp, &oval);
+ } else {
+ ipmatch_print_parse_error(byte_codes, pattern,
+ saved_cp, cp,
+ "numeric range error");
+ return (STR(byte_codes));
+ }
+ }
+ /* Single number. */
+ else {
+ VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_OVAL);
+ VSTRING_ADDCH(byte_codes, saved_oval);
+ }
+ /* Require ";" or end-of-wildcard. */
+ token_type = look_ahead;
+ if (token_type == ';') {
+ continue;
+ } else if (token_type == IP_MATCH_CODE_CLOSE) {
+ break;
+ } else {
+ ipmatch_print_parse_error(byte_codes, pattern,
+ saved_cp, cp,
+ "need \";\" or \"%c\"",
+ IP_MATCH_CODE_CLOSE);
+ return (STR(byte_codes));
+ }
+ } else {
+ ipmatch_print_parse_error(byte_codes, pattern, saved_cp, cp,
+ "need decimal number 0..255");
+ return (STR(byte_codes));
+ }
+ }
+ VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_CLOSE);
+ break;
+
+ /*
+ * Invalid field.
+ */
+ default:
+ ipmatch_print_parse_error(byte_codes, pattern, saved_cp, cp,
+ "need decimal number 0..255 or \"%c\"",
+ IP_MATCH_CODE_OPEN);
+ return (STR(byte_codes));
+ }
+ octet_count += 1;
+
+ /*
+ * Require four address fields. Not one more, not one less.
+ */
+ if (octet_count == 4) {
+ if (*cp != 0) {
+ (void) ip_match_next_token(&cp, &saved_cp, &oval);
+ ipmatch_print_parse_error(byte_codes, pattern, saved_cp, cp,
+ "garbage after pattern");
+ return (STR(byte_codes));
+ }
+ VSTRING_ADDCH(byte_codes, 0);
+ return (0);
+ }
+
+ /*
+ * Require "." before the next address field.
+ */
+ if (ip_match_next_token(&cp, &saved_cp, &oval) != '.') {
+ ipmatch_print_parse_error(byte_codes, pattern, saved_cp, cp,
+ "need \".\"");
+ return (STR(byte_codes));
+ }
+ }
+}
+
+#ifdef TEST
+
+ /*
+ * Dummy main program for regression tests.
+ */
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <stringops.h>
+
+int main(int argc, char **argv)
+{
+ VSTRING *byte_codes = vstring_alloc(100);
+ VSTRING *line_buf = vstring_alloc(100);
+ char *bufp;
+ char *err;
+ char *user_pattern;
+ char *user_address;
+ int echo_input = !isatty(0);
+
+ /*
+ * Iterate over the input stream. The input format is a pattern, followed
+ * by optional addresses to match against.
+ */
+ while (vstring_fgets_nonl(line_buf, VSTREAM_IN)) {
+ bufp = STR(line_buf);
+ if (echo_input) {
+ vstream_printf("> %s\n", bufp);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ if (*bufp == '#')
+ continue;
+ if ((user_pattern = mystrtok(&bufp, " \t")) == 0)
+ continue;
+
+ /*
+ * Parse and dump the pattern.
+ */
+ if ((err = ip_match_parse(byte_codes, user_pattern)) != 0) {
+ vstream_printf("Error: %s\n", err);
+ } else {
+ vstream_printf("Code: %s\n",
+ ip_match_dump(line_buf, STR(byte_codes)));
+ }
+ vstream_fflush(VSTREAM_OUT);
+
+ /*
+ * Match the optional patterns.
+ */
+ while ((user_address = mystrtok(&bufp, " \t")) != 0) {
+ struct in_addr netw_addr;
+
+ switch (inet_pton(AF_INET, user_address, &netw_addr)) {
+ case 1:
+ vstream_printf("Match %s: %s\n", user_address,
+ ip_match_execute(STR(byte_codes),
+ (char *) &netw_addr.s_addr) ?
+ "yes" : "no");
+ break;
+ case 0:
+ vstream_printf("bad address syntax: %s\n", user_address);
+ break;
+ case -1:
+ vstream_printf("%s: %m\n", user_address);
+ break;
+ }
+ vstream_fflush(VSTREAM_OUT);
+ }
+ }
+ vstring_free(line_buf);
+ vstring_free(byte_codes);
+ exit(0);
+}
+
+#endif
diff --git a/src/util/ip_match.h b/src/util/ip_match.h
new file mode 100644
index 0000000..781a04a
--- /dev/null
+++ b/src/util/ip_match.h
@@ -0,0 +1,38 @@
+#ifndef _IP_MATCH_H_INCLUDED_
+#define _IP_MATCH_H_INCLUDED_
+
+/*++
+/* NAME
+/* ip_match 3h
+/* SUMMARY
+/* IP address pattern matching
+/* SYNOPSIS
+/* #include <ip_match.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern char *ip_match_parse(VSTRING *, char *);
+extern char *ip_match_save(const VSTRING *);
+extern char *ip_match_dump(VSTRING *, const char *);
+extern int ip_match_execute(const char *, const char *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/ip_match.in b/src/util/ip_match.in
new file mode 100644
index 0000000..eec13e3
--- /dev/null
+++ b/src/util/ip_match.in
@@ -0,0 +1,26 @@
+1.2.3.4
+1.2.300.4
+1.2.3000.4
+1.2.3.
+1.2.3
+a
+1.2.3;4
+1.2.[3].4
+1.2.[].4
+1.2.[.4
+1.2.].4
+1.2.[1..127;128..255].5
+1.2.[1-255].5
+1.2.[1..127.128..255].5
+1.2.3.[4]
+1.2.3.[4..1]
+1.2.3.[4.1]
+1.2.3.[4.x]
+1.2.3.[x]
+1.2.3.4x
+1.2.[3..11].5 1.2.3.5 1.2.2.5 1.2.11.5 1.2.12.5 1.2.11.6
+1.2.[3;5;7;9;11].5 1.2.3.5 1.2.2.5 1.2.4.5 1.2.11.5 1.2.12.5 1.2.11.6
+[1;2].3.4.5 1.3.4.5 2.3.4.5 3.3.4.5
+[[1;2].3.4.5] 1.3.4.5 2.3.4.5 3.3.4.5
+[[1;2].3.4.5
+1;2].3.4.5
diff --git a/src/util/ip_match.ref b/src/util/ip_match.ref
new file mode 100644
index 0000000..04b291f
--- /dev/null
+++ b/src/util/ip_match.ref
@@ -0,0 +1,69 @@
+> 1.2.3.4
+Code: 1.2.3.4
+> 1.2.300.4
+Error: need decimal number 0..255 or "[" at "1.2.>300<.4"
+> 1.2.3000.4
+Error: need decimal number 0..255 or "[" at "1.2.>3000<.4"
+> 1.2.3.
+Error: need decimal number 0..255 or "[" at "1.2.3.><"
+> 1.2.3
+Error: need "." at "1.2.3><"
+> a
+Error: need decimal number 0..255 or "[" at ">a<"
+> 1.2.3;4
+Error: need "." at "1.2.3>;<4"
+> 1.2.[3].4
+Code: 1.2.[3].4
+> 1.2.[].4
+Error: need decimal number 0..255 at "1.2.[>]<.4"
+> 1.2.[.4
+Error: need decimal number 0..255 at "1.2.[>.<4"
+> 1.2.].4
+Error: need decimal number 0..255 or "[" at "1.2.>]<.4"
+> 1.2.[1..127;128..255].5
+Code: 1.2.[1..127;128..255].5
+> 1.2.[1-255].5
+Error: need ";" or "]" at "1.2.[1>-<255].5"
+> 1.2.[1..127.128..255].5
+Error: need ";" or "]" at "1.2.[1..127>.<128..255].5"
+> 1.2.3.[4]
+Code: 1.2.3.[4]
+> 1.2.3.[4..1]
+Error: numeric range error at "1.2.3.[4..>1<]"
+> 1.2.3.[4.1]
+Error: numeric range error at "1.2.3.[4.>1<]"
+> 1.2.3.[4.x]
+Error: numeric range error at "1.2.3.[4.>x<]"
+> 1.2.3.[x]
+Error: need decimal number 0..255 at "1.2.3.[>x<]"
+> 1.2.3.4x
+Error: garbage after pattern at "1.2.3.4>x<"
+> 1.2.[3..11].5 1.2.3.5 1.2.2.5 1.2.11.5 1.2.12.5 1.2.11.6
+Code: 1.2.[3..11].5
+Match 1.2.3.5: yes
+Match 1.2.2.5: no
+Match 1.2.11.5: yes
+Match 1.2.12.5: no
+Match 1.2.11.6: no
+> 1.2.[3;5;7;9;11].5 1.2.3.5 1.2.2.5 1.2.4.5 1.2.11.5 1.2.12.5 1.2.11.6
+Code: 1.2.[3;5;7;9;11].5
+Match 1.2.3.5: yes
+Match 1.2.2.5: no
+Match 1.2.4.5: no
+Match 1.2.11.5: yes
+Match 1.2.12.5: no
+Match 1.2.11.6: no
+> [1;2].3.4.5 1.3.4.5 2.3.4.5 3.3.4.5
+Code: [1;2].3.4.5
+Match 1.3.4.5: yes
+Match 2.3.4.5: yes
+Match 3.3.4.5: no
+> [[1;2].3.4.5] 1.3.4.5 2.3.4.5 3.3.4.5
+Code: [1;2].3.4.5
+Match 1.3.4.5: yes
+Match 2.3.4.5: yes
+Match 3.3.4.5: no
+> [[1;2].3.4.5
+Error: missing "]" character
+> 1;2].3.4.5
+Error: need "." at "1>;<2].3.4.5"
diff --git a/src/util/killme_after.c b/src/util/killme_after.c
new file mode 100644
index 0000000..34e0434
--- /dev/null
+++ b/src/util/killme_after.c
@@ -0,0 +1,69 @@
+/*++
+/* NAME
+/* killme_after 3
+/* SUMMARY
+/* programmed death
+/* SYNOPSIS
+/* #include <killme_after.h>
+/*
+/* void killme_after(seconds)
+/* unsigned int seconds;
+/* DESCRIPTION
+/* The killme_after() function does a best effort to terminate
+/* the process after the specified time, should it still exist.
+/* It is meant to be used in a signal handler, as an insurance
+/* against getting stuck somewhere while preparing for exit.
+/* DIAGNOSTICS
+/* None. This routine does a best effort, damn the torpedoes.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <signal.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include <killme_after.h>
+
+/* killme_after - self-assured death */
+
+void killme_after(unsigned int seconds)
+{
+ struct sigaction sig_action;
+
+ /*
+ * Schedule an ALARM signal, and make sure the signal will be delivered
+ * even if we are being called from a signal handler and SIGALRM delivery
+ * is blocked.
+ *
+ * Undocumented: when a process runs with PID 1, Linux won't deliver a
+ * signal unless the process specifies a handler (i.e. SIG_DFL is treated
+ * as SIG_IGN). Conveniently, _exit() can be used directly as a signal
+ * handler. This changes the wait status that a parent would see, but in
+ * the case of "init" mode on Linux, no-one would care.
+ */
+ alarm(0);
+ sigemptyset(&sig_action.sa_mask);
+ sig_action.sa_flags = 0;
+ sig_action.sa_handler = (getpid() == 1 ? _exit : SIG_DFL);
+ sigaction(SIGALRM, &sig_action, (struct sigaction *) 0);
+ alarm(seconds);
+ sigaddset(&sig_action.sa_mask, SIGALRM);
+ sigprocmask(SIG_UNBLOCK, &sig_action.sa_mask, (sigset_t *) 0);
+}
diff --git a/src/util/killme_after.h b/src/util/killme_after.h
new file mode 100644
index 0000000..9505b2d
--- /dev/null
+++ b/src/util/killme_after.h
@@ -0,0 +1,30 @@
+#ifndef _KILLME_AFTER_H_INCLUDED_
+#define _KILLME_AFTER_H_INCLUDED_
+
+/*++
+/* NAME
+/* killme_after 3h
+/* SUMMARY
+/* programmed death
+/* SYNOPSIS
+/* #include "killme_after.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern void killme_after(unsigned int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/known_tcp_ports.c b/src/util/known_tcp_ports.c
new file mode 100644
index 0000000..1d524d4
--- /dev/null
+++ b/src/util/known_tcp_ports.c
@@ -0,0 +1,253 @@
+/*++
+/* NAME
+/* known_tcp_ports 3
+/* SUMMARY
+/* reduce dependency on the services(5) database
+/* SYNOPSIS
+/* #include <known_tcp_ports.h>
+/*
+/* const char *add_known_tcp_port(
+/* const char *name)
+/* const char *port)
+/*
+/* const char *filter_known_tcp_port(
+/* const char *name_or_port)
+/*
+/* void clear_known_tcp_ports(void)
+/* AUXILIARY FUNCTIONS
+/* char *export_known_tcp_ports(
+/* VSTRING *result)
+/* DESCRIPTION
+/* This module reduces dependency on the services(5) database.
+/*
+/* add_known_tcp_port() associates a symbolic name with a numerical
+/* port. The function returns a pointer to error text if the
+/* arguments are malformed or if the symbolic name already has
+/* an association.
+/*
+/* filter_known_tcp_port() returns the argument if it does not
+/* specify a symbolic name, or if the argument specifies a symbolic
+/* name that is not associated with a numerical port. Otherwise,
+/* it returns the associated numerical port.
+/*
+/* clear_known_tcp_ports() destroys all name-number associations.
+/* string.
+/*
+/* export_known_tcp_ports() overwrites a VSTRING with all known
+/* name=port associations, sorted by service name, and separated
+/* by whitespace. The result is pointer to the VSTRING payload.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library
+ */
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <string.h>
+
+ /*
+ * Utility library
+ */
+#include <htable.h>
+#include <mymalloc.h>
+#include <stringops.h>
+
+ /*
+ * Application-specific.
+ */
+#include <known_tcp_ports.h>
+
+#define STR(x) vstring_str(x)
+
+static HTABLE *known_tcp_ports;
+
+/* add_known_tcp_port - associate symbolic name with numerical port */
+
+const char *add_known_tcp_port(const char *name, const char *port)
+{
+ if (alldig(name))
+ return ("numerical service name");
+ if (!alldig(port))
+ return ("non-numerical service port");
+ if (known_tcp_ports == 0)
+ known_tcp_ports = htable_create(10);
+ if (htable_locate(known_tcp_ports, name) != 0)
+ return ("duplicate service name");
+ (void) htable_enter(known_tcp_ports, name, mystrdup(port));
+ return (0);
+}
+
+/* filter_known_tcp_port - replace argument if associated with known port */
+
+const char *filter_known_tcp_port(const char *name_or_port)
+{
+ HTABLE_INFO *ht;
+
+ if (name_or_port == 0 || known_tcp_ports == 0 || alldig(name_or_port)) {
+ return (name_or_port);
+ } else if ((ht = htable_locate(known_tcp_ports, name_or_port)) != 0) {
+ return (ht->value);
+ } else {
+ return (name_or_port);
+ }
+}
+
+/* clear_known_tcp_ports - destroy all name-port associations */
+
+void clear_known_tcp_ports(void)
+{
+ htable_free(known_tcp_ports, myfree);
+ known_tcp_ports = 0;
+}
+
+/* compare_ht_keys - compare table keys */
+
+static int compare_ht_keys(const void *a, const void *b)
+{
+ HTABLE_INFO **ap = (HTABLE_INFO **) a;
+ HTABLE_INFO **bp = (HTABLE_INFO **) b;
+
+ return (strcmp((const char *) ap[0]->key, (const char *) bp[0]->key));
+}
+
+/* export_known_tcp_ports - sorted dump */
+
+char *export_known_tcp_ports(VSTRING *out)
+{
+ HTABLE_INFO **list;
+ HTABLE_INFO **ht;
+
+ VSTRING_RESET(out);
+ if (known_tcp_ports) {
+ list = htable_list(known_tcp_ports);
+ qsort((void *) list, known_tcp_ports->used, sizeof(*list),
+ compare_ht_keys);
+ for (ht = list; *ht; ht++)
+ vstring_sprintf_append(out, "%s%s=%s", ht > list ? " " : "",
+ ht[0]->key, (const char *) ht[0]->value);
+ myfree((void *) list);
+ }
+ VSTRING_TERMINATE(out);
+ return (STR(out));
+}
+
+#ifdef TEST
+
+#include <msg.h>
+
+struct association {
+ const char *lhs; /* service name */
+ const char *rhs; /* service port */
+};
+
+struct probe {
+ const char *query; /* query */
+ const char *exp_reply; /* expected reply */
+};
+
+struct test_case {
+ const char *label; /* identifies test case */
+ struct association associations[10];
+ const char *exp_err; /* expected error */
+ const char *exp_export; /* expected export output */
+ struct probe probes[10];
+};
+
+struct test_case test_cases[] = {
+ {"good",
+ /* association */ {{"smtp", "25"}, {"lmtp", "24"}, 0},
+ /* error */ 0,
+ /* export */ "lmtp=24 smtp=25",
+ /* probe */ {{"smtp", "25"}, {"1", "1"}, {"x", "x"}, {"lmtp", "24"}, 0}
+ },
+ {"duplicate lhs",
+ /* association */ {{"smtp", "25"}, {"smtp", "100"}, 0},
+ /* error */ "duplicate service name"
+ },
+ {"numerical lhs",
+ /* association */ {{"100", "100"}, 0},
+ /* error */ "numerical service name"
+ },
+ {"symbolic rhs",
+ /* association */ {{"smtp", "lmtp"}, 0},
+ /* error */ "non-numerical service port"
+ },
+ {"uninitialized",
+ /* association */ {0},
+ /* error */ 0,
+ /* export */ "",
+ /* probe */ {{"smtp", "smtp"}, {"1", "1"}, {"x", "x"}, 0}
+ },
+ 0,
+};
+
+int main(int argc, char **argv)
+{
+ VSTRING *export_buf;
+ struct test_case *tp;
+ struct association *ap;
+ struct probe *pp;
+ int pass = 0;
+ int fail = 0;
+ const char *err;
+ int test_failed;
+ const char *reply;
+ const char *export;
+
+#define STRING_OR_NULL(s) ((s) ? (s) : "(null)")
+
+ export_buf = vstring_alloc(100);
+ for (tp = test_cases; tp->label != 0; tp++) {
+ test_failed = 0;
+ for (err = 0, ap = tp->associations; err == 0 && ap->lhs != 0; ap++)
+ err = add_known_tcp_port(ap->lhs, ap->rhs);
+ if (!err != !tp->exp_err) {
+ msg_warn("test case %s: got error: \"%s\", want: \"%s\"",
+ tp->label, STRING_OR_NULL(err), STRING_OR_NULL(tp->exp_err));
+ test_failed = 1;
+ } else if (err != 0) {
+ if (strcmp(err, tp->exp_err) != 0) {
+ msg_warn("test case %s: got err: \"%s\", want: \"%s\"",
+ tp->label, err, tp->exp_err);
+ test_failed = 1;
+ }
+ } else {
+ export = export_known_tcp_ports(export_buf);
+ if (strcmp(export, tp->exp_export) != 0) {
+ msg_warn("test case %s: got export: \"%s\", want: \"%s\"",
+ tp->label, export, tp->exp_export);
+ test_failed = 1;
+ }
+ for (pp = tp->probes; test_failed == 0 && pp->query != 0; pp++) {
+ reply = filter_known_tcp_port(pp->query);
+ if (strcmp(reply, pp->exp_reply) != 0) {
+ msg_warn("test case %s: got reply: \"%s\", want: \"%s\"",
+ tp->label, reply, pp->exp_reply);
+ test_failed = 1;
+ }
+ }
+ }
+ clear_known_tcp_ports();
+ if (test_failed) {
+ msg_info("%s: FAIL", tp->label);
+ fail++;
+ } else {
+ msg_info("%s: PASS", tp->label);
+ pass++;
+ }
+ }
+ msg_info("PASS=%d FAIL=%d", pass, fail);
+ vstring_free(export_buf);
+ exit(fail != 0);
+}
+
+#endif
diff --git a/src/util/known_tcp_ports.h b/src/util/known_tcp_ports.h
new file mode 100644
index 0000000..bc254f2
--- /dev/null
+++ b/src/util/known_tcp_ports.h
@@ -0,0 +1,38 @@
+#ifndef _KNOWN_TCP_PORTS_H_INCLUDED_
+#define _KNOWN_TCP_PORTS_H_INCLUDED_
+
+/*++
+/* NAME
+/* known_tcp_port 3h
+/* SUMMARY
+/* reduce dependency on the services(5) database
+/* SYNOPSIS
+/* #include <known_tcp_ports.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern const char *add_known_tcp_port(const char *name, const char *port);
+extern const char *filter_known_tcp_port(const char *name_or_port);
+extern void clear_known_tcp_ports(void);
+extern char *export_known_tcp_ports(VSTRING *out);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/known_tcp_ports.ref b/src/util/known_tcp_ports.ref
new file mode 100644
index 0000000..adcf182
--- /dev/null
+++ b/src/util/known_tcp_ports.ref
@@ -0,0 +1,6 @@
+unknown: good: PASS
+unknown: duplicate lhs: PASS
+unknown: numerical lhs: PASS
+unknown: symbolic rhs: PASS
+unknown: uninitialized: PASS
+unknown: PASS=5 FAIL=0
diff --git a/src/util/ldseed.c b/src/util/ldseed.c
new file mode 100644
index 0000000..c231152
--- /dev/null
+++ b/src/util/ldseed.c
@@ -0,0 +1,138 @@
+/*++
+/* NAME
+/* ldseed 3
+/* SUMMARY
+/* seed for non-cryptographic applications
+/* SYNOPSIS
+/* #include <ldseed.h>
+/*
+/* void ldseed(
+/* void *dst,
+/* size_t len)
+/* DESCRIPTION
+/* ldseed() preferably extracts pseudo-random bits from
+/* /dev/urandom, a non-blocking device that is available on
+/* modern systems.
+/*
+/* On systems where /dev/urandom is unavailable or does not
+/* immediately return the requested amount of randomness,
+/* ldseed() falls back to a combination of wallclock time,
+/* the time since boot, and the process ID.
+/* BUGS
+/* With Linux "the O_NONBLOCK flag has no effect when opening
+/* /dev/urandom", but reads "can incur an appreciable delay
+/* when requesting large amounts of data". Apparently, "large"
+/* means more than 256 bytes.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library
+ */
+#include <sys_defs.h>
+#include <string.h>
+#include <sys/time.h>
+#include <time.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h> /* CHAR_BIT */
+
+ /*
+ * Utility library.
+ */
+#include <iostuff.h>
+#include <msg.h>
+#include <ldseed.h>
+
+ /*
+ * Different systems have different names for non-wallclock time.
+ */
+#ifdef CLOCK_UPTIME
+#define NON_WALLTIME_CLOCK CLOCK_UPTIME
+#elif defined(CLOCK_BOOTTIME)
+#define NON_WALLTIME_CLOCK CLOCK_BOOTTIME
+#elif defined(CLOCK_MONOTONIC)
+#define NON_WALLTIME_CLOCK CLOCK_MONOTONIC
+#elif defined(CLOCK_HIGHRES)
+#define NON_WALLTIME_CLOCK CLOCK_HIGHRES
+#endif
+
+/* ldseed - best-effort, low-dependency seed */
+
+void ldseed(void *dst, size_t len)
+{
+ int count;
+ int fd;
+ int n;
+ time_t fallback = 0;
+
+ /*
+ * Medium-quality seed.
+ */
+ if ((fd = open("/dev/urandom", O_RDONLY)) > 0) {
+ non_blocking(fd, NON_BLOCKING);
+ count = read(fd, dst, len);
+ (void) close(fd);
+ if (count == len)
+ return;
+ }
+
+ /*
+ * Low-quality seed. Based on 1) the time since boot (good when an
+ * attacker knows the program start time but not the system boot time),
+ * and 2) absolute time (good when an attacker does not know the program
+ * start time). Assumes a system with better than microsecond resolution,
+ * and a network stack that does not leak the time since boot, for
+ * example, through TCP or ICMP timestamps. With those caveats, this seed
+ * is good for 20-30 bits of randomness.
+ */
+#ifdef NON_WALLTIME_CLOCK
+ {
+ struct timespec ts;
+
+ if (clock_gettime(NON_WALLTIME_CLOCK, &ts) != 0)
+ msg_fatal("clock_gettime() failed: %m");
+ fallback += ts.tv_sec ^ ts.tv_nsec;
+ }
+#elif defined(USE_GETHRTIME)
+ fallback += gethrtime();
+#endif
+
+#ifdef CLOCK_REALTIME
+ {
+ struct timespec ts;
+
+ if (clock_gettime(CLOCK_REALTIME, &ts) != 0)
+ msg_fatal("clock_gettime() failed: %m");
+ fallback += ts.tv_sec ^ ts.tv_nsec;
+ }
+#else
+ {
+ struct timeval tv;
+
+ if (GETTIMEOFDAY(&tv) != 0)
+ msg_fatal("gettimeofday() failed: %m");
+ fallback += tv.tv_sec + tv.tv_usec;
+ }
+#endif
+ fallback += getpid();
+
+ /*
+ * Copy the least significant bytes first, because those are the most
+ * volatile.
+ */
+ for (n = 0; n < sizeof(fallback) && n < len; n++) {
+ *(char *) dst++ ^= (fallback & 0xff);
+ fallback >>= CHAR_BIT;
+ }
+ return;
+}
diff --git a/src/util/ldseed.h b/src/util/ldseed.h
new file mode 100644
index 0000000..891986e
--- /dev/null
+++ b/src/util/ldseed.h
@@ -0,0 +1,30 @@
+#ifndef _LDSEED_H_INCLUDED_
+#define _LDSEED_H_INCLUDED_
+
+/*++
+/* NAME
+/* ldseed 3h
+/* SUMMARY
+/* seed for non-cryptographic applications
+/* SYNOPSIS
+/* #include <ldseed.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern void ldseed(void *, size_t);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/line_number.c b/src/util/line_number.c
new file mode 100644
index 0000000..557e053
--- /dev/null
+++ b/src/util/line_number.c
@@ -0,0 +1,71 @@
+/*++
+/* NAME
+/* line_number 3
+/* SUMMARY
+/* line number utilities
+/* SYNOPSIS
+/* #include <line_number.h>
+/*
+/* char *format_line_number(result, first, last)
+/* VSTRING *buffer;
+/* ssize_t first;
+/* ssize_t lastl
+/* DESCRIPTION
+/* format_line_number() formats a line number or number range.
+/* The output is <first-number>-<last-number> when the numbers
+/* differ, <first-number> when the numbers are identical.
+/* .IP result
+/* Result buffer, or null-pointer. In the latter case the
+/* result is stored in a static buffer that is overwritten
+/* with subsequent calls. The function result value is a
+/* pointer into the result buffer.
+/* .IP first
+/* First line number.
+/* .IP last
+/* Last line number.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+#include <line_number.h>
+
+/* format_line_number - pretty-print line number or number range */
+
+char *format_line_number(VSTRING *result, ssize_t first, ssize_t last)
+{
+ static VSTRING *buf;
+
+ /*
+ * Your buffer or mine?
+ */
+ if (result == 0) {
+ if (buf == 0)
+ buf = vstring_alloc(10);
+ result = buf;
+ }
+
+ /*
+ * Print a range only when the numbers differ.
+ */
+ vstring_sprintf(result, "%ld", (long) first);
+ if (first != last)
+ vstring_sprintf_append(result, "-%ld", (long) last);
+
+ return (vstring_str(result));
+}
diff --git a/src/util/line_number.h b/src/util/line_number.h
new file mode 100644
index 0000000..0a53d15
--- /dev/null
+++ b/src/util/line_number.h
@@ -0,0 +1,35 @@
+#ifndef _LINE_NUMBER_H_INCLUDED_
+#define _LINE_NUMBER_H_INCLUDED_
+
+/*++
+/* NAME
+/* line_number 3h
+/* SUMMARY
+/* line number utilities
+/* SYNOPSIS
+/* #include <line_number.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern char *format_line_number(VSTRING *, ssize_t, ssize_t);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/line_wrap.c b/src/util/line_wrap.c
new file mode 100644
index 0000000..0f399e8
--- /dev/null
+++ b/src/util/line_wrap.c
@@ -0,0 +1,122 @@
+/*++
+/* NAME
+/* line_wrap 3
+/* SUMMARY
+/* wrap long lines upon output
+/* SYNOPSIS
+/* #include <line_wrap.h>
+/*
+/* void line_wrap(string, len, indent, output_fn, context)
+/* const char *buf;
+/* int len;
+/* int indent;
+/* void (*output_fn)(const char *str, int len, int indent, void *context);
+/* void *context;
+/* DESCRIPTION
+/* The \fBline_wrap\fR routine outputs the specified string via
+/* the specified output function, and attempts to keep output lines
+/* shorter than the specified length. The routine does not attempt to
+/* break long words that do not fit on a single line. Upon output,
+/* trailing whitespace is stripped.
+/*
+/* Arguments
+/* .IP string
+/* The input, which cannot contain any newline characters.
+/* .IP len
+/* The desired maximal output line length.
+/* .IP indent
+/* The desired amount of indentation of the second etc. output lines
+/* with respect to the first output line. A negative indent causes
+/* only the first line to be indented; a positive indent causes all
+/* but the first line to be indented. A zero count causes no indentation.
+/* .IP output_fn
+/* The output function that is called with as arguments a string
+/* pointer, a string length, a non-negative indentation count, and
+/* application context. A typical implementation looks like this:
+/* .sp
+/* .nf
+/* .na
+void print(const char *str, int len, int indent, void *context)
+{
+ VSTREAM *fp = (VSTREAM *) context;
+
+ vstream_fprintf(fp, "%*s%.*s", indent, "", len, str);
+}
+/* .fi
+/* .ad
+/* .IP context
+/* Application context that is passed on to the output function.
+/* For example, a VSTREAM pointer, or a structure that contains
+/* a VSTREAM pointer.
+/* BUGS
+/* No tab expansion and no backspace processing.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <line_wrap.h>
+
+/* line_wrap - wrap long lines upon output */
+
+void line_wrap(const char *str, int len, int indent, LINE_WRAP_FN output_fn,
+ void *context)
+{
+ const char *start_line;
+ const char *word;
+ const char *next_word;
+ const char *next_space;
+ int line_len;
+ int curr_len;
+ int curr_indent;
+
+ if (indent < 0) {
+ curr_indent = -indent;
+ curr_len = len + indent;
+ } else {
+ curr_indent = 0;
+ curr_len = len;
+ }
+
+ /*
+ * At strategic positions, output what we have seen, after stripping off
+ * trailing blanks.
+ */
+ for (start_line = word = str; word != 0; word = next_word) {
+ next_space = word + strcspn(word, " \t");
+ if (word > start_line) {
+ if (next_space - start_line > curr_len) {
+ line_len = word - start_line;
+ while (line_len > 0 && ISSPACE(start_line[line_len - 1]))
+ line_len--;
+ output_fn(start_line, line_len, curr_indent, context);
+ while (*word && ISSPACE(*word))
+ word++;
+ if (start_line == str) {
+ curr_indent += indent;
+ curr_len -= indent;
+ }
+ start_line = word;
+ }
+ }
+ next_word = *next_space ? next_space + 1 : 0;
+ }
+ line_len = strlen(start_line);
+ while (line_len > 0 && ISSPACE(start_line[line_len - 1]))
+ line_len--;
+ output_fn(start_line, line_len, curr_indent, context);
+}
diff --git a/src/util/line_wrap.h b/src/util/line_wrap.h
new file mode 100644
index 0000000..32c3548
--- /dev/null
+++ b/src/util/line_wrap.h
@@ -0,0 +1,31 @@
+#ifndef _LINE_WRAP_H_INCLUDED_
+#define _LINE_WRAP_H_INCLUDED_
+
+/*++
+/* NAME
+/* line_wrap 3h
+/* SUMMARY
+/* wrap long lines upon output
+/* SYNOPSIS
+/* #include <line_wrap.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+typedef void (*LINE_WRAP_FN) (const char *, int, int, void *);
+extern void line_wrap(const char *, int, int, LINE_WRAP_FN, void *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/listen.h b/src/util/listen.h
new file mode 100644
index 0000000..131755c
--- /dev/null
+++ b/src/util/listen.h
@@ -0,0 +1,53 @@
+#ifndef _LISTEN_H_INCLUDED_
+#define _LISTEN_H_INCLUDED_
+
+/*++
+/* NAME
+/* listen 3h
+/* SUMMARY
+/* listener interface file
+/* SYNOPSIS
+/* #include <listen.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <iostuff.h>
+#include <htable.h>
+
+ /*
+ * Listener external interface.
+ */
+extern int unix_listen(const char *, int, int);
+extern int inet_listen(const char *, int, int);
+extern int fifo_listen(const char *, int, int);
+extern int stream_listen(const char *, int, int);
+extern int unix_dgram_listen(const char *, int);
+
+extern int inet_accept(int);
+extern int unix_accept(int);
+extern int stream_accept(int);
+
+extern int WARN_UNUSED_RESULT recv_pass_attr(int, HTABLE **, int, ssize_t);
+extern int pass_accept(int);
+extern int pass_accept_attr(int, HTABLE **);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/lmdb_cache_test_1.sh b/src/util/lmdb_cache_test_1.sh
new file mode 100644
index 0000000..f5d4767
--- /dev/null
+++ b/src/util/lmdb_cache_test_1.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+
+# Torture test: run update and purge operations in parallel processes.
+# This will result in some purge operations not finding all entries,
+# but the final sequential purge should eliminate all.
+
+set -e
+
+rm -f foo.lmdb
+./dict_cache <<EOF
+lmdb_map_size 20000
+cache lmdb:foo
+EOF
+
+(./dict_cache <<EOF
+cache lmdb:foo
+update x ${1-10000}
+run
+update y ${1-10000}
+purge x
+run
+purge y
+run
+EOF
+) &
+
+(./dict_cache <<EOF
+cache lmdb:foo
+update a ${1-10000}
+run
+update b ${1-10000}
+purge a
+run
+purge b
+run
+EOF
+) &
+
+wait
+
+./dict_cache <<EOF
+cache lmdb:foo
+purge a
+run
+purge b
+run
+purge x
+run
+purge y
+run
+EOF
+
+../../bin/postmap -s lmdb:foo | diff /dev/null -
+
+rm -f foo.lmdb
diff --git a/src/util/lmdb_cache_test_2.sh b/src/util/lmdb_cache_test_2.sh
new file mode 100644
index 0000000..a54e7bd
--- /dev/null
+++ b/src/util/lmdb_cache_test_2.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+# Torture test: run update and delete ${1-10000}perations in
+# parallel processes. None should remain.
+
+set -e
+
+rm -f foo.lmdb
+./dict_cache <<EOF
+lmdb_map_size 20000
+cache lmdb:foo
+EOF
+
+(./dict_cache <<EOF
+cache lmdb:foo
+update x ${1-10000}
+run
+update y ${1-10000}
+delete x ${1-10000}
+run
+delete y ${1-10000}
+run
+EOF
+) &
+
+(./dict_cache <<EOF
+cache lmdb:foo
+update a ${1-10000}
+run
+update b ${1-10000}
+delete a ${1-10000}
+run
+delete b ${1-10000}
+run
+EOF
+) &
+
+wait
+
+../../bin/postmap -s lmdb:foo | diff /dev/null -
+
+rm -f foo.lmdb
diff --git a/src/util/load_file.c b/src/util/load_file.c
new file mode 100644
index 0000000..4e575d1
--- /dev/null
+++ b/src/util/load_file.c
@@ -0,0 +1,80 @@
+/*++
+/* NAME
+/* load_file 3
+/* SUMMARY
+/* load file with some prejudice
+/* SYNOPSIS
+/* #include <load_file.h>
+/*
+/* void load_file(path, action, context)
+/* const char *path;
+/* void (*action)(VSTREAM, void *);
+/* void *context;
+/* DESCRIPTION
+/* This routine reads a file and reads it again when the
+/* file changed recently.
+/*
+/* Arguments:
+/* .IP path
+/* The file to be opened, read-only.
+/* .IP action
+/* The function that presumably reads the file.
+/* .IP context
+/* Application-specific context for the action routine.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory, cannot open file.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <time.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <iostuff.h>
+#include <load_file.h>
+#include <warn_stat.h>
+
+/* load_file - load file with some prejudice */
+
+void load_file(const char *path, LOAD_FILE_FN action, void *context)
+{
+ VSTREAM *fp;
+ struct stat st;
+ time_t before;
+ time_t after;
+
+ /*
+ * Read the file again if it is hot. This may result in reading a partial
+ * parameter name or missing end marker when a file changes in the middle
+ * of a read.
+ */
+ for (before = time((time_t *) 0); /* see below */ ; before = after) {
+ if ((fp = vstream_fopen(path, O_RDONLY, 0)) == 0)
+ msg_fatal("open %s: %m", path);
+ action(fp, context);
+ if (fstat(vstream_fileno(fp), &st) < 0)
+ msg_fatal("fstat %s: %m", path);
+ if (vstream_ferror(fp) || vstream_fclose(fp))
+ msg_fatal("read %s: %m", path);
+ after = time((time_t *) 0);
+ if (st.st_mtime < before - 1 || st.st_mtime > after)
+ break;
+ if (msg_verbose)
+ msg_info("pausing to let %s cool down", path);
+ doze(300000);
+ }
+}
diff --git a/src/util/load_file.h b/src/util/load_file.h
new file mode 100644
index 0000000..3e635f3
--- /dev/null
+++ b/src/util/load_file.h
@@ -0,0 +1,32 @@
+#ifndef LOAD_FILE_H_INCLUDED_
+#define LOAD_FILE_H_INCLUDED_
+
+/*++
+/* NAME
+/* load_file 3h
+/* SUMMARY
+/* load file with some prejudice
+/* SYNOPSIS
+/* #include <load_file.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+typedef void (*LOAD_FILE_FN)(VSTREAM *, void *);
+
+extern void load_file(const char *, LOAD_FILE_FN, void *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/load_lib.c b/src/util/load_lib.c
new file mode 100644
index 0000000..44e540d
--- /dev/null
+++ b/src/util/load_lib.c
@@ -0,0 +1,146 @@
+/*++
+/* NAME
+/* load_lib 3
+/* SUMMARY
+/* library loading wrappers
+/* SYNOPSIS
+/* #include <load_lib.h>
+/*
+/* void load_library_symbols(const char *, LIB_FN *, LIB_DP *);
+/* const char *libname;
+/* LIB_FN *libfuncs;
+/* LIB_DP *libdata;
+/* DESCRIPTION
+/* load_library_symbols() loads the specified shared object
+/* and looks up the function or data pointers for the specified
+/* symbols. All errors are fatal.
+/*
+/* Arguments:
+/* .IP libname
+/* shared-library pathname.
+/* .IP libfuncs
+/* Array of LIB_FN structures. The last name member must be null.
+/* .IP libdata
+/* Array of LIB_DP structures. The last name member must be null.
+/* SEE ALSO
+/* msg(3) diagnostics interface
+/* DIAGNOSTICS
+/* Problems are reported via the msg(3) diagnostics routines:
+/* library not found, symbols not found, other fatal errors.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* LaMont Jones
+/* Hewlett-Packard Company
+/* 3404 Harmony Road
+/* Fort Collins, CO 80528, USA
+/*
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+ /*
+ * System libraries.
+ */
+#include "sys_defs.h"
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#ifdef USE_DYNAMIC_MAPS
+#if defined(HAS_DLOPEN)
+#include <dlfcn.h>
+#elif defined(HAS_SHL_LOAD)
+#include <dl.h>
+#else
+#error "USE_DYNAMIC_LIBS requires HAS_DLOPEN or HAS_SHL_LOAD"
+#endif
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <load_lib.h>
+
+/* load_library_symbols - load shared library and look up symbols */
+
+void load_library_symbols(const char *libname, LIB_FN *libfuncs,
+ LIB_DP *libdata)
+{
+ static const char myname[] = "load_library_symbols";
+ LIB_FN *fn;
+ LIB_DP *dp;
+
+#if defined(HAS_DLOPEN)
+ void *handle;
+ char *emsg;
+
+ /*
+ * XXX This is basically how FreeBSD dlfunc() silences a compiler warning
+ * about a data/function pointer conversion. The solution below is non-
+ * portable: it assumes that both data and function pointers are the same
+ * in size, and that both have the same representation.
+ */
+ union {
+ void *dptr; /* data pointer */
+ void (*fptr) (void); /* function pointer */
+ } non_portable_union;
+
+ if ((handle = dlopen(libname, RTLD_NOW)) == 0) {
+ emsg = dlerror();
+ msg_fatal("%s: dlopen failure loading %s: %s", myname, libname,
+ emsg ? emsg : "don't know why");
+ }
+ if (libfuncs) {
+ for (fn = libfuncs; fn->name; fn++) {
+ if ((non_portable_union.dptr = dlsym(handle, fn->name)) == 0) {
+ emsg = dlerror();
+ msg_fatal("%s: dlsym failure looking up %s in %s: %s", myname,
+ fn->name, libname, emsg ? emsg : "don't know why");
+ }
+ fn->fptr = non_portable_union.fptr;
+ if (msg_verbose > 1)
+ msg_info("loaded %s = %p", fn->name, non_portable_union.dptr);
+ }
+ }
+ if (libdata) {
+ for (dp = libdata; dp->name; dp++) {
+ if ((dp->dptr = dlsym(handle, dp->name)) == 0) {
+ emsg = dlerror();
+ msg_fatal("%s: dlsym failure looking up %s in %s: %s", myname,
+ dp->name, libname, emsg ? emsg : "don't know why");
+ }
+ if (msg_verbose > 1)
+ msg_info("loaded %s = %p", dp->name, dp->dptr);
+ }
+ }
+#elif defined(HAS_SHL_LOAD)
+ shl_t handle;
+
+ handle = shl_load(libname, BIND_IMMEDIATE, 0);
+
+ if (libfuncs) {
+ for (fn = libfuncs; fn->name; fn++) {
+ if (shl_findsym(&handle, fn->name, TYPE_PROCEDURE, &fn->fptr) != 0)
+ msg_fatal("%s: shl_findsym failure looking up %s in %s: %m",
+ myname, fn->name, libname);
+ if (msg_verbose > 1)
+ msg_info("loaded %s = %p", fn->name, (void *) fn->fptr);
+ }
+ }
+ if (libdata) {
+ for (dp = libdata; dp->name; dp++) {
+ if (shl_findsym(&handle, dp->name, TYPE_DATA, &dp->dptr) != 0)
+ msg_fatal("%s: shl_findsym failure looking up %s in %s: %m",
+ myname, dp->name, libname);
+ if (msg_verbose > 1)
+ msg_info("loaded %s = %p", dp->name, dp->dptr);
+ }
+ }
+#endif
+}
+
+#endif
diff --git a/src/util/load_lib.h b/src/util/load_lib.h
new file mode 100644
index 0000000..1c999c5
--- /dev/null
+++ b/src/util/load_lib.h
@@ -0,0 +1,46 @@
+#ifndef _LOAD_LIB_H_INCLUDED_
+#define _LOAD_LIB_H_INCLUDED_
+
+/*++
+/* NAME
+/* load_lib 3h
+/* SUMMARY
+/* library loading wrappers
+/* SYNOPSIS
+/* #include "load_lib.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+/* NULL name terminates list */
+typedef struct LIB_FN {
+ const char *name;
+ void (*fptr)(void);
+} LIB_FN;
+
+typedef struct LIB_DP {
+ const char *name;
+ void *dptr;
+} LIB_DP;
+
+extern void load_library_symbols(const char *, LIB_FN *, LIB_DP *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* LaMont Jones
+/* Hewlett-Packard Company
+/* 3404 Harmony Road
+/* Fort Collins, CO 80528, USA
+/*
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/logwriter.c b/src/util/logwriter.c
new file mode 100644
index 0000000..aea2767
--- /dev/null
+++ b/src/util/logwriter.c
@@ -0,0 +1,124 @@
+/*++
+/* NAME
+/* logwriter 3
+/* SUMMARY
+/* logfile writer
+/* SYNOPSIS
+/* #include <logwriter.h>
+/*
+/* VSTREAM *logwriter_open_or_die(
+/* const char *path)
+/*
+/* int logwriter_write(
+/* VSTREAM *file,
+/* const char *buffer.
+/* ssize_t buflen)
+/*
+/* int logwriter_close(
+/* VSTREAM *file)
+/*
+/* int logwriter_one_shot(
+/* const char *path,
+/* const char *buffer,
+/* ssize_t buflen)
+/* DESCRIPTION
+/* This module manages a logfile writer.
+/*
+/* logwriter_open_or_die() safely opens the specified file in
+/* write+append mode. File open/create errors are fatal.
+/*
+/* logwriter_write() writes the buffer plus newline to the
+/* open logfile. The result is zero if successful, VSTREAM_EOF
+/* if the operation failed.
+/*
+/* logwriter_close() closes the logfile and destroys the VSTREAM
+/* instance. The result is zero if there were no errors writing
+/* the file, VSTREAM_EOF otherwise.
+/*
+/* logwriter_one_shot() combines all the above operations. The
+/* result is zero if successful, VSTREAM_EOF if any operation
+/* failed.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+ /*
+ * Utility library.
+ */
+#include <iostuff.h>
+#include <logwriter.h>
+#include <msg.h>
+#include <mymalloc.h>
+#include <safe_open.h>
+#include <vstream.h>
+
+ /*
+ * Application-specific.
+ */
+
+/* logwriter_open_or_die - open logfile */
+
+VSTREAM *logwriter_open_or_die(const char *path)
+{
+ VSTREAM *fp;
+ VSTRING *why = vstring_alloc(100);
+
+#define NO_STATP ((struct stat *) 0)
+#define NO_CHOWN (-1)
+#define NO_CHGRP (-1)
+
+ fp = safe_open(path, O_CREAT | O_WRONLY | O_APPEND, 0644,
+ NO_STATP, NO_CHOWN, NO_CHGRP, why);
+ if (fp == 0)
+ msg_fatal("open logfile '%s': %s", path, vstring_str(why));
+ close_on_exec(vstream_fileno(fp), CLOSE_ON_EXEC);
+ vstring_free(why);
+ return (fp);
+}
+
+/* logwriter_write - append to logfile */
+
+int logwriter_write(VSTREAM *fp, const char *buf, ssize_t len)
+{
+ if (len < 0)
+ msg_panic("logwriter_write: negative length %ld", (long) len);
+ if (vstream_fwrite(fp, buf, len) != len)
+ return (VSTREAM_EOF);
+ VSTREAM_PUTC('\n', fp);
+ return (vstream_fflush(fp));
+}
+
+/* logwriter_close - close logfile */
+
+int logwriter_close(VSTREAM *fp)
+{
+ return (vstream_fclose(fp));
+}
+
+/* logwriter_one_shot - one-shot logwriter */
+
+int logwriter_one_shot(const char *path, const char *buf, ssize_t len)
+{
+ VSTREAM *fp;
+ int err;
+
+ fp = logwriter_open_or_die(path);
+ err = logwriter_write(fp, buf, len);
+ err |= logwriter_close(fp);
+ return (err ? VSTREAM_EOF : 0);
+}
diff --git a/src/util/logwriter.h b/src/util/logwriter.h
new file mode 100644
index 0000000..f5266e4
--- /dev/null
+++ b/src/util/logwriter.h
@@ -0,0 +1,38 @@
+#ifndef _LOGWRITER_H_INCLUDED_
+#define _LOGWRITER_H_INCLUDED_
+
+/*++
+/* NAME
+/* logwriter 3h
+/* SUMMARY
+/* logfile writer
+/* SYNOPSIS
+/* #include <logwriter.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+
+ /*
+ * External interface.
+ */
+extern VSTREAM *logwriter_open_or_die(const char *);
+extern int logwriter_write(VSTREAM *, const char *, ssize_t);
+extern int logwriter_close(VSTREAM *);
+extern int logwriter_one_shot(const char *, const char *, ssize_t);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/lowercase.c b/src/util/lowercase.c
new file mode 100644
index 0000000..f7388b4
--- /dev/null
+++ b/src/util/lowercase.c
@@ -0,0 +1,43 @@
+/*++
+/* NAME
+/* lowercase 3
+/* SUMMARY
+/* map uppercase characters to lowercase
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* char *lowercase(buf)
+/* char *buf;
+/* DESCRIPTION
+/* lowercase() replaces uppercase characters in its null-terminated
+/* input by their lowercase equivalent.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <ctype.h>
+
+/* Utility library. */
+
+#include "stringops.h"
+
+char *lowercase(char *string)
+{
+ char *cp;
+ int ch;
+
+ for (cp = string; (ch = *cp) != 0; cp++)
+ if (ISUPPER(ch))
+ *cp = TOLOWER(ch);
+ return (string);
+}
diff --git a/src/util/lstat_as.c b/src/util/lstat_as.c
new file mode 100644
index 0000000..18e0f9f
--- /dev/null
+++ b/src/util/lstat_as.c
@@ -0,0 +1,73 @@
+/*++
+/* NAME
+/* lstat_as 3
+/* SUMMARY
+/* lstat file as user
+/* SYNOPSIS
+/* #include <sys/stat.h>
+/* #include <lstat_as.h>
+/*
+/* int lstat_as(path, st, euid, egid)
+/* const char *path;
+/* struct stat *st;
+/* uid_t euid;
+/* gid_t egid;
+/* DESCRIPTION
+/* lstat_as() looks up the file status of the named \fIpath\fR,
+/* using the effective rights specified by \fIeuid\fR
+/* and \fIegid\fR, and stores the result into the structure pointed
+/* to by \fIst\fR. A -1 result means the lookup failed.
+/* This call does not follow symbolic links.
+/* DIAGNOSTICS
+/* Fatal error: no permission to change privilege level.
+/* SEE ALSO
+/* set_eugid(3) switch effective rights
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "set_eugid.h"
+#include "lstat_as.h"
+#include "warn_stat.h"
+
+/* lstat_as - lstat file as user */
+
+int lstat_as(const char *path, struct stat * st, uid_t euid, gid_t egid)
+{
+ uid_t saved_euid = geteuid();
+ gid_t saved_egid = getegid();
+ int status;
+
+ /*
+ * Switch to the target user privileges.
+ */
+ set_eugid(euid, egid);
+
+ /*
+ * Lstat that file.
+ */
+ status = lstat(path, st);
+
+ /*
+ * Restore saved privileges.
+ */
+ set_eugid(saved_euid, saved_egid);
+
+ return (status);
+}
diff --git a/src/util/lstat_as.h b/src/util/lstat_as.h
new file mode 100644
index 0000000..2195a1e
--- /dev/null
+++ b/src/util/lstat_as.h
@@ -0,0 +1,35 @@
+#ifndef _LSTAT_AS_H_INCLUDED_
+#define _LSTAT_AS_H_INCLUDED_
+
+/*++
+/* NAME
+/* lstat_as 3h
+/* SUMMARY
+/* lstat file as user
+/* SYNOPSIS
+/* #include <sys/stat.h>
+/* #include <lstat_as.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern int WARN_UNUSED_RESULT lstat_as(const char *, struct stat *, uid_t, gid_t);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/mac_expand.c b/src/util/mac_expand.c
new file mode 100644
index 0000000..f40819e
--- /dev/null
+++ b/src/util/mac_expand.c
@@ -0,0 +1,848 @@
+/*++
+/* NAME
+/* mac_expand 3
+/* SUMMARY
+/* attribute expansion
+/* SYNOPSIS
+/* #include <mac_expand.h>
+/*
+/* int mac_expand(result, pattern, flags, filter, lookup, context)
+/* VSTRING *result;
+/* const char *pattern;
+/* int flags;
+/* const char *filter;
+/* const char *lookup(const char *key, int mode, void *context)
+/* void *context;
+/* AUXILIARY FUNCTIONS
+/* typedef MAC_EXP_OP_RES (*MAC_EXPAND_RELOP_FN) (
+/* const char *left,
+/* int tok_val,
+/* const char *rite)
+/*
+/* void mac_expand_add_relop(
+/* int *tok_list,
+/* const char *suffix,
+/* MAC_EXPAND_RELOP_FN relop_eval)
+/*
+/* MAC_EXP_OP_RES mac_exp_op_res_bool[2];
+/* DESCRIPTION
+/* This module implements parameter-less named attribute
+/* expansions, both conditional and unconditional. As of Postfix
+/* 3.0 this code supports relational expression evaluation.
+/*
+/* In this text, an attribute is considered "undefined" when its value
+/* is a null pointer. Otherwise, the attribute is considered "defined"
+/* and is expected to have as value a null-terminated string.
+/*
+/* In the text below, the legacy form $(...) is equivalent to
+/* ${...}. The legacy form $(...) may eventually disappear
+/* from documentation. In the text below, the name in $name
+/* and ${name...} must contain only characters from the set
+/* [a-zA-Z0-9_].
+/*
+/* The following substitutions are supported:
+/* .IP "$name, ${name}"
+/* Unconditional attribute-based substitution. The result is the
+/* named attribute value (empty if the attribute is not defined)
+/* after optional further named attribute substitution.
+/* .IP "${name?text}, ${name?{text}}"
+/* Conditional attribute-based substitution. If the named attribute
+/* value is non-empty, the result is the given text, after
+/* named attribute expansion and relational expression evaluation.
+/* Otherwise, the result is empty. Whitespace before or after
+/* {text} is ignored.
+/* .IP "${name:text}, ${name:{text}}"
+/* Conditional attribute-based substitution. If the attribute
+/* value is empty or undefined, the expansion is the given
+/* text, after named attribute expansion and relational expression
+/* evaluation. Otherwise, the result is empty. Whitespace
+/* before or after {text} is ignored.
+/* .IP "${name?{text1}:{text2}}, ${name?{text1}:text2}"
+/* Conditional attribute-based substitution. If the named attribute
+/* value is non-empty, the result is text1. Otherwise, the
+/* result is text2. In both cases the result is subject to
+/* named attribute expansion and relational expression evaluation.
+/* Whitespace before or after {text1} or {text2} is ignored.
+/* .IP "${{text1} == ${text2} ? {text3} : {text4}}"
+/* Relational expression-based substitution. First, the content
+/* of {text1} and ${text2} is subjected to named attribute and
+/* relational expression-based substitution. Next, the relational
+/* expression is evaluated. If it evaluates to "true", the
+/* result is the content of {text3}, otherwise it is the content
+/* of {text4}, after named attribute and relational expression-based
+/* substitution. In addition to ==, this supports !=, <, <=,
+/* >=, and >. Comparisons are numerical when both operands are
+/* all digits, otherwise the comparisons are lexicographical.
+/*
+/* Arguments:
+/* .IP result
+/* Storage for the result of expansion. By default, the result
+/* is truncated upon entry.
+/* .IP pattern
+/* The string to be expanded.
+/* .IP flags
+/* Bit-wise OR of zero or more of the following:
+/* .RS
+/* .IP MAC_EXP_FLAG_RECURSE
+/* Expand attributes in lookup results. This should never be
+/* done with data whose origin is untrusted.
+/* .IP MAC_EXP_FLAG_APPEND
+/* Append text to the result buffer without truncating it.
+/* .IP MAC_EXP_FLAG_SCAN
+/* Scan the input for named attributes, including named
+/* attributes in all conditional result values. Do not expand
+/* named attributes, and do not truncate or write to the result
+/* argument.
+/* .IP MAC_EXP_FLAG_PRINTABLE
+/* Use the printable() function instead of \fIfilter\fR.
+/* .PP
+/* The constant MAC_EXP_FLAG_NONE specifies a manifest null value.
+/* .RE
+/* .IP filter
+/* A null pointer, or a null-terminated array of characters that
+/* are allowed to appear in an expansion. Illegal characters are
+/* replaced by underscores.
+/* .IP lookup
+/* The attribute lookup routine. Arguments are: the attribute name,
+/* MAC_EXP_MODE_TEST to test the existence of the named attribute
+/* or MAC_EXP_MODE_USE to use the value of the named attribute,
+/* and the caller context that was given to mac_expand(). A null
+/* result value means that the requested attribute was not defined.
+/* .IP context
+/* Caller context that is passed on to the attribute lookup routine.
+/* .PP
+/* mac_expand_add_relop() registers a function that implements
+/* support for custom relational operators. Custom operator names
+/* such as "==xxx" have two parts: a prefix that is identical to
+/* a built-in operator such as "==", and an application-specified
+/* suffix such as "xxx".
+/*
+/* Arguments:
+/* .IP tok_list
+/* A null-terminated list of MAC_EXP_OP_TOK_* values that support
+/* the custom operator suffix.
+/* .IP suffix
+/* A null-terminated alphanumeric string that specifies the custom
+/* operator suffix.
+/* .IP relop_eval
+/* A function that compares two strings according to the
+/* MAC_EXP_OP_TOK_* value specified with the tok_val argument,
+/* and that returns non-zero if the custom operator evaluates to
+/* true, zero otherwise.
+/*
+/* mac_exp_op_res_bool provides an array that converts a boolean
+/* value (0 or 1) to the corresponding MAX_EXP_OP_RES_TRUE or
+/* MAX_EXP_OP_RES_FALSE value.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory. Warnings: syntax errors, unreasonable
+/* recursion depth.
+/*
+/* The result value is the binary OR of zero or more of the following:
+/* .IP MAC_PARSE_ERROR
+/* A syntax error was found in \fBpattern\fR, or some attribute had
+/* an unreasonable nesting depth.
+/* .IP MAC_PARSE_UNDEF
+/* An attribute was expanded but its value was not defined.
+/* SEE ALSO
+/* mac_parse(3) locate macro references in string.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <htable.h>
+#include <vstring.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <name_code.h>
+#include <sane_strtol.h>
+#include <mac_parse.h>
+#include <mac_expand.h>
+
+ /*
+ * Simplifies the return of common relational operator results.
+ */
+MAC_EXP_OP_RES mac_exp_op_res_bool[2] = {
+ MAC_EXP_OP_RES_FALSE,
+ MAC_EXP_OP_RES_TRUE
+};
+
+ /*
+ * Little helper structure.
+ */
+typedef struct {
+ VSTRING *result; /* result buffer */
+ int flags; /* features */
+ const char *filter; /* character filter */
+ MAC_EXP_LOOKUP_FN lookup; /* lookup routine */
+ void *context; /* caller context */
+ int status; /* findings */
+ int level; /* nesting level */
+} MAC_EXP_CONTEXT;
+
+ /*
+ * Support for relational expressions.
+ *
+ * As of Postfix 2.2, ${attr-name?result} or ${attr-name:result} return the
+ * result respectively when the parameter value is non-empty, or when the
+ * parameter value is undefined or empty; support for the ternary ?:
+ * operator was anticipated, but not implemented for 10 years.
+ *
+ * To make ${relational-expr?result} and ${relational-expr:result} work as
+ * expected without breaking the way that ? and : work, relational
+ * expressions evaluate to a non-empty or empty value. It does not matter
+ * what non-empty value we use for TRUE. However we must not use the
+ * undefined (null pointer) value for FALSE - that would raise the
+ * MAC_PARSE_UNDEF flag.
+ *
+ * The value of a relational expression can be exposed with ${relational-expr},
+ * i.e. a relational expression that is not followed by ? or : conditional
+ * expansion.
+ */
+#define MAC_EXP_BVAL_TRUE "true"
+#define MAC_EXP_BVAL_FALSE ""
+
+ /*
+ * Relational operators. The MAC_EXP_OP_TOK_* are defined in the header
+ * file.
+ */
+#define MAC_EXP_OP_STR_EQ "=="
+#define MAC_EXP_OP_STR_NE "!="
+#define MAC_EXP_OP_STR_LT "<"
+#define MAC_EXP_OP_STR_LE "<="
+#define MAC_EXP_OP_STR_GE ">="
+#define MAC_EXP_OP_STR_GT ">"
+#define MAC_EXP_OP_STR_ANY "\"" MAC_EXP_OP_STR_EQ \
+ "\" or \"" MAC_EXP_OP_STR_NE "\"" \
+ "\" or \"" MAC_EXP_OP_STR_LT "\"" \
+ "\" or \"" MAC_EXP_OP_STR_LE "\"" \
+ "\" or \"" MAC_EXP_OP_STR_GE "\"" \
+ "\" or \"" MAC_EXP_OP_STR_GT "\""
+
+static const NAME_CODE mac_exp_op_table[] =
+{
+ MAC_EXP_OP_STR_EQ, MAC_EXP_OP_TOK_EQ,
+ MAC_EXP_OP_STR_NE, MAC_EXP_OP_TOK_NE,
+ MAC_EXP_OP_STR_LT, MAC_EXP_OP_TOK_LT,
+ MAC_EXP_OP_STR_LE, MAC_EXP_OP_TOK_LE,
+ MAC_EXP_OP_STR_GE, MAC_EXP_OP_TOK_GE,
+ MAC_EXP_OP_STR_GT, MAC_EXP_OP_TOK_GT,
+ 0, MAC_EXP_OP_TOK_NONE,
+};
+
+ /*
+ * The whitespace separator set.
+ */
+#define MAC_EXP_WHITESPACE CHARS_SPACE
+
+ /*
+ * Support for operator extensions.
+ */
+static HTABLE *mac_exp_ext_table;
+static VSTRING *mac_exp_ext_key;
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+
+/* atol_or_die - convert or die */
+
+static long atol_or_die(const char *strval)
+{
+ long result;
+ char *remainder;
+
+ result = sane_strtol(strval, &remainder, 10);
+ if (*strval == 0 /* can't happen */ || *remainder != 0 || errno == ERANGE)
+ msg_fatal("mac_exp_eval: bad conversion: %s", strval);
+ return (result);
+}
+
+/* mac_exp_eval - evaluate binary expression */
+
+static MAC_EXP_OP_RES mac_exp_eval(const char *left, int tok_val,
+ const char *rite)
+{
+ static const char myname[] = "mac_exp_eval";
+ long delta;
+
+ /*
+ * Numerical or string comparison.
+ */
+ if (alldig(left) && alldig(rite)) {
+ delta = atol_or_die(left) - atol_or_die(rite);
+ } else {
+ delta = strcmp(left, rite);
+ }
+ switch (tok_val) {
+ case MAC_EXP_OP_TOK_EQ:
+ return (mac_exp_op_res_bool[delta == 0]);
+ case MAC_EXP_OP_TOK_NE:
+ return (mac_exp_op_res_bool[delta != 0]);
+ case MAC_EXP_OP_TOK_LT:
+ return (mac_exp_op_res_bool[delta < 0]);
+ case MAC_EXP_OP_TOK_LE:
+ return (mac_exp_op_res_bool[delta <= 0]);
+ case MAC_EXP_OP_TOK_GE:
+ return (mac_exp_op_res_bool[delta >= 0]);
+ case MAC_EXP_OP_TOK_GT:
+ return (mac_exp_op_res_bool[delta > 0]);
+ default:
+ msg_panic("%s: unknown operator: %d",
+ myname, tok_val);
+ }
+}
+
+/* mac_exp_parse_error - report parse error, set error flag, return status */
+
+static int PRINTFLIKE(2, 3) mac_exp_parse_error(MAC_EXP_CONTEXT *mc,
+ const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vmsg_warn(fmt, ap);
+ va_end(ap);
+ return (mc->status |= MAC_PARSE_ERROR);
+};
+
+/* MAC_EXP_ERR_RETURN - report parse error, set error flag, return status */
+
+#define MAC_EXP_ERR_RETURN(mc, fmt, ...) do { \
+ return (mac_exp_parse_error(mc, fmt, __VA_ARGS__)); \
+ } while (0)
+
+ /*
+ * Postfix 3.0 introduces support for {text} operands. Only with these do we
+ * support the ternary ?: operator and relational operators.
+ *
+ * We cannot support operators in random text, because that would break Postfix
+ * 2.11 compatibility. For example, with the expression "${name?value}", the
+ * value is random text that may contain ':', '?', '{' and '}' characters.
+ * In particular, with Postfix 2.2 .. 2.11, "${name??foo:{b}ar}" evaluates
+ * to "?foo:{b}ar" or empty. There are explicit tests in this directory and
+ * the postconf directory to ensure that Postfix 2.11 compatibility is
+ * maintained.
+ *
+ * Ideally, future Postfix configurations enclose random text operands inside
+ * {} braces. These allow whitespace around operands, which improves
+ * readability.
+ */
+
+/* MAC_EXP_FIND_LEFT_CURLY - skip over whitespace to '{', advance read ptr */
+
+#define MAC_EXP_FIND_LEFT_CURLY(len, cp) \
+ ((cp[len = strspn(cp, MAC_EXP_WHITESPACE)] == '{') ? \
+ (cp += len) : 0)
+
+/* mac_exp_extract_curly_payload - balance {}, skip whitespace, return payload */
+
+static char *mac_exp_extract_curly_payload(MAC_EXP_CONTEXT *mc, char **bp)
+{
+ char *payload;
+ char *cp;
+ int level;
+ int ch;
+
+ /*
+ * Extract the payload and balance the {}. The caller is expected to skip
+ * leading whitespace before the {. See MAC_EXP_FIND_LEFT_CURLY().
+ */
+ for (level = 1, cp = *bp, payload = ++cp; /* see below */ ; cp++) {
+ if ((ch = *cp) == 0) {
+ mac_exp_parse_error(mc, "unbalanced {} in attribute expression: "
+ "\"%s\"",
+ *bp);
+ return (0);
+ } else if (ch == '{') {
+ level++;
+ } else if (ch == '}') {
+ if (--level <= 0)
+ break;
+ }
+ }
+ *cp++ = 0;
+
+ /*
+ * Skip trailing whitespace after }.
+ */
+ *bp = cp + strspn(cp, MAC_EXP_WHITESPACE);
+ return (payload);
+}
+
+/* mac_exp_parse_relational - parse relational expression, advance read ptr */
+
+static int mac_exp_parse_relational(MAC_EXP_CONTEXT *mc, const char **lookup,
+ char **bp)
+{
+ char *cp = *bp;
+ VSTRING *left_op_buf;
+ VSTRING *rite_op_buf;
+ const char *left_op_strval;
+ const char *rite_op_strval;
+ char *op_pos;
+ char *op_strval;
+ size_t op_len;
+ int op_tokval;
+ int op_result;
+ size_t tmp_len;
+ char *type_pos;
+ size_t type_len;
+ MAC_EXPAND_RELOP_FN relop_eval;
+
+ /*
+ * Left operand. The caller is expected to skip leading whitespace before
+ * the {. See MAC_EXP_FIND_LEFT_CURLY().
+ */
+ if ((left_op_strval = mac_exp_extract_curly_payload(mc, &cp)) == 0)
+ return (mc->status);
+
+ /*
+ * Operator. Todo: regexp operator.
+ */
+ op_pos = cp;
+ op_len = strspn(cp, "<>!=?+-*/~&|%"); /* for better diagnostics. */
+ op_strval = mystrndup(cp, op_len);
+ op_tokval = name_code(mac_exp_op_table, NAME_CODE_FLAG_NONE, op_strval);
+ myfree(op_strval);
+ if (op_tokval == MAC_EXP_OP_TOK_NONE)
+ MAC_EXP_ERR_RETURN(mc, "%s expected at: \"...%s}>>>%.20s\"",
+ MAC_EXP_OP_STR_ANY, left_op_strval, cp);
+ cp += op_len;
+
+ /*
+ * Custom operator suffix.
+ */
+ if (mac_exp_ext_table && ISALNUM(*cp)) {
+ type_pos = cp;
+ for (type_len = 1; ISALNUM(cp[type_len]); type_len++)
+ /* void */ ;
+ cp += type_len;
+ vstring_sprintf(mac_exp_ext_key, "%.*s",
+ (int) (op_len + type_len), op_pos);
+ if ((relop_eval = (MAC_EXPAND_RELOP_FN) htable_find(mac_exp_ext_table,
+ STR(mac_exp_ext_key))) == 0)
+ MAC_EXP_ERR_RETURN(mc, "bad operator suffix at: \"...%.*s>>>%.*s\"",
+ (int) op_len, op_pos, (int) type_len, type_pos);
+ } else {
+ relop_eval = mac_exp_eval;
+ }
+
+ /*
+ * Right operand. Todo: syntax may depend on operator.
+ */
+ if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp) == 0)
+ MAC_EXP_ERR_RETURN(mc, "\"{expression}\" expected at: "
+ "\"...{%s} %.*s>>>%.20s\"",
+ left_op_strval, (int) op_len, op_pos, cp);
+ if ((rite_op_strval = mac_exp_extract_curly_payload(mc, &cp)) == 0)
+ return (mc->status);
+
+ /*
+ * Evaluate the relational expression. Todo: regexp support.
+ */
+ mc->status |=
+ mac_expand(left_op_buf = vstring_alloc(100), left_op_strval,
+ mc->flags, mc->filter, mc->lookup, mc->context);
+ mc->status |=
+ mac_expand(rite_op_buf = vstring_alloc(100), rite_op_strval,
+ mc->flags, mc->filter, mc->lookup, mc->context);
+ if ((mc->flags & MAC_EXP_FLAG_SCAN) == 0
+ && (op_result = relop_eval(vstring_str(left_op_buf), op_tokval,
+ vstring_str(rite_op_buf))) == MAC_EXP_OP_RES_ERROR)
+ mc->status |= MAC_PARSE_ERROR;
+ vstring_free(left_op_buf);
+ vstring_free(rite_op_buf);
+ if (mc->status & MAC_PARSE_ERROR)
+ return (mc->status);
+
+ /*
+ * Here, we fake up a non-empty or empty parameter value lookup result,
+ * for compatibility with the historical code that looks named parameter
+ * values.
+ */
+ if (mc->flags & MAC_EXP_FLAG_SCAN) {
+ *lookup = 0;
+ } else {
+ switch (op_result) {
+ case MAC_EXP_OP_RES_TRUE:
+ *lookup = MAC_EXP_BVAL_TRUE;
+ break;
+ case MAC_EXP_OP_RES_FALSE:
+ *lookup = MAC_EXP_BVAL_FALSE;
+ break;
+ default:
+ msg_panic("mac_expand: unexpected operator result: %d", op_result);
+ }
+ }
+ *bp = cp;
+ return (0);
+}
+
+/* mac_expand_add_relop - register operator extensions */
+
+void mac_expand_add_relop(int *tok_list, const char *suffix,
+ MAC_EXPAND_RELOP_FN relop_eval)
+{
+ const char myname[] = "mac_expand_add_relop";
+ const char *tok_name;
+ int *tp;
+
+ /*
+ * Sanity checks.
+ */
+ if (!allalnum(suffix))
+ msg_panic("%s: bad operator suffix: %s", myname, suffix);
+
+ /*
+ * One-time initialization.
+ */
+ if (mac_exp_ext_table == 0) {
+ mac_exp_ext_table = htable_create(10);
+ mac_exp_ext_key = vstring_alloc(10);
+ }
+ for (tp = tok_list; *tp; tp++) {
+ if ((tok_name = str_name_code(mac_exp_op_table, *tp)) == 0)
+ msg_panic("%s: unknown token code: %d", myname, *tp);
+ vstring_sprintf(mac_exp_ext_key, "%s%s", tok_name, suffix);
+ if (htable_locate(mac_exp_ext_table, STR(mac_exp_ext_key)) != 0)
+ msg_panic("%s: duplicate key: %s", myname, STR(mac_exp_ext_key));
+ (void) htable_enter(mac_exp_ext_table,
+ STR(mac_exp_ext_key), (void *) relop_eval);
+ }
+}
+
+/* mac_expand_callback - callback for mac_parse */
+
+static int mac_expand_callback(int type, VSTRING *buf, void *ptr)
+{
+ static const char myname[] = "mac_expand_callback";
+ MAC_EXP_CONTEXT *mc = (MAC_EXP_CONTEXT *) ptr;
+ int lookup_mode;
+ const char *lookup;
+ char *cp;
+ int ch;
+ ssize_t res_len;
+ ssize_t tmp_len;
+ const char *res_iftrue;
+ const char *res_iffalse;
+
+ /*
+ * Sanity check.
+ */
+ if (mc->level++ > 100)
+ mac_exp_parse_error(mc, "unreasonable macro call nesting: \"%s\"",
+ vstring_str(buf));
+ if (mc->status & MAC_PARSE_ERROR)
+ return (mc->status);
+
+ /*
+ * Named parameter or relational expression. In case of a syntax error,
+ * return without doing damage, and issue a warning instead.
+ */
+ if (type == MAC_PARSE_EXPR) {
+
+ cp = vstring_str(buf);
+
+ /*
+ * Relational expression. If recursion is disabled, perform only one
+ * level of $name expansion.
+ */
+ if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) {
+ if (mac_exp_parse_relational(mc, &lookup, &cp) != 0)
+ return (mc->status);
+
+ /*
+ * Look for the ? or : operator.
+ */
+ if ((ch = *cp) != 0) {
+ if (ch != '?' && ch != ':')
+ MAC_EXP_ERR_RETURN(mc, "\"?\" or \":\" expected at: "
+ "\"...}>>>%.20s\"", cp);
+ cp++;
+ }
+ }
+
+ /*
+ * Named parameter.
+ */
+ else {
+ char *start;
+
+ /*
+ * Look for the ? or : operator. In case of a syntax error,
+ * return without doing damage, and issue a warning instead.
+ */
+ start = (cp += strspn(cp, MAC_EXP_WHITESPACE));
+ for ( /* void */ ; /* void */ ; cp++) {
+ if ((ch = cp[tmp_len = strspn(cp, MAC_EXP_WHITESPACE)]) == 0) {
+ *cp = 0;
+ lookup_mode = MAC_EXP_MODE_USE;
+ break;
+ }
+ if (ch == '?' || ch == ':') {
+ *cp++ = 0;
+ cp += tmp_len;
+ lookup_mode = MAC_EXP_MODE_TEST;
+ break;
+ }
+ ch = *cp;
+ if (!ISALNUM(ch) && ch != '_') {
+ MAC_EXP_ERR_RETURN(mc, "attribute name syntax error at: "
+ "\"...%.*s>>>%.20s\"",
+ (int) (cp - vstring_str(buf)),
+ vstring_str(buf), cp);
+ }
+ }
+
+ /*
+ * Look up the named parameter. Todo: allow the lookup function
+ * to specify if the result is safe for $name expansion.
+ */
+ lookup = mc->lookup(start, lookup_mode, mc->context);
+ }
+
+ /*
+ * Return the requested result. After parsing the result operand
+ * following ?, we fall through to parse the result operand following
+ * :. This is necessary with the ternary ?: operator: first, with
+ * MAC_EXP_FLAG_SCAN to parse both result operands with mac_parse(),
+ * and second, to find garbage after any result operand. Without
+ * MAC_EXP_FLAG_SCAN the content of only one of the ?: result
+ * operands will be parsed with mac_parse(); syntax errors in the
+ * other operand will be missed.
+ */
+ switch (ch) {
+ case '?':
+ if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) {
+ if ((res_iftrue = mac_exp_extract_curly_payload(mc, &cp)) == 0)
+ return (mc->status);
+ } else {
+ res_iftrue = cp;
+ cp = ""; /* no left-over text */
+ }
+ if ((lookup != 0 && *lookup != 0) || (mc->flags & MAC_EXP_FLAG_SCAN))
+ mc->status |= mac_parse(res_iftrue, mac_expand_callback,
+ (void *) mc);
+ if (*cp == 0) /* end of input, OK */
+ break;
+ if (*cp != ':') /* garbage */
+ MAC_EXP_ERR_RETURN(mc, "\":\" expected at: "
+ "\"...%s}>>>%.20s\"", res_iftrue, cp);
+ cp += 1;
+ /* FALLTHROUGH: do not remove, see comment above. */
+ case ':':
+ if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) {
+ if ((res_iffalse = mac_exp_extract_curly_payload(mc, &cp)) == 0)
+ return (mc->status);
+ } else {
+ res_iffalse = cp;
+ cp = ""; /* no left-over text */
+ }
+ if (lookup == 0 || *lookup == 0 || (mc->flags & MAC_EXP_FLAG_SCAN))
+ mc->status |= mac_parse(res_iffalse, mac_expand_callback,
+ (void *) mc);
+ if (*cp != 0) /* garbage */
+ MAC_EXP_ERR_RETURN(mc, "unexpected input at: "
+ "\"...%s}>>>%.20s\"", res_iffalse, cp);
+ break;
+ case 0:
+ if (lookup == 0) {
+ mc->status |= MAC_PARSE_UNDEF;
+ } else if (*lookup == 0 || (mc->flags & MAC_EXP_FLAG_SCAN)) {
+ /* void */ ;
+ } else if (mc->flags & MAC_EXP_FLAG_RECURSE) {
+ vstring_strcpy(buf, lookup);
+ mc->status |= mac_parse(vstring_str(buf), mac_expand_callback,
+ (void *) mc);
+ } else {
+ res_len = VSTRING_LEN(mc->result);
+ vstring_strcat(mc->result, lookup);
+ if (mc->flags & MAC_EXP_FLAG_PRINTABLE) {
+ printable(vstring_str(mc->result) + res_len, '_');
+ } else if (mc->filter) {
+ cp = vstring_str(mc->result) + res_len;
+ while (*(cp += strspn(cp, mc->filter)))
+ *cp++ = '_';
+ }
+ }
+ break;
+ default:
+ msg_panic("%s: unknown operator code %d", myname, ch);
+ }
+ }
+
+ /*
+ * Literal text.
+ */
+ else if ((mc->flags & MAC_EXP_FLAG_SCAN) == 0) {
+ vstring_strcat(mc->result, vstring_str(buf));
+ }
+ mc->level--;
+
+ return (mc->status);
+}
+
+/* mac_expand - expand $name instances */
+
+int mac_expand(VSTRING *result, const char *pattern, int flags,
+ const char *filter,
+ MAC_EXP_LOOKUP_FN lookup, void *context)
+{
+ MAC_EXP_CONTEXT mc;
+ int status;
+
+ /*
+ * Bundle up the request and do the substitutions.
+ */
+ mc.result = result;
+ mc.flags = flags;
+ mc.filter = filter;
+ mc.lookup = lookup;
+ mc.context = context;
+ mc.status = 0;
+ mc.level = 0;
+ if ((flags & (MAC_EXP_FLAG_APPEND | MAC_EXP_FLAG_SCAN)) == 0)
+ VSTRING_RESET(result);
+ status = mac_parse(pattern, mac_expand_callback, (void *) &mc);
+ if ((flags & MAC_EXP_FLAG_SCAN) == 0)
+ VSTRING_TERMINATE(result);
+
+ return (status);
+}
+
+#ifdef TEST
+
+ /*
+ * This code certainly deserves a stand-alone test program.
+ */
+#include <stringops.h>
+#include <htable.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+
+static const char *lookup(const char *name, int unused_mode, void *context)
+{
+ HTABLE *table = (HTABLE *) context;
+
+ return (htable_find(table, name));
+}
+
+static MAC_EXP_OP_RES length_relop_eval(const char *left, int relop,
+ const char *rite)
+{
+ const char myname[] = "length_relop_eval";
+ ssize_t delta = strlen(left) - strlen(rite);
+
+ switch (relop) {
+ case MAC_EXP_OP_TOK_EQ:
+ return (mac_exp_op_res_bool[delta == 0]);
+ case MAC_EXP_OP_TOK_NE:
+ return (mac_exp_op_res_bool[delta != 0]);
+ case MAC_EXP_OP_TOK_LT:
+ return (mac_exp_op_res_bool[delta < 0]);
+ case MAC_EXP_OP_TOK_LE:
+ return (mac_exp_op_res_bool[delta <= 0]);
+ case MAC_EXP_OP_TOK_GE:
+ return (mac_exp_op_res_bool[delta >= 0]);
+ case MAC_EXP_OP_TOK_GT:
+ return (mac_exp_op_res_bool[delta > 0]);
+ default:
+ msg_panic("%s: unknown operator: %d",
+ myname, relop);
+ }
+}
+
+int main(int unused_argc, char **argv)
+{
+ VSTRING *buf = vstring_alloc(100);
+ VSTRING *result = vstring_alloc(100);
+ char *cp;
+ char *name;
+ char *value;
+ HTABLE *table;
+ int stat;
+ int length_relops[] = {
+ MAC_EXP_OP_TOK_EQ, MAC_EXP_OP_TOK_NE,
+ MAC_EXP_OP_TOK_GT, MAC_EXP_OP_TOK_GE,
+ MAC_EXP_OP_TOK_LT, MAC_EXP_OP_TOK_LE,
+ 0,
+ };
+
+ /*
+ * Add relops that compare string lengths instead of content.
+ */
+ mac_expand_add_relop(length_relops, "length", length_relop_eval);
+
+ /*
+ * Loop over the inputs.
+ */
+ while (!vstream_feof(VSTREAM_IN)) {
+
+ table = htable_create(0);
+
+ /*
+ * Read a block of definitions, terminated with an empty line.
+ */
+ while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
+ vstream_printf("<< %s\n", vstring_str(buf));
+ vstream_fflush(VSTREAM_OUT);
+ if (VSTRING_LEN(buf) == 0)
+ break;
+ cp = vstring_str(buf);
+ name = mystrtok(&cp, CHARS_SPACE "=");
+ value = mystrtok(&cp, CHARS_SPACE "=");
+ htable_enter(table, name, value ? mystrdup(value) : 0);
+ }
+
+ /*
+ * Read a block of patterns, terminated with an empty line or EOF.
+ */
+ while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
+ vstream_printf("<< %s\n", vstring_str(buf));
+ vstream_fflush(VSTREAM_OUT);
+ if (VSTRING_LEN(buf) == 0)
+ break;
+ cp = vstring_str(buf);
+ VSTRING_RESET(result);
+ stat = mac_expand(result, vstring_str(buf), MAC_EXP_FLAG_NONE,
+ (char *) 0, lookup, (void *) table);
+ vstream_printf("stat=%d result=%s\n", stat, vstring_str(result));
+ vstream_fflush(VSTREAM_OUT);
+ }
+ htable_free(table, myfree);
+ vstream_printf("\n");
+ }
+
+ /*
+ * Clean up.
+ */
+ vstring_free(buf);
+ vstring_free(result);
+ exit(0);
+}
+
+#endif
diff --git a/src/util/mac_expand.h b/src/util/mac_expand.h
new file mode 100644
index 0000000..fbe6347
--- /dev/null
+++ b/src/util/mac_expand.h
@@ -0,0 +1,81 @@
+#ifndef _MAC_EXPAND_H_INCLUDED_
+#define _MAC_EXPAND_H_INCLUDED_
+
+/*++
+/* NAME
+/* mac_expand 3h
+/* SUMMARY
+/* expand macro references in string
+/* SYNOPSIS
+/* #include <mac_expand.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+#include <mac_parse.h>
+
+ /*
+ * Features.
+ */
+#define MAC_EXP_FLAG_NONE (0)
+#define MAC_EXP_FLAG_RECURSE (1<<0)
+#define MAC_EXP_FLAG_APPEND (1<<1)
+#define MAC_EXP_FLAG_SCAN (1<<2)
+#define MAC_EXP_FLAG_PRINTABLE (1<<3)
+
+ /*
+ * Token codes, public so tha they are available to mac_expand_add_relop()
+ */
+#define MAC_EXP_OP_TOK_NONE 0 /* Sentinel */
+#define MAC_EXP_OP_TOK_EQ 1 /* == */
+#define MAC_EXP_OP_TOK_NE 2 /* != */
+#define MAC_EXP_OP_TOK_LT 3 /* < */
+#define MAC_EXP_OP_TOK_LE 4 /* <= */
+#define MAC_EXP_OP_TOK_GE 5 /* >= */
+#define MAC_EXP_OP_TOK_GT 6 /* > */
+
+ /*
+ * Relational operator results. An enum to discourage assuming that 0 is
+ * false, !0 is true.
+ */
+typedef enum MAC_EXP_OP_RES {
+ MAC_EXP_OP_RES_TRUE,
+ MAC_EXP_OP_RES_FALSE,
+ MAC_EXP_OP_RES_ERROR,
+} MAC_EXP_OP_RES;
+
+
+extern MAC_EXP_OP_RES mac_exp_op_res_bool[2];
+
+ /*
+ * Real lookup or just a test?
+ */
+#define MAC_EXP_MODE_TEST (0)
+#define MAC_EXP_MODE_USE (1)
+
+typedef const char *(*MAC_EXP_LOOKUP_FN) (const char *, int, void *);
+typedef MAC_EXP_OP_RES (*MAC_EXPAND_RELOP_FN) (const char *, int, const char *);
+
+extern int mac_expand(VSTRING *, const char *, int, const char *, MAC_EXP_LOOKUP_FN, void *);
+void mac_expand_add_relop(int *, const char *, MAC_EXPAND_RELOP_FN);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/mac_expand.in b/src/util/mac_expand.in
new file mode 100644
index 0000000..1d61906
--- /dev/null
+++ b/src/util/mac_expand.in
@@ -0,0 +1,105 @@
+name1 = name1-value
+
+$name1
+$(name1
+$(name1)
+$( name1)
+$(name1 )
+$(na me1)
+${na me1}
+${${name1} != {}?name 1 defined, |$name1|$name2|}
+${ ${name1} != {}?name 1 defined, |$name1|$name2|}
+${ ${name1} ?name 1 defined, |$name1|$name2|}
+${{$name1} ? {name 1 defined, |$name1|$name2|} : {name 1 undefined, |$name1|$name2|} }
+${x{$name1} != {}?{name 1 defined, |$name1|$name2|}}
+${{$name1}x?{name 1 defined, |$name1|$name2|}}
+${{$name1} != {}x{name 1 defined, |$name1|$name2|}}
+${{$name1} != {}?x{name 1 defined, |$name1|$name2|}}
+${{$name2} != {}?x{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}}
+${{$name1} != {}?{name 1 defined, |$name1|$name2|}x}
+${{$name1} != {}?{name 1 defined, |$name1|$name2|}x:{name 1 undefined, |$name1|$name2|}}
+${{$name1} != {}?{name 1 defined, |$name1|$name2|}:x{name 1 undefined, |$name1|$name2|}}
+${{$name2} != {}?{name 2 defined, |$name1|$name2|}:x{name 2 undefined, |$name1|$name2|}}
+${{text}}
+${{text}?{non-empty}:{empty}}
+${{text} = {}}
+${{${ name1}} == {}}
+${name1?{${ name1}}:{${name2}}}
+${name2?{${ name1}}:{${name2}}}
+${name2?{${name1}}:{${ name2}}}
+${name2:{${name1}}:{${name2}}}
+${name2?{${name1}}?{${name2}}}
+${{${name1?bug:test}} != {bug:test}?{Error: NOT}:{Good:}} Postfix 2.11 compatible
+${{${name1??bug}} != {?bug}?{Error: NOT}:{Good:}} Postfix 2.11 compatible
+${{${name2::bug}} != {:bug}?{Error: NOT}:{Good:}} Postfix 2.11 compatible
+${{xx}==(yy)?{oops}:{phew}}
+
+name1 = name1-value
+
+${name1?name 1 defined, |$name1|$name2|}
+${name1:name 1 undefined, |$name1|$name2|}
+${name2?name 2 defined, |$name1|$name2|}
+${name2:name 2 undefined, |$name1|$name2|}
+|$name1|$name2|
+${{$name1} != {}?{name 1 defined, |$name1|$name2|}}
+${{$name1} != {}:{name 1 undefined, |$name1|$name2|}}
+${{$name1} == {}?{name 1 undefined, |$name1|$name2|}}
+${{$name1} == {}:{name 1 defined, |$name1|$name2|}}
+${name1?{name 1 defined, |$name1|$name2|}:{name 1 undefined, |$name1|$name2|}}
+${{$name1} != {}?{name 1 defined, |$name1|$name2|}:{name 1 undefined, |$name1|$name2|}}
+${{$name1} != {} ? {name 1 defined, |$name1|$name2|} : {name 1 undefined, |$name1|$name2|}}
+${{$name1} != {}?{name 1 defined, |$name1|$name2|}:name 1 undefined, |$name1|$name2|}
+${{$name1} != {} ? {name 1 defined, |$name1|$name2|} : name 1 undefined, |$name1|$name2|}
+${{$name1} != {}}
+${{$name1} == {}}
+${{$name2} != {}?{name 2 defined, |$name1|$name2|}}
+${{$name2} != {}:{name 2 undefined, |$name1|$name2|}}
+${{$name2} == {}?{name 2 undefined, |$name1|$name2|}}
+${{$name2} == {}:{name 2 defined, |$name1|$name2|}}
+${name2?{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}}
+${{$name2} != {}?{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}}
+${{$name2} != {} ? {name 2 defined, |$name1|$name2|} : {name 2 undefined, |$name1|$name2|}}
+${{$name2} != {}?{name 2 defined, |$name1|$name2|}:name 2 undefined, |$name1|$name2|}
+${{$name2} != {} ? {name 2 defined, |$name1|$name2|} : name 2 undefined, |$name1|$name2|}
+${{$name2} != {}}
+${{$name2} == {}}
+
+
+${{1} == {1}}
+${{1} < {1}}
+${{1} <= {1}}
+${{1} >= {1}}
+${{1} > {1}}
+${{1} == {2}}
+${{1} < {2}}
+${{1} <= {2}}
+${{1} >= {2}}
+${{1} > {2}}
+${{a} == {a}}
+${{a} < {a}}
+${{a} <= {a}}
+${{a} >= {a}}
+${{a} > {a}}
+${{a} == {b}}
+${{a} < {b}}
+${{a} <= {b}}
+${{a} >= {b}}
+${{a} > {b}}
+
+name1 = foo
+
+${{$name1} >=blah {bar}}
+${{aaa} == {bbb}}
+${{aaa} ==length {bbb}}
+${{aaa} <=length {bbb}}
+${{aaa} >=length {bbb}}
+${{aaa} != {bbb}}
+${{aaa} !=length {bbb}}
+${{aaa} > {bb}}
+${{aaa} >length {bb}}
+${{aaa} >= {bb}}
+${{aaa} >=length {bb}}
+${{aaa} < {bb}}
+${{aaa} <length {bb}}
+${{aaa} <= {bb}}
+${{aaa} <=length {bb}}
diff --git a/src/util/mac_expand.ref b/src/util/mac_expand.ref
new file mode 100644
index 0000000..854c4f9
--- /dev/null
+++ b/src/util/mac_expand.ref
@@ -0,0 +1,222 @@
+<< name1 = name1-value
+<<
+<< $name1
+stat=0 result=name1-value
+<< $(name1
+unknown: warning: truncated macro reference: "$(name1"
+stat=1 result=
+<< $(name1)
+stat=0 result=name1-value
+<< $( name1)
+stat=0 result=name1-value
+<< $(name1 )
+stat=0 result=name1-value
+<< $(na me1)
+unknown: warning: attribute name syntax error at: "...na>>> me1"
+stat=1 result=
+<< ${na me1}
+unknown: warning: attribute name syntax error at: "...na>>> me1"
+stat=1 result=
+<< ${${name1} != {}?name 1 defined, |$name1|$name2|}
+unknown: warning: attribute name syntax error at: "...>>>${name1} != {}?name "
+stat=1 result=
+<< ${ ${name1} != {}?name 1 defined, |$name1|$name2|}
+unknown: warning: attribute name syntax error at: "... >>>${name1} != {}?name "
+stat=1 result=
+<< ${ ${name1} ?name 1 defined, |$name1|$name2|}
+unknown: warning: attribute name syntax error at: "... >>>${name1} ?name 1 def"
+stat=1 result=
+<< ${{$name1} ? {name 1 defined, |$name1|$name2|} : {name 1 undefined, |$name1|$name2|} }
+unknown: warning: "==" or "!="" or "<"" or "<="" or ">="" or ">" expected at: "...$name1}>>>? {name 1 defined, |"
+stat=1 result=
+<< ${x{$name1} != {}?{name 1 defined, |$name1|$name2|}}
+unknown: warning: attribute name syntax error at: "...x>>>{$name1} != {}?{name"
+stat=1 result=
+<< ${{$name1}x?{name 1 defined, |$name1|$name2|}}
+unknown: warning: "==" or "!="" or "<"" or "<="" or ">="" or ">" expected at: "...$name1}>>>x?{name 1 defined, |"
+stat=1 result=
+<< ${{$name1} != {}x{name 1 defined, |$name1|$name2|}}
+unknown: warning: "?" or ":" expected at: "...}>>>x{name 1 defined, |$"
+stat=1 result=
+<< ${{$name1} != {}?x{name 1 defined, |$name1|$name2|}}
+stat=2 result=x{name 1 defined, |name1-value||}
+<< ${{$name2} != {}?x{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}}
+stat=2 result=
+<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}x}
+unknown: warning: ":" expected at: "...name 1 defined, |$name1|$name2|}>>>x"
+stat=3 result=name 1 defined, |name1-value||
+<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}x:{name 1 undefined, |$name1|$name2|}}
+unknown: warning: ":" expected at: "...name 1 defined, |$name1|$name2|}>>>x:{name 1 undefined,"
+stat=3 result=name 1 defined, |name1-value||
+<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}:x{name 1 undefined, |$name1|$name2|}}
+stat=2 result=name 1 defined, |name1-value||
+<< ${{$name2} != {}?{name 2 defined, |$name1|$name2|}:x{name 2 undefined, |$name1|$name2|}}
+stat=2 result=x{name 2 undefined, |name1-value||}
+<< ${{text}}
+unknown: warning: "==" or "!="" or "<"" or "<="" or ">="" or ">" expected at: "...text}>>>"
+stat=1 result=
+<< ${{text}?{non-empty}:{empty}}
+unknown: warning: "==" or "!="" or "<"" or "<="" or ">="" or ">" expected at: "...text}>>>?{non-empty}:{empty}"
+stat=1 result=
+<< ${{text} = {}}
+unknown: warning: "==" or "!="" or "<"" or "<="" or ">="" or ">" expected at: "...text}>>>= {}"
+stat=1 result=
+<< ${{${ name1}} == {}}
+stat=0 result=
+<< ${name1?{${ name1}}:{${name2}}}
+stat=0 result=name1-value
+<< ${name2?{${ name1}}:{${name2}}}
+stat=2 result=
+<< ${name2?{${name1}}:{${ name2}}}
+stat=2 result=
+<< ${name2:{${name1}}:{${name2}}}
+unknown: warning: unexpected input at: "...${name1}}>>>:{${name2}}"
+stat=1 result=name1-value
+<< ${name2?{${name1}}?{${name2}}}
+unknown: warning: ":" expected at: "...${name1}}>>>?{${name2}}"
+stat=1 result=
+<< ${{${name1?bug:test}} != {bug:test}?{Error: NOT}:{Good:}} Postfix 2.11 compatible
+stat=0 result=Good: Postfix 2.11 compatible
+<< ${{${name1??bug}} != {?bug}?{Error: NOT}:{Good:}} Postfix 2.11 compatible
+stat=0 result=Good: Postfix 2.11 compatible
+<< ${{${name2::bug}} != {:bug}?{Error: NOT}:{Good:}} Postfix 2.11 compatible
+stat=0 result=Good: Postfix 2.11 compatible
+<< ${{xx}==(yy)?{oops}:{phew}}
+unknown: warning: "{expression}" expected at: "...{xx} ==>>>(yy)?{oops}:{phew}"
+stat=1 result=
+<<
+
+<< name1 = name1-value
+<<
+<< ${name1?name 1 defined, |$name1|$name2|}
+stat=2 result=name 1 defined, |name1-value||
+<< ${name1:name 1 undefined, |$name1|$name2|}
+stat=0 result=
+<< ${name2?name 2 defined, |$name1|$name2|}
+stat=0 result=
+<< ${name2:name 2 undefined, |$name1|$name2|}
+stat=2 result=name 2 undefined, |name1-value||
+<< |$name1|$name2|
+stat=2 result=|name1-value||
+<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}}
+stat=2 result=name 1 defined, |name1-value||
+<< ${{$name1} != {}:{name 1 undefined, |$name1|$name2|}}
+stat=0 result=
+<< ${{$name1} == {}?{name 1 undefined, |$name1|$name2|}}
+stat=0 result=
+<< ${{$name1} == {}:{name 1 defined, |$name1|$name2|}}
+stat=2 result=name 1 defined, |name1-value||
+<< ${name1?{name 1 defined, |$name1|$name2|}:{name 1 undefined, |$name1|$name2|}}
+stat=2 result=name 1 defined, |name1-value||
+<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}:{name 1 undefined, |$name1|$name2|}}
+stat=2 result=name 1 defined, |name1-value||
+<< ${{$name1} != {} ? {name 1 defined, |$name1|$name2|} : {name 1 undefined, |$name1|$name2|}}
+stat=2 result=name 1 defined, |name1-value||
+<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}:name 1 undefined, |$name1|$name2|}
+stat=2 result=name 1 defined, |name1-value||
+<< ${{$name1} != {} ? {name 1 defined, |$name1|$name2|} : name 1 undefined, |$name1|$name2|}
+stat=2 result=name 1 defined, |name1-value||
+<< ${{$name1} != {}}
+stat=0 result=true
+<< ${{$name1} == {}}
+stat=0 result=
+<< ${{$name2} != {}?{name 2 defined, |$name1|$name2|}}
+stat=2 result=
+<< ${{$name2} != {}:{name 2 undefined, |$name1|$name2|}}
+stat=2 result=name 2 undefined, |name1-value||
+<< ${{$name2} == {}?{name 2 undefined, |$name1|$name2|}}
+stat=2 result=name 2 undefined, |name1-value||
+<< ${{$name2} == {}:{name 2 defined, |$name1|$name2|}}
+stat=2 result=
+<< ${name2?{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}}
+stat=2 result=name 2 undefined, |name1-value||
+<< ${{$name2} != {}?{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}}
+stat=2 result=name 2 undefined, |name1-value||
+<< ${{$name2} != {} ? {name 2 defined, |$name1|$name2|} : {name 2 undefined, |$name1|$name2|}}
+stat=2 result=name 2 undefined, |name1-value||
+<< ${{$name2} != {}?{name 2 defined, |$name1|$name2|}:name 2 undefined, |$name1|$name2|}
+stat=2 result=name 2 undefined, |name1-value||
+<< ${{$name2} != {} ? {name 2 defined, |$name1|$name2|} : name 2 undefined, |$name1|$name2|}
+stat=2 result= name 2 undefined, |name1-value||
+<< ${{$name2} != {}}
+stat=2 result=
+<< ${{$name2} == {}}
+stat=2 result=true
+<<
+
+<<
+<< ${{1} == {1}}
+stat=0 result=true
+<< ${{1} < {1}}
+stat=0 result=
+<< ${{1} <= {1}}
+stat=0 result=true
+<< ${{1} >= {1}}
+stat=0 result=true
+<< ${{1} > {1}}
+stat=0 result=
+<< ${{1} == {2}}
+stat=0 result=
+<< ${{1} < {2}}
+stat=0 result=true
+<< ${{1} <= {2}}
+stat=0 result=true
+<< ${{1} >= {2}}
+stat=0 result=
+<< ${{1} > {2}}
+stat=0 result=
+<< ${{a} == {a}}
+stat=0 result=true
+<< ${{a} < {a}}
+stat=0 result=
+<< ${{a} <= {a}}
+stat=0 result=true
+<< ${{a} >= {a}}
+stat=0 result=true
+<< ${{a} > {a}}
+stat=0 result=
+<< ${{a} == {b}}
+stat=0 result=
+<< ${{a} < {b}}
+stat=0 result=true
+<< ${{a} <= {b}}
+stat=0 result=true
+<< ${{a} >= {b}}
+stat=0 result=
+<< ${{a} > {b}}
+stat=0 result=
+<<
+
+<< name1 = foo
+<<
+<< ${{$name1} >=blah {bar}}
+unknown: warning: bad operator suffix at: "...>=>>>blah"
+stat=1 result=
+<< ${{aaa} == {bbb}}
+stat=0 result=
+<< ${{aaa} ==length {bbb}}
+stat=0 result=true
+<< ${{aaa} <=length {bbb}}
+stat=0 result=true
+<< ${{aaa} >=length {bbb}}
+stat=0 result=true
+<< ${{aaa} != {bbb}}
+stat=0 result=true
+<< ${{aaa} !=length {bbb}}
+stat=0 result=
+<< ${{aaa} > {bb}}
+stat=0 result=
+<< ${{aaa} >length {bb}}
+stat=0 result=true
+<< ${{aaa} >= {bb}}
+stat=0 result=
+<< ${{aaa} >=length {bb}}
+stat=0 result=true
+<< ${{aaa} < {bb}}
+stat=0 result=true
+<< ${{aaa} <length {bb}}
+stat=0 result=
+<< ${{aaa} <= {bb}}
+stat=0 result=true
+<< ${{aaa} <=length {bb}}
+stat=0 result=
diff --git a/src/util/mac_parse.c b/src/util/mac_parse.c
new file mode 100644
index 0000000..45b717a
--- /dev/null
+++ b/src/util/mac_parse.c
@@ -0,0 +1,199 @@
+/*++
+/* NAME
+/* mac_parse 3
+/* SUMMARY
+/* locate macro references in string
+/* SYNOPSIS
+/* #include <mac_parse.h>
+/*
+/* int mac_parse(string, action, context)
+/* const char *string;
+/* int (*action)(int type, VSTRING *buf, void *context);
+/* DESCRIPTION
+/* This module recognizes macro expressions in null-terminated
+/* strings. Macro expressions have the form $name, $(text) or
+/* ${text}. A macro name consists of alphanumerics and/or
+/* underscore. Text other than macro expressions is treated
+/* as literal text.
+/*
+/* mac_parse() breaks up its string argument into macro references
+/* and other text, and invokes the \fIaction\fR routine for each item
+/* found. With each action routine call, the \fItype\fR argument
+/* indicates what was found, \fIbuf\fR contains a copy of the text
+/* found, and \fIcontext\fR is passed on unmodified from the caller.
+/* The application is at liberty to clobber \fIbuf\fR.
+/* .IP MAC_PARSE_LITERAL
+/* The content of \fIbuf\fR is literal text.
+/* .IP MAC_PARSE_EXPR
+/* The content of \fIbuf\fR is a macro expression: either a
+/* bare macro name without the preceding "$", or all the text
+/* inside $() or ${}.
+/* .PP
+/* The action routine result value is the bit-wise OR of zero or more
+/* of the following:
+/* .IP MAC_PARSE_ERROR
+/* A parsing error was detected.
+/* .IP MAC_PARSE_UNDEF
+/* A macro was expanded but not defined.
+/* .PP
+/* Use the constant MAC_PARSE_OK when no error was detected.
+/* SEE ALSO
+/* dict(3) dictionary interface.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory. malformed macro name.
+/*
+/* The result value is the bit-wise OR of zero or more of the
+/* following:
+/* .IP MAC_PARSE_ERROR
+/* A parsing error was detected.
+/* .IP MAC_PARSE_UNDEF
+/* A macro was expanded but not defined.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mac_parse.h>
+
+ /*
+ * Helper macro for consistency. Null-terminate the temporary buffer,
+ * execute the action, and reset the temporary buffer for re-use.
+ */
+#define MAC_PARSE_ACTION(status, type, buf, context) \
+ do { \
+ VSTRING_TERMINATE(buf); \
+ status |= action((type), (buf), (context)); \
+ VSTRING_RESET(buf); \
+ } while(0)
+
+/* mac_parse - split string into literal text and macro references */
+
+int mac_parse(const char *value, MAC_PARSE_FN action, void *context)
+{
+ const char *myname = "mac_parse";
+ VSTRING *buf = vstring_alloc(1); /* result buffer */
+ const char *vp; /* value pointer */
+ const char *pp; /* open_paren pointer */
+ const char *ep; /* string end pointer */
+ static char open_paren[] = "({";
+ static char close_paren[] = ")}";
+ int level;
+ int status = 0;
+
+#define SKIP(start, var, cond) do { \
+ for (var = start; *var && (cond); var++) \
+ /* void */; \
+ } while (0)
+
+ if (msg_verbose > 1)
+ msg_info("%s: %s", myname, value);
+
+ for (vp = value; *vp;) {
+ if (*vp != '$') { /* ordinary character */
+ VSTRING_ADDCH(buf, *vp);
+ vp += 1;
+ } else if (vp[1] == '$') { /* $$ becomes $ */
+ VSTRING_ADDCH(buf, *vp);
+ vp += 2;
+ } else { /* found bare $ */
+ if (VSTRING_LEN(buf) > 0)
+ MAC_PARSE_ACTION(status, MAC_PARSE_LITERAL, buf, context);
+ vp += 1;
+ pp = open_paren;
+ if (*vp == *pp || *vp == *++pp) { /* ${x} or $(x) */
+ level = 1;
+ vp += 1;
+ for (ep = vp; level > 0; ep++) {
+ if (*ep == 0) {
+ msg_warn("truncated macro reference: \"%s\"", value);
+ status |= MAC_PARSE_ERROR;
+ break;
+ }
+ if (*ep == *pp)
+ level++;
+ if (*ep == close_paren[pp - open_paren])
+ level--;
+ }
+ if (status & MAC_PARSE_ERROR)
+ break;
+ vstring_strncat(buf, vp, level > 0 ? ep - vp : ep - vp - 1);
+ vp = ep;
+ } else { /* plain $x */
+ SKIP(vp, ep, ISALNUM(*ep) || *ep == '_');
+ vstring_strncat(buf, vp, ep - vp);
+ vp = ep;
+ }
+ if (VSTRING_LEN(buf) == 0) {
+ status |= MAC_PARSE_ERROR;
+ msg_warn("empty macro name: \"%s\"", value);
+ break;
+ }
+ MAC_PARSE_ACTION(status, MAC_PARSE_EXPR, buf, context);
+ }
+ }
+ if (VSTRING_LEN(buf) > 0 && (status & MAC_PARSE_ERROR) == 0)
+ MAC_PARSE_ACTION(status, MAC_PARSE_LITERAL, buf, context);
+
+ /*
+ * Cleanup.
+ */
+ vstring_free(buf);
+
+ return (status);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program. Read strings from stdin, print parsed
+ * result to stdout.
+ */
+#include <vstring_vstream.h>
+
+/* mac_parse_print - print parse tree */
+
+static int mac_parse_print(int type, VSTRING *buf, void *unused_context)
+{
+ char *type_name;
+
+ switch (type) {
+ case MAC_PARSE_EXPR:
+ type_name = "MAC_PARSE_EXPR";
+ break;
+ case MAC_PARSE_LITERAL:
+ type_name = "MAC_PARSE_LITERAL";
+ break;
+ default:
+ msg_panic("unknown token type %d", type);
+ }
+ vstream_printf("%s \"%s\"\n", type_name, vstring_str(buf));
+ return (0);
+}
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *buf = vstring_alloc(1);
+
+ while (vstring_fgets_nonl(buf, VSTREAM_IN)) {
+ mac_parse(vstring_str(buf), mac_parse_print, (void *) 0);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ vstring_free(buf);
+ return (0);
+}
+
+#endif
diff --git a/src/util/mac_parse.h b/src/util/mac_parse.h
new file mode 100644
index 0000000..5ed0dc1
--- /dev/null
+++ b/src/util/mac_parse.h
@@ -0,0 +1,51 @@
+#ifndef _MAC_PARSE_H_INCLUDED_
+#define _MAC_PARSE_H_INCLUDED_
+
+/*++
+/* NAME
+/* mac_parse 3h
+/* SUMMARY
+/* locate macro references in string
+/* SYNOPSIS
+/* #include <mac_parse.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+#define MAC_PARSE_LITERAL 1
+#define MAC_PARSE_EXPR 2
+#define MAC_PARSE_VARNAME MAC_PARSE_EXPR /* 2.1 compatibility */
+
+#define MAC_PARSE_OK 0
+#define MAC_PARSE_ERROR (1<<0)
+#define MAC_PARSE_UNDEF (1<<1)
+#define MAC_PARSE_USER 2 /* start user definitions */
+
+typedef int (*MAC_PARSE_FN) (int, VSTRING *, void *);
+
+extern int WARN_UNUSED_RESULT mac_parse(const char *, MAC_PARSE_FN, void *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/make_dirs.c b/src/util/make_dirs.c
new file mode 100644
index 0000000..7550350
--- /dev/null
+++ b/src/util/make_dirs.c
@@ -0,0 +1,173 @@
+/*++
+/* NAME
+/* make_dirs 3
+/* SUMMARY
+/* create directory hierarchy
+/* SYNOPSIS
+/* #include <make_dirs.h>
+/*
+/* int make_dirs(path, perms)
+/* const char *path;
+/* int perms;
+/* DESCRIPTION
+/* make_dirs() creates the directory specified in \fIpath\fR, and
+/* creates any missing intermediate directories as well. Directories
+/* are created with the permissions specified in \fIperms\fR, as
+/* modified by the process umask.
+/* DIAGNOSTICS:
+/* Fatal: out of memory. make_dirs() returns 0 in case of success.
+/* In case of problems. make_dirs() returns -1 and \fIerrno\fR
+/* reflects the nature of the problem.
+/* SEE ALSO
+/* mkdir(2)
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "mymalloc.h"
+#include "stringops.h"
+#include "make_dirs.h"
+#include "warn_stat.h"
+
+/* make_dirs - create directory hierarchy */
+
+int make_dirs(const char *path, int perms)
+{
+ const char *myname = "make_dirs";
+ char *saved_path;
+ unsigned char *cp;
+ int saved_ch;
+ struct stat st;
+ int ret;
+ mode_t saved_mode;
+ gid_t egid = -1;
+
+ /*
+ * Initialize. Make a copy of the path that we can safely clobber.
+ */
+ cp = (unsigned char *) (saved_path = mystrdup(path));
+
+ /*
+ * I didn't like the 4.4BSD "mkdir -p" implementation, but coming up with
+ * my own took a day, spread out over several days.
+ */
+#define SKIP_WHILE(cond, ptr) { while(*ptr && (cond)) ptr++; }
+
+ SKIP_WHILE(*cp == '/', cp);
+
+ for (;;) {
+ SKIP_WHILE(*cp != '/', cp);
+ if ((saved_ch = *cp) != 0)
+ *cp = 0;
+ if ((ret = stat(saved_path, &st)) >= 0) {
+ if (!S_ISDIR(st.st_mode)) {
+ errno = ENOTDIR;
+ ret = -1;
+ break;
+ }
+ saved_mode = st.st_mode;
+ } else {
+ if (errno != ENOENT)
+ break;
+
+ /*
+ * mkdir(foo) fails with EEXIST if foo is a symlink.
+ */
+#if 0
+
+ /*
+ * Create a new directory. Unfortunately, mkdir(2) has no
+ * equivalent of open(2)'s O_CREAT|O_EXCL safety net, so we must
+ * require that the parent directory is not world writable.
+ * Detecting a lost race condition after the fact is not
+ * sufficient, as an attacker could repeat the attack and add one
+ * directory level at a time.
+ */
+ if (saved_mode & S_IWOTH) {
+ msg_warn("refusing to mkdir %s: parent directory is writable by everyone",
+ saved_path);
+ errno = EPERM;
+ ret = -1;
+ break;
+ }
+#endif
+ if ((ret = mkdir(saved_path, perms)) < 0) {
+ if (errno != EEXIST)
+ break;
+ /* Race condition? */
+ if ((ret = stat(saved_path, &st)) < 0)
+ break;
+ if (!S_ISDIR(st.st_mode)) {
+ errno = ENOTDIR;
+ ret = -1;
+ break;
+ }
+ }
+
+ /*
+ * Fix directory ownership when mkdir() ignores the effective
+ * GID. Don't change the effective UID for doing this.
+ */
+ if ((ret = stat(saved_path, &st)) < 0) {
+ msg_warn("%s: stat %s: %m", myname, saved_path);
+ break;
+ }
+ if (egid == -1)
+ egid = getegid();
+ if (st.st_gid != egid && (ret = chown(saved_path, -1, egid)) < 0) {
+ msg_warn("%s: chgrp %s: %m", myname, saved_path);
+ break;
+ }
+ }
+ if (saved_ch != 0)
+ *cp = saved_ch;
+ SKIP_WHILE(*cp == '/', cp);
+ if (*cp == 0)
+ break;
+ }
+
+ /*
+ * Cleanup.
+ */
+ myfree(saved_path);
+ return (ret);
+}
+
+#ifdef TEST
+
+ /*
+ * Test program. Usage: make_dirs path...
+ */
+#include <stdlib.h>
+#include <msg_vstream.h>
+
+int main(int argc, char **argv)
+{
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ if (argc < 2)
+ msg_fatal("usage: %s path...", argv[0]);
+ while (--argc > 0 && *++argv != 0)
+ if (make_dirs(*argv, 0755))
+ msg_fatal("%s: %m", *argv);
+ exit(0);
+}
+
+#endif
diff --git a/src/util/make_dirs.h b/src/util/make_dirs.h
new file mode 100644
index 0000000..0df6117
--- /dev/null
+++ b/src/util/make_dirs.h
@@ -0,0 +1,30 @@
+#ifndef MAKE_DIRS_H_INCLUDED_
+#define MAKE_DIRS_H_INCLUDED_
+
+/*++
+/* NAME
+/* make_dirs 3h
+/* SUMMARY
+/* create directory hierarchy
+/* SYNOPSIS
+/* #include <make_dirs.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern int make_dirs(const char *, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/mask_addr.c b/src/util/mask_addr.c
new file mode 100644
index 0000000..5ddd0ca
--- /dev/null
+++ b/src/util/mask_addr.c
@@ -0,0 +1,68 @@
+/*++
+/* NAME
+/* mask_addr 3
+/* SUMMARY
+/* address bit banging
+/* SYNOPSIS
+/* #include <mask_addr.h>
+/*
+/* void mask_addr(addr_bytes, addr_byte_count, network_bits)
+/* unsigned char *addr_bytes;
+/* unsigned addr_byte_count;
+/* unsigned network_bits;
+/* DESCRIPTION
+/* mask_addr() clears all the host bits in the specified
+/* address. The code can handle addresses of any length,
+/* and bytes of any width.
+/*
+/* Arguments:
+/* .IP addr_bytes
+/* The network address in network byte order.
+/* .IP addr_byte_count
+/* The network address length in bytes.
+/* .IP network_bits
+/* The number of initial bits that will not be cleared.
+/* DIAGNOSTICS
+/* Fatal errors: the number of network bits exceeds the address size.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <limits.h> /* CHAR_BIT */
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mask_addr.h>
+
+/* mask_addr - mask off a variable-length address */
+
+void mask_addr(unsigned char *addr_bytes,
+ unsigned addr_byte_count,
+ unsigned network_bits)
+{
+ unsigned char *p;
+
+ if (network_bits > addr_byte_count * CHAR_BIT)
+ msg_panic("mask_addr: address byte count %d too small for bit count %d",
+ addr_byte_count, network_bits);
+
+ p = addr_bytes + network_bits / CHAR_BIT;
+ network_bits %= CHAR_BIT;
+
+ if (network_bits != 0)
+ *p++ &= ~0U << (CHAR_BIT - network_bits);
+
+ while (p < addr_bytes + addr_byte_count)
+ *p++ = 0;
+}
diff --git a/src/util/mask_addr.h b/src/util/mask_addr.h
new file mode 100644
index 0000000..0e70a41
--- /dev/null
+++ b/src/util/mask_addr.h
@@ -0,0 +1,30 @@
+#ifndef _MASK_ADDR_H_INCLUDED_
+#define _MASK_ADDR_H_INCLUDED_
+
+/*++
+/* NAME
+/* mask_addr 3h
+/* SUMMARY
+/* address bit banging
+/* SYNOPSIS
+/* #include <mask_addr.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern void mask_addr(unsigned char *, unsigned, unsigned);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/match_list.c b/src/util/match_list.c
new file mode 100644
index 0000000..fa533ba
--- /dev/null
+++ b/src/util/match_list.c
@@ -0,0 +1,276 @@
+/*++
+/* NAME
+/* match_list 3
+/* SUMMARY
+/* generic list-based pattern matching
+/* SYNOPSIS
+/* #include <match_list.h>
+/*
+/* MATCH_LIST *match_list_init(pname, flags, pattern_list, count, func,...)
+/* const char *pname;
+/* int flags;
+/* const char *pattern_list;
+/* int count;
+/* int (*func)(int flags, const char *string, const char *pattern);
+/*
+/* int match_list_match(list, string,...)
+/* MATCH_LIST *list;
+/* const char *string;
+/*
+/* void match_list_free(list)
+/* MATCH_LIST *list;
+/* DESCRIPTION
+/* This module implements a framework for tests for list
+/* membership. The actual tests are done by user-supplied
+/* functions.
+/*
+/* Patterns are separated by whitespace and/or commas. A pattern
+/* is either a string, a file name (in which case the contents
+/* of the file are substituted for the file name) or a type:name
+/* lookup table specification. In order to reverse the result
+/* of a pattern match, precede a pattern with an exclamation
+/* point (!).
+/*
+/* match_list_init() performs initializations. When the global
+/* util_utf8_enable variable is non-zero, and when the code
+/* is compiled with EAI support, string comparison will use
+/* caseless UTF-8 mode. Otherwise, only ASCII characters will
+/* be casefolded.
+/*
+/* match_list_match() matches strings against the specified
+/* pattern list, passing the first string to the first function
+/* given to match_list_init(), the second string to the second
+/* function, and so on.
+/*
+/* match_list_free() releases storage allocated by match_list_init().
+/*
+/* Arguments:
+/* .IP pname
+/* Parameter name or other identifying information that is
+/* prepended to error messages.
+/* .IP flags
+/* Specifies the bit-wise OR of zero or more of the following:
+/* .RS
+/* .IP MATCH_FLAG_PARENT
+/* The hostname pattern foo.com matches any name within the
+/* domain foo.com. If this flag is cleared, foo.com matches
+/* itself only, and .foo.com matches any name below the domain
+/* foo.com.
+/* .IP MATCH_FLAG_RETURN
+/* Request that match_list_match() logs a warning and returns
+/* zero (with list->error set to a non-zero dictionary error
+/* code) instead of raising a fatal run-time error.
+/* .RE
+/* Specify MATCH_FLAG_NONE to request none of the above.
+/* .IP pattern_list
+/* A list of patterns.
+/* .IP count
+/* Specifies how many match functions follow.
+/* .IP list
+/* Pattern list produced by match_list_init().
+/* .IP string
+/* Search string.
+/* DIAGNOSTICS
+/* Fatal error: unable to open or read a match_list file; invalid
+/* match_list pattern; casefold error (UTF-8 mode only).
+/* SEE ALSO
+/* host_match(3) match hosts by name or by address
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <readlline.h>
+#include <stringops.h>
+#include <argv.h>
+#include <dict.h>
+#include <match_list.h>
+
+/* Application-specific */
+
+#define MATCH_DICTIONARY(pattern) \
+ ((pattern)[0] != '[' && strchr((pattern), ':') != 0)
+
+/* match_list_parse - parse buffer, destroy buffer */
+
+static ARGV *match_list_parse(MATCH_LIST *match_list, ARGV *pat_list,
+ char *string, int init_match)
+{
+ const char *myname = "match_list_parse";
+ VSTRING *buf = vstring_alloc(10);
+ VSTREAM *fp;
+ const char *delim = CHARS_COMMA_SP;
+ char *bp = string;
+ char *start;
+ char *item;
+ char *map_type_name_flags;
+ int match;
+
+ /*
+ * We do not use DICT_FLAG_FOLD_FIX, because we casefold the search
+ * string at the beginning of a search, and we use strcmp() for string
+ * comparison. This works because string patterns are casefolded during
+ * match_list initialization, and databases are supposed to fold case
+ * upon creation.
+ */
+#define OPEN_FLAGS O_RDONLY
+#define DICT_FLAGS (DICT_FLAG_LOCK | DICT_FLAG_UTF8_REQUEST)
+#define STR(x) vstring_str(x)
+
+ /*
+ * /filename contents are expanded in-line. To support !/filename we
+ * prepend the negation operator to each item from the file.
+ *
+ * If there is an error, implement graceful degradation by inserting a
+ * pseudo table whose lookups fail with a warning message.
+ */
+ while ((start = mystrtokq_cw(&bp, delim, CHARS_BRACE,
+ match_list->pname)) != 0) {
+ 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 (readlline(buf, fp, (int *) 0))
+ pat_list = match_list_parse(match_list, pat_list,
+ vstring_str(buf), match);
+ if (vstream_fclose(fp))
+ msg_fatal("%s: read file %s: %m", myname, item);
+ }
+ } else if (MATCH_DICTIONARY(item)) { /* type:table */
+ vstring_sprintf(buf, "%s%s(%o,%s)", match ? "" : "!",
+ item, OPEN_FLAGS, dict_flags_str(DICT_FLAGS));
+ map_type_name_flags = STR(buf) + (match == 0);
+ if (dict_handle(map_type_name_flags) == 0)
+ dict_register(map_type_name_flags,
+ dict_open(item, OPEN_FLAGS, DICT_FLAGS));
+ argv_add(pat_list, STR(buf), (char *) 0);
+ } else { /* other pattern */
+ casefold(match_list->fold_buf, match ?
+ item : STR(vstring_sprintf(buf, "!%s", item)));
+ argv_add(pat_list, STR(match_list->fold_buf), (char *) 0);
+ }
+ }
+ vstring_free(buf);
+ return (pat_list);
+}
+
+/* match_list_init - initialize pattern list */
+
+MATCH_LIST *match_list_init(const char *pname, int flags,
+ const char *patterns, int match_count,...)
+{
+ MATCH_LIST *list;
+ char *saved_patterns;
+ va_list ap;
+ int i;
+
+ if (flags & ~MATCH_FLAG_ALL)
+ msg_panic("match_list_init: bad flags 0x%x", flags);
+
+ list = (MATCH_LIST *) mymalloc(sizeof(*list));
+ list->pname = mystrdup(pname);
+ list->flags = flags;
+ list->match_count = match_count;
+ list->match_func =
+ (MATCH_LIST_FN *) mymalloc(match_count * sizeof(MATCH_LIST_FN));
+ list->match_args =
+ (const char **) mymalloc(match_count * sizeof(const char *));
+ va_start(ap, match_count);
+ for (i = 0; i < match_count; i++)
+ list->match_func[i] = va_arg(ap, MATCH_LIST_FN);
+ va_end(ap);
+ list->error = 0;
+ list->fold_buf = vstring_alloc(20);
+
+#define DO_MATCH 1
+
+ saved_patterns = mystrdup(patterns);
+ list->patterns = match_list_parse(list, argv_alloc(1), saved_patterns,
+ DO_MATCH);
+ argv_terminate(list->patterns);
+ myfree(saved_patterns);
+ return (list);
+}
+
+/* match_list_match - match strings against pattern list */
+
+int match_list_match(MATCH_LIST *list,...)
+{
+ const char *myname = "match_list_match";
+ char **cpp;
+ char *pat;
+ int match;
+ int i;
+ va_list ap;
+
+ /*
+ * Iterate over all patterns in the list, stop at the first match.
+ */
+ va_start(ap, list);
+ for (i = 0; i < list->match_count; i++)
+ list->match_args[i] = va_arg(ap, const char *);
+ va_end(ap);
+
+ list->error = 0;
+ for (cpp = list->patterns->argv; (pat = *cpp) != 0; cpp++) {
+ for (match = 1; *pat == '!'; pat++)
+ match = !match;
+ for (i = 0; i < list->match_count; i++) {
+ casefold(list->fold_buf, list->match_args[i]);
+ if (list->match_func[i] (list, STR(list->fold_buf), pat))
+ return (match);
+ else if (list->error != 0)
+ return (0);
+ }
+ }
+ if (msg_verbose)
+ for (i = 0; i < list->match_count; i++)
+ msg_info("%s: %s: no match", myname, list->match_args[i]);
+ return (0);
+}
+
+/* match_list_free - release storage */
+
+void match_list_free(MATCH_LIST *list)
+{
+ /* XXX Should decrement map refcounts. */
+ myfree(list->pname);
+ argv_free(list->patterns);
+ myfree((void *) list->match_func);
+ myfree((void *) list->match_args);
+ vstring_free(list->fold_buf);
+ myfree((void *) list);
+}
diff --git a/src/util/match_list.h b/src/util/match_list.h
new file mode 100644
index 0000000..d8b7794
--- /dev/null
+++ b/src/util/match_list.h
@@ -0,0 +1,66 @@
+#ifndef _MATCH_LIST_H_INCLUDED_
+#define _MATCH_LIST_H_INCLUDED_
+
+/*++
+/* NAME
+/* match_list 3h
+/* SUMMARY
+/* generic list-based pattern matching
+/* SYNOPSIS
+/* #include <match_list.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <argv.h>
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+typedef struct MATCH_LIST MATCH_LIST;
+
+typedef int (*MATCH_LIST_FN) (MATCH_LIST *, const char *, const char *);
+
+struct MATCH_LIST {
+ char *pname; /* used in error messages */
+ int flags; /* processing options */
+ ARGV *patterns; /* one pattern each */
+ int match_count; /* match function/argument count */
+ MATCH_LIST_FN *match_func; /* match functions */
+ const char **match_args; /* match arguments */
+ VSTRING *fold_buf; /* case-folded pattern string */
+ int error; /* last operation */
+};
+
+#define MATCH_FLAG_NONE 0
+#define MATCH_FLAG_PARENT (1<<0)
+#define MATCH_FLAG_RETURN (1<<1)
+#define MATCH_FLAG_ALL (MATCH_FLAG_PARENT | MATCH_FLAG_RETURN)
+
+extern MATCH_LIST *match_list_init(const char *, int, const char *, int,...);
+extern int match_list_match(MATCH_LIST *,...);
+extern void match_list_free(MATCH_LIST *);
+
+ /*
+ * The following functions are not part of the public interface. These
+ * functions may be called only through match_list_match().
+ */
+extern int match_string(MATCH_LIST *, const char *, const char *);
+extern int match_hostname(MATCH_LIST *, const char *, const char *);
+extern int match_hostaddr(MATCH_LIST *, const char *, const char *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/match_ops.c b/src/util/match_ops.c
new file mode 100644
index 0000000..e0b7779
--- /dev/null
+++ b/src/util/match_ops.c
@@ -0,0 +1,312 @@
+/*++
+/* NAME
+/* match_ops 3
+/* SUMMARY
+/* simple string or host pattern matching
+/* SYNOPSIS
+/* #include <match_list.h>
+/*
+/* int match_string(list, string, pattern)
+/* MATCH_LIST *list;
+/* const char *string;
+/* const char *pattern;
+/*
+/* int match_hostname(list, name, pattern)
+/* MATCH_LIST *list;
+/* const char *name;
+/* const char *pattern;
+/*
+/* int match_hostaddr(list, addr, pattern)
+/* MATCH_LIST *list;
+/* const char *addr;
+/* const char *pattern;
+/* DESCRIPTION
+/* This module implements simple string and host name or address
+/* matching. The matching process is case insensitive. If a pattern
+/* has the form type:name, table lookup is used instead of string
+/* or address comparison.
+/*
+/* match_string() matches the string against the pattern, requiring
+/* an exact (case-insensitive) match. The flags argument is not used.
+/*
+/* match_hostname() matches the host name when the hostname matches
+/* the pattern exactly, or when the pattern matches a parent domain
+/* of the named host. The flags argument specifies the bit-wise OR
+/* of zero or more of the following:
+/* .IP MATCH_FLAG_PARENT
+/* The hostname pattern foo.com matches itself and any name below
+/* the domain foo.com. If this flag is cleared, foo.com matches itself
+/* only, and .foo.com matches any name below the domain foo.com.
+/* .IP MATCH_FLAG_RETURN
+/* Log a warning, return "not found", and set list->error to
+/* a non-zero dictionary error code, instead of raising a fatal
+/* run-time error.
+/* .RE
+/* Specify MATCH_FLAG_NONE to request none of the above.
+/*
+/* match_hostaddr() matches a host address when the pattern is
+/* identical to the host address, or when the pattern is a net/mask
+/* that contains the address. The mask specifies the number of
+/* bits in the network part of the pattern. The flags argument is
+/* not used.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <split_at.h>
+#include <dict.h>
+#include <match_list.h>
+#include <stringops.h>
+#include <cidr_match.h>
+
+#define MATCH_DICTIONARY(pattern) \
+ ((pattern)[0] != '[' && strchr((pattern), ':') != 0)
+
+/* match_error - return or raise fatal error */
+
+static int match_error(MATCH_LIST *list, const char *fmt,...)
+{
+ VSTRING *buf = vstring_alloc(100);
+ va_list ap;
+
+ /*
+ * Report, and maybe return.
+ */
+ va_start(ap, fmt);
+ vstring_vsprintf(buf, fmt, ap);
+ va_end(ap);
+ if (list->flags & MATCH_FLAG_RETURN) {
+ msg_warn("%s: %s", list->pname, vstring_str(buf));
+ } else {
+ msg_fatal("%s: %s", list->pname, vstring_str(buf));
+ }
+ vstring_free(buf);
+ return (0);
+}
+
+/* match_string - match a string literal */
+
+int match_string(MATCH_LIST *list, const char *string, const char *pattern)
+{
+ const char *myname = "match_string";
+ DICT *dict;
+
+ if (msg_verbose)
+ msg_info("%s: %s: %s ~? %s", myname, list->pname, string, pattern);
+
+ /*
+ * Try dictionary lookup: exact match.
+ */
+ if (MATCH_DICTIONARY(pattern)) {
+ if ((dict = dict_handle(pattern)) == 0)
+ msg_panic("%s: unknown dictionary: %s", myname, pattern);
+ if (dict_get(dict, string) != 0)
+ return (1);
+ if ((list->error = dict->error) != 0)
+ return (match_error(list, "%s:%s: table lookup problem",
+ dict->type, dict->name));
+ return (0);
+ }
+
+ /*
+ * Try an exact string match. Note that the string and pattern are
+ * already casefolded.
+ */
+ if (strcmp(string, pattern) == 0) {
+ return (1);
+ }
+
+ /*
+ * No match found.
+ */
+ return (0);
+}
+
+/* match_hostname - match a host by name */
+
+int match_hostname(MATCH_LIST *list, const char *name, const char *pattern)
+{
+ const char *myname = "match_hostname";
+ const char *pd;
+ const char *entry;
+ const char *next;
+ int match;
+ DICT *dict;
+
+ if (msg_verbose)
+ msg_info("%s: %s: %s ~? %s", myname, list->pname, name, pattern);
+
+ /*
+ * Try dictionary lookup: exact match and parent domains.
+ *
+ * Don't look up parent domain substrings with regexp maps etc.
+ */
+ if (MATCH_DICTIONARY(pattern)) {
+ if ((dict = dict_handle(pattern)) == 0)
+ msg_panic("%s: unknown dictionary: %s", myname, pattern);
+ match = 0;
+ for (entry = name; *entry != 0; entry = next) {
+ if (entry == name || (dict->flags & DICT_FLAG_FIXED)) {
+ match = (dict_get(dict, entry) != 0);
+ if (msg_verbose > 1)
+ msg_info("%s: %s: lookup %s:%s %s: %s",
+ myname, list->pname, dict->type, dict->name,
+ entry, match ? "found" : "notfound");
+ if (match != 0)
+ break;
+ if ((list->error = dict->error) != 0)
+ return (match_error(list, "%s:%s: table lookup problem",
+ dict->type, dict->name));
+ }
+ if ((next = strchr(entry + 1, '.')) == 0)
+ break;
+ if (list->flags & MATCH_FLAG_PARENT)
+ next += 1;
+ }
+ return (match);
+ }
+
+ /*
+ * Try an exact match with the host name. Note that the name and the
+ * pattern are already casefolded.
+ */
+ if (strcmp(name, pattern) == 0) {
+ return (1);
+ }
+
+ /*
+ * See if the pattern is a parent domain of the hostname. Note that the
+ * name and the pattern are already casefolded.
+ */
+ else {
+ if (list->flags & MATCH_FLAG_PARENT) {
+ pd = name + strlen(name) - strlen(pattern);
+ if (pd > name && pd[-1] == '.' && strcmp(pd, pattern) == 0)
+ return (1);
+ } else if (pattern[0] == '.') {
+ pd = name + strlen(name) - strlen(pattern);
+ if (pd > name && strcmp(pd, pattern) == 0)
+ return (1);
+ }
+ }
+ return (0);
+}
+
+/* match_hostaddr - match host by address */
+
+int match_hostaddr(MATCH_LIST *list, const char *addr, const char *pattern)
+{
+ const char *myname = "match_hostaddr";
+ char *saved_patt;
+ CIDR_MATCH match_info;
+ DICT *dict;
+ VSTRING *err;
+ int rc;
+
+ if (msg_verbose)
+ msg_info("%s: %s: %s ~? %s", myname, list->pname, addr, pattern);
+
+#define V4_ADDR_STRING_CHARS "01234567890."
+#define V6_ADDR_STRING_CHARS V4_ADDR_STRING_CHARS "abcdefABCDEF:"
+
+ if (addr[strspn(addr, V6_ADDR_STRING_CHARS)] != 0)
+ return (0);
+
+ /*
+ * Try dictionary lookup. This can be case insensitive.
+ */
+ if (MATCH_DICTIONARY(pattern)) {
+ if ((dict = dict_handle(pattern)) == 0)
+ msg_panic("%s: unknown dictionary: %s", myname, pattern);
+ if (dict_get(dict, addr) != 0)
+ return (1);
+ if ((list->error = dict->error) != 0)
+ return (match_error(list, "%s:%s: table lookup problem",
+ dict->type, dict->name));
+ return (0);
+ }
+
+ /*
+ * Try an exact match with the host address. Note that the address and
+ * pattern are already casefolded.
+ */
+ if (pattern[0] != '[') {
+ if (strcmp(addr, pattern) == 0)
+ return (1);
+ } else {
+ size_t addr_len = strlen(addr);
+
+ if (strncmp(addr, pattern + 1, addr_len) == 0
+ && strcmp(pattern + 1 + addr_len, "]") == 0)
+ return (1);
+ }
+
+ /*
+ * Light-weight tests before we get into expensive operations.
+ *
+ * - Don't bother matching IPv4 against IPv6. Postfix transforms
+ * IPv4-in-IPv6 to native IPv4 form when IPv4 support is enabled in
+ * Postfix; if not, then Postfix has no business dealing with IPv4
+ * addresses anyway.
+ *
+ * - Don't bother unless the pattern is either an IPv6 address or net/mask.
+ *
+ * We can safely skip IPv4 address patterns because their form is
+ * unambiguous and they did not match in the strcmp() calls above.
+ *
+ * XXX We MUST skip (parent) domain names, which may appear in NAMADR_LIST
+ * input, to avoid triggering false cidr_match_parse() errors.
+ *
+ * The last two conditions below are for backwards compatibility with
+ * earlier Postfix versions: don't abort with fatal errors on junk that
+ * was silently ignored (principle of least astonishment).
+ */
+ if (!strchr(addr, ':') != !strchr(pattern, ':')
+ || pattern[strcspn(pattern, ":/")] == 0
+ || pattern[strspn(pattern, V4_ADDR_STRING_CHARS)] == 0
+ || pattern[strspn(pattern, V6_ADDR_STRING_CHARS "[]/")] != 0)
+ return (0);
+
+ /*
+ * No escape from expensive operations: either we have a net/mask
+ * pattern, or we have an address that can have multiple valid
+ * representations (e.g., 0:0:0:0:0:0:0:1 versus ::1, etc.). The only way
+ * to find out if the address matches the pattern is to transform
+ * everything into to binary form, and to do the comparison there.
+ */
+ saved_patt = mystrdup(pattern);
+ err = cidr_match_parse(&match_info, saved_patt, CIDR_MATCH_TRUE,
+ (VSTRING *) 0);
+ myfree(saved_patt);
+ if (err != 0) {
+ list->error = DICT_ERR_RETRY;
+ rc = match_error(list, "%s", vstring_str(err));
+ vstring_free(err);
+ return (rc);
+ }
+ return (cidr_match_execute(&match_info, addr) != 0);
+}
diff --git a/src/util/midna_domain.c b/src/util/midna_domain.c
new file mode 100644
index 0000000..333a5c9
--- /dev/null
+++ b/src/util/midna_domain.c
@@ -0,0 +1,419 @@
+/*++
+/* NAME
+/* midna_domain 3
+/* SUMMARY
+/* ASCII/UTF-8 domain name conversion
+/* SYNOPSIS
+/* #include <midna_domain.h>
+/*
+/* int midna_domain_cache_size;
+/* int midna_domain_transitional;
+/*
+/* const char *midna_domain_to_ascii(
+/* const char *name)
+/*
+/* const char *midna_domain_to_utf8(
+/* const char *name)
+/*
+/* const char *midna_domain_suffix_to_ascii(
+/* const char *name)
+/*
+/* const char *midna_domain_suffix_to_utf8(
+/* const char *name)
+/* AUXILIARY FUNCTIONS
+/* void midna_domain_pre_chroot(void)
+/* DESCRIPTION
+/* The functions in this module transform domain names from/to
+/* ASCII and UTF-8 form. The result is cached to avoid repeated
+/* conversion.
+/*
+/* This module builds on the ICU library implementation of the
+/* UTS #46 specification, using default ICU library options
+/* because those are likely best tested: with transitional
+/* processing, with case mapping, with normalization, with
+/* limited IDNA2003 compatibility, without STD3 ASCII rules.
+/*
+/* midna_domain_to_ascii() converts an UTF-8 or ASCII domain
+/* name to ASCII. The result is a null pointer in case of
+/* error. This function verifies that the result passes
+/* valid_hostname().
+/*
+/* midna_domain_to_utf8() converts an UTF-8 or ASCII domain
+/* name to UTF-8. The result is a null pointer in case of
+/* error. This function verifies that the result, after
+/* conversion to ASCII, passes valid_hostname().
+/*
+/* midna_domain_suffix_to_ascii() and midna_domain_suffix_to_utf8()
+/* take a name that starts with '.' and otherwise perform the
+/* same operations as midna_domain_to_ascii() and
+/* midna_domain_to_utf8().
+/*
+/* midna_domain_cache_size specifies the size of the conversion
+/* result cache. This value is used only once, upon the first
+/* lookup request.
+/*
+/* midna_domain_transitional enables transitional conversion
+/* between UTF8 and ASCII labels.
+/*
+/* midna_domain_pre_chroot() does some pre-chroot initialization.
+/* SEE ALSO
+/* http://unicode.org/reports/tr46/ Unicode IDNA Compatibility processing
+/* msg(3) diagnostics interface
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation problem.
+/* Warnings: conversion error or result validation error.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Arnt Gulbrandsen
+/*
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <string.h>
+#include <ctype.h>
+
+#ifndef NO_EAI
+#include <unicode/uidna.h>
+
+ /*
+ * Utility library.
+ */
+#include <mymalloc.h>
+#include <msg.h>
+#include <ctable.h>
+#include <stringops.h>
+#include <valid_hostname.h>
+#include <name_mask.h>
+#include <midna_domain.h>
+
+ /*
+ * Application-specific.
+ */
+#define DEF_MIDNA_CACHE_SIZE 256
+
+int midna_domain_cache_size = DEF_MIDNA_CACHE_SIZE;
+int midna_domain_transitional = 0;
+static VSTRING *midna_domain_buf; /* x.suffix */
+
+#define STR(x) vstring_str(x)
+
+/* midna_domain_strerror - pick one for error reporting */
+
+static const char *midna_domain_strerror(UErrorCode error, int info_errors)
+{
+
+ /*
+ * XXX The UIDNA_ERROR_EMPTY_LABEL etc. names are defined in an ENUM, so
+ * we can't use #ifdef to dynamically determine which names exist.
+ */
+ static LONG_NAME_MASK uidna_errors[] = {
+ "UIDNA_ERROR_EMPTY_LABEL", UIDNA_ERROR_EMPTY_LABEL,
+ "UIDNA_ERROR_LABEL_TOO_LONG", UIDNA_ERROR_LABEL_TOO_LONG,
+ "UIDNA_ERROR_DOMAIN_NAME_TOO_LONG", UIDNA_ERROR_DOMAIN_NAME_TOO_LONG,
+ "UIDNA_ERROR_LEADING_HYPHEN", UIDNA_ERROR_LEADING_HYPHEN,
+ "UIDNA_ERROR_TRAILING_HYPHEN", UIDNA_ERROR_TRAILING_HYPHEN,
+ "UIDNA_ERROR_HYPHEN_3_4", UIDNA_ERROR_HYPHEN_3_4,
+ "UIDNA_ERROR_LEADING_COMBINING_MARK", UIDNA_ERROR_LEADING_COMBINING_MARK,
+ "UIDNA_ERROR_DISALLOWED", UIDNA_ERROR_DISALLOWED,
+ "UIDNA_ERROR_PUNYCODE", UIDNA_ERROR_PUNYCODE,
+ "UIDNA_ERROR_LABEL_HAS_DOT", UIDNA_ERROR_LABEL_HAS_DOT,
+ "UIDNA_ERROR_INVALID_ACE_LABEL", UIDNA_ERROR_INVALID_ACE_LABEL,
+ "UIDNA_ERROR_BIDI", UIDNA_ERROR_BIDI,
+ "UIDNA_ERROR_CONTEXTJ", UIDNA_ERROR_CONTEXTJ,
+ /* The above errors are defined with ICU 46 and later. */
+ 0,
+ };
+
+ if (info_errors) {
+ return (str_long_name_mask_opt((VSTRING *) 0, "idna error",
+ uidna_errors, info_errors,
+ NAME_MASK_NUMBER | NAME_MASK_COMMA));
+ } else {
+ return u_errorName(error);
+ }
+}
+
+/* midna_domain_pre_chroot - pre-chroot initialization */
+
+void midna_domain_pre_chroot(void)
+{
+ UErrorCode error = U_ZERO_ERROR;
+ UIDNAInfo info = UIDNA_INFO_INITIALIZER;
+ UIDNA *idna;
+
+ idna = uidna_openUTS46(midna_domain_transitional ? UIDNA_DEFAULT
+ : UIDNA_NONTRANSITIONAL_TO_ASCII, &error);
+ if (U_FAILURE(error))
+ msg_warn("ICU library initialization failed: %s",
+ midna_domain_strerror(error, info.errors));
+ uidna_close(idna);
+}
+
+/* midna_domain_to_ascii_create - convert domain to ASCII */
+
+static void *midna_domain_to_ascii_create(const char *name, void *unused_context)
+{
+ static const char myname[] = "midna_domain_to_ascii_create";
+ char buf[1024]; /* XXX */
+ UErrorCode error = U_ZERO_ERROR;
+ UIDNAInfo info = UIDNA_INFO_INITIALIZER;
+ UIDNA *idna;
+ int anl;
+
+ /*
+ * Paranoia: do not expose uidna_*() to unfiltered network data.
+ */
+ if (allascii(name) == 0 && valid_utf8_string(name, strlen(name)) == 0) {
+ msg_warn("%s: Problem translating domain \"%.100s\" to ASCII form: %s",
+ myname, name, "malformed UTF-8");
+ return (0);
+ }
+
+ /*
+ * Perform the requested conversion.
+ */
+ idna = uidna_openUTS46(midna_domain_transitional ? UIDNA_DEFAULT
+ : UIDNA_NONTRANSITIONAL_TO_ASCII, &error);
+ anl = uidna_nameToASCII_UTF8(idna,
+ name, strlen(name),
+ buf, sizeof(buf) - 1,
+ &info,
+ &error);
+ uidna_close(idna);
+
+ /*
+ * Paranoia: verify that the result passes valid_hostname(). A quick
+ * check shows that UTS46 ToASCII by default rejects inputs with labels
+ * that start or end in '-', with names or labels that are over-long, or
+ * "fake" A-labels, as required by UTS 46 section 4.1, but we rely on
+ * valid_hostname() on the output side just to be sure.
+ */
+ if (U_SUCCESS(error) && info.errors == 0 && anl > 0) {
+ buf[anl] = 0; /* XXX */
+ if (!valid_hostname(buf, DONT_GRIPE)) {
+ msg_warn("%s: Problem translating domain \"%.100s\" to ASCII form: %s",
+ myname, name, "malformed ASCII label(s)");
+ return (0);
+ }
+ return (mystrndup(buf, anl));
+ } else {
+ msg_warn("%s: Problem translating domain \"%.100s\" to ASCII form: %s",
+ myname, name, midna_domain_strerror(error, info.errors));
+ return (0);
+ }
+}
+
+/* midna_domain_to_utf8_create - convert domain to UTF8 */
+
+static void *midna_domain_to_utf8_create(const char *name, void *unused_context)
+{
+ static const char myname[] = "midna_domain_to_utf8_create";
+ char buf[1024]; /* XXX */
+ UErrorCode error = U_ZERO_ERROR;
+ UIDNAInfo info = UIDNA_INFO_INITIALIZER;
+ UIDNA *idna;
+ int anl;
+
+ /*
+ * Paranoia: do not expose uidna_*() to unfiltered network data.
+ */
+ if (allascii(name) == 0 && valid_utf8_string(name, strlen(name)) == 0) {
+ msg_warn("%s: Problem translating domain \"%.100s\" to UTF-8 form: %s",
+ myname, name, "malformed UTF-8");
+ return (0);
+ }
+
+ /*
+ * Perform the requested conversion.
+ */
+ idna = uidna_openUTS46(midna_domain_transitional ? UIDNA_DEFAULT
+ : UIDNA_NONTRANSITIONAL_TO_UNICODE, &error);
+ anl = uidna_nameToUnicodeUTF8(idna,
+ name, strlen(name),
+ buf, sizeof(buf) - 1,
+ &info,
+ &error);
+ uidna_close(idna);
+
+ /*
+ * Paranoia: UTS46 toUTF8 by default accepts and produces an over-long
+ * name or a name that contains an over-long NR-LDH label (and perhaps
+ * other invalid forms that are not covered in UTS 46, section 4.1). We
+ * rely on midna_domain_to_ascii() to validate the output.
+ */
+ if (U_SUCCESS(error) && info.errors == 0 && anl > 0) {
+ buf[anl] = 0; /* XXX */
+ if (midna_domain_to_ascii(buf) == 0)
+ return (0);
+ return (mystrndup(buf, anl));
+ } else {
+ msg_warn("%s: Problem translating domain \"%.100s\" to UTF8 form: %s",
+ myname, name, midna_domain_strerror(error, info.errors));
+ return (0);
+ }
+}
+
+/* midna_domain_cache_free - cache element destructor */
+
+static void midna_domain_cache_free(void *value, void *unused_context)
+{
+ if (value)
+ myfree(value);
+}
+
+/* midna_domain_to_ascii - convert name to ASCII */
+
+const char *midna_domain_to_ascii(const char *name)
+{
+ static CTABLE *midna_domain_to_ascii_cache = 0;
+
+ if (midna_domain_to_ascii_cache == 0)
+ midna_domain_to_ascii_cache = ctable_create(midna_domain_cache_size,
+ midna_domain_to_ascii_create,
+ midna_domain_cache_free,
+ (void *) 0);
+ return (ctable_locate(midna_domain_to_ascii_cache, name));
+}
+
+/* midna_domain_to_utf8 - convert name to UTF8 */
+
+const char *midna_domain_to_utf8(const char *name)
+{
+ static CTABLE *midna_domain_to_utf8_cache = 0;
+
+ if (midna_domain_to_utf8_cache == 0)
+ midna_domain_to_utf8_cache = ctable_create(midna_domain_cache_size,
+ midna_domain_to_utf8_create,
+ midna_domain_cache_free,
+ (void *) 0);
+ return (ctable_locate(midna_domain_to_utf8_cache, name));
+}
+
+/* midna_domain_suffix_to_ascii - convert .name to ASCII */
+
+const char *midna_domain_suffix_to_ascii(const char *suffix)
+{
+ const char *cache_res;
+
+ /*
+ * If prepending x to .name causes the result to become too long, then
+ * the suffix is bad.
+ */
+ if (midna_domain_buf == 0)
+ midna_domain_buf = vstring_alloc(100);
+ vstring_sprintf(midna_domain_buf, "x%s", suffix);
+ if ((cache_res = midna_domain_to_ascii(STR(midna_domain_buf))) == 0)
+ return (0);
+ else
+ return (cache_res + 1);
+}
+
+/* midna_domain_suffix_to_utf8 - convert .name to UTF8 */
+
+const char *midna_domain_suffix_to_utf8(const char *name)
+{
+ const char *cache_res;
+
+ /*
+ * If prepending x to .name causes the result to become too long, then
+ * the suffix is bad.
+ */
+ if (midna_domain_buf == 0)
+ midna_domain_buf = vstring_alloc(100);
+ vstring_sprintf(midna_domain_buf, "x%s", name);
+ if ((cache_res = midna_domain_to_utf8(STR(midna_domain_buf))) == 0)
+ return (0);
+ else
+ return (cache_res + 1);
+}
+
+#ifdef TEST
+
+ /*
+ * Test program - reads names from stdin, reports invalid names to stderr.
+ */
+#include <unistd.h>
+#include <stdlib.h>
+#include <locale.h>
+
+#include <stringops.h> /* XXX util_utf8_enable */
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <msg_vstream.h>
+
+int main(int argc, char **argv)
+{
+ VSTRING *buffer = vstring_alloc(1);
+ const char *bp;
+ const char *ascii;
+ const char *utf8;
+
+ if (setlocale(LC_ALL, "C") == 0)
+ msg_fatal("setlocale(LC_ALL, C) failed: %m");
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ /* msg_verbose = 1; */
+ util_utf8_enable = 1;
+
+ if (geteuid() == 0) {
+ midna_domain_pre_chroot();
+ if (chroot(".") != 0)
+ msg_fatal("chroot(\".\"): %m");
+ }
+ while (vstring_fgets_nonl(buffer, VSTREAM_IN)) {
+ bp = STR(buffer);
+ msg_info("> %s", bp);
+ while (ISSPACE(*bp))
+ bp++;
+ if (*bp == '#' || *bp == 0)
+ continue;
+ msg_info("unconditional conversions:");
+ utf8 = midna_domain_to_utf8(bp);
+ msg_info("\"%s\" ->utf8 \"%s\"", bp, utf8 ? utf8 : "(error)");
+ ascii = midna_domain_to_ascii(bp);
+ msg_info("\"%s\" ->ascii \"%s\"", bp, ascii ? ascii : "(error)");
+ msg_info("conditional conversions:");
+ if (!allascii(bp)) {
+ if (ascii != 0) {
+ utf8 = midna_domain_to_utf8(ascii);
+ msg_info("\"%s\" ->ascii \"%s\" ->utf8 \"%s\"",
+ bp, ascii, utf8 ? utf8 : "(error)");
+ if (utf8 != 0) {
+ if (strcmp(utf8, bp) != 0)
+ msg_warn("\"%s\" != \"%s\"", bp, utf8);
+ }
+ }
+ } else {
+ if (utf8 != 0) {
+ ascii = midna_domain_to_ascii(utf8);
+ msg_info("\"%s\" ->utf8 \"%s\" ->ascii \"%s\"",
+ bp, utf8, ascii ? ascii : "(error)");
+ if (ascii != 0) {
+ if (strcmp(ascii, bp) != 0)
+ msg_warn("\"%s\" != \"%s\"", bp, ascii);
+ }
+ }
+ }
+ }
+ exit(0);
+}
+
+#endif /* TEST */
+
+#endif /* NO_EAI */
diff --git a/src/util/midna_domain.h b/src/util/midna_domain.h
new file mode 100644
index 0000000..1abe2a1
--- /dev/null
+++ b/src/util/midna_domain.h
@@ -0,0 +1,43 @@
+#ifndef _MIDNA_H_INCLUDED_
+#define _MIDNA_H_INCLUDED_
+
+/*++
+/* NAME
+/* midna_domain 3h
+/* SUMMARY
+/* ASCII/UTF-8 domain name conversion
+/* SYNOPSIS
+/* #include <midna_domain.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern const char *midna_domain_to_ascii(const char *);
+extern const char *midna_domain_to_utf8(const char *);
+extern const char *midna_domain_suffix_to_ascii(const char *);
+extern const char *midna_domain_suffix_to_utf8(const char *);
+extern void midna_domain_pre_chroot(void);
+
+extern int midna_domain_cache_size;
+extern int midna_domain_transitional;
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Arnt Gulbrandsen
+/*
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/midna_domain_test.in b/src/util/midna_domain_test.in
new file mode 100644
index 0000000..55491e4
--- /dev/null
+++ b/src/util/midna_domain_test.in
@@ -0,0 +1,21 @@
+# Upper-case greek -> lower-case greek.
+Δημοσθένους.example.com
+# Upper-case ASCII -> lower-case ASCII.
+Hello.example.com
+# Invalid LDH label('-' at begin or end).
+bad-.example.com
+-bad.example.com
+# Invalid LDH (label > 63 bytes).
+abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789.example.com
+# Valid LDH label (label <= 63 bytes).
+abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com
+# Invalid name (length > 255 bytes).
+abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.example.com
+# Aliases for '.' -> '.'.
+x。example.com
+x.example.com
+x。example.com
+# Good a-label.
+xn--mumble.example.com
+# Bad a-label.
+xn--123456.example.com
diff --git a/src/util/midna_domain_test.ref b/src/util/midna_domain_test.ref
new file mode 100644
index 0000000..c1db0bd
--- /dev/null
+++ b/src/util/midna_domain_test.ref
@@ -0,0 +1,89 @@
+./midna_domain: > # Upper-case greek -> lower-case greek.
+./midna_domain: > Δημοσθένους.example.com
+./midna_domain: unconditional conversions:
+./midna_domain: "Δημοσθένους.example.com" ->utf8 "δημοσθένους.example.com"
+./midna_domain: "Δημοσθένους.example.com" ->ascii "xn--ixanjetild1af0a.example.com"
+./midna_domain: conditional conversions:
+./midna_domain: "Δημοσθένους.example.com" ->ascii "xn--ixanjetild1af0a.example.com" ->utf8 "δημοσθένους.example.com"
+./midna_domain: warning: "Δημοσθένους.example.com" != "δημοσθένους.example.com"
+./midna_domain: > # Upper-case ASCII -> lower-case ASCII.
+./midna_domain: > Hello.example.com
+./midna_domain: unconditional conversions:
+./midna_domain: "Hello.example.com" ->utf8 "hello.example.com"
+./midna_domain: "Hello.example.com" ->ascii "hello.example.com"
+./midna_domain: conditional conversions:
+./midna_domain: "Hello.example.com" ->utf8 "hello.example.com" ->ascii "hello.example.com"
+./midna_domain: warning: "Hello.example.com" != "hello.example.com"
+./midna_domain: > # Invalid LDH label('-' at begin or end).
+./midna_domain: > bad-.example.com
+./midna_domain: unconditional conversions:
+./midna_domain: warning: midna_domain_to_utf8_create: Problem translating domain "bad-.example.com" to UTF8 form: UIDNA_ERROR_TRAILING_HYPHEN
+./midna_domain: "bad-.example.com" ->utf8 "(error)"
+./midna_domain: warning: midna_domain_to_ascii_create: Problem translating domain "bad-.example.com" to ASCII form: UIDNA_ERROR_TRAILING_HYPHEN
+./midna_domain: "bad-.example.com" ->ascii "(error)"
+./midna_domain: conditional conversions:
+./midna_domain: > -bad.example.com
+./midna_domain: unconditional conversions:
+./midna_domain: warning: midna_domain_to_utf8_create: Problem translating domain "-bad.example.com" to UTF8 form: UIDNA_ERROR_LEADING_HYPHEN
+./midna_domain: "-bad.example.com" ->utf8 "(error)"
+./midna_domain: warning: midna_domain_to_ascii_create: Problem translating domain "-bad.example.com" to ASCII form: UIDNA_ERROR_LEADING_HYPHEN
+./midna_domain: "-bad.example.com" ->ascii "(error)"
+./midna_domain: conditional conversions:
+./midna_domain: > # Invalid LDH (label > 63 bytes).
+./midna_domain: > abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789.example.com
+./midna_domain: unconditional conversions:
+./midna_domain: warning: midna_domain_to_ascii_create: Problem translating domain "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789.example.com" to ASCII form: UIDNA_ERROR_LABEL_TOO_LONG
+./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789.example.com" ->utf8 "(error)"
+./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789.example.com" ->ascii "(error)"
+./midna_domain: conditional conversions:
+./midna_domain: > # Valid LDH label (label <= 63 bytes).
+./midna_domain: > abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com
+./midna_domain: unconditional conversions:
+./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" ->utf8 "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com"
+./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" ->ascii "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com"
+./midna_domain: conditional conversions:
+./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" ->utf8 "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" ->ascii "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com"
+./midna_domain: > # Invalid name (length > 255 bytes).
+./midna_domain: > abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.example.com
+./midna_domain: unconditional conversions:
+./midna_domain: warning: midna_domain_to_ascii_create: Problem translating domain "abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcde" to ASCII form: UIDNA_ERROR_DOMAIN_NAME_TOO_LONG
+./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.example.com" ->utf8 "(error)"
+./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.example.com" ->ascii "(error)"
+./midna_domain: conditional conversions:
+./midna_domain: > # Aliases for '.' -> '.'.
+./midna_domain: > x。example.com
+./midna_domain: unconditional conversions:
+./midna_domain: "x。example.com" ->utf8 "x.example.com"
+./midna_domain: "x。example.com" ->ascii "x.example.com"
+./midna_domain: conditional conversions:
+./midna_domain: "x。example.com" ->ascii "x.example.com" ->utf8 "x.example.com"
+./midna_domain: warning: "x。example.com" != "x.example.com"
+./midna_domain: > x.example.com
+./midna_domain: unconditional conversions:
+./midna_domain: "x.example.com" ->utf8 "x.example.com"
+./midna_domain: "x.example.com" ->ascii "x.example.com"
+./midna_domain: conditional conversions:
+./midna_domain: "x.example.com" ->ascii "x.example.com" ->utf8 "x.example.com"
+./midna_domain: warning: "x.example.com" != "x.example.com"
+./midna_domain: > x。example.com
+./midna_domain: unconditional conversions:
+./midna_domain: "x。example.com" ->utf8 "x.example.com"
+./midna_domain: "x。example.com" ->ascii "x.example.com"
+./midna_domain: conditional conversions:
+./midna_domain: "x。example.com" ->ascii "x.example.com" ->utf8 "x.example.com"
+./midna_domain: warning: "x。example.com" != "x.example.com"
+./midna_domain: > # Good a-label.
+./midna_domain: > xn--mumble.example.com
+./midna_domain: unconditional conversions:
+./midna_domain: "xn--mumble.example.com" ->utf8 "㲹㲺㲵㲴.example.com"
+./midna_domain: "xn--mumble.example.com" ->ascii "xn--mumble.example.com"
+./midna_domain: conditional conversions:
+./midna_domain: "xn--mumble.example.com" ->utf8 "㲹㲺㲵㲴.example.com" ->ascii "xn--mumble.example.com"
+./midna_domain: > # Bad a-label.
+./midna_domain: > xn--123456.example.com
+./midna_domain: unconditional conversions:
+./midna_domain: warning: midna_domain_to_utf8_create: Problem translating domain "xn--123456.example.com" to UTF8 form: UIDNA_ERROR_PUNYCODE
+./midna_domain: "xn--123456.example.com" ->utf8 "(error)"
+./midna_domain: warning: midna_domain_to_ascii_create: Problem translating domain "xn--123456.example.com" to ASCII form: UIDNA_ERROR_PUNYCODE
+./midna_domain: "xn--123456.example.com" ->ascii "(error)"
+./midna_domain: conditional conversions:
diff --git a/src/util/miss_endif_cidr.map b/src/util/miss_endif_cidr.map
new file mode 100644
index 0000000..7c88208
--- /dev/null
+++ b/src/util/miss_endif_cidr.map
@@ -0,0 +1 @@
+if 1.2.3.4
diff --git a/src/util/miss_endif_cidr.ref b/src/util/miss_endif_cidr.ref
new file mode 100644
index 0000000..df5dc6c
--- /dev/null
+++ b/src/util/miss_endif_cidr.ref
@@ -0,0 +1,4 @@
+./dict_open: warning: cidr map miss_endif_cidr.map, line 1: IF has no matching ENDIF
+owner=untrusted (uid=USER)
+> get 1.2.3.5
+1.2.3.5: not found
diff --git a/src/util/miss_endif_pcre.ref b/src/util/miss_endif_pcre.ref
new file mode 100644
index 0000000..21d7402
--- /dev/null
+++ b/src/util/miss_endif_pcre.ref
@@ -0,0 +1,4 @@
+./dict_open: warning: pcre map miss_endif_re.map, line 1: IF has no matching ENDIF
+owner=untrusted (uid=USER)
+> get 1.2.3.5
+1.2.3.5: not found
diff --git a/src/util/miss_endif_re.map b/src/util/miss_endif_re.map
new file mode 100644
index 0000000..b085ecb
--- /dev/null
+++ b/src/util/miss_endif_re.map
@@ -0,0 +1 @@
+if /foo/
diff --git a/src/util/miss_endif_regexp.ref b/src/util/miss_endif_regexp.ref
new file mode 100644
index 0000000..f77f95a
--- /dev/null
+++ b/src/util/miss_endif_regexp.ref
@@ -0,0 +1,4 @@
+./dict_open: warning: regexp map miss_endif_re.map, line 1: IF has no matching ENDIF
+owner=untrusted (uid=USER)
+> get 1.2.3.5
+1.2.3.5: not found
diff --git a/src/util/mkmap.h b/src/util/mkmap.h
new file mode 100644
index 0000000..01c09f2
--- /dev/null
+++ b/src/util/mkmap.h
@@ -0,0 +1,50 @@
+#ifndef _MKMAP_H_INCLUDED_
+#define _MKMAP_H_INCLUDED_
+
+/*++
+/* NAME
+/* mkmap 3h
+/* SUMMARY
+/* create or rewrite Postfix database
+/* SYNOPSIS
+/* #include <mkmap.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * 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 {
+ struct DICT *(*open) (const char *, int, int); /* 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_close(MKMAP *);
+
+#define mkmap_append(map, key, val) dict_put((map)->dict, (key), (val))
+
+typedef MKMAP *(*MKMAP_OPEN_FN) (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/mkmap_cdb.c b/src/util/mkmap_cdb.c
new file mode 100644
index 0000000..abb2c7d
--- /dev/null
+++ b/src/util/mkmap_cdb.c
@@ -0,0 +1,65 @@
+/*++
+/* NAME
+/* mkmap_cdb 3
+/* SUMMARY
+/* create or open database, CDB style
+/* SYNOPSIS
+/* #include <dict_cdb.h>
+/*
+/* MKMAP *mkmap_cdb_open(path)
+/* const char *path;
+/*
+/* DESCRIPTION
+/* This module implements support for creating DJB's CDB "constant
+/* databases".
+/*
+/* mkmap_cdb_open() take a file name, append the ".cdb.tmp" suffix,
+/* create the named DB database. On close, this file renamed to
+/* file name with ".cdb" suffix appended (without ".tmp" part).
+/* This routine is a CDB-specific helper for the more
+/* general mkmap_open() interface.
+/*
+/* All errors are fatal.
+/* SEE ALSO
+/* dict_cdb(3), CDB dictionary interface.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Written by Michael Tokarev <mjt@tls.msk.ru> based on mkmap_db by
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+#ifdef HAS_CDB
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <dict_cdb.h>
+
+/* This is a dummy module, since CDB has all the functionality
+ * built-in, as cdb creation requires one global lock anyway. */
+
+MKMAP *mkmap_cdb_open(const char *unused_path)
+{
+ MKMAP *mkmap = (MKMAP *) mymalloc(sizeof(*mkmap));
+ mkmap->open = dict_cdb_open;
+ mkmap->after_open = 0;
+ mkmap->after_close = 0;
+ return (mkmap);
+}
+
+#endif /* HAS_CDB */
diff --git a/src/util/mkmap_db.c b/src/util/mkmap_db.c
new file mode 100644
index 0000000..b2d830f
--- /dev/null
+++ b/src/util/mkmap_db.c
@@ -0,0 +1,172 @@
+/*++
+/* NAME
+/* mkmap_db 3
+/* SUMMARY
+/* create or open database, DB style
+/* SYNOPSIS
+/* #include <dict_db.h>
+/*
+/* MKMAP *mkmap_hash_open(path)
+/* const char *path;
+/*
+/* MKMAP *mkmap_btree_open(path)
+/* const char *path;
+/* DESCRIPTION
+/* This module implements support for creating DB databases.
+/*
+/* mkmap_hash_open() and mkmap_btree_open() take a file name,
+/* append the ".db" suffix, and do whatever initialization is
+/* required before the Berkeley DB open routine is called.
+/*
+/* All errors are fatal.
+/* SEE ALSO
+/* dict_db(3), DB dictionary interface.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <dict_db.h>
+#include <myflock.h>
+#include <warn_stat.h>
+
+#ifdef HAS_DB
+#ifdef PATH_DB_H
+#include PATH_DB_H
+#else
+#include <db.h>
+#endif
+
+typedef struct MKMAP_DB {
+ MKMAP mkmap; /* parent class */
+ char *lock_file; /* path name */
+ int lock_fd; /* -1 or open locked file */
+} MKMAP_DB;
+
+/* mkmap_db_after_close - clean up after closing database */
+
+static void mkmap_db_after_close(MKMAP *mp)
+{
+ MKMAP_DB *mkmap = (MKMAP_DB *) mp;
+
+ if (mkmap->lock_fd >= 0 && close(mkmap->lock_fd) < 0)
+ msg_warn("close %s: %m", mkmap->lock_file);
+ myfree(mkmap->lock_file);
+}
+
+/* mkmap_db_after_open - lock newly created database */
+
+static void mkmap_db_after_open(MKMAP *mp)
+{
+ MKMAP_DB *mkmap = (MKMAP_DB *) mp;
+
+ if (mkmap->lock_fd < 0) {
+ if ((mkmap->lock_fd = open(mkmap->lock_file, O_RDWR, 0644)) < 0)
+ msg_fatal("open lockfile %s: %m", mkmap->lock_file);
+ if (myflock(mkmap->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("lock %s: %m", mkmap->lock_file);
+ }
+}
+
+/* mkmap_db_before_open - lock existing database */
+
+static MKMAP *mkmap_db_before_open(const char *path,
+ DICT *(*db_open) (const char *, int, int))
+{
+ MKMAP_DB *mkmap = (MKMAP_DB *) mymalloc(sizeof(*mkmap));
+ struct stat st;
+
+ /*
+ * Assumes that dict_db_cache_size = var_db_create_buf was done in the
+ * caller, because this code has no access to Postfix variables.
+ */
+
+ /*
+ * 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/util/mkmap_dbm.c b/src/util/mkmap_dbm.c
new file mode 100644
index 0000000..b131478
--- /dev/null
+++ b/src/util/mkmap_dbm.c
@@ -0,0 +1,116 @@
+/*++
+/* NAME
+/* mkmap_dbm 3
+/* SUMMARY
+/* create or open database, DBM style
+/* SYNOPSIS
+/* #include <dict_dbm.h>
+/*
+/* MKMAP *mkmap_dbm_open(path)
+/* const char *path;
+/* DESCRIPTION
+/* This module implements support for creating DBM databases.
+/*
+/* mkmap_dbm_open() takes a file name, appends the ".dir" and ".pag"
+/* suffixes, and creates or opens the named DBM database.
+/* This routine is a DBM-specific helper for the more general
+/* mkmap_open() routine.
+/*
+/* All errors are fatal.
+/* SEE ALSO
+/* dict_dbm(3), DBM dictionary interface.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <dict_dbm.h>
+#include <myflock.h>
+
+#ifdef HAS_DBM
+#ifdef PATH_NDBM_H
+#include PATH_NDBM_H
+#else
+#include <ndbm.h>
+#endif
+
+typedef struct MKMAP_DBM {
+ MKMAP mkmap; /* parent class */
+ char *lock_file; /* path name */
+ int lock_fd; /* -1 or open locked file */
+} MKMAP_DBM;
+
+/* mkmap_dbm_after_close - clean up after closing database */
+
+static void mkmap_dbm_after_close(MKMAP *mp)
+{
+ MKMAP_DBM *mkmap = (MKMAP_DBM *) mp;
+
+ if (mkmap->lock_fd >= 0 && close(mkmap->lock_fd) < 0)
+ msg_warn("close %s: %m", mkmap->lock_file);
+ myfree(mkmap->lock_file);
+}
+
+/* mkmap_dbm_open - create or open database */
+
+MKMAP *mkmap_dbm_open(const char *path)
+{
+ MKMAP_DBM *mkmap = (MKMAP_DBM *) mymalloc(sizeof(*mkmap));
+ char *pag_file;
+ int pag_fd;
+
+ /*
+ * Fill in the generic members.
+ */
+ mkmap->lock_file = concatenate(path, ".dir", (char *) 0);
+ mkmap->mkmap.open = dict_dbm_open;
+ mkmap->mkmap.after_open = 0;
+ mkmap->mkmap.after_close = mkmap_dbm_after_close;
+
+ /*
+ * Unfortunately, not all systems support locking on open(), so we open
+ * the .dir and .pag files before truncating them. Keep one file open for
+ * locking.
+ */
+ if ((mkmap->lock_fd = open(mkmap->lock_file, O_CREAT | O_RDWR, 0644)) < 0)
+ msg_fatal("open %s: %m", mkmap->lock_file);
+
+ pag_file = concatenate(path, ".pag", (char *) 0);
+ if ((pag_fd = open(pag_file, O_CREAT | O_RDWR, 0644)) < 0)
+ msg_fatal("open %s: %m", pag_file);
+ if (close(pag_fd))
+ msg_warn("close %s: %m", pag_file);
+ myfree(pag_file);
+
+ /*
+ * Get an exclusive lock - we're going to change the database so we can't
+ * have any spectators.
+ */
+ if (myflock(mkmap->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("lock %s: %m", mkmap->lock_file);
+
+ return (&mkmap->mkmap);
+}
+
+#endif
diff --git a/src/util/mkmap_fail.c b/src/util/mkmap_fail.c
new file mode 100644
index 0000000..b2a2297
--- /dev/null
+++ b/src/util/mkmap_fail.c
@@ -0,0 +1,53 @@
+/*++
+/* NAME
+/* mkmap_fail 3
+/* SUMMARY
+/* create or open database, fail: style
+/* SYNOPSIS
+/* #include <dict_fail.h>
+/*
+/* MKMAP *mkmap_fail_open(path)
+/* const char *path;
+/*
+/* DESCRIPTION
+/* This module implements support for error testing postmap
+/* and postalias with the fail: table type.
+/* SEE ALSO
+/* dict_fail(3), fail dictionary interface.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <dict_fail.h>
+
+ /*
+ * Dummy module: the dict_fail module has all the functionality built-in.
+ */
+MKMAP *mkmap_fail_open(const char *unused_path)
+{
+ MKMAP *mkmap = (MKMAP *) mymalloc(sizeof(*mkmap));
+
+ mkmap->open = dict_fail_open;
+ mkmap->after_open = 0;
+ mkmap->after_close = 0;
+ return (mkmap);
+}
diff --git a/src/util/mkmap_lmdb.c b/src/util/mkmap_lmdb.c
new file mode 100644
index 0000000..ac8b211
--- /dev/null
+++ b/src/util/mkmap_lmdb.c
@@ -0,0 +1,76 @@
+/*++
+/* NAME
+/* mkmap_lmdb 3
+/* SUMMARY
+/* create or open database, LMDB style
+/* SYNOPSIS
+/* #include <dict_lmdb.h>
+/*
+/* MKMAP *mkmap_lmdb_open(path)
+/* const char *path;
+/*
+/* DESCRIPTION
+/* This module implements support for creating LMDB databases.
+/*
+/* mkmap_lmdb_open() takes a file name, appends the ".lmdb"
+/* suffix, and does whatever initialization is required
+/* before the OpenLDAP LMDB open routine is called.
+/*
+/* All errors are fatal.
+/* SEE ALSO
+/* dict_lmdb(3), LMDB dictionary interface.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Howard Chu
+/* Symas Corporation
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <dict_lmdb.h>
+#include <mkmap.h>
+
+#ifdef HAS_LMDB
+#ifdef PATH_LMDB_H
+#include PATH_LMDB_H
+#else
+#include <lmdb.h>
+#endif
+
+/* 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/util/mkmap_open.c b/src/util/mkmap_open.c
new file mode 100644
index 0000000..961f544
--- /dev/null
+++ b/src/util/mkmap_open.c
@@ -0,0 +1,185 @@
+/*++
+/* NAME
+/* mkmap_open 3
+/* SUMMARY
+/* create or rewrite database, generic interface
+/* SYNOPSIS
+/* #include <mkmap.h>
+/*
+/* typedef struct MKMAP {
+/* struct DICT *(*open) (const char *, int, int); /* 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;
+/*
+/* 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;
+/* 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.
+/* 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
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <unistd.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <dict.h>
+#include <sigdelay.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <mkmap.h>
+
+/* 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 DICT_OPEN_INFO *dp;
+
+ /*
+ * Find out what map type to use.
+ */
+ if ((dp = dict_open_lookup(type)) == 0)
+ msg_fatal("unsupported map type: %s", type);
+ if (dp->mkmap_fn == 0)
+ msg_fatal("no 'map create' support for this type: %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 = dp->mkmap_fn(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/util/mkmap_sdbm.c b/src/util/mkmap_sdbm.c
new file mode 100644
index 0000000..54608f1
--- /dev/null
+++ b/src/util/mkmap_sdbm.c
@@ -0,0 +1,113 @@
+/*++
+/* NAME
+/* mkmap_sdbm 3
+/* SUMMARY
+/* create or open database, SDBM style
+/* SYNOPSIS
+/* #include <dict_sdbm.h>
+/*
+/* MKMAP *mkmap_sdbm_open(path)
+/* const char *path;
+/* DESCRIPTION
+/* This module implements support for creating SDBM databases.
+/*
+/* mkmap_sdbm_open() takes a file name, appends the ".dir" and ".pag"
+/* suffixes, and creates or opens the named SDBM database.
+/* This routine is a SDBM-specific helper for the more general
+/* mkmap_open() routine.
+/*
+/* All errors are fatal.
+/* SEE ALSO
+/* dict_sdbm(3), SDBM dictionary interface.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <dict_sdbm.h>
+#include <myflock.h>
+
+#ifdef HAS_SDBM
+
+#include <sdbm.h>
+
+typedef struct MKMAP_SDBM {
+ MKMAP mkmap; /* parent class */
+ char *lock_file; /* path name */
+ int lock_fd; /* -1 or open locked file */
+} MKMAP_SDBM;
+
+/* mkmap_sdbm_after_close - clean up after closing database */
+
+static void mkmap_sdbm_after_close(MKMAP *mp)
+{
+ MKMAP_SDBM *mkmap = (MKMAP_SDBM *) mp;
+
+ if (mkmap->lock_fd >= 0 && close(mkmap->lock_fd) < 0)
+ msg_warn("close %s: %m", mkmap->lock_file);
+ myfree(mkmap->lock_file);
+}
+
+/* mkmap_sdbm_open - create or open database */
+
+MKMAP *mkmap_sdbm_open(const char *path)
+{
+ MKMAP_SDBM *mkmap = (MKMAP_SDBM *) mymalloc(sizeof(*mkmap));
+ char *pag_file;
+ int pag_fd;
+
+ /*
+ * Fill in the generic members.
+ */
+ mkmap->lock_file = concatenate(path, ".dir", (char *) 0);
+ mkmap->mkmap.open = dict_sdbm_open;
+ mkmap->mkmap.after_open = 0;
+ mkmap->mkmap.after_close = mkmap_sdbm_after_close;
+
+ /*
+ * Unfortunately, not all systems support locking on open(), so we open
+ * the .dir and .pag files before truncating them. Keep one file open for
+ * locking.
+ */
+ if ((mkmap->lock_fd = open(mkmap->lock_file, O_CREAT | O_RDWR, 0644)) < 0)
+ msg_fatal("open %s: %m", mkmap->lock_file);
+
+ pag_file = concatenate(path, ".pag", (char *) 0);
+ if ((pag_fd = open(pag_file, O_CREAT | O_RDWR, 0644)) < 0)
+ msg_fatal("open %s: %m", pag_file);
+ if (close(pag_fd))
+ msg_warn("close %s: %m", pag_file);
+ myfree(pag_file);
+
+ /*
+ * Get an exclusive lock - we're going to change the database so we can't
+ * have any spectators.
+ */
+ if (myflock(mkmap->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("lock %s: %m", mkmap->lock_file);
+
+ return (&mkmap->mkmap);
+}
+
+#endif
diff --git a/src/util/msg.c b/src/util/msg.c
new file mode 100644
index 0000000..70c6eab
--- /dev/null
+++ b/src/util/msg.c
@@ -0,0 +1,340 @@
+/*++
+/* NAME
+/* msg 3
+/* SUMMARY
+/* diagnostic interface
+/* SYNOPSIS
+/* #include <msg.h>
+/*
+/* int msg_verbose;
+/*
+/* void msg_info(format, ...)
+/* const char *format;
+/*
+/* void vmsg_info(format, ap)
+/* const char *format;
+/* va_list ap;
+/*
+/* void msg_warn(format, ...)
+/* const char *format;
+/*
+/* void vmsg_warn(format, ap)
+/* const char *format;
+/* va_list ap;
+/*
+/* void msg_error(format, ...)
+/* const char *format;
+/*
+/* void vmsg_error(format, ap)
+/* const char *format;
+/* va_list ap;
+/*
+/* NORETURN msg_fatal(format, ...)
+/* const char *format;
+/*
+/* NORETURN vmsg_fatal(format, ap)
+/* const char *format;
+/* va_list ap;
+/*
+/* NORETURN msg_fatal_status(status, format, ...)
+/* int status;
+/* const char *format;
+/*
+/* NORETURN vmsg_fatal_status(status, format, ap)
+/* int status;
+/* const char *format;
+/* va_list ap;
+/*
+/* NORETURN msg_panic(format, ...)
+/* const char *format;
+/*
+/* NORETURN vmsg_panic(format, ap)
+/* const char *format;
+/* va_list ap;
+/*
+/* MSG_CLEANUP_FN msg_cleanup(cleanup)
+/* void (*cleanup)(void);
+/* AUXILIARY FUNCTIONS
+/* int msg_error_limit(count)
+/* int count;
+/*
+/* void msg_error_clear()
+/* DESCRIPTION
+/* This module reports diagnostics. By default, diagnostics are sent
+/* to the standard error stream, but the disposition can be changed
+/* by the user. See the hints below in the SEE ALSO section.
+/*
+/* msg_info(), msg_warn(), msg_error(), msg_fatal*() and msg_panic()
+/* produce a one-line record with the program name, a severity code
+/* (except for msg_info()), and an informative message. The program
+/* name must have been set by calling one of the msg_XXX_init()
+/* functions (see the SEE ALSO section).
+/*
+/* msg_error() reports a recoverable error and increments the error
+/* counter. When the error count exceeds a pre-set limit (default: 13)
+/* the program terminates by calling msg_fatal().
+/*
+/* msg_fatal() reports an unrecoverable error and terminates the program
+/* with a non-zero exit status.
+/*
+/* msg_fatal_status() reports an unrecoverable error and terminates the
+/* program with the specified exit status.
+/*
+/* msg_panic() reports an internal inconsistency, terminates the
+/* program immediately (i.e. without calling the optional user-specified
+/* cleanup routine), and forces a core dump when possible.
+/*
+/* msg_cleanup() specifies a function that msg_fatal[_status]() should
+/* invoke before terminating the program, and returns the
+/* current function pointer. Specify a null argument to disable
+/* this feature.
+/*
+/* msg_error_limit() sets the error message count limit, and returns.
+/* the old limit.
+/*
+/* msg_error_clear() sets the error message count to zero.
+/*
+/* msg_verbose is a global flag that can be set to make software
+/* more verbose about what it is doing. By default the flag is zero.
+/* By convention, a larger value means more noise.
+/* REENTRANCY
+/* .ad
+/* .fi
+/* The msg_info() etc. output routines are protected against
+/* ordinary recursive calls and against re-entry by signal
+/* handlers.
+/*
+/* Protection against re-entry by signal handlers is subject
+/* to the following limitations:
+/* .IP \(bu
+/* The signal handlers must never return. In other words, the
+/* signal handlers must do one or more of the following: call
+/* _exit(), kill the process with a signal, and permanently block
+/* the process.
+/* .IP \(bu
+/* The signal handlers must invoke msg_info() etc. not until
+/* after the msg_XXX_init() functions complete initialization,
+/* and not until after the first formatted output to a VSTRING
+/* or VSTREAM.
+/* .IP \(bu
+/* Each msg_cleanup() call-back function, and each Postfix or
+/* system function invoked by that call-back function, either
+/* protects itself against recursive calls and re-entry by a
+/* terminating signal handler, or is called exclusively by the
+/* msg(3) module.
+/* .PP
+/* When re-entrancy is detected, the requested output and
+/* optional cleanup operations are skipped. Skipping the output
+/* operations prevents memory corruption of VSTREAM_ERR data
+/* structures, and prevents deadlock on Linux releases that
+/* use mutexes within system library routines such as syslog().
+/* This protection exists under the condition that these
+/* specific resources are accessed exclusively via the msg_info()
+/* etc. functions.
+/* SEE ALSO
+/* msg_output(3) specify diagnostics disposition
+/* msg_stdio(3) direct diagnostics to standard I/O stream
+/* msg_vstream(3) direct diagnostics to VSTREAM.
+/* msg_syslog(3) direct diagnostics to syslog daemon
+/* BUGS
+/* Some output functions may suffer from intentional or accidental
+/* record length restrictions that are imposed by library routines
+/* and/or by the runtime environment.
+/*
+/* Code that spawns a child process should almost always reset
+/* the cleanup handler. The exception is when the parent exits
+/* immediately and the child continues.
+/*
+/* msg_cleanup() may be unsafe in code that changes process
+/* privileges, because the call-back routine may run with the
+/* wrong privileges.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System libraries. */
+
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+
+/* Application-specific. */
+
+#include "msg.h"
+#include "msg_output.h"
+
+ /*
+ * Default is verbose logging off.
+ */
+int msg_verbose = 0;
+
+ /*
+ * Private state.
+ */
+static MSG_CLEANUP_FN msg_cleanup_fn = 0;
+static int msg_error_count = 0;
+static int msg_error_bound = 13;
+
+ /*
+ * The msg_exiting flag prevents us from recursively reporting an error with
+ * msg_fatal*() or msg_panic(), and provides a first-level safety net for
+ * optional cleanup actions against signal handler re-entry problems. Note
+ * that msg_vprintf() implements its own guard against re-entry.
+ *
+ * XXX We specify global scope, to discourage the compiler from doing smart
+ * things.
+ */
+volatile int msg_exiting = 0;
+
+/* msg_info - report informative message */
+
+void msg_info(const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vmsg_info(fmt, ap);
+ va_end(ap);
+}
+
+void vmsg_info(const char *fmt, va_list ap)
+{
+ msg_vprintf(MSG_INFO, fmt, ap);
+}
+
+/* msg_warn - report warning message */
+
+void msg_warn(const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vmsg_warn(fmt, ap);
+ va_end(ap);
+}
+
+void vmsg_warn(const char *fmt, va_list ap)
+{
+ msg_vprintf(MSG_WARN, fmt, ap);
+}
+
+/* msg_error - report recoverable error */
+
+void msg_error(const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vmsg_error(fmt, ap);
+ va_end(ap);
+}
+
+void vmsg_error(const char *fmt, va_list ap)
+{
+ msg_vprintf(MSG_ERROR, fmt, ap);
+ if (++msg_error_count >= msg_error_bound)
+ msg_fatal("too many errors - program terminated");
+}
+
+/* msg_fatal - report error and terminate gracefully */
+
+NORETURN msg_fatal(const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vmsg_fatal(fmt, ap);
+ /* NOTREACHED */
+}
+
+NORETURN vmsg_fatal(const char *fmt, va_list ap)
+{
+ if (msg_exiting++ == 0) {
+ msg_vprintf(MSG_FATAL, fmt, ap);
+ if (msg_cleanup_fn)
+ msg_cleanup_fn();
+ }
+ sleep(1);
+ /* In case we're running as a signal handler. */
+ _exit(1);
+}
+
+/* msg_fatal_status - report error and terminate gracefully */
+
+NORETURN msg_fatal_status(int status, const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vmsg_fatal_status(status, fmt, ap);
+ /* NOTREACHED */
+}
+
+NORETURN vmsg_fatal_status(int status, const char *fmt, va_list ap)
+{
+ if (msg_exiting++ == 0) {
+ msg_vprintf(MSG_FATAL, fmt, ap);
+ if (msg_cleanup_fn)
+ msg_cleanup_fn();
+ }
+ sleep(1);
+ /* In case we're running as a signal handler. */
+ _exit(status);
+}
+
+/* msg_panic - report error and dump core */
+
+NORETURN msg_panic(const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vmsg_panic(fmt, ap);
+ /* NOTREACHED */
+}
+
+NORETURN vmsg_panic(const char *fmt, va_list ap)
+{
+ if (msg_exiting++ == 0) {
+ msg_vprintf(MSG_PANIC, fmt, ap);
+ }
+ sleep(1);
+ abort(); /* Die! */
+ /* In case we're running as a signal handler. */
+ _exit(1); /* DIE!! */
+}
+
+/* msg_cleanup - specify cleanup routine */
+
+MSG_CLEANUP_FN msg_cleanup(MSG_CLEANUP_FN cleanup_fn)
+{
+ MSG_CLEANUP_FN old_fn = msg_cleanup_fn;
+
+ msg_cleanup_fn = cleanup_fn;
+ return (old_fn);
+}
+
+/* msg_error_limit - set error message counter limit */
+
+int msg_error_limit(int limit)
+{
+ int old = msg_error_bound;
+
+ msg_error_bound = limit;
+ return (old);
+}
+
+/* msg_error_clear - reset error message counter */
+
+void msg_error_clear(void)
+{
+ msg_error_count = 0;
+}
diff --git a/src/util/msg.h b/src/util/msg.h
new file mode 100644
index 0000000..6c75baf
--- /dev/null
+++ b/src/util/msg.h
@@ -0,0 +1,60 @@
+#ifndef _MSG_H_INCLUDED_
+#define _MSG_H_INCLUDED_
+
+/*++
+/* NAME
+/* msg 3h
+/* SUMMARY
+/* diagnostics interface
+/* SYNOPSIS
+/* #include "msg.h"
+/* DESCRIPTION
+/* .nf
+
+/*
+ * System library.
+ */
+#include <stdarg.h>
+#include <time.h>
+
+/*
+ * External interface.
+ */
+typedef void (*MSG_CLEANUP_FN) (void);
+
+extern int msg_verbose;
+
+extern void PRINTFLIKE(1, 2) msg_info(const char *,...);
+extern void PRINTFLIKE(1, 2) msg_warn(const char *,...);
+extern void PRINTFLIKE(1, 2) msg_error(const char *,...);
+extern NORETURN PRINTFLIKE(1, 2) msg_fatal(const char *,...);
+extern NORETURN PRINTFLIKE(2, 3) msg_fatal_status(int, const char *,...);
+extern NORETURN PRINTFLIKE(1, 2) msg_panic(const char *,...);
+
+extern void vmsg_info(const char *, va_list);
+extern void vmsg_warn(const char *, va_list);
+extern void vmsg_error(const char *, va_list);
+extern NORETURN vmsg_fatal(const char *, va_list);
+extern NORETURN vmsg_fatal_status(int, const char *, va_list);
+extern NORETURN vmsg_panic(const char *, va_list);
+
+extern int msg_error_limit(int);
+extern void msg_error_clear(void);
+extern MSG_CLEANUP_FN msg_cleanup(MSG_CLEANUP_FN);
+
+extern void PRINTFLIKE(4, 5) msg_rate_delay(time_t *, int,
+ void PRINTFPTRLIKE(1, 2) (*log_fn) (const char *,...),
+ const char *,...);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/msg_logger.c b/src/util/msg_logger.c
new file mode 100644
index 0000000..07c9e92
--- /dev/null
+++ b/src/util/msg_logger.c
@@ -0,0 +1,371 @@
+/*++
+/* NAME
+/* msg_logger 3
+/* SUMMARY
+/* direct diagnostics to logger service
+/* SYNOPSIS
+/* #include <msg_logger.h>
+/*
+/* void msg_logger_init(
+/* const char *progname,
+/* const char *hostname,
+/* const char *unix_path,
+/* void (*fallback)(const char *))
+/*
+/* void msg_logger_control(
+/* int key,...)
+/* DESCRIPTION
+/* This module implements support to report msg(3) diagnostics
+/* through a logger daemon, with an optional fallback mechanism.
+/* The log record format is like traditional syslog:
+/*
+/* .nf
+/* Mmm dd host progname[pid]: text...
+/* .fi
+/*
+/* msg_logger_init() arranges that subsequent msg(3) calls
+/* will write to an internal logging service. This function
+/* may also be used to update msg_logger settings.
+/*
+/* Arguments:
+/* .IP progname
+/* The program name that is prepended to a log record.
+/* .IP hostname
+/* The host name that is prepended to a log record. Only the
+/* first hostname label will be used.
+/* .IP unix_path
+/* Pathname of a unix-domain datagram service endpoint. A
+/* typical use case is the pathname of the postlog socket.
+/* .IP fallback
+/* Null pointer, or pointer to function that will be called
+/* with a formatted message when the logger service is not
+/* (yet) available. A typical use case is to pass the record
+/* to the logwriter(3) module.
+/* .PP
+/* msg_logger_control() makes adjustments to the msg_logger
+/* client. These adjustments remain in effect until the next
+/* msg_logger_init() or msg_logger_control() call. The arguments
+/* are a list of macros with zero or more arguments, terminated
+/* with CA_MSG_LOGGER_CTL_END which has none. The following
+/* lists the names and the types of the corresponding value
+/* arguments.
+/*
+/* Arguments:
+/* .IP CA_MSG_LOGGER_CTL_FALLBACK_ONLY
+/* Disable the logging socket, and use the fallback function
+/* only. This remains in effect until the next msg_logger_init()
+/* call.
+/* .IP CA_MSG_LOGGER_CTL_FALLBACK(void (*)(const char *))
+/* Override the fallback setting (see above) with the specified
+/* function pointer. This remains in effect until the next
+/* msg_logger_init() or msg_logger_control() call.
+/* .IP CA_MSG_LOGGER_CTL_DISABLE
+/* Disable the msg_logger. This remains in effect until the
+/* next msg_logger_init() call.
+/* .IP CA_MSG_LOGGER_CTL_CONNECT_NOW
+/* Close the logging socket if it was already open, and open
+/* the logging socket now, if permitted by current settings.
+/* Otherwise, the open is delayed until a logging request.
+/* SEE ALSO
+/* msg(3) diagnostics module
+/* BUGS
+/* Output records are truncated to ~2000 characters, because
+/* unlimited logging is a liability.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System libraries.
+ */
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+ /*
+ * Application-specific.
+ */
+#include <connect.h>
+#include <logwriter.h>
+#include <msg.h>
+#include <msg_logger.h>
+#include <msg_output.h>
+#include <mymalloc.h>
+#include <safe.h>
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+ * Saved state from msg_logger_init().
+ */
+static char *msg_logger_progname;
+static char *msg_logger_hostname;
+static char *msg_logger_unix_path;
+static void (*msg_logger_fallback_fn) (const char *);
+static int msg_logger_fallback_only_override = 0;
+static int msg_logger_enable = 0;
+
+#define MSG_LOGGER_NEED_SOCKET() (msg_logger_fallback_only_override == 0)
+
+ /*
+ * Other state.
+ */
+#define MSG_LOGGER_SOCK_NONE (-1)
+
+static VSTRING *msg_logger_buf;
+static int msg_logger_sock = MSG_LOGGER_SOCK_NONE;
+
+ /*
+ * Safety limit.
+ */
+#define MSG_LOGGER_RECLEN 2000
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* msg_logger_connect - connect to logger service */
+
+static void msg_logger_connect(void)
+{
+ if (msg_logger_sock == MSG_LOGGER_SOCK_NONE) {
+ msg_logger_sock = unix_dgram_connect(msg_logger_unix_path, BLOCKING);
+ if (msg_logger_sock >= 0)
+ close_on_exec(msg_logger_sock, CLOSE_ON_EXEC);
+ }
+}
+
+/* msg_logger_disconnect - disconnect from logger service */
+
+static void msg_logger_disconnect(void)
+{
+ if (msg_logger_sock != MSG_LOGGER_SOCK_NONE) {
+ (void) close(msg_logger_sock);
+ msg_logger_sock = MSG_LOGGER_SOCK_NONE;
+ }
+}
+
+/* msg_logger_print - log info to service or file */
+
+static void msg_logger_print(int level, const char *text)
+{
+ time_t now;
+ struct tm *lt;
+ ssize_t len;
+
+ /*
+ * TODO: this should be a reusable NAME_CODE table plus lookup function.
+ */
+ static int log_level[] = {
+ MSG_INFO, MSG_WARN, MSG_ERROR, MSG_FATAL, MSG_PANIC,
+ };
+ static char *severity_name[] = {
+ "info", "warning", "error", "fatal", "panic",
+ };
+
+ /*
+ * This test is simple enough that we don't bother with unregistering the
+ * msg_logger_print() function.
+ */
+ if (msg_logger_enable == 0)
+ return;
+
+ /*
+ * Note: there is code in postlogd(8) that attempts to strip off
+ * information that is prepended here. If the formatting below is
+ * changed, then postlogd needs to be updated as well.
+ */
+
+ /*
+ * Format the time stamp.
+ */
+ if (time(&now) < 0)
+ msg_fatal("no time: %m");
+ lt = localtime(&now);
+ VSTRING_RESET(msg_logger_buf);
+ if ((len = strftime(vstring_str(msg_logger_buf),
+ vstring_avail(msg_logger_buf),
+ "%b %d %H:%M:%S ", lt)) == 0)
+ msg_fatal("strftime: %m");
+ vstring_set_payload_size(msg_logger_buf, len);
+
+ /*
+ * Format the host name (first name label only).
+ */
+ vstring_sprintf_append(msg_logger_buf, "%.*s ",
+ (int) strcspn(msg_logger_hostname, "."),
+ msg_logger_hostname);
+
+ /*
+ * Format the message.
+ */
+ if (level < 0 || level >= (int) (sizeof(log_level) / sizeof(log_level[0])))
+ msg_panic("msg_logger_print: invalid severity level: %d", level);
+
+ if (level == MSG_INFO) {
+ vstring_sprintf_append(msg_logger_buf, "%s[%ld]: %.*s",
+ msg_logger_progname, (long) getpid(),
+ (int) MSG_LOGGER_RECLEN, text);
+ } else {
+ vstring_sprintf_append(msg_logger_buf, "%s[%ld]: %s: %.*s",
+ msg_logger_progname, (long) getpid(),
+ severity_name[level], (int) MSG_LOGGER_RECLEN, text);
+ }
+
+ /*
+ * Connect to logging service, or fall back to direct log. Many systems
+ * will report ENOENT if the endpoint does not exist, ECONNREFUSED if no
+ * server has opened the endpoint.
+ */
+ if (MSG_LOGGER_NEED_SOCKET())
+ msg_logger_connect();
+ if (msg_logger_sock != MSG_LOGGER_SOCK_NONE) {
+ send(msg_logger_sock, STR(msg_logger_buf), LEN(msg_logger_buf), 0);
+ } else if (msg_logger_fallback_fn) {
+ msg_logger_fallback_fn(STR(msg_logger_buf));
+ }
+}
+
+/* msg_logger_init - initialize */
+
+void msg_logger_init(const char *progname, const char *hostname,
+ const char *unix_path, void (*fallback) (const char *))
+{
+ static int first_call = 1;
+ extern char **environ;
+
+ /*
+ * XXX If this program is set-gid, then TZ must not be trusted. This
+ * scrubbing code is in the wrong place.
+ */
+ if (first_call) {
+ if (unsafe())
+ while (getenv("TZ")) /* There may be multiple. */
+ if (unsetenv("TZ") < 0) { /* Desperate measures. */
+ environ[0] = 0;
+ msg_fatal("unsetenv: %m");
+ }
+ tzset();
+ }
+
+ /*
+ * Save the request info. Use free-after-update because this data will be
+ * accessed when mystrdup() runs out of memory.
+ */
+#define UPDATE_AND_FREE(dst, src) do { \
+ if ((dst) == 0 || strcmp((dst), (src)) != 0) { \
+ char *_bak = (dst); \
+ (dst) = mystrdup(src); \
+ if ((_bak)) myfree(_bak); \
+ } \
+ } while (0)
+
+ UPDATE_AND_FREE(msg_logger_progname, progname);
+ UPDATE_AND_FREE(msg_logger_hostname, hostname);
+ UPDATE_AND_FREE(msg_logger_unix_path, unix_path);
+ msg_logger_fallback_fn = fallback;
+
+ /*
+ * One-time activity: register the output handler, and allocate a buffer.
+ */
+ if (first_call) {
+ first_call = 0;
+ msg_output(msg_logger_print);
+ msg_logger_buf = vstring_alloc(2048);
+ }
+
+ /*
+ * Always.
+ */
+ msg_logger_enable = 1;
+ msg_logger_fallback_only_override = 0;
+}
+
+/* msg_logger_control - tweak the client */
+
+void msg_logger_control(int name,...)
+{
+ const char *myname = "msg_logger_control";
+ va_list ap;
+
+ /*
+ * Overrides remain in effect until the next msg_logger_init() or
+ * msg_logger_control() call,
+ */
+ for (va_start(ap, name); name != MSG_LOGGER_CTL_END; name = va_arg(ap, int)) {
+ switch (name) {
+ case MSG_LOGGER_CTL_FALLBACK_ONLY:
+ msg_logger_fallback_only_override = 1;
+ msg_logger_disconnect();
+ break;
+ case MSG_LOGGER_CTL_FALLBACK_FN:
+ msg_logger_fallback_fn = va_arg(ap, MSG_LOGGER_FALLBACK_FN);
+ break;
+ case MSG_LOGGER_CTL_DISABLE:
+ msg_logger_enable = 0;
+ break;
+ case MSG_LOGGER_CTL_CONNECT_NOW:
+ msg_logger_disconnect();
+ if (MSG_LOGGER_NEED_SOCKET())
+ msg_logger_connect();
+ break;
+ default:
+ msg_panic("%s: bad name %d", myname, name);
+ }
+ }
+ va_end(ap);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept program to test the msg_logger module.
+ *
+ * Usage: msg_logger hostname unix_path fallback_path text...
+ */
+static char *fallback_path;
+
+static void fallback(const char *msg)
+{
+ if (logwriter_one_shot(fallback_path, msg) != 0)
+ msg_fatal("unable to fall back to directly write %s: %m",
+ fallback_path);
+}
+
+int main(int argc, char **argv)
+{
+ VSTRING *vp = vstring_alloc(256);
+
+ if (argc < 4)
+ msg_fatal("usage: %s host port path text to log", argv[0]);
+ msg_logger_init(argv[0], argv[1], argv[2], fallback);
+ fallback_path = argv[3];
+ argc -= 3;
+ argv += 3;
+ while (--argc && *++argv) {
+ vstring_strcat(vp, *argv);
+ if (argv[1])
+ vstring_strcat(vp, " ");
+ }
+ msg_warn("static text");
+ msg_warn("dynamic text: >%s<", vstring_str(vp));
+ msg_warn("dynamic numeric: >%d<", 42);
+ msg_warn("error text: >%m<");
+ msg_warn("dynamic: >%s<: error: >%m<", vstring_str(vp));
+ vstring_free(vp);
+ return (0);
+}
+
+#endif
diff --git a/src/util/msg_logger.h b/src/util/msg_logger.h
new file mode 100644
index 0000000..4179f8b
--- /dev/null
+++ b/src/util/msg_logger.h
@@ -0,0 +1,62 @@
+#ifndef _MSG_LOGGER_H_INCLUDED_
+#define _MSG_LOGGER_H_INCLUDED_
+
+/*++
+/* NAME
+/* msg_logger 3h
+/* SUMMARY
+/* direct diagnostics to logger service
+/* SYNOPSIS
+/* #include <msg_logger.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <stdarg.h>
+
+ /*
+ * Utility library.
+ */
+#include <check_arg.h>
+
+ /*
+ * External interface.
+ */
+typedef void (*MSG_LOGGER_FALLBACK_FN) (const char *);
+
+extern void msg_logger_init(const char *, const char *, const char *,
+ MSG_LOGGER_FALLBACK_FN);
+extern void msg_logger_control(int,...);
+
+/* Internal-only API: type-unchecked arguments. */
+#define MSG_LOGGER_CTL_END 0
+#define MSG_LOGGER_CTL_FALLBACK_ONLY 1
+#define MSG_LOGGER_CTL_FALLBACK_FN 2
+#define MSG_LOGGER_CTL_DISABLE 3
+#define MSG_LOGGER_CTL_CONNECT_NOW 4
+
+/* Safer API: type-checked arguments, external use. */
+#define CA_MSG_LOGGER_CTL_END MSG_LOGGER_CTL_END
+#define CA_MSG_LOGGER_CTL_FALLBACK_ONLY MSG_LOGGER_CTL_FALLBACK_ONLY
+#define CA_MSG_LOGGER_CTL_FALLBACK_FN(v) \
+ MSG_LOGGER_CTL_FALLBACK_FN, CHECK_VAL(MSG_LOGGER_CTL, \
+ MSG_LOGGER_FALLBACK_FN, (v))
+#define CA_MSG_LOGGER_CTL_DISABLE MSG_LOGGER_CTL_DISABLE
+#define CA_MSG_LOGGER_CTL_CONNECT_NOW MSG_LOGGER_CTL_CONNECT_NOW
+
+CHECK_VAL_HELPER_DCL(MSG_LOGGER_CTL, MSG_LOGGER_FALLBACK_FN);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/msg_output.c b/src/util/msg_output.c
new file mode 100644
index 0000000..6663877
--- /dev/null
+++ b/src/util/msg_output.c
@@ -0,0 +1,174 @@
+/*++
+/* NAME
+/* msg_output 3
+/* SUMMARY
+/* diagnostics output management
+/* SYNOPSIS
+/* #include <msg_output.h>
+/*
+/* typedef void (*MSG_OUTPUT_FN)(int level, char *text)
+/*
+/* void msg_output(output_fn)
+/* MSG_OUTPUT_FN output_fn;
+/*
+/* void msg_printf(level, format, ...)
+/* int level;
+/* const char *format;
+/*
+/* void msg_vprintf(level, format, ap)
+/* int level;
+/* const char *format;
+/* va_list ap;
+/* DESCRIPTION
+/* This module implements low-level output management for the
+/* msg(3) diagnostics interface.
+/*
+/* msg_output() registers an output handler for the diagnostics
+/* interface. An application can register multiple output handlers.
+/* Output handlers are called in the specified order.
+/* An output handler takes as arguments a severity level (MSG_INFO,
+/* MSG_WARN, MSG_ERROR, MSG_FATAL, MSG_PANIC, monotonically increasing
+/* integer values ranging from 0 to MSG_LAST) and pre-formatted,
+/* sanitized, text in the form of a null-terminated string.
+/*
+/* msg_printf() and msg_vprintf() format their arguments, sanitize the
+/* result, and call the output handlers registered with msg_output().
+/*
+/* msg_text() copies a pre-formatted text, sanitizes the result, and
+/* calls the output handlers registered with msg_output().
+/* REENTRANCY
+/* .ad
+/* .fi
+/* The above output routines are protected against ordinary
+/* recursive calls and against re-entry by signal
+/* handlers, with the following limitations:
+/* .IP \(bu
+/* The signal handlers must never return. In other words, the
+/* signal handlers must do one or more of the following: call
+/* _exit(), kill the process with a signal, and permanently
+/* block the process.
+/* .IP \(bu
+/* The signal handlers must call the above output routines not
+/* until after msg_output() completes initialization, and not
+/* until after the first formatted output to a VSTRING or
+/* VSTREAM.
+/* .IP \(bu
+/* Each msg_output() call-back function, and each Postfix or
+/* system function called by that call-back function, either
+/* must protect itself against recursive calls and re-entry
+/* by a terminating signal handler, or it must be called
+/* exclusively by functions in the msg_output(3) module.
+/* .PP
+/* When re-entrancy is detected, the requested output operation
+/* is skipped. This prevents memory corruption of VSTREAM_ERR
+/* data structures, and prevents deadlock on Linux releases
+/* that use mutexes within system library routines such as
+/* syslog(). This protection exists under the condition that
+/* these specific resources are accessed exclusively via
+/* msg_output() call-back functions.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdarg.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <stringops.h>
+#include <msg_output.h>
+
+ /*
+ * Global scope, to discourage the compiler from doing smart things.
+ */
+volatile int msg_vprintf_level;
+
+ /*
+ * Private state. Allow one nested call, so that one logging error can be
+ * reported to stderr before bailing out.
+ */
+#define MSG_OUT_NESTING_LIMIT 2
+static MSG_OUTPUT_FN *msg_output_fn = 0;
+static int msg_output_fn_count = 0;
+static VSTRING *msg_buffers[MSG_OUT_NESTING_LIMIT];
+
+/* msg_output - specify output handler */
+
+void msg_output(MSG_OUTPUT_FN output_fn)
+{
+ int i;
+
+ /*
+ * Allocate all resources during initialization. This may result in a
+ * recursive call due to memory allocation error.
+ */
+ if (msg_buffers[MSG_OUT_NESTING_LIMIT - 1] == 0) {
+ for (i = 0; i < MSG_OUT_NESTING_LIMIT; i++)
+ msg_buffers[i] = vstring_alloc(100);
+ }
+
+ /*
+ * We're not doing this often, so avoid complexity and allocate memory
+ * for an exact fit.
+ */
+ if (msg_output_fn_count == 0)
+ msg_output_fn = (MSG_OUTPUT_FN *) mymalloc(sizeof(*msg_output_fn));
+ else
+ msg_output_fn = (MSG_OUTPUT_FN *) myrealloc((void *) msg_output_fn,
+ (msg_output_fn_count + 1) * sizeof(*msg_output_fn));
+ msg_output_fn[msg_output_fn_count++] = output_fn;
+}
+
+/* msg_printf - format text and log it */
+
+void msg_printf(int level, const char *format,...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ msg_vprintf(level, format, ap);
+ va_end(ap);
+}
+
+/* msg_vprintf - format text and log it */
+
+void msg_vprintf(int level, const char *format, va_list ap)
+{
+ int saved_errno = errno;
+ VSTRING *vp;
+ int i;
+
+ if (msg_vprintf_level < MSG_OUT_NESTING_LIMIT) {
+ msg_vprintf_level += 1;
+ /* On-the-fly initialization for test programs and startup errors. */
+ if (msg_output_fn_count == 0)
+ msg_vstream_init("unknown", VSTREAM_ERR);
+ vp = msg_buffers[msg_vprintf_level - 1];
+ /* OK if terminating signal handler hijacks control before next stmt. */
+ vstring_vsprintf(vp, format, ap);
+ printable(vstring_str(vp), '?');
+ for (i = 0; i < msg_output_fn_count; i++)
+ msg_output_fn[i] (level, vstring_str(vp));
+ msg_vprintf_level -= 1;
+ }
+ errno = saved_errno;
+}
diff --git a/src/util/msg_output.h b/src/util/msg_output.h
new file mode 100644
index 0000000..bd84276
--- /dev/null
+++ b/src/util/msg_output.h
@@ -0,0 +1,51 @@
+#ifndef _MSG_OUTPUT_FN_
+#define _MSG_OUTPUT_FN_
+
+/*++
+/* NAME
+/* msg_output 3h
+/* SUMMARY
+/* diagnostics output management
+/* SYNOPSIS
+/* #include <msg_output.h>
+/* DESCRIPTION
+
+ /*
+ * System library.
+ */
+#include <stdarg.h>
+
+ /*
+ * External interface. Severity levels are documented to be monotonically
+ * increasing from 0 up to MSG_LAST.
+ */
+typedef void (*MSG_OUTPUT_FN) (int, const char *);
+extern void msg_output(MSG_OUTPUT_FN);
+extern void PRINTFLIKE(2, 3) msg_printf(int, const char *,...);
+extern void msg_vprintf(int, const char *, va_list);
+
+#define MSG_INFO 0 /* informative */
+#define MSG_WARN 1 /* warning (non-fatal) */
+#define MSG_ERROR 2 /* error (fatal) */
+#define MSG_FATAL 3 /* software error (fatal) */
+#define MSG_PANIC 4 /* software error (fatal) */
+
+#define MSG_LAST 4 /* highest-numbered severity level */
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/msg_rate_delay.c b/src/util/msg_rate_delay.c
new file mode 100644
index 0000000..e21b021
--- /dev/null
+++ b/src/util/msg_rate_delay.c
@@ -0,0 +1,138 @@
+/*++
+/* NAME
+/* msg_rate_delay 3
+/* SUMMARY
+/* diagnostic interface
+/* SYNOPSIS
+/* #include <msg.h>
+/*
+/* void msg_rate_delay(stamp, delay, log_fn, fmt, ...)
+/* time_t *stamp;
+/* int delay;
+/* void (*log_fn)(const char *fmt, ...);
+/* const char *fmt;
+/* DESCRIPTION
+/* msg_rate_delay() produces log output at a reduced rate: no
+/* more than one message per 'delay' seconds. It discards log
+/* output that would violate the output rate policy.
+/*
+/* This is typically used to log errors accessing a cache with
+/* high-frequency access but low-value information, to avoid
+/* spamming the logfile with the same kind of message.
+/*
+/* Arguments:
+/* .IP stamp
+/* Time stamp of last log output; specify a zero time stamp
+/* on the first call. This is an input-output parameter.
+/* This parameter is ignored when verbose logging is enabled
+/* or when the delay value is zero.
+/* .IP delay
+/* The minimum time between log outputs; specify zero to log
+/* all output for debugging purposes. This parameter is ignored
+/* when verbose logging is enabled.
+/* .IP log_fn
+/* The function that produces log output. Typically, this will
+/* be msg_info() or msg_warn().
+/* .IP fmt
+/* Format string as used with msg(3) routines.
+/* SEE ALSO
+/* msg(3) diagnostics interface
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation problem.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <time.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <events.h>
+
+/* SLMs. */
+
+#define STR(x) vstring_str(x)
+
+/* msg_rate_delay - rate-limit message logging */
+
+void msg_rate_delay(time_t *stamp, int delay,
+ void (*log_fn) (const char *,...),
+ const char *fmt,...)
+{
+ const char *myname = "msg_rate_delay";
+ static time_t saved_event_time;
+ time_t now;
+ VSTRING *buf;
+ va_list ap;
+
+ /*
+ * Sanity check.
+ */
+ if (delay < 0)
+ msg_panic("%s: bad message rate delay: %d", myname, delay);
+
+ /*
+ * This function may be called frequently. Avoid an unnecessary syscall
+ * if possible. Deal with the possibility that a program does not use the
+ * events(3) engine, so that event_time() always produces the same
+ * result.
+ */
+ if (msg_verbose == 0 && delay > 0) {
+ if (saved_event_time == 0)
+ now = saved_event_time = event_time();
+ else if ((now = event_time()) == saved_event_time)
+ now = time((time_t *) 0);
+
+ /*
+ * Don't log if time is too early.
+ */
+ if (*stamp + delay > now)
+ return;
+ *stamp = now;
+ }
+
+ /*
+ * OK to log. This is a low-rate event, so we can afford some overhead.
+ */
+ buf = vstring_alloc(100);
+ va_start(ap, fmt);
+ vstring_vsprintf(buf, fmt, ap);
+ va_end(ap);
+ log_fn("%s", STR(buf));
+ vstring_free(buf);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program: log messages but skip messages during a
+ * two-second gap.
+ */
+#include <unistd.h>
+
+int main(int argc, char **argv)
+{
+ int n;
+ time_t stamp = 0;
+
+ for (n = 0; n < 6; n++) {
+ msg_rate_delay(&stamp, 2, msg_info, "text here %d", n);
+ sleep(1);
+ }
+ return (0);
+}
+
+#endif
diff --git a/src/util/msg_syslog.c b/src/util/msg_syslog.c
new file mode 100644
index 0000000..7c979c6
--- /dev/null
+++ b/src/util/msg_syslog.c
@@ -0,0 +1,266 @@
+/*++
+/* NAME
+/* msg_syslog 3
+/* SUMMARY
+/* direct diagnostics to syslog daemon
+/* SYNOPSIS
+/* #include <msg_syslog.h>
+/*
+/* void msg_syslog_init(progname, log_opt, facility)
+/* const char *progname;
+/* int log_opt;
+/* int facility;
+/*
+/* int msg_syslog_set_facility(facility_name)
+/* const char *facility_name;
+/*
+/* void msg_syslog_disable(void)
+/* DESCRIPTION
+/* This module implements support to report msg(3) diagnostics
+/* via the syslog daemon.
+/*
+/* msg_syslog_init() is a wrapper around the openlog(3) routine
+/* that directs subsequent msg(3) output to the syslog daemon.
+/* This function may also be called to update msg_syslog
+/* settings. If the program name appears to contain a process ID
+/* then msg_syslog_init will attempt to suppress its own PID.
+/*
+/* msg_syslog_set_facility() is a helper routine that overrides the
+/* logging facility that is specified with msg_syslog_init().
+/* The result is zero in case of an unknown facility name.
+/*
+/* msg_syslog_disable() turns off the msg_syslog client,
+/* until a subsequent msg_syslog_init() call.
+/* SEE ALSO
+/* syslog(3) syslog library
+/* msg(3) diagnostics module
+/* BUGS
+/* Output records are truncated to 2000 characters. This is done in
+/* order to defend against a buffer overflow problem in some
+/* implementations of the syslog() library routine.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System libraries. */
+
+#include <sys_defs.h>
+#include <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+#include <errno.h>
+#include <syslog.h>
+#include <string.h>
+#include <time.h>
+
+/* Application-specific. */
+
+#include "vstring.h"
+#include "stringops.h"
+#include "msg.h"
+#include "msg_output.h"
+#include "msg_syslog.h"
+#include "safe.h"
+#include <mymalloc.h>
+
+ /*
+ * Stay a little below the 2048-byte limit of older syslog()
+ * implementations.
+ */
+#define MSG_SYSLOG_RECLEN 2000
+
+struct facility_list {
+ const char *name;
+ int facility;
+};
+
+static struct facility_list facility_list[] = {
+#ifdef LOG_AUTH
+ "auth", LOG_AUTH,
+#endif
+#ifdef LOG_AUTHPRIV
+ "authpriv", LOG_AUTHPRIV,
+#endif
+#ifdef LOG_CRON
+ "cron", LOG_CRON,
+#endif
+#ifdef LOG_DAEMON
+ "daemon", LOG_DAEMON,
+#endif
+#ifdef LOG_FTP
+ "ftp", LOG_FTP,
+#endif
+#ifdef LOG_KERN
+ "kern", LOG_KERN,
+#endif
+#ifdef LOG_LPR
+ "lpr", LOG_LPR,
+#endif
+#ifdef LOG_MAIL
+ "mail", LOG_MAIL,
+#endif
+#ifdef LOG_NEWS
+ "news", LOG_NEWS,
+#endif
+#ifdef LOG_SECURITY
+ "security", LOG_SECURITY,
+#endif
+#ifdef LOG_SYSLOG
+ "syslog", LOG_SYSLOG,
+#endif
+#ifdef LOG_USER
+ "user", LOG_USER,
+#endif
+#ifdef LOG_UUCP
+ "uucp", LOG_UUCP,
+#endif
+#ifdef LOG_LOCAL0
+ "local0", LOG_LOCAL0,
+#endif
+#ifdef LOG_LOCAL1
+ "local1", LOG_LOCAL1,
+#endif
+#ifdef LOG_LOCAL2
+ "local2", LOG_LOCAL2,
+#endif
+#ifdef LOG_LOCAL3
+ "local3", LOG_LOCAL3,
+#endif
+#ifdef LOG_LOCAL4
+ "local4", LOG_LOCAL4,
+#endif
+#ifdef LOG_LOCAL5
+ "local5", LOG_LOCAL5,
+#endif
+#ifdef LOG_LOCAL6
+ "local6", LOG_LOCAL6,
+#endif
+#ifdef LOG_LOCAL7
+ "local7", LOG_LOCAL7,
+#endif
+ 0,
+};
+
+static int msg_syslog_facility;
+static int msg_syslog_enable;
+
+/* msg_syslog_print - log info to syslog daemon */
+
+static void msg_syslog_print(int level, const char *text)
+{
+ static int log_level[] = {
+ LOG_INFO, LOG_WARNING, LOG_ERR, LOG_CRIT, LOG_CRIT,
+ };
+ static char *severity_name[] = {
+ "info", "warning", "error", "fatal", "panic",
+ };
+
+ if (msg_syslog_enable == 0)
+ return;
+
+ if (level < 0 || level >= (int) (sizeof(log_level) / sizeof(log_level[0])))
+ msg_panic("msg_syslog_print: invalid severity level: %d", level);
+
+ if (level == MSG_INFO) {
+ syslog(msg_syslog_facility | log_level[level], "%.*s",
+ (int) MSG_SYSLOG_RECLEN, text);
+ } else {
+ syslog(msg_syslog_facility | log_level[level], "%s: %.*s",
+ severity_name[level], (int) MSG_SYSLOG_RECLEN, text);
+ }
+}
+
+/* msg_syslog_init - initialize */
+
+void msg_syslog_init(const char *name, int logopt, int facility)
+{
+ static int first_call = 1;
+ extern char **environ;
+
+ /*
+ * XXX If this program is set-gid, then TZ must not be trusted. This
+ * scrubbing code is in the wrong place.
+ */
+ if (first_call) {
+ if (unsafe())
+ while (getenv("TZ")) /* There may be multiple. */
+ if (unsetenv("TZ") < 0) { /* Desperate measures. */
+ environ[0] = 0;
+ msg_fatal("unsetenv: %m");
+ }
+ tzset();
+ }
+ /* Hack for internal logging forwarding after config change. */
+ if (strchr(name, '[') != 0)
+ logopt &= ~LOG_PID;
+ openlog(name, LOG_NDELAY | logopt, facility);
+ if (first_call) {
+ first_call = 0;
+ msg_output(msg_syslog_print);
+ }
+ msg_syslog_enable = 1;
+}
+
+/* msg_syslog_set_facility - set logging facility by name */
+
+int msg_syslog_set_facility(const char *facility_name)
+{
+ struct facility_list *fnp;
+
+ for (fnp = facility_list; fnp->name; ++fnp) {
+ if (!strcmp(fnp->name, facility_name)) {
+ msg_syslog_facility = fnp->facility;
+ return (1);
+ }
+ }
+ return 0;
+}
+
+/* msg_syslog_disable - disable the msg_syslog client */
+
+void msg_syslog_disable(void)
+{
+ msg_syslog_enable = 0;
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept program to test the syslogging diagnostics interface
+ *
+ * Usage: msg_syslog_test text...
+ */
+
+int main(int argc, char **argv)
+{
+ VSTRING *vp = vstring_alloc(256);
+
+ msg_syslog_init(argv[0], LOG_PID, LOG_MAIL);
+ if (argc < 2)
+ msg_error("usage: %s text to be logged", argv[0]);
+ while (--argc && *++argv) {
+ vstring_strcat(vp, *argv);
+ if (argv[1])
+ vstring_strcat(vp, " ");
+ }
+ msg_warn("static text");
+ msg_warn("dynamic text: >%s<", vstring_str(vp));
+ msg_warn("dynamic numeric: >%d<", 42);
+ msg_warn("error text: >%m<");
+ msg_warn("dynamic: >%s<: error: >%m<", vstring_str(vp));
+ vstring_free(vp);
+ return (0);
+}
+
+#endif
diff --git a/src/util/msg_syslog.h b/src/util/msg_syslog.h
new file mode 100644
index 0000000..d0441bb
--- /dev/null
+++ b/src/util/msg_syslog.h
@@ -0,0 +1,41 @@
+#ifndef _MSG_SYSLOG_H_INCLUDED_
+#define _MSG_SYSLOG_H_INCLUDED_
+
+/*++
+/* NAME
+/* msg_syslog 3h
+/* SUMMARY
+/* direct diagnostics to syslog daemon
+/* SYNOPSIS
+/* #include <msg_syslog.h>
+/* DESCRIPTION
+
+ /*
+ * System library.
+ */
+#include <syslog.h>
+
+ /*
+ * External interface.
+ */
+extern void msg_syslog_init(const char *, int, int);
+extern int msg_syslog_set_facility(const char *);
+extern void msg_syslog_disable(void);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/msg_vstream.c b/src/util/msg_vstream.c
new file mode 100644
index 0000000..b6e24e6
--- /dev/null
+++ b/src/util/msg_vstream.c
@@ -0,0 +1,87 @@
+/*++
+/* NAME
+/* msg_vstream 3
+/* SUMMARY
+/* report diagnostics to VSTREAM
+/* SYNOPSIS
+/* #include <msg_vstream.h>
+/*
+/* void msg_vstream_init(progname, stream)
+/* const char *progname;
+/* VSTREAM *stream;
+/* DESCRIPTION
+/* This module implements support to report msg(3) diagnostics
+/* to a VSTREAM.
+/*
+/* msg_vstream_init() sets the program name that appears in each output
+/* record, and directs diagnostics (see msg(3)) to the specified
+/* VSTREAM. The \fIprogname\fR argument is not copied.
+/* SEE ALSO
+/* msg(3)
+/* BUGS
+/* No guarantee that long records are written atomically.
+/* Only the last msg_vstream_init() call takes effect.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System libraries. */
+
+#include <sys_defs.h>
+#include <errno.h>
+#include <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+
+/* Utility library. */
+
+#include "vstream.h"
+#include "msg.h"
+#include "msg_output.h"
+#include "msg_vstream.h"
+
+ /*
+ * Private state.
+ */
+static const char *msg_tag;
+static VSTREAM *msg_stream;
+
+/* msg_vstream_print - log diagnostic to VSTREAM */
+
+static void msg_vstream_print(int level, const char *text)
+{
+ static const char *level_text[] = {
+ "info", "warning", "error", "fatal", "panic",
+ };
+
+ if (level < 0 || level >= (int) (sizeof(level_text) / sizeof(level_text[0])))
+ msg_panic("invalid severity level: %d", level);
+ if (level == MSG_INFO) {
+ vstream_fprintf(msg_stream, "%s: %s\n",
+ msg_tag, text);
+ } else {
+ vstream_fprintf(msg_stream, "%s: %s: %s\n",
+ msg_tag, level_text[level], text);
+ }
+ vstream_fflush(msg_stream);
+}
+
+/* msg_vstream_init - initialize */
+
+void msg_vstream_init(const char *name, VSTREAM *vp)
+{
+ static int first_call = 1;
+
+ msg_tag = name;
+ msg_stream = vp;
+ if (first_call) {
+ first_call = 0;
+ msg_output(msg_vstream_print);
+ }
+}
diff --git a/src/util/msg_vstream.h b/src/util/msg_vstream.h
new file mode 100644
index 0000000..d0679a0
--- /dev/null
+++ b/src/util/msg_vstream.h
@@ -0,0 +1,34 @@
+#ifndef _MSG_VSTREAM_H_INCLUDED_
+#define _MSG_VSTREAM_H_INCLUDED_
+
+/*++
+/* NAME
+/* msg_vstream 3h
+/* SUMMARY
+/* direct diagnostics to VSTREAM
+/* SYNOPSIS
+/* #include <msg_vstream.h>
+/* DESCRIPTION
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+
+ /*
+ * External interface.
+ */
+extern void msg_vstream_init(const char *, VSTREAM *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/mvect.c b/src/util/mvect.c
new file mode 100644
index 0000000..cf4b0d5
--- /dev/null
+++ b/src/util/mvect.c
@@ -0,0 +1,117 @@
+/*++
+/* NAME
+/* mvect 3
+/* SUMMARY
+/* memory vector management
+/* SYNOPSIS
+/* #include <mvect.h>
+/*
+/* char *mvect_alloc(vector, elsize, nelm, init_fn, wipe_fn)
+/* MVECT *vector;
+/* ssize_t elsize;
+/* ssize_t nelm;
+/* void (*init_fn)(char *ptr, ssize_t count);
+/* void (*wipe_fn)(char *ptr, ssize_t count);
+/*
+/* char *mvect_realloc(vector, nelm)
+/* MVECT *vector;
+/* ssize_t nelm;
+/*
+/* char *mvect_free(vector)
+/* MVECT *vector;
+/* DESCRIPTION
+/* This module supports memory management for arrays of arbitrary
+/* objects. It is up to the application to provide specific code
+/* that initializes and uses object memory.
+/*
+/* mvect_alloc() initializes memory for a vector with elements
+/* of \fIelsize\fR bytes, and with at least \fInelm\fR elements.
+/* \fIinit_fn\fR is a null pointer, or a pointer to a function
+/* that initializes \fIcount\fR vector elements.
+/* \fIwipe_fn\fR is a null pointer, or a pointer to a function
+/* that is complementary to \fIinit_fn\fR. This routine is called
+/* by mvect_free(). The result of mvect_alloc() is a pointer to
+/* the allocated vector.
+/*
+/* mvect_realloc() guarantees that the specified vector has space
+/* for at least \fInelm\fR elements. The result is a pointer to the
+/* allocated vector, which may change across calls.
+/*
+/* mvect_free() releases storage for the named vector. The result
+/* is a convenient null pointer.
+/* SEE ALSO
+/* mymalloc(3) memory management
+/* DIAGNOSTICS
+/* Problems are reported via the msg(3) diagnostics routines:
+/* the requested amount of memory is not available; improper use
+/* is detected; other fatal errors.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include "mymalloc.h"
+#include "mvect.h"
+
+/* mvect_alloc - allocate memory vector */
+
+char *mvect_alloc(MVECT *vect, ssize_t elsize, ssize_t nelm,
+ void (*init_fn) (char *, ssize_t), void (*wipe_fn) (char *, ssize_t))
+{
+ vect->init_fn = init_fn;
+ vect->wipe_fn = wipe_fn;
+ vect->nelm = 0;
+ vect->ptr = mymalloc(elsize * nelm);
+ vect->nelm = nelm;
+ vect->elsize = elsize;
+ if (vect->init_fn)
+ vect->init_fn(vect->ptr, vect->nelm);
+ return (vect->ptr);
+}
+
+/* mvect_realloc - adjust memory vector allocation */
+
+char *mvect_realloc(MVECT *vect, ssize_t nelm)
+{
+ ssize_t old_len = vect->nelm;
+ ssize_t incr = nelm - old_len;
+ ssize_t new_nelm;
+
+ if (incr > 0) {
+ if (incr < old_len)
+ incr = old_len;
+ new_nelm = vect->nelm + incr;
+ vect->ptr = myrealloc(vect->ptr, vect->elsize * new_nelm);
+ vect->nelm = new_nelm;
+ if (vect->init_fn)
+ vect->init_fn(vect->ptr + old_len * vect->elsize, incr);
+ }
+ return (vect->ptr);
+}
+
+/* mvect_free - release memory vector storage */
+
+char *mvect_free(MVECT *vect)
+{
+ if (vect->wipe_fn)
+ vect->wipe_fn(vect->ptr, vect->nelm);
+ myfree(vect->ptr);
+ return (0);
+}
diff --git a/src/util/mvect.h b/src/util/mvect.h
new file mode 100644
index 0000000..bdbb701
--- /dev/null
+++ b/src/util/mvect.h
@@ -0,0 +1,42 @@
+#ifndef _MVECT_H_INCLUDED_
+#define _MVECT_H_INCLUDED_
+
+/*++
+/* NAME
+/* mvect 3h
+/* SUMMARY
+/* memory vector management
+/* SYNOPSIS
+/* #include <mvect.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Generic memory vector interface.
+ */
+typedef void (*MVECT_FN) (char *, ssize_t);
+
+typedef struct {
+ char *ptr;
+ ssize_t elsize;
+ ssize_t nelm;
+ MVECT_FN init_fn;
+ MVECT_FN wipe_fn;
+} MVECT;
+
+extern char *mvect_alloc(MVECT *, ssize_t, ssize_t, MVECT_FN, MVECT_FN);
+extern char *mvect_realloc(MVECT *, ssize_t);
+extern char *mvect_free(MVECT *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/myaddrinfo.c b/src/util/myaddrinfo.c
new file mode 100644
index 0000000..5edafde
--- /dev/null
+++ b/src/util/myaddrinfo.c
@@ -0,0 +1,905 @@
+/*++
+/* NAME
+/* myaddrinfo 3
+/* SUMMARY
+/* addrinfo encapsulation and emulation
+/* SYNOPSIS
+/* #include <myaddrinfo.h>
+/*
+/* #define MAI_V4ADDR_BITS ...
+/* #define MAI_V6ADDR_BITS ...
+/* #define MAI_V4ADDR_BYTES ...
+/* #define MAI_V6ADDR_BYTES ...
+/*
+/* typedef struct { char buf[....]; } MAI_HOSTNAME_STR;
+/* typedef struct { char buf[....]; } MAI_HOSTADDR_STR;
+/* typedef struct { char buf[....]; } MAI_SERVNAME_STR;
+/* typedef struct { char buf[....]; } MAI_SERVPORT_STR;
+/*
+/* int hostname_to_sockaddr(hostname, service, socktype, result)
+/* const char *hostname;
+/* const char *service;
+/* int socktype;
+/* struct addrinfo **result;
+/*
+/* int hostname_to_sockaddr_pf(hostname, pf, service, socktype, result)
+/* const char *hostname;
+/* int pf;
+/* const char *service;
+/* int socktype;
+/* struct addrinfo **result;
+/*
+/* int hostaddr_to_sockaddr(hostaddr, service, socktype, result)
+/* const char *hostaddr;
+/* const char *service;
+/* int socktype;
+/* struct addrinfo **result;
+/*
+/* int sockaddr_to_hostaddr(sa, salen, hostaddr, portnum, socktype)
+/* const struct sockaddr *sa;
+/* SOCKADDR_SIZE salen;
+/* MAI_HOSTADDR_STR *hostaddr;
+/* MAI_SERVPORT_STR *portnum;
+/* int socktype;
+/*
+/* int sockaddr_to_hostname(sa, salen, hostname, service, socktype)
+/* const struct sockaddr *sa;
+/* SOCKADDR_SIZE salen;
+/* MAI_HOSTNAME_STR *hostname;
+/* MAI_SERVNAME_STR *service;
+/* int socktype;
+/*
+/* const char *MAI_STRERROR(error)
+/* int error;
+/* DESCRIPTION
+/* This module provides a simplified user interface to the
+/* getaddrinfo(3) and getnameinfo(3) routines (which provide
+/* a unified interface to manipulate IPv4 and IPv6 socket
+/* address structures).
+/*
+/* On systems without getaddrinfo(3) and getnameinfo(3) support,
+/* emulation for IPv4 only can be enabled by defining
+/* EMULATE_IPV4_ADDRINFO.
+/*
+/* hostname_to_sockaddr() looks up the binary addresses for
+/* the specified symbolic hostname or numeric address. The
+/* result should be destroyed with freeaddrinfo(). A null host
+/* pointer converts to the null host address.
+/*
+/* hostname_to_sockaddr_pf() is an extended interface that
+/* provides a protocol family override.
+/*
+/* hostaddr_to_sockaddr() converts a printable network address
+/* into the corresponding binary form. The result should be
+/* destroyed with freeaddrinfo(). A null host pointer converts
+/* to the null host address.
+/*
+/* sockaddr_to_hostaddr() converts a binary network address
+/* into printable form. The result buffers should be large
+/* enough to hold the printable address or port including the
+/* null terminator.
+/* This function strips off the IPv6 datalink suffix.
+/*
+/* sockaddr_to_hostname() converts a binary network address
+/* into a hostname or service. The result buffer should be
+/* large enough to hold the hostname or service including the
+/* null terminator. This routine rejects malformed hostnames
+/* or numeric hostnames and pretends that the lookup failed.
+/*
+/* MAI_STRERROR() is an unsafe macro (it evaluates the argument
+/* multiple times) that invokes strerror() or gai_strerror()
+/* as appropriate.
+/*
+/* This module exports the following constants that should be
+/* user for storage allocation of name or address information:
+/* .IP MAI_V4ADDR_BITS
+/* .IP MAI_V6ADDR_BITS
+/* .IP MAI_V4ADDR_BYTES
+/* .IP MAI_V6ADDR_BYTES
+/* The number of bits or bytes needed to store a binary
+/* IPv4 or IPv6 network address.
+/* .PP
+/* The types MAI_HOST{NAME,ADDR}_STR and MAI_SERV{NAME,PORT}_STR
+/* implement buffers for the storage of the string representations
+/* of symbolic or numerical hosts or services. Do not use
+/* buffer types other than the ones that are expected here,
+/* or things will blow up with buffer overflow problems.
+/*
+/* Arguments:
+/* .IP hostname
+/* On input to hostname_to_sockaddr(), a numeric or symbolic
+/* hostname, or a null pointer (meaning the wild-card listen
+/* address). On output from sockaddr_to_hostname(), storage
+/* for the result hostname, or a null pointer.
+/* .IP pf
+/* Protocol type: PF_UNSPEC (meaning: use any protocol that is
+/* available), PF_INET, or PF_INET6. This argument is ignored
+/* in EMULATE_IPV4_ADDRINFO mode.
+/* .IP hostaddr
+/* On input to hostaddr_to_sockaddr(), a numeric hostname,
+/* or a null pointer (meaning the wild-card listen address).
+/* On output from sockaddr_to_hostaddr(), storage for the
+/* result hostaddress, or a null pointer.
+/* .IP service
+/* On input to hostname/addr_to_sockaddr(), a numeric or
+/* symbolic service name, or a null pointer in which case the
+/* socktype argument is ignored. On output from
+/* sockaddr_to_hostname/addr(), storage for the result service
+/* name, or a null pointer.
+/* .IP portnum
+/* Storage for the result service port number, or a null pointer.
+/* .IP socktype
+/* Socket type: SOCK_STREAM, SOCK_DGRAM, etc. This argument is
+/* ignored when no service or port are specified.
+/* .IP sa
+/* Protocol-independent socket address structure.
+/* .IP salen
+/* Protocol-dependent socket address structure size in bytes.
+/* SEE ALSO
+/* getaddrinfo(3), getnameinfo(3), freeaddrinfo(3), gai_strerror(3)
+/* DIAGNOSTICS
+/* All routines either return 0 upon success, or an error code
+/* that is compatible with gai_strerror().
+/*
+/* On systems where addrinfo support is emulated by Postfix,
+/* some out-of-memory errors are not reported to the caller,
+/* but are handled by mymalloc().
+/* BUGS
+/* The IPv4-only emulation code does not support requests that
+/* specify a service but no socket type. It returns an error
+/* indication, instead of enumerating all the possible answers.
+/*
+/* The hostname/addr_to_sockaddr() routines should accept a
+/* list of address families that the caller is interested in,
+/* and they should return only information of those types.
+/*
+/* Unfortunately, it is not possible to remove unwanted address
+/* family results from hostname_to_sockaddr(), because we
+/* don't know how the system library routine getaddrinfo()
+/* allocates memory. For example, getaddrinfo() could save
+/* space by referencing the same string object from multiple
+/* addrinfo structures; or it could allocate a string object
+/* and the addrinfo structure as one memory block.
+/*
+/* We could get around this by copying getaddrinfo() results
+/* to our own private data structures, but that would only
+/* make an already expensive API even more expensive.
+/*
+/* A better workaround is to return a vector of addrinfo
+/* pointers to the elements that contain only the elements
+/* that the caller is interested in. The pointer to the
+/* original getaddrinfo() result can be hidden at the end
+/* after the null terminator, or before the first element.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h> /* sprintf() */
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <valid_hostname.h>
+#include <sock_addr.h>
+#include <stringops.h>
+#include <msg.h>
+#include <inet_proto.h>
+#include <myaddrinfo.h>
+#include <split_at.h>
+#include <known_tcp_ports.h>
+
+/* Application-specific. */
+
+ /*
+ * Use an old trick to save some space: allocate space for two objects in
+ * one. In Postfix we often use this trick for structures that have an array
+ * of things at the end.
+ */
+struct ipv4addrinfo {
+ struct addrinfo info;
+ struct sockaddr_in sin;
+};
+
+ /*
+ * When we're not interested in service ports, we must pick a socket type
+ * otherwise getaddrinfo() will give us duplicate results: one set for TCP,
+ * and another set for UDP. For consistency, we'll use the same default
+ * socket type for the results from emulation mode.
+ */
+#define MAI_SOCKTYPE SOCK_STREAM /* getaddrinfo() query */
+
+#ifdef EMULATE_IPV4_ADDRINFO
+
+/* clone_ipv4addrinfo - clone ipv4addrinfo structure */
+
+static struct ipv4addrinfo *clone_ipv4addrinfo(struct ipv4addrinfo * tp)
+{
+ struct ipv4addrinfo *ip;
+
+ ip = (struct ipv4addrinfo *) mymalloc(sizeof(*ip));
+ *ip = *tp;
+ ip->info.ai_addr = (struct sockaddr *) &(ip->sin);
+ return (ip);
+}
+
+/* init_ipv4addrinfo - initialize an ipv4addrinfo structure */
+
+static void init_ipv4addrinfo(struct ipv4addrinfo * ip, int socktype)
+{
+
+ /*
+ * Portability: null pointers aren't necessarily all-zero bits, so we
+ * make explicit assignments to all the pointers that we're aware of.
+ */
+ memset((void *) ip, 0, sizeof(*ip));
+ ip->info.ai_family = PF_INET;
+ ip->info.ai_socktype = socktype;
+ ip->info.ai_protocol = 0; /* XXX */
+ ip->info.ai_addrlen = sizeof(ip->sin);
+ ip->info.ai_canonname = 0;
+ ip->info.ai_addr = (struct sockaddr *) &(ip->sin);
+ ip->info.ai_next = 0;
+ ip->sin.sin_family = AF_INET;
+#ifdef HAS_SA_LEN
+ ip->sin.sin_len = sizeof(ip->sin);
+#endif
+}
+
+/* find_service - translate numeric or symbolic service name */
+
+static int find_service(const char *service, int socktype)
+{
+ struct servent *sp;
+ const char *proto;
+ unsigned port;
+
+ service = filter_known_tcp_port(service);
+ if (alldig(service)) {
+ port = atoi(service);
+ return (port < 65536 ? htons(port) : -1);
+ }
+ if (socktype == SOCK_STREAM) {
+ proto = "tcp";
+ } else if (socktype == SOCK_DGRAM) {
+ proto = "udp";
+ } else {
+ return (-1);
+ }
+ if ((sp = getservbyname(service, proto)) != 0) {
+ return (sp->s_port);
+ } else {
+ return (-1);
+ }
+}
+
+#endif
+
+/* hostname_to_sockaddr_pf - hostname to binary address form */
+
+int hostname_to_sockaddr_pf(const char *hostname, int pf,
+ const char *service, int socktype,
+ struct addrinfo ** res)
+{
+#ifdef EMULATE_IPV4_ADDRINFO
+
+ /*
+ * Emulated getaddrinfo(3) version.
+ */
+ static struct ipv4addrinfo template;
+ struct ipv4addrinfo *ip;
+ struct ipv4addrinfo *prev;
+ struct in_addr addr;
+ struct hostent *hp;
+ char **name_list;
+ int port;
+
+ /*
+ * Validate the service.
+ */
+ if (service) {
+ if ((port = find_service(service, socktype)) < 0)
+ return (EAI_SERVICE);
+ } else {
+ port = 0;
+ socktype = MAI_SOCKTYPE;
+ }
+
+ /*
+ * No host means INADDR_ANY.
+ */
+ if (hostname == 0) {
+ ip = (struct ipv4addrinfo *) mymalloc(sizeof(*ip));
+ init_ipv4addrinfo(ip, socktype);
+ ip->sin.sin_addr.s_addr = INADDR_ANY;
+ ip->sin.sin_port = port;
+ *res = &(ip->info);
+ return (0);
+ }
+
+ /*
+ * Numeric host.
+ */
+ if (inet_pton(AF_INET, hostname, (void *) &addr) == 1) {
+ ip = (struct ipv4addrinfo *) mymalloc(sizeof(*ip));
+ init_ipv4addrinfo(ip, socktype);
+ ip->sin.sin_addr = addr;
+ ip->sin.sin_port = port;
+ *res = &(ip->info);
+ return (0);
+ }
+
+ /*
+ * Look up the IPv4 address list.
+ */
+ if ((hp = gethostbyname(hostname)) == 0)
+ return (h_errno == TRY_AGAIN ? EAI_AGAIN : EAI_NODATA);
+ if (hp->h_addrtype != AF_INET
+ || hp->h_length != sizeof(template.sin.sin_addr))
+ return (EAI_NODATA);
+
+ /*
+ * Initialize the result template.
+ */
+ if (template.info.ai_addrlen == 0)
+ init_ipv4addrinfo(&template, socktype);
+
+ /*
+ * Copy the address information into an addrinfo structure.
+ */
+ prev = &template;
+ for (name_list = hp->h_addr_list; name_list[0]; name_list++) {
+ ip = clone_ipv4addrinfo(prev);
+ ip->sin.sin_addr = IN_ADDR(name_list[0]);
+ ip->sin.sin_port = port;
+ if (prev == &template)
+ *res = &(ip->info);
+ else
+ prev->info.ai_next = &(ip->info);
+ prev = ip;
+ }
+ return (0);
+#else
+
+ /*
+ * Native getaddrinfo(3) version.
+ *
+ * XXX Wild-card listener issues.
+ *
+ * With most IPv4 plus IPv6 systems, an IPv6 wild-card listener also listens
+ * on the IPv4 wild-card address. Connections from IPv4 clients appear as
+ * IPv4-in-IPv6 addresses; when Postfix support for IPv4 is turned on,
+ * Postfix automatically maps these embedded addresses to their original
+ * IPv4 form. So everything seems to be fine.
+ *
+ * However, some applications prefer to use separate listener sockets for
+ * IPv4 and IPv6. The Postfix IPv6 patch provided such an example. And
+ * this is where things become tricky. On many systems the IPv6 and IPv4
+ * wild-card listeners cannot coexist. When one is already active, the
+ * other fails with EADDRINUSE. Solaris 9, however, will automagically
+ * "do the right thing" and allow both listeners to coexist.
+ *
+ * Recent systems have the IPV6_V6ONLY feature (RFC 3493), which tells the
+ * system that we really mean IPv6 when we say IPv6. This allows us to
+ * set up separate wild-card listener sockets for IPv4 and IPv6. So
+ * everything seems to be fine again.
+ *
+ * The following workaround disables the wild-card IPv4 listener when
+ * IPV6_V6ONLY is unavailable. This is necessary for some Linux versions,
+ * but is not needed for Solaris 9 (which allows IPv4 and IPv6 wild-card
+ * listeners to coexist). Solaris 10 beta already has IPV6_V6ONLY.
+ *
+ * XXX This workaround obviously breaks if we want to support protocols in
+ * addition to IPv6 and IPv4, but it is needed only until IPv6
+ * implementations catch up with RFC 3493. A nicer fix is to filter the
+ * getaddrinfo() result, and to return a vector of addrinfo pointers to
+ * only those types of elements that the caller has expressed interested
+ * in.
+ *
+ * XXX Vanilla AIX 5.1 getaddrinfo() does not support a null hostname with
+ * AI_PASSIVE. And since we don't know how getaddrinfo() manages its
+ * memory we can't bypass it for this special case, or freeaddrinfo()
+ * might blow up. Instead we turn off IPV6_V6ONLY in inet_listen(), and
+ * supply a protocol-dependent hard-coded string value to getaddrinfo()
+ * below, so that it will convert into the appropriate wild-card address.
+ *
+ * XXX AIX 5.[1-3] getaddrinfo() may return a non-null port when a null
+ * service argument is specified.
+ */
+ struct addrinfo hints;
+ int err;
+
+ memset((void *) &hints, 0, sizeof(hints));
+ hints.ai_family = (pf != PF_UNSPEC) ? pf : inet_proto_info()->ai_family;
+ hints.ai_socktype = service ? socktype : MAI_SOCKTYPE;
+ if (!hostname) {
+ hints.ai_flags = AI_PASSIVE;
+#if !defined(IPV6_V6ONLY) || defined(BROKEN_AI_PASSIVE_NULL_HOST)
+ switch (hints.ai_family) {
+ case PF_UNSPEC:
+ hints.ai_family = PF_INET6;
+#ifdef BROKEN_AI_PASSIVE_NULL_HOST
+ case PF_INET6:
+ hostname = "::";
+ break;
+ case PF_INET:
+ hostname = "0.0.0.0";
+ break;
+#endif
+ }
+#endif
+ }
+ if (service) {
+ service = filter_known_tcp_port(service);
+ if (alldig(service))
+ hints.ai_flags |= AI_NUMERICSERV;
+ }
+ err = getaddrinfo(hostname, service, &hints, res);
+#if defined(BROKEN_AI_NULL_SERVICE)
+ if (service == 0 && err == 0) {
+ struct addrinfo *r;
+ unsigned short *portp;
+
+ for (r = *res; r != 0; r = r->ai_next)
+ if (*(portp = SOCK_ADDR_PORTP(r->ai_addr)) != 0)
+ *portp = 0;
+ }
+#endif
+ return (err);
+#endif
+}
+
+/* hostaddr_to_sockaddr - printable address to binary address form */
+
+int hostaddr_to_sockaddr(const char *hostaddr, const char *service,
+ int socktype, struct addrinfo ** res)
+{
+#ifdef EMULATE_IPV4_ADDRINFO
+
+ /*
+ * Emulated getaddrinfo(3) version.
+ */
+ struct ipv4addrinfo *ip;
+ struct in_addr addr;
+ int port;
+
+ /*
+ * Validate the service.
+ */
+ if (service) {
+ if ((port = find_service(service, socktype)) < 0)
+ return (EAI_SERVICE);
+ } else {
+ port = 0;
+ socktype = MAI_SOCKTYPE;
+ }
+
+ /*
+ * No host means INADDR_ANY.
+ */
+ if (hostaddr == 0) {
+ ip = (struct ipv4addrinfo *) mymalloc(sizeof(*ip));
+ init_ipv4addrinfo(ip, socktype);
+ ip->sin.sin_addr.s_addr = INADDR_ANY;
+ ip->sin.sin_port = port;
+ *res = &(ip->info);
+ return (0);
+ }
+
+ /*
+ * Deal with bad address forms.
+ */
+ switch (inet_pton(AF_INET, hostaddr, (void *) &addr)) {
+ case 1: /* Success */
+ break;
+ default: /* Unparsable */
+ return (EAI_NONAME);
+ case -1: /* See errno */
+ return (EAI_SYSTEM);
+ }
+
+ /*
+ * Initialize the result structure.
+ */
+ ip = (struct ipv4addrinfo *) mymalloc(sizeof(*ip));
+ init_ipv4addrinfo(ip, socktype);
+
+ /*
+ * And copy the result.
+ */
+ ip->sin.sin_addr = addr;
+ ip->sin.sin_port = port;
+ *res = &(ip->info);
+
+ return (0);
+#else
+
+ /*
+ * Native getaddrinfo(3) version. See comments in hostname_to_sockaddr().
+ *
+ * XXX Vanilla AIX 5.1 getaddrinfo() returns multiple results when
+ * converting a printable ipv4 or ipv6 address to socket address with
+ * ai_family=PF_UNSPEC, ai_flags=AI_NUMERICHOST, ai_socktype=SOCK_STREAM,
+ * ai_protocol=0 or IPPROTO_TCP, and service=0. The workaround is to
+ * ignore all but the first result.
+ *
+ * XXX AIX 5.[1-3] getaddrinfo() may return a non-null port when a null
+ * service argument is specified.
+ */
+ struct addrinfo hints;
+ int err;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = inet_proto_info()->ai_family;
+ hints.ai_socktype = service ? socktype : MAI_SOCKTYPE;
+ hints.ai_flags = AI_NUMERICHOST;
+ if (!hostaddr) {
+ hints.ai_flags |= AI_PASSIVE;
+#if !defined(IPV6_V6ONLY) || defined(BROKEN_AI_PASSIVE_NULL_HOST)
+ switch (hints.ai_family) {
+ case PF_UNSPEC:
+ hints.ai_family = PF_INET6;
+#ifdef BROKEN_AI_PASSIVE_NULL_HOST
+ case PF_INET6:
+ hostaddr = "::";
+ break;
+ case PF_INET:
+ hostaddr = "0.0.0.0";
+ break;
+#endif
+ }
+#endif
+ }
+ if (service) {
+ service = filter_known_tcp_port(service);
+ if (alldig(service))
+ hints.ai_flags |= AI_NUMERICSERV;
+ }
+ err = getaddrinfo(hostaddr, service, &hints, res);
+#if defined(BROKEN_AI_NULL_SERVICE)
+ if (service == 0 && err == 0) {
+ struct addrinfo *r;
+ unsigned short *portp;
+
+ for (r = *res; r != 0; r = r->ai_next)
+ if (*(portp = SOCK_ADDR_PORTP(r->ai_addr)) != 0)
+ *portp = 0;
+ }
+#endif
+ return (err);
+#endif
+}
+
+/* sockaddr_to_hostaddr - binary address to printable address form */
+
+int sockaddr_to_hostaddr(const struct sockaddr *sa, SOCKADDR_SIZE salen,
+ MAI_HOSTADDR_STR *hostaddr,
+ MAI_SERVPORT_STR *portnum,
+ int unused_socktype)
+{
+#ifdef EMULATE_IPV4_ADDRINFO
+ char portbuf[sizeof("65535")];
+ ssize_t len;
+
+ /*
+ * Emulated getnameinfo(3) version. The buffer length includes the space
+ * for the null terminator.
+ */
+ if (sa->sa_family != AF_INET) {
+ errno = EAFNOSUPPORT;
+ return (EAI_SYSTEM);
+ }
+ if (hostaddr != 0) {
+ if (inet_ntop(AF_INET, (void *) &(SOCK_ADDR_IN_ADDR(sa)),
+ hostaddr->buf, sizeof(hostaddr->buf)) == 0)
+ return (EAI_SYSTEM);
+ }
+ if (portnum != 0) {
+ sprintf(portbuf, "%d", ntohs(SOCK_ADDR_IN_PORT(sa)) & 0xffff);
+ if ((len = strlen(portbuf)) >= sizeof(portnum->buf)) {
+ errno = ENOSPC;
+ return (EAI_SYSTEM);
+ }
+ memcpy(portnum->buf, portbuf, len + 1);
+ }
+ return (0);
+#else
+ int ret;
+
+ /*
+ * Native getnameinfo(3) version.
+ */
+ ret = getnameinfo(sa, salen,
+ hostaddr ? hostaddr->buf : (char *) 0,
+ hostaddr ? sizeof(hostaddr->buf) : 0,
+ portnum ? portnum->buf : (char *) 0,
+ portnum ? sizeof(portnum->buf) : 0,
+ NI_NUMERICHOST | NI_NUMERICSERV);
+ if (hostaddr != 0 && ret == 0 && sa->sa_family == AF_INET6)
+ (void) split_at(hostaddr->buf, '%');
+ return (ret);
+#endif
+}
+
+/* sockaddr_to_hostname - binary address to printable hostname */
+
+int sockaddr_to_hostname(const struct sockaddr *sa, SOCKADDR_SIZE salen,
+ MAI_HOSTNAME_STR *hostname,
+ MAI_SERVNAME_STR *service,
+ int socktype)
+{
+#ifdef EMULATE_IPV4_ADDRINFO
+
+ /*
+ * Emulated getnameinfo(3) version.
+ */
+ struct hostent *hp;
+ struct servent *sp;
+ size_t len;
+
+ /*
+ * Sanity check.
+ */
+ if (sa->sa_family != AF_INET)
+ return (EAI_NODATA);
+
+ /*
+ * Look up the host name.
+ */
+ if (hostname != 0) {
+ if ((hp = gethostbyaddr((char *) &(SOCK_ADDR_IN_ADDR(sa)),
+ sizeof(SOCK_ADDR_IN_ADDR(sa)),
+ AF_INET)) == 0)
+ return (h_errno == TRY_AGAIN ? EAI_AGAIN : EAI_NONAME);
+
+ /*
+ * Save the result. The buffer length includes the space for the null
+ * terminator. Hostname sanity checks are at the end of this
+ * function.
+ */
+ if ((len = strlen(hp->h_name)) >= sizeof(hostname->buf)) {
+ errno = ENOSPC;
+ return (EAI_SYSTEM);
+ }
+ memcpy(hostname->buf, hp->h_name, len + 1);
+ }
+
+ /*
+ * Look up the service.
+ */
+ if (service != 0) {
+ if ((sp = getservbyport(ntohs(SOCK_ADDR_IN_PORT(sa)),
+ socktype == SOCK_DGRAM ? "udp" : "tcp")) == 0)
+ return (EAI_NONAME);
+
+ /*
+ * Save the result. The buffer length includes the space for the null
+ * terminator.
+ */
+ if ((len = strlen(sp->s_name)) >= sizeof(service->buf)) {
+ errno = ENOSPC;
+ return (EAI_SYSTEM);
+ }
+ memcpy(service->buf, sp->s_name, len + 1);
+ }
+#else
+
+ /*
+ * Native getnameinfo(3) version.
+ */
+ int err;
+
+ err = getnameinfo(sa, salen,
+ hostname ? hostname->buf : (char *) 0,
+ hostname ? sizeof(hostname->buf) : 0,
+ service ? service->buf : (char *) 0,
+ service ? sizeof(service->buf) : 0,
+ socktype == SOCK_DGRAM ?
+ NI_NAMEREQD | NI_DGRAM : NI_NAMEREQD);
+ if (err != 0)
+ return (err);
+#endif
+
+ /*
+ * Hostname sanity checks.
+ */
+ if (hostname != 0) {
+ if (valid_hostaddr(hostname->buf, DONT_GRIPE)) {
+ msg_warn("numeric hostname: %s", hostname->buf);
+ return (EAI_NONAME);
+ }
+ if (!valid_hostname(hostname->buf, DO_GRIPE))
+ return (EAI_NONAME);
+ }
+ return (0);
+}
+
+/* myaddrinfo_control - fine control */
+
+void myaddrinfo_control(int name,...)
+{
+ const char *myname = "myaddrinfo_control";
+ va_list ap;
+
+ for (va_start(ap, name); name != 0; name = va_arg(ap, int)) {
+ switch (name) {
+ default:
+ msg_panic("%s: bad name %d", myname, name);
+ }
+ }
+ va_end(ap);
+}
+
+#ifdef EMULATE_IPV4_ADDRINFO
+
+/* freeaddrinfo - release storage */
+
+void freeaddrinfo(struct addrinfo * ai)
+{
+ struct addrinfo *ap;
+ struct addrinfo *next;
+
+ /*
+ * Artifact of implementation: tolerate a null pointer argument.
+ */
+ for (ap = ai; ap != 0; ap = next) {
+ next = ap->ai_next;
+ if (ap->ai_canonname)
+ myfree(ap->ai_canonname);
+ /* ap->ai_addr is allocated within this memory block */
+ myfree((void *) ap);
+ }
+}
+
+static char *ai_errlist[] = {
+ "Success",
+ "Address family for hostname not supported", /* EAI_ADDRFAMILY */
+ "Temporary failure in name resolution", /* EAI_AGAIN */
+ "Invalid value for ai_flags", /* EAI_BADFLAGS */
+ "Non-recoverable failure in name resolution", /* EAI_FAIL */
+ "ai_family not supported", /* EAI_FAMILY */
+ "Memory allocation failure", /* EAI_MEMORY */
+ "No address associated with hostname", /* EAI_NODATA */
+ "hostname nor servname provided, or not known", /* EAI_NONAME */
+ "service name not supported for ai_socktype", /* EAI_SERVICE */
+ "ai_socktype not supported", /* EAI_SOCKTYPE */
+ "System error returned in errno", /* EAI_SYSTEM */
+ "Invalid value for hints", /* EAI_BADHINTS */
+ "Resolved protocol is unknown", /* EAI_PROTOCOL */
+ "Unknown error", /* EAI_MAX */
+};
+
+/* gai_strerror - error number to string */
+
+char *gai_strerror(int ecode)
+{
+
+ /*
+ * Note: EAI_SYSTEM errors are not automatically handed over to
+ * strerror(). The application decides.
+ */
+ if (ecode < 0 || ecode > EAI_MAX)
+ ecode = EAI_MAX;
+ return (ai_errlist[ecode]);
+}
+
+#endif
+
+#ifdef TEST
+
+ /*
+ * A test program that takes some info from the command line and runs it
+ * forward and backward through the above conversion routines.
+ */
+#include <stdlib.h>
+#include <msg.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+
+static int compare_family(const void *a, const void *b)
+{
+ struct addrinfo *resa = *(struct addrinfo **) a;
+ struct addrinfo *resb = *(struct addrinfo **) b;
+
+ return (resa->ai_family - resb->ai_family);
+}
+
+int main(int argc, char **argv)
+{
+ struct addrinfo *info;
+ struct addrinfo *ip;
+ struct addrinfo **resv;
+ MAI_HOSTNAME_STR host;
+ MAI_HOSTADDR_STR addr;
+ size_t len, n;
+ int err;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ if (argc != 4)
+ msg_fatal("usage: %s protocols hostname hostaddress", argv[0]);
+
+ inet_proto_init(argv[0], argv[1]);
+
+ msg_info("=== hostname %s ===", argv[2]);
+
+ if ((err = hostname_to_sockaddr(argv[2], (char *) 0, 0, &info)) != 0) {
+ msg_info("hostname_to_sockaddr(%s): %s",
+ argv[2], err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err));
+ } else {
+ for (len = 0, ip = info; ip != 0; ip = ip->ai_next)
+ len += 1;
+ resv = (struct addrinfo **) mymalloc(len * sizeof(*resv));
+ for (len = 0, ip = info; ip != 0; ip = ip->ai_next)
+ resv[len++] = ip;
+ qsort((void *) resv, len, sizeof(*resv), compare_family);
+ for (n = 0; n < len; n++) {
+ ip = resv[n];
+ if ((err = sockaddr_to_hostaddr(ip->ai_addr, ip->ai_addrlen, &addr,
+ (MAI_SERVPORT_STR *) 0, 0)) != 0) {
+ msg_info("sockaddr_to_hostaddr: %s",
+ err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err));
+ continue;
+ }
+ msg_info("%s -> family=%d sock=%d proto=%d %s", argv[2],
+ ip->ai_family, ip->ai_socktype, ip->ai_protocol, addr.buf);
+ if ((err = sockaddr_to_hostname(ip->ai_addr, ip->ai_addrlen, &host,
+ (MAI_SERVNAME_STR *) 0, 0)) != 0) {
+ msg_info("sockaddr_to_hostname: %s",
+ err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err));
+ continue;
+ }
+ msg_info("%s -> %s", addr.buf, host.buf);
+ }
+ freeaddrinfo(info);
+ myfree((void *) resv);
+ }
+
+ msg_info("=== host address %s ===", argv[3]);
+
+ if ((err = hostaddr_to_sockaddr(argv[3], (char *) 0, 0, &ip)) != 0) {
+ msg_info("hostaddr_to_sockaddr(%s): %s",
+ argv[3], err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err));
+ } else {
+ if ((err = sockaddr_to_hostaddr(ip->ai_addr, ip->ai_addrlen, &addr,
+ (MAI_SERVPORT_STR *) 0, 0)) != 0) {
+ msg_info("sockaddr_to_hostaddr: %s",
+ err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err));
+ } else {
+ msg_info("%s -> family=%d sock=%d proto=%d %s", argv[3],
+ ip->ai_family, ip->ai_socktype, ip->ai_protocol, addr.buf);
+ if ((err = sockaddr_to_hostname(ip->ai_addr, ip->ai_addrlen, &host,
+ (MAI_SERVNAME_STR *) 0, 0)) != 0) {
+ msg_info("sockaddr_to_hostname: %s",
+ err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err));
+ } else
+ msg_info("%s -> %s", addr.buf, host.buf);
+ freeaddrinfo(ip);
+ }
+ }
+ exit(0);
+}
+
+#endif
diff --git a/src/util/myaddrinfo.h b/src/util/myaddrinfo.h
new file mode 100644
index 0000000..94f1e9f
--- /dev/null
+++ b/src/util/myaddrinfo.h
@@ -0,0 +1,229 @@
+#ifndef _MYADDRINFO_H_INCLUDED_
+#define _MYADDRINFO_H_INCLUDED_
+
+/*++
+/* NAME
+/* myaddrinfo 3h
+/* SUMMARY
+/* addrinfo encapsulation and emulation
+/* SYNOPSIS
+/* #include <myaddrinfo.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <string.h>
+#include <errno.h> /* MAI_STRERROR() */
+#include <limits.h> /* CHAR_BIT */
+
+ /*
+ * Backwards compatibility support for IPV4 systems without addrinfo API.
+ */
+#ifdef EMULATE_IPV4_ADDRINFO
+
+ /*
+ * Avoid clashes with global symbols, just in case some third-party library
+ * provides its own addrinfo() implementation. This also allows us to test
+ * the IPV4 emulation code on an IPV6 enabled system.
+ */
+#undef freeaddrinfo
+#define freeaddrinfo mai_freeaddrinfo
+#undef gai_strerror
+#define gai_strerror mai_strerror
+#undef addrinfo
+#define addrinfo mai_addrinfo
+#undef sockaddr_storage
+#define sockaddr_storage mai_sockaddr_storage
+
+ /*
+ * Modern systems define this in <netdb.h>.
+ */
+struct addrinfo {
+ int ai_flags; /* AI_PASSIVE|CANONNAME|NUMERICHOST */
+ int ai_family; /* PF_xxx */
+ int ai_socktype; /* SOCK_xxx */
+ int ai_protocol; /* 0 or IPPROTO_xxx */
+ size_t ai_addrlen; /* length of ai_addr */
+ char *ai_canonname; /* canonical name for nodename */
+ struct sockaddr *ai_addr; /* binary address */
+ struct addrinfo *ai_next; /* next structure in linked list */
+};
+
+ /*
+ * Modern systems define this in <sys/socket.h>.
+ */
+struct sockaddr_storage {
+ struct sockaddr_in dummy; /* alignment!! */
+};
+
+ /*
+ * Result codes. See gai_strerror() for text. Undefine already imported
+ * definitions so that we can test the IPv4-only emulation on a modern
+ * system without getting a ton of compiler warnings.
+ */
+#undef EAI_ADDRFAMILY
+#define EAI_ADDRFAMILY 1
+#undef EAI_AGAIN
+#define EAI_AGAIN 2
+#undef EAI_BADFLAGS
+#define EAI_BADFLAGS 3
+#undef EAI_FAIL
+#define EAI_FAIL 4
+#undef EAI_FAMILY
+#define EAI_FAMILY 5
+#undef EAI_MEMORY
+#define EAI_MEMORY 6
+#undef EAI_NODATA
+#define EAI_NODATA 7
+#undef EAI_NONAME
+#define EAI_NONAME 8
+#undef EAI_SERVICE
+#define EAI_SERVICE 9
+#undef EAI_SOCKTYPE
+#define EAI_SOCKTYPE 10
+#undef EAI_SYSTEM
+#define EAI_SYSTEM 11
+#undef EAI_BADHINTS
+#define EAI_BADHINTS 12
+#undef EAI_PROTOCOL
+#define EAI_PROTOCOL 13
+#undef EAI_RESNULL
+#define EAI_RESNULL 14
+#undef EAI_MAX
+#define EAI_MAX 15
+
+extern void freeaddrinfo(struct addrinfo *);
+extern char *gai_strerror(int);
+
+#endif
+
+ /*
+ * Bounds grow in leaps. These macros attempt to keep non-library code free
+ * from IPV6 #ifdef pollution. Avoid macro names that end in STRLEN because
+ * they suggest that space for the null terminator is not included.
+ */
+#ifdef HAS_IPV6
+# define MAI_HOSTADDR_STRSIZE INET6_ADDRSTRLEN
+#else
+# ifndef INET_ADDRSTRLEN
+# define INET_ADDRSTRLEN 16
+# endif
+# define MAI_HOSTADDR_STRSIZE INET_ADDRSTRLEN
+#endif
+
+#define MAI_HOSTNAME_STRSIZE 1025
+#define MAI_SERVNAME_STRSIZE 32
+#define MAI_SERVPORT_STRSIZE sizeof("65535")
+
+#define MAI_V4ADDR_BITS 32
+#define MAI_V6ADDR_BITS 128
+#define MAI_V4ADDR_BYTES ((MAI_V4ADDR_BITS + (CHAR_BIT - 1))/CHAR_BIT)
+#define MAI_V6ADDR_BYTES ((MAI_V6ADDR_BITS + (CHAR_BIT - 1))/CHAR_BIT)
+
+ /*
+ * Routines and data structures to hide some of the complexity of the
+ * addrinfo API. They still don't hide that we may get results for address
+ * families that we aren't interested in.
+ *
+ * Note: the getnameinfo() and inet_ntop() system library functions use unsafe
+ * APIs with separate pointer and length arguments. To avoid buffer overflow
+ * problems with these functions, Postfix uses pointers to structures
+ * internally. This way the compiler can enforce that callers provide
+ * buffers with the appropriate length, instead of having to trust that
+ * callers will never mess up some length calculation.
+ */
+typedef struct {
+ char buf[MAI_HOSTNAME_STRSIZE];
+} MAI_HOSTNAME_STR;
+
+typedef struct {
+ char buf[MAI_HOSTADDR_STRSIZE];
+} MAI_HOSTADDR_STR;
+
+typedef struct {
+ char buf[MAI_SERVNAME_STRSIZE];
+} MAI_SERVNAME_STR;
+
+typedef struct {
+ char buf[MAI_SERVPORT_STRSIZE];
+} MAI_SERVPORT_STR;
+
+extern int WARN_UNUSED_RESULT hostname_to_sockaddr_pf(const char *,
+ int, const char *, int, struct addrinfo **);
+extern int WARN_UNUSED_RESULT hostaddr_to_sockaddr(const char *,
+ const char *, int, struct addrinfo **);
+extern int WARN_UNUSED_RESULT sockaddr_to_hostaddr(const struct sockaddr *,
+ SOCKADDR_SIZE, MAI_HOSTADDR_STR *, MAI_SERVPORT_STR *, int);
+extern int WARN_UNUSED_RESULT sockaddr_to_hostname(const struct sockaddr *,
+ SOCKADDR_SIZE, MAI_HOSTNAME_STR *, MAI_SERVNAME_STR *, int);
+extern void myaddrinfo_control(int,...);
+
+#define MAI_CTL_END 0 /* list terminator */
+
+#define MAI_STRERROR(e) ((e) == EAI_SYSTEM ? strerror(errno) : gai_strerror(e))
+
+#define hostname_to_sockaddr(host, serv, sock, res) \
+ hostname_to_sockaddr_pf((host), PF_UNSPEC, (serv), (sock), (res))
+
+ /*
+ * Macros for the case where we really don't want to be bothered with things
+ * that may fail.
+ */
+#define HOSTNAME_TO_SOCKADDR_PF(host, pf, serv, sock, res) \
+ do { \
+ int _aierr; \
+ _aierr = hostname_to_sockaddr_pf((host), (pf), (serv), (sock), (res)); \
+ if (_aierr) \
+ msg_fatal("hostname_to_sockaddr_pf: %s", MAI_STRERROR(_aierr)); \
+ } while (0)
+
+#define HOSTNAME_TO_SOCKADDR(host, serv, sock, res) \
+ HOSTNAME_TO_SOCKADDR_PF((host), PF_UNSPEC, (serv), (sock), (res))
+
+#define HOSTADDR_TO_SOCKADDR(host, serv, sock, res) \
+ do { \
+ int _aierr; \
+ _aierr = hostaddr_to_sockaddr((host), (serv), (sock), (res)); \
+ if (_aierr) \
+ msg_fatal("hostaddr_to_sockaddr: %s", MAI_STRERROR(_aierr)); \
+ } while (0)
+
+#define SOCKADDR_TO_HOSTADDR(sa, salen, host, port, sock) \
+ do { \
+ int _aierr; \
+ _aierr = sockaddr_to_hostaddr((sa), (salen), (host), (port), (sock)); \
+ if (_aierr) \
+ msg_fatal("sockaddr_to_hostaddr: %s", MAI_STRERROR(_aierr)); \
+ } while (0)
+
+#define SOCKADDR_TO_HOSTNAME(sa, salen, host, service, sock) \
+ do { \
+ int _aierr; \
+ _aierr = sockaddr_to_hostname((sa), (salen), (host), (service), (sock)); \
+ if (_aierr) \
+ msg_fatal("sockaddr_to_hostname: %s", MAI_STRERROR(_aierr)); \
+ } while (0)
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/myaddrinfo.ref b/src/util/myaddrinfo.ref
new file mode 100644
index 0000000..7dccdb0
--- /dev/null
+++ b/src/util/myaddrinfo.ref
@@ -0,0 +1,8 @@
+./myaddrinfo: === hostname belly.porcupine.org ===
+./myaddrinfo: belly.porcupine.org -> family=2 sock=1 proto=6 168.100.3.6
+./myaddrinfo: 168.100.3.6 -> belly.porcupine.org
+./myaddrinfo: belly.porcupine.org -> family=28 sock=1 proto=6 2604:8d00:189::6
+./myaddrinfo: 2604:8d00:189::6 -> belly.porcupine.org
+./myaddrinfo: === host address 168.100.3.2 ===
+./myaddrinfo: 168.100.3.2 -> family=2 sock=1 proto=6 168.100.3.2
+./myaddrinfo: 168.100.3.2 -> spike.porcupine.org
diff --git a/src/util/myaddrinfo.ref2 b/src/util/myaddrinfo.ref2
new file mode 100644
index 0000000..f2305dc
--- /dev/null
+++ b/src/util/myaddrinfo.ref2
@@ -0,0 +1,5 @@
+./myaddrinfo: === hostname null.porcupine.org ===
+./myaddrinfo: hostname_to_sockaddr(null.porcupine.org): hostname nor servname provided, or not known
+./myaddrinfo: === host address 10.0.0.0 ===
+./myaddrinfo: 10.0.0.0 -> family=2 sock=1 proto=6 10.0.0.0
+./myaddrinfo: sockaddr_to_hostname: hostname nor servname provided, or not known
diff --git a/src/util/myaddrinfo4.ref b/src/util/myaddrinfo4.ref
new file mode 100644
index 0000000..33c1284
--- /dev/null
+++ b/src/util/myaddrinfo4.ref
@@ -0,0 +1,6 @@
+./myaddrinfo4: === hostname belly.porcupine.org ===
+./myaddrinfo4: belly.porcupine.org -> family=2 sock=1 proto=6 168.100.3.6
+./myaddrinfo4: 168.100.3.6 -> belly.porcupine.org
+./myaddrinfo4: === host address 168.100.3.2 ===
+./myaddrinfo4: 168.100.3.2 -> family=2 sock=1 proto=6 168.100.3.2
+./myaddrinfo4: 168.100.3.2 -> spike.porcupine.org
diff --git a/src/util/myaddrinfo4.ref2 b/src/util/myaddrinfo4.ref2
new file mode 100644
index 0000000..f73560b
--- /dev/null
+++ b/src/util/myaddrinfo4.ref2
@@ -0,0 +1,5 @@
+./myaddrinfo4: === hostname null.porcupine.org ===
+./myaddrinfo4: hostname2sockaddr(null.porcupine.org): No address associated with hostname
+./myaddrinfo4: === host address 10.0.0.0 ===
+./myaddrinfo4: 10.0.0.0 -> family=2 sock=1 proto=6 10.0.0.0
+./myaddrinfo4: sockaddr2hostname: hostname nor servname provided, or not known
diff --git a/src/util/myflock.c b/src/util/myflock.c
new file mode 100644
index 0000000..bd903ee
--- /dev/null
+++ b/src/util/myflock.c
@@ -0,0 +1,152 @@
+/*++
+/* NAME
+/* myflock 3
+/* SUMMARY
+/* lock open file
+/* SYNOPSIS
+/* #include <myflock.h>
+/*
+/* int myflock(fd, lock_style, operation)
+/* int fd;
+/* int lock_style;
+/* int operation;
+/* DESCRIPTION
+/* myflock() locks or unlocks an entire open file.
+/*
+/* In the case of a blocking request, a call that fails due to
+/* foreseeable transient problems is retried once per second.
+/*
+/* Arguments:
+/* .IP fd
+/* The open file to be locked/unlocked.
+/* .IP lock_style
+/* One of the following values:
+/* .RS
+/* .IP MYFLOCK_STYLE_FLOCK
+/* Use BSD-style flock() locking.
+/* .IP MYFLOCK_STYLE_FCNTL
+/* Use POSIX-style fcntl() locking.
+/* .RE
+/* .IP operation
+/* One of the following values:
+/* .RS
+/* .IP MYFLOCK_OP_NONE
+/* Release any locks the process has on the specified open file.
+/* .IP MYFLOCK_OP_SHARED
+/* Attempt to acquire a shared lock on the specified open file.
+/* This is appropriate for read-only access.
+/* .IP MYFLOCK_OP_EXCLUSIVE
+/* Attempt to acquire an exclusive lock on the specified open
+/* file. This is appropriate for write access.
+/* .PP
+/* In addition, setting the MYFLOCK_OP_NOWAIT bit causes the
+/* call to return immediately when the requested lock cannot
+/* be acquired.
+/* .RE
+/* DIAGNOSTICS
+/* myflock() returns 0 in case of success, -1 in case of failure.
+/* A problem description is returned via the global \fIerrno\fR
+/* variable. In the case of a non-blocking lock request the value
+/* EAGAIN means that a lock is claimed by someone else.
+/*
+/* Panic: attempts to use an unsupported file locking method or
+/* to implement an unsupported operation.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <errno.h>
+#include <unistd.h>
+
+#ifdef HAS_FCNTL_LOCK
+#include <fcntl.h>
+#include <string.h>
+#endif
+
+#ifdef HAS_FLOCK_LOCK
+#include <sys/file.h>
+#endif
+
+/* Utility library. */
+
+#include "msg.h"
+#include "myflock.h"
+
+/* myflock - lock/unlock entire open file */
+
+int myflock(int fd, int lock_style, int operation)
+{
+ int status;
+
+ /*
+ * Sanity check.
+ */
+ if ((operation & (MYFLOCK_OP_BITS)) != operation)
+ msg_panic("myflock: improper operation type: 0x%x", operation);
+
+ switch (lock_style) {
+
+ /*
+ * flock() does exactly what we need. Too bad it is not standard.
+ */
+#ifdef HAS_FLOCK_LOCK
+ case MYFLOCK_STYLE_FLOCK:
+ {
+ static int lock_ops[] = {
+ LOCK_UN, LOCK_SH, LOCK_EX, -1,
+ -1, LOCK_SH | LOCK_NB, LOCK_EX | LOCK_NB, -1
+ };
+
+ while ((status = flock(fd, lock_ops[operation])) < 0
+ && errno == EINTR)
+ sleep(1);
+ break;
+ }
+#endif
+
+ /*
+ * fcntl() is standard and does more than we need, but we can handle
+ * it.
+ */
+#ifdef HAS_FCNTL_LOCK
+ case MYFLOCK_STYLE_FCNTL:
+ {
+ struct flock lock;
+ int request;
+ static int lock_ops[] = {
+ F_UNLCK, F_RDLCK, F_WRLCK
+ };
+
+ memset((void *) &lock, 0, sizeof(lock));
+ lock.l_type = lock_ops[operation & ~MYFLOCK_OP_NOWAIT];
+ request = (operation & MYFLOCK_OP_NOWAIT) ? F_SETLK : F_SETLKW;
+ while ((status = fcntl(fd, request, &lock)) < 0
+ && errno == EINTR)
+ sleep(1);
+ break;
+ }
+#endif
+ default:
+ msg_panic("myflock: unsupported lock style: 0x%x", lock_style);
+ }
+
+ /*
+ * Return a consistent result. Some systems return EACCES when a lock is
+ * taken by someone else, and that would complicate error processing.
+ */
+ if (status < 0 && (operation & MYFLOCK_OP_NOWAIT) != 0)
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EACCES)
+ errno = EAGAIN;
+
+ return (status);
+}
diff --git a/src/util/myflock.h b/src/util/myflock.h
new file mode 100644
index 0000000..ee18bdb
--- /dev/null
+++ b/src/util/myflock.h
@@ -0,0 +1,52 @@
+#ifndef _MYFLOCK_H_INCLUDED_
+#define _MYFLOCK_H_INCLUDED_
+
+/*++
+/* NAME
+/* myflock 3h
+/* SUMMARY
+/* lock open file
+/* SYNOPSIS
+/* #include <myflock.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern int WARN_UNUSED_RESULT myflock(int, int, int);
+
+ /*
+ * Lock styles.
+ */
+#define MYFLOCK_STYLE_FLOCK 1
+#define MYFLOCK_STYLE_FCNTL 2
+
+ /*
+ * Lock request types.
+ */
+#define MYFLOCK_OP_NONE 0
+#define MYFLOCK_OP_SHARED 1
+#define MYFLOCK_OP_EXCLUSIVE 2
+#define MYFLOCK_OP_NOWAIT 4
+
+#define MYFLOCK_OP_BITS \
+ (MYFLOCK_OP_SHARED | MYFLOCK_OP_EXCLUSIVE | MYFLOCK_OP_NOWAIT)
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/mymalloc.c b/src/util/mymalloc.c
new file mode 100644
index 0000000..94f7bb3
--- /dev/null
+++ b/src/util/mymalloc.c
@@ -0,0 +1,269 @@
+/*++
+/* NAME
+/* mymalloc 3
+/* SUMMARY
+/* memory management wrappers
+/* SYNOPSIS
+/* #include <mymalloc.h>
+/*
+/* void *mymalloc(len)
+/* ssize_t len;
+/*
+/* void *myrealloc(ptr, len)
+/* void *ptr;
+/* ssize_t len;
+/*
+/* void myfree(ptr)
+/* void *ptr;
+/*
+/* char *mystrdup(str)
+/* const char *str;
+/*
+/* char *mystrndup(str, len)
+/* const char *str;
+/* ssize_t len;
+/*
+/* void *mymemdup(ptr, len)
+/* const void *ptr;
+/* ssize_t len;
+/* DESCRIPTION
+/* This module performs low-level memory management with error
+/* handling. A call of these functions either succeeds or it does
+/* not return at all.
+/*
+/* To save memory, zero-length strings are shared and read-only.
+/* The caller must not attempt to modify the null terminator.
+/* This code is enabled unless NO_SHARED_EMPTY_STRINGS is
+/* defined at compile time (for example, you have an sscanf()
+/* routine that pushes characters back into its input).
+/*
+/* mymalloc() allocates the requested amount of memory. The memory
+/* is not set to zero.
+/*
+/* myrealloc() resizes memory obtained from mymalloc() or myrealloc()
+/* to the requested size. The result pointer value may differ from
+/* that given via the \fIptr\fR argument.
+/*
+/* myfree() takes memory obtained from mymalloc() or myrealloc()
+/* and makes it available for other use.
+/*
+/* mystrdup() returns a dynamic-memory copy of its null-terminated
+/* argument. This routine uses mymalloc().
+/*
+/* mystrndup() returns a dynamic-memory copy of at most \fIlen\fR
+/* leading characters of its null-terminated
+/* argument. The result is null-terminated. This routine uses mymalloc().
+/*
+/* mymemdup() makes a copy of the memory pointed to by \fIptr\fR
+/* with length \fIlen\fR. The result is NOT null-terminated.
+/* This routine uses mymalloc().
+/* SEE ALSO
+/* msg(3) diagnostics interface
+/* DIAGNOSTICS
+/* Problems are reported via the msg(3) diagnostics routines:
+/* the requested amount of memory is not available; improper use
+/* is detected; other fatal errors.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System libraries. */
+
+#include "sys_defs.h"
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+
+/* Application-specific. */
+
+#include "msg.h"
+#include "mymalloc.h"
+
+ /*
+ * Structure of an annotated memory block. In order to detect spurious
+ * free() calls we prepend a signature to memory given to the application.
+ * In order to detect access to free()d blocks, overwrite each block as soon
+ * as it is passed to myfree(). With the code below, the user data has
+ * integer alignment or better.
+ */
+typedef struct MBLOCK {
+ int signature; /* set when block is active */
+ ssize_t length; /* user requested length */
+ union {
+ ALIGN_TYPE align;
+ char payload[1]; /* actually a bunch of bytes */
+ } u;
+} MBLOCK;
+
+#define SIGNATURE 0xdead
+#define FILLER 0xff
+
+#define CHECK_IN_PTR(ptr, real_ptr, len, fname) { \
+ if (ptr == 0) \
+ msg_panic("%s: null pointer input", fname); \
+ real_ptr = (MBLOCK *) (ptr - offsetof(MBLOCK, u.payload[0])); \
+ if (real_ptr->signature != SIGNATURE) \
+ msg_panic("%s: corrupt or unallocated memory block", fname); \
+ real_ptr->signature = 0; \
+ if ((len = real_ptr->length) < 1) \
+ msg_panic("%s: corrupt memory block length", fname); \
+}
+
+#define CHECK_OUT_PTR(ptr, real_ptr, len) { \
+ real_ptr->signature = SIGNATURE; \
+ real_ptr->length = len; \
+ ptr = real_ptr->u.payload; \
+}
+
+#define SPACE_FOR(len) (offsetof(MBLOCK, u.payload[0]) + len)
+
+ /*
+ * Optimization for short strings. We share one copy with multiple callers.
+ * This differs from normal heap memory in two ways, because the memory is
+ * shared:
+ *
+ * - It must be read-only to avoid horrible bugs. This is OK because there is
+ * no legitimate reason to modify the null terminator.
+ *
+ * - myfree() cannot overwrite the memory with a filler pattern like it can do
+ * with heap memory. Therefore, some dangling pointer bugs will be masked.
+ */
+#ifndef NO_SHARED_EMPTY_STRINGS
+static const char empty_string[] = "";
+
+#endif
+
+/* mymalloc - allocate memory or bust */
+
+void *mymalloc(ssize_t len)
+{
+ void *ptr;
+ MBLOCK *real_ptr;
+
+ /*
+ * Note: for safety reasons the request length is a signed type. This
+ * allows us to catch integer overflow problems that weren't already
+ * caught up-stream.
+ */
+ if (len < 1)
+ msg_panic("mymalloc: requested length %ld", (long) len);
+#ifdef MYMALLOC_FUZZ
+ len += MYMALLOC_FUZZ;
+#endif
+ if ((real_ptr = (MBLOCK *) malloc(SPACE_FOR(len))) == 0)
+ msg_fatal("mymalloc: insufficient memory for %ld bytes: %m",
+ (long) len);
+ CHECK_OUT_PTR(ptr, real_ptr, len);
+ memset(ptr, FILLER, len);
+ return (ptr);
+}
+
+/* myrealloc - reallocate memory or bust */
+
+void *myrealloc(void *ptr, ssize_t len)
+{
+ MBLOCK *real_ptr;
+ ssize_t old_len;
+
+#ifndef NO_SHARED_EMPTY_STRINGS
+ if (ptr == empty_string)
+ return (mymalloc(len));
+#endif
+
+ /*
+ * Note: for safety reasons the request length is a signed type. This
+ * allows us to catch integer overflow problems that weren't already
+ * caught up-stream.
+ */
+ if (len < 1)
+ msg_panic("myrealloc: requested length %ld", (long) len);
+#ifdef MYMALLOC_FUZZ
+ len += MYMALLOC_FUZZ;
+#endif
+ CHECK_IN_PTR(ptr, real_ptr, old_len, "myrealloc");
+ if ((real_ptr = (MBLOCK *) realloc((void *) real_ptr, SPACE_FOR(len))) == 0)
+ msg_fatal("myrealloc: insufficient memory for %ld bytes: %m",
+ (long) len);
+ CHECK_OUT_PTR(ptr, real_ptr, len);
+ if (len > old_len)
+ memset(ptr + old_len, FILLER, len - old_len);
+ return (ptr);
+}
+
+/* myfree - release memory */
+
+void myfree(void *ptr)
+{
+ MBLOCK *real_ptr;
+ ssize_t len;
+
+#ifndef NO_SHARED_EMPTY_STRINGS
+ if (ptr != empty_string) {
+#endif
+ CHECK_IN_PTR(ptr, real_ptr, len, "myfree");
+ memset((void *) real_ptr, FILLER, SPACE_FOR(len));
+ free((void *) real_ptr);
+#ifndef NO_SHARED_EMPTY_STRINGS
+ }
+#endif
+}
+
+/* mystrdup - save string to heap */
+
+char *mystrdup(const char *str)
+{
+ size_t len;
+
+ if (str == 0)
+ msg_panic("mystrdup: null pointer argument");
+#ifndef NO_SHARED_EMPTY_STRINGS
+ if (*str == 0)
+ return ((char *) empty_string);
+#endif
+ if ((len = strlen(str) + 1) > SSIZE_T_MAX)
+ msg_panic("mystrdup: string length >= SSIZE_T_MAX");
+ return (memcpy(mymalloc(len), str, len));
+}
+
+/* mystrndup - save substring to heap */
+
+char *mystrndup(const char *str, ssize_t len)
+{
+ char *result;
+ char *cp;
+
+ if (str == 0)
+ msg_panic("mystrndup: null pointer argument");
+ if (len < 0)
+ msg_panic("mystrndup: requested length %ld", (long) len);
+#ifndef NO_SHARED_EMPTY_STRINGS
+ if (*str == 0)
+ return ((char *) empty_string);
+#endif
+ if ((cp = memchr(str, 0, len)) != 0)
+ len = cp - str;
+ result = memcpy(mymalloc(len + 1), str, len);
+ result[len] = 0;
+ return (result);
+}
+
+/* mymemdup - copy memory */
+
+void *mymemdup(const void *ptr, ssize_t len)
+{
+ if (ptr == 0)
+ msg_panic("mymemdup: null pointer argument");
+ return (memcpy(mymalloc(len), ptr, len));
+}
diff --git a/src/util/mymalloc.h b/src/util/mymalloc.h
new file mode 100644
index 0000000..e2190f7
--- /dev/null
+++ b/src/util/mymalloc.h
@@ -0,0 +1,40 @@
+#ifndef _MALLOC_H_INCLUDED_
+#define _MALLOC_H_INCLUDED_
+
+/*++
+/* NAME
+/* mymalloc 3h
+/* SUMMARY
+/* memory management wrappers
+/* SYNOPSIS
+/* #include "mymalloc.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern void *mymalloc(ssize_t);
+extern void *myrealloc(void *, ssize_t);
+extern void myfree(void *);
+extern char *mystrdup(const char *);
+extern char *mystrndup(const char *, ssize_t);
+extern void *mymemdup(const void *, ssize_t);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/myrand.c b/src/util/myrand.c
new file mode 100644
index 0000000..39fcbfd
--- /dev/null
+++ b/src/util/myrand.c
@@ -0,0 +1,63 @@
+/*++
+/* NAME
+/* myrand 3
+/* SUMMARY
+/* rand wrapper
+/* SYNOPSIS
+/* #include <myrand.h>
+/*
+/* void mysrand(seed)
+/* int seed;
+/*
+/* int myrand()
+/* DESCRIPTION
+/* This module implements a wrapper for the portable, pseudo-random
+/* number generator. The wrapper adds automatic initialization.
+/*
+/* mysrand() performs initialization. This call may be skipped.
+/*
+/* myrand() returns a pseudo-random number in the range [0, RAND_MAX].
+/* If mysrand() was not called, it is invoked with the process ID
+/* ex-or-ed with the time of day in seconds.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* WARNING
+/* Do not use this code for generating unpredictable numbers.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+
+/* Utility library. */
+
+#include <myrand.h>
+
+static int myrand_initdone = 0;
+
+/* mysrand - initialize */
+
+void mysrand(int seed)
+{
+ srand(seed);
+ myrand_initdone = 1;
+}
+
+/* myrand - pseudo-random number */
+
+int myrand(void)
+{
+ if (myrand_initdone == 0)
+ mysrand(getpid() ^ time((time_t *) 0));
+ return (rand());
+}
diff --git a/src/util/myrand.h b/src/util/myrand.h
new file mode 100644
index 0000000..4cc88db
--- /dev/null
+++ b/src/util/myrand.h
@@ -0,0 +1,35 @@
+#ifndef _MYRAND_H_INCLUDED_
+#define _MYRAND_H_INCLUDED_
+
+/*++
+/* NAME
+/* myrand 3h
+/* SUMMARY
+/* rand wrapper
+/* SYNOPSIS
+/* #include <myrand.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+#ifndef RAND_MAX
+#define RAND_MAX 0x7fffffff
+#endif
+
+extern void mysrand(int);
+extern int myrand(void);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/mystrtok.c b/src/util/mystrtok.c
new file mode 100644
index 0000000..d5f32b7
--- /dev/null
+++ b/src/util/mystrtok.c
@@ -0,0 +1,347 @@
+/*++
+/* NAME
+/* mystrtok 3
+/* SUMMARY
+/* safe tokenizer
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* char *mystrtok(bufp, delimiters)
+/* char **bufp;
+/* const char *delimiters;
+/*
+/* char *mystrtokq(bufp, delimiters, parens)
+/* char **bufp;
+/* const char *delimiters;
+/* const char *parens;
+/*
+/* char *mystrtokdq(bufp, delimiters)
+/* char **bufp;
+/* const char *delimiters;
+/*
+/* char *mystrtok_cw(bufp, delimiters, blame)
+/* char **bufp;
+/* const char *delimiters;
+/* const char *blame;
+/*
+/* char *mystrtokq_cw(bufp, delimiters, parens, blame)
+/* char **bufp;
+/* const char *delimiters;
+/* const char *parens;
+/* const char *blame;
+/*
+/* char *mystrtokdq_cw(bufp, delimiters, blame)
+/* char **bufp;
+/* const char *delimiters;
+/* const char *blame;
+/* 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.
+/*
+/* mystrtok_cw(), mystrtokq_cw(), and mystrtokdq_cw, log a
+/* warning and return null when the result would look like
+/* comment. The \fBblame\fR argument provides context for
+/* warning messages. Specify a null pointer to disable the
+/* comment check.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <stringops.h>
+
+/* mystrtok_warn - warn for #comment after other text */
+
+static void mystrtok_warn(const char *start, const char *bufp, const char *blame)
+{
+ msg_warn("%s: #comment after other text is not allowed: %s %.20s...",
+ blame, start, bufp);
+}
+
+/* mystrtok - ABI compatibility wrapper */
+
+#undef mystrtok
+
+char *mystrtok(char **src, const char *sep)
+{
+ return (mystrtok_cw(src, sep, (char *) 0));
+}
+
+/* mystrtok - safe tokenizer */
+
+char *mystrtok_cw(char **src, const char *sep, const char *blame)
+{
+ 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;
+
+ if (blame && *start == '#') {
+ mystrtok_warn(start, *src, blame);
+ return (0);
+ } else {
+ return (start);
+ }
+}
+
+/* mystrtokq - ABI compatibility wrapper */
+
+#undef mystrtokq
+
+char *mystrtokq(char **src, const char *sep, const char *parens)
+{
+ return (mystrtokq_cw(src, sep, parens, (char *) 0));
+}
+
+/* mystrtokq_cw - safe tokenizer with quoting support */
+
+char *mystrtokq_cw(char **src, const char *sep, const char *parens,
+ const char *blame)
+{
+ 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;
+
+ if (blame && *start == '#') {
+ mystrtok_warn(start, *src, blame);
+ return (0);
+ } else {
+ return (start);
+ }
+}
+
+/* mystrtokdq - ABI compatibility wrapper */
+
+#undef mystrtokdq
+
+char *mystrtokdq(char **src, const char *sep)
+{
+ return (mystrtokdq_cw(src, sep, (char *) 0));
+}
+
+/* mystrtokdq_cw - safe tokenizer, double quote and backslash support */
+
+char *mystrtokdq_cw(char **src, const char *sep, const char *blame)
+{
+ 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;
+
+ if (blame && start && *start == '#') {
+ mystrtok_warn(start, *src, blame);
+ return (0);
+ } else {
+ 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\""}},
+ {"mystrtok_cw", "#after text"},
+ {"mystrtok_cw", "before-text #after text", {"before-text"}},
+ {"mystrtokq_cw", "#after text"},
+ {"mystrtokq_cw", "{ before text } #after text", "{ before text }"},
+ {"mystrtokdq_cw", "#after text"},
+ {"mystrtokdq_cw", "\"before text\" #after text", {"\"before text\""}},
+};
+
+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 if (strcmp(tp->action, "mystrtok_cw") == 0) {
+ actual = mystrtok_cw(&cp, CHARS_SPACE, "test");
+ } else if (strcmp(tp->action, "mystrtokq_cw") == 0) {
+ actual = mystrtokq_cw(&cp, CHARS_SPACE, CHARS_BRACE, "test");
+ } else if (strcmp(tp->action, "mystrtokdq_cw") == 0) {
+ actual = mystrtokdq_cw(&cp, CHARS_SPACE, "test");
+ } 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..d0ca58a
--- /dev/null
+++ b/src/util/mystrtok.ref
@@ -0,0 +1,48 @@
+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
+unknown: RUN test case 15 mystrtok_cw >#after text<
+unknown: warning: test: #comment after other text is not allowed: #after text...
+unknown: PASS test 15
+unknown: RUN test case 16 mystrtok_cw >before-text #after text<
+unknown: warning: test: #comment after other text is not allowed: #after text...
+unknown: PASS test 16
+unknown: RUN test case 17 mystrtokq_cw >#after text<
+unknown: warning: test: #comment after other text is not allowed: #after text...
+unknown: PASS test 17
+unknown: RUN test case 18 mystrtokq_cw >{ before text } #after text<
+unknown: warning: test: #comment after other text is not allowed: #after text...
+unknown: PASS test 18
+unknown: RUN test case 19 mystrtokdq_cw >#after text<
+unknown: warning: test: #comment after other text is not allowed: #after text...
+unknown: PASS test 19
+unknown: RUN test case 20 mystrtokdq_cw >"before text" #after text<
+unknown: warning: test: #comment after other text is not allowed: #after text...
+unknown: PASS test 20
diff --git a/src/util/name_code.c b/src/util/name_code.c
new file mode 100644
index 0000000..ca6d94a
--- /dev/null
+++ b/src/util/name_code.c
@@ -0,0 +1,91 @@
+/*++
+/* NAME
+/* name_code 3
+/* SUMMARY
+/* name to number table mapping
+/* SYNOPSIS
+/* #include <name_code.h>
+/*
+/* typedef struct {
+/* .in +4
+/* const char *name;
+/* int code;
+/* .in -4
+/* } NAME_CODE;
+/*
+/* int name_code(table, flags, name)
+/* const NAME_CODE *table;
+/* int flags;
+/* const char *name;
+/*
+/* const char *str_name_code(table, code)
+/* const NAME_CODE *table;
+/* int code;
+/* DESCRIPTION
+/* This module does simple name<->number mapping. The process
+/* is controlled by a table of (name, code) values.
+/* The table is terminated with a null pointer and a code that
+/* corresponds to "name not found".
+/*
+/* name_code() looks up the code that corresponds with the name.
+/* The lookup is case insensitive. The flags argument specifies
+/* zero or more of the following:
+/* .IP NAME_CODE_FLAG_STRICT_CASE
+/* String lookups are case sensitive.
+/* .PP
+/* For convenience the constant NAME_CODE_FLAG_NONE requests
+/* no special processing.
+/*
+/* str_name_code() translates a number to its equivalent string.
+/* DIAGNOSTICS
+/* When the search fails, the result is the "name not found" code
+/* or the null pointer, respectively.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <name_code.h>
+
+/* name_code - look up code by name */
+
+int name_code(const NAME_CODE *table, int flags, const char *name)
+{
+ const NAME_CODE *np;
+ int (*lookup) (const char *, const char *);
+
+ if (flags & NAME_CODE_FLAG_STRICT_CASE)
+ lookup = strcmp;
+ else
+ lookup = strcasecmp;
+
+ for (np = table; np->name; np++)
+ if (lookup(name, np->name) == 0)
+ break;
+ return (np->code);
+}
+
+/* str_name_code - look up name by code */
+
+const char *str_name_code(const NAME_CODE *table, int code)
+{
+ const NAME_CODE *np;
+
+ for (np = table; np->name; np++)
+ if (code == np->code)
+ break;
+ return (np->name);
+}
diff --git a/src/util/name_code.h b/src/util/name_code.h
new file mode 100644
index 0000000..e08618d
--- /dev/null
+++ b/src/util/name_code.h
@@ -0,0 +1,39 @@
+#ifndef _NAME_CODE_H_INCLUDED_
+#define _NAME_CODE_H_INCLUDED_
+
+/*++
+/* NAME
+/* name_mask 3h
+/* SUMMARY
+/* name to number table mapping
+/* SYNOPSIS
+/* #include <name_code.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+typedef struct {
+ const char *name;
+ int code;
+} NAME_CODE;
+
+#define NAME_CODE_FLAG_NONE 0
+#define NAME_CODE_FLAG_STRICT_CASE (1<<0)
+
+extern int name_code(const NAME_CODE *, int, const char *);
+extern const char *str_name_code(const NAME_CODE *, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/name_mask.c b/src/util/name_mask.c
new file mode 100644
index 0000000..284d4fa
--- /dev/null
+++ b/src/util/name_mask.c
@@ -0,0 +1,511 @@
+/*++
+/* NAME
+/* name_mask 3
+/* SUMMARY
+/* map names to bit mask
+/* SYNOPSIS
+/* #include <name_mask.h>
+/*
+/* int name_mask(context, table, names)
+/* const char *context;
+/* const NAME_MASK *table;
+/* const char *names;
+/*
+/* long long_name_mask(context, table, names)
+/* const char *context;
+/* const LONG_NAME_MASK *table;
+/* const char *names;
+/*
+/* const char *str_name_mask(context, table, mask)
+/* const char *context;
+/* const NAME_MASK *table;
+/* int mask;
+/*
+/* const char *str_long_name_mask(context, table, mask)
+/* const char *context;
+/* const LONG_NAME_MASK *table;
+/* long mask;
+/*
+/* int name_mask_opt(context, table, names, flags)
+/* const char *context;
+/* const NAME_MASK *table;
+/* const char *names;
+/* int flags;
+/*
+/* long long_name_mask_opt(context, table, names, flags)
+/* const char *context;
+/* const LONG_NAME_MASK *table;
+/* const char *names;
+/* int flags;
+/*
+/* int name_mask_delim_opt(context, table, names, delim, flags)
+/* const char *context;
+/* const NAME_MASK *table;
+/* const char *names;
+/* const char *delim;
+/* int flags;
+/*
+/* long long_name_mask_delim_opt(context, table, names, delim, flags)
+/* const char *context;
+/* const LONG_NAME_MASK *table;
+/* const char *names;
+/* const char *delim;
+/* int flags;
+/*
+/* const char *str_name_mask_opt(buf, context, table, mask, flags)
+/* VSTRING *buf;
+/* const char *context;
+/* const NAME_MASK *table;
+/* int mask;
+/* int flags;
+/*
+/* const char *str_long_name_mask_opt(buf, context, table, mask, flags)
+/* VSTRING *buf;
+/* const char *context;
+/* const LONG_NAME_MASK *table;
+/* long mask;
+/* int flags;
+/* DESCRIPTION
+/* name_mask() takes a null-terminated \fItable\fR with (name, mask)
+/* values and computes the bit-wise OR of the masks that correspond
+/* to the names listed in the \fInames\fR argument, separated by
+/* comma and/or whitespace characters. The "long_" version returns
+/* a "long int" bitmask, rather than an "int" bitmask.
+/*
+/* str_name_mask() translates a mask into its equivalent names.
+/* The result is written to a static buffer that is overwritten
+/* upon each call. The "long_" version converts a "long int"
+/* bitmask, rather than an "int" bitmask.
+/*
+/* name_mask_opt() and str_name_mask_opt() are extended versions
+/* with additional fine control. name_mask_delim_opt() supports
+/* non-default delimiter characters.
+/*
+/* Arguments:
+/* .IP buf
+/* Null pointer or pointer to buffer storage.
+/* .IP context
+/* What kind of names and
+/* masks are being manipulated, in order to make error messages
+/* more understandable. Typically, this would be the name of a
+/* user-configurable parameter.
+/* .IP table
+/* Table with (name, bit mask) pairs.
+/* .IP names
+/* A list of names that is to be converted into a bit mask.
+/* .IP mask
+/* A bit mask.
+/* .IP delim
+/* Delimiter characters to use instead of whitespace and commas.
+/* .IP flags
+/* Bit-wise OR of one or more of the following. Where features
+/* would have conflicting results (e.g., FATAL versus IGNORE),
+/* the feature that takes precedence is described first.
+/*
+/* When converting from string to mask, at least one of the
+/* following must be specified: NAME_MASK_FATAL, NAME_MASK_RETURN,
+/* NAME_MASK_WARN or NAME_MASK_IGNORE.
+/*
+/* When converting from mask to string, at least one of the
+/* following must be specified: NAME_MASK_NUMBER, NAME_MASK_FATAL,
+/* NAME_MASK_RETURN, NAME_MASK_WARN or NAME_MASK_IGNORE.
+/* .RS
+/* .IP NAME_MASK_NUMBER
+/* When converting from string to mask, accept hexadecimal
+/* inputs starting with "0x" followed by hexadecimal digits.
+/* Each hexadecimal input may specify multiple bits. This
+/* feature is ignored for hexadecimal inputs that cannot be
+/* converted (malformed, out of range, etc.).
+/*
+/* When converting from mask to string, represent bits not
+/* defined in \fItable\fR as "0x" followed by hexadecimal
+/* digits. This conversion always succeeds.
+/* .IP NAME_MASK_FATAL
+/* Require that all names listed in \fIname\fR exist in
+/* \fItable\fR or that they can be parsed as a hexadecimal
+/* string, and require that all bits listed in \fImask\fR exist
+/* in \fItable\fR or that they can be converted to hexadecimal
+/* string. Terminate with a fatal run-time error if this
+/* condition is not met. This feature is enabled by default
+/* when calling name_mask() or str_name_mask().
+/* .IP NAME_MASK_RETURN
+/* Require that all names listed in \fIname\fR exist in
+/* \fItable\fR or that they can be parsed as a hexadecimal
+/* string, and require that all bits listed in \fImask\fR exist
+/* in \fItable\fR or that they can be converted to hexadecimal
+/* string. Log a warning, and return 0 (name_mask()) or a
+/* null pointer (str_name_mask()) if this condition is not
+/* met. This feature is not enabled by default when calling
+/* name_mask() or str_name_mask().
+/* .IP NAME_MASK_WARN
+/* Require that all names listed in \fIname\fR exist in
+/* \fItable\fR or that they can be parsed as a hexadecimal
+/* string, and require that all bits listed in \fImask\fR exist
+/* in \fItable\fR or that they can be converted to hexadecimal
+/* string. Log a warning if this condition is not met, continue
+/* processing, and return all valid bits or names. This feature
+/* is not enabled by default when calling name_mask() or
+/* str_name_mask().
+/* .IP NAME_MASK_IGNORE
+/* Silently ignore names listed in \fIname\fR that don't exist
+/* in \fItable\fR and that can't be parsed as a hexadecimal
+/* string, and silently ignore bits listed in \fImask\fR that
+/* don't exist in \fItable\fR and that can't be converted to
+/* hexadecimal string.
+/* .IP NAME_MASK_ANY_CASE
+/* Enable case-insensitive matching.
+/* This feature is not enabled by default when calling name_mask();
+/* it has no effect with str_name_mask().
+/* .IP NAME_MASK_COMMA
+/* Use comma instead of space when converting a mask to string.
+/* .IP NAME_MASK_PIPE
+/* Use "|" instead of space when converting a mask to string.
+/* .RE
+/* The value NAME_MASK_NONE explicitly requests no features,
+/* and NAME_MASK_DEFAULT enables the default options.
+/* DIAGNOSTICS
+/* Fatal: the \fInames\fR argument specifies a name not found in
+/* \fItable\fR, or the \fImask\fR specifies a bit not found in
+/* \fItable\fR.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <name_mask.h>
+#include <vstring.h>
+
+static int hex_to_ulong(char *, unsigned long, unsigned long *);
+
+#define STR(x) vstring_str(x)
+
+/* name_mask_delim_opt - compute mask corresponding to list of names */
+
+int name_mask_delim_opt(const char *context, const NAME_MASK *table,
+ const char *names, const char *delim, int flags)
+{
+ const char *myname = "name_mask";
+ char *saved_names = mystrdup(names);
+ char *bp = saved_names;
+ int result = 0;
+ const NAME_MASK *np;
+ char *name;
+ int (*lookup) (const char *, const char *);
+ unsigned long ulval;
+
+ if ((flags & NAME_MASK_REQUIRED) == 0)
+ msg_panic("%s: missing NAME_MASK_FATAL/RETURN/WARN/IGNORE flag",
+ myname);
+
+ if (flags & NAME_MASK_ANY_CASE)
+ lookup = strcasecmp;
+ else
+ lookup = strcmp;
+
+ /*
+ * Break up the names string, and look up each component in the table. If
+ * the name is found, merge its mask with the result.
+ */
+ while ((name = mystrtok(&bp, delim)) != 0) {
+ for (np = table; /* void */ ; np++) {
+ if (np->name == 0) {
+ if ((flags & NAME_MASK_NUMBER)
+ && hex_to_ulong(name, ~0U, &ulval)) {
+ result |= (unsigned int) ulval;
+ } else if (flags & NAME_MASK_FATAL) {
+ msg_fatal("unknown %s value \"%s\" in \"%s\"",
+ context, name, names);
+ } else if (flags & NAME_MASK_RETURN) {
+ msg_warn("unknown %s value \"%s\" in \"%s\"",
+ context, name, names);
+ myfree(saved_names);
+ return (0);
+ } else if (flags & NAME_MASK_WARN) {
+ msg_warn("unknown %s value \"%s\" in \"%s\"",
+ context, name, names);
+ }
+ break;
+ }
+ if (lookup(name, np->name) == 0) {
+ if (msg_verbose)
+ msg_info("%s: %s", myname, name);
+ result |= np->mask;
+ break;
+ }
+ }
+ }
+ myfree(saved_names);
+ return (result);
+}
+
+/* str_name_mask_opt - mask to string */
+
+const char *str_name_mask_opt(VSTRING *buf, const char *context,
+ const NAME_MASK *table,
+ int mask, int flags)
+{
+ const char *myname = "name_mask";
+ const NAME_MASK *np;
+ ssize_t len;
+ static VSTRING *my_buf = 0;
+ int delim = (flags & NAME_MASK_COMMA ? ',' :
+ (flags & NAME_MASK_PIPE ? '|' : ' '));
+
+ if ((flags & STR_NAME_MASK_REQUIRED) == 0)
+ msg_panic("%s: missing NAME_MASK_NUMBER/FATAL/RETURN/WARN/IGNORE flag",
+ myname);
+
+ if (buf == 0) {
+ if (my_buf == 0)
+ my_buf = vstring_alloc(1);
+ buf = my_buf;
+ }
+ VSTRING_RESET(buf);
+
+ for (np = table; mask != 0; np++) {
+ if (np->name == 0) {
+ if (flags & NAME_MASK_NUMBER) {
+ vstring_sprintf_append(buf, "0x%x%c", mask, delim);
+ } else if (flags & NAME_MASK_FATAL) {
+ msg_fatal("%s: unknown %s bit in mask: 0x%x",
+ myname, context, mask);
+ } else if (flags & NAME_MASK_RETURN) {
+ msg_warn("%s: unknown %s bit in mask: 0x%x",
+ myname, context, mask);
+ return (0);
+ } else if (flags & NAME_MASK_WARN) {
+ msg_warn("%s: unknown %s bit in mask: 0x%x",
+ myname, context, mask);
+ }
+ break;
+ }
+ if (mask & np->mask) {
+ mask &= ~np->mask;
+ vstring_sprintf_append(buf, "%s%c", np->name, delim);
+ }
+ }
+ if ((len = VSTRING_LEN(buf)) > 0)
+ vstring_truncate(buf, len - 1);
+ VSTRING_TERMINATE(buf);
+
+ return (STR(buf));
+}
+
+/* long_name_mask_delim_opt - compute mask corresponding to list of names */
+
+long long_name_mask_delim_opt(const char *context,
+ const LONG_NAME_MASK * table,
+ const char *names, const char *delim,
+ int flags)
+{
+ const char *myname = "name_mask";
+ char *saved_names = mystrdup(names);
+ char *bp = saved_names;
+ long result = 0;
+ const LONG_NAME_MASK *np;
+ char *name;
+ int (*lookup) (const char *, const char *);
+ unsigned long ulval;
+
+ if ((flags & NAME_MASK_REQUIRED) == 0)
+ msg_panic("%s: missing NAME_MASK_FATAL/RETURN/WARN/IGNORE flag",
+ myname);
+
+ if (flags & NAME_MASK_ANY_CASE)
+ lookup = strcasecmp;
+ else
+ lookup = strcmp;
+
+ /*
+ * Break up the names string, and look up each component in the table. If
+ * the name is found, merge its mask with the result.
+ */
+ while ((name = mystrtok(&bp, delim)) != 0) {
+ for (np = table; /* void */ ; np++) {
+ if (np->name == 0) {
+ if ((flags & NAME_MASK_NUMBER)
+ && hex_to_ulong(name, ~0UL, &ulval)) {
+ result |= ulval;
+ } else if (flags & NAME_MASK_FATAL) {
+ msg_fatal("unknown %s value \"%s\" in \"%s\"",
+ context, name, names);
+ } else if (flags & NAME_MASK_RETURN) {
+ msg_warn("unknown %s value \"%s\" in \"%s\"",
+ context, name, names);
+ myfree(saved_names);
+ return (0);
+ } else if (flags & NAME_MASK_WARN) {
+ msg_warn("unknown %s value \"%s\" in \"%s\"",
+ context, name, names);
+ }
+ break;
+ }
+ if (lookup(name, np->name) == 0) {
+ if (msg_verbose)
+ msg_info("%s: %s", myname, name);
+ result |= np->mask;
+ break;
+ }
+ }
+ }
+
+ myfree(saved_names);
+ return (result);
+}
+
+/* str_long_name_mask_opt - mask to string */
+
+const char *str_long_name_mask_opt(VSTRING *buf, const char *context,
+ const LONG_NAME_MASK * table,
+ long mask, int flags)
+{
+ const char *myname = "name_mask";
+ ssize_t len;
+ static VSTRING *my_buf = 0;
+ int delim = (flags & NAME_MASK_COMMA ? ',' :
+ (flags & NAME_MASK_PIPE ? '|' : ' '));
+ const LONG_NAME_MASK *np;
+
+ if ((flags & STR_NAME_MASK_REQUIRED) == 0)
+ msg_panic("%s: missing NAME_MASK_NUMBER/FATAL/RETURN/WARN/IGNORE flag",
+ myname);
+
+ if (buf == 0) {
+ if (my_buf == 0)
+ my_buf = vstring_alloc(1);
+ buf = my_buf;
+ }
+ VSTRING_RESET(buf);
+
+ for (np = table; mask != 0; np++) {
+ if (np->name == 0) {
+ if (flags & NAME_MASK_NUMBER) {
+ vstring_sprintf_append(buf, "0x%lx%c", mask, delim);
+ } else if (flags & NAME_MASK_FATAL) {
+ msg_fatal("%s: unknown %s bit in mask: 0x%lx",
+ myname, context, mask);
+ } else if (flags & NAME_MASK_RETURN) {
+ msg_warn("%s: unknown %s bit in mask: 0x%lx",
+ myname, context, mask);
+ return (0);
+ } else if (flags & NAME_MASK_WARN) {
+ msg_warn("%s: unknown %s bit in mask: 0x%lx",
+ myname, context, mask);
+ }
+ break;
+ }
+ if (mask & np->mask) {
+ mask &= ~np->mask;
+ vstring_sprintf_append(buf, "%s%c", np->name, delim);
+ }
+ }
+ if ((len = VSTRING_LEN(buf)) > 0)
+ vstring_truncate(buf, len - 1);
+ VSTRING_TERMINATE(buf);
+
+ return (STR(buf));
+}
+
+/* hex_to_ulong - 0x... to unsigned long or smaller */
+
+static int hex_to_ulong(char *value, unsigned long mask, unsigned long *ulp)
+{
+ unsigned long result;
+ char *cp;
+
+ if (strncasecmp(value, "0x", 2) != 0)
+ return (0);
+
+ /*
+ * Check for valid hex number. Since the value starts with 0x, strtoul()
+ * will not allow a negative sign before the first nibble. So we don't
+ * need to worry about explicit +/- signs.
+ */
+ errno = 0;
+ result = strtoul(value, &cp, 16);
+ if (*cp != '\0' || errno == ERANGE)
+ return (0);
+
+ *ulp = (result & mask);
+ return (*ulp == result);
+}
+
+#ifdef TEST
+
+ /*
+ * Stand-alone test program.
+ */
+#include <stdlib.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+
+int main(int argc, char **argv)
+{
+ static const NAME_MASK demo_table[] = {
+ "zero", 1 << 0,
+ "one", 1 << 1,
+ "two", 1 << 2,
+ "three", 1 << 3,
+ 0, 0,
+ };
+ static const NAME_MASK feature_table[] = {
+ "DEFAULT", NAME_MASK_DEFAULT,
+ "FATAL", NAME_MASK_FATAL,
+ "ANY_CASE", NAME_MASK_ANY_CASE,
+ "RETURN", NAME_MASK_RETURN,
+ "COMMA", NAME_MASK_COMMA,
+ "PIPE", NAME_MASK_PIPE,
+ "NUMBER", NAME_MASK_NUMBER,
+ "WARN", NAME_MASK_WARN,
+ "IGNORE", NAME_MASK_IGNORE,
+ 0,
+ };
+ int in_feature_mask;
+ int out_feature_mask;
+ int demo_mask;
+ const char *demo_str;
+ VSTRING *out_buf = vstring_alloc(1);
+ VSTRING *in_buf = vstring_alloc(1);
+
+ if (argc != 3)
+ msg_fatal("usage: %s in-feature-mask out-feature-mask", argv[0]);
+ in_feature_mask = name_mask(argv[1], feature_table, argv[1]);
+ out_feature_mask = name_mask(argv[2], feature_table, argv[2]);
+ while (vstring_get_nonl(in_buf, VSTREAM_IN) != VSTREAM_EOF) {
+ demo_mask = name_mask_opt("name", demo_table,
+ STR(in_buf), in_feature_mask);
+ demo_str = str_name_mask_opt(out_buf, "mask", demo_table,
+ demo_mask, out_feature_mask);
+ vstream_printf("%s -> 0x%x -> %s\n",
+ STR(in_buf), demo_mask,
+ demo_str ? demo_str : "(null)");
+ vstream_fflush(VSTREAM_OUT);
+ }
+ vstring_free(in_buf);
+ vstring_free(out_buf);
+ exit(0);
+}
+
+#endif
diff --git a/src/util/name_mask.h b/src/util/name_mask.h
new file mode 100644
index 0000000..05e45ec
--- /dev/null
+++ b/src/util/name_mask.h
@@ -0,0 +1,86 @@
+#ifndef _NAME_MASK_H_INCLUDED_
+#define _NAME_MASK_H_INCLUDED_
+
+/*++
+/* NAME
+/* name_mask 3h
+/* SUMMARY
+/* map names to bit mask
+/* SYNOPSIS
+/* #include <name_mask.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+typedef struct {
+ const char *name;
+ int mask;
+} NAME_MASK;
+
+#define NAME_MASK_FATAL (1<<0)
+#define NAME_MASK_ANY_CASE (1<<1)
+#define NAME_MASK_RETURN (1<<2)
+#define NAME_MASK_COMMA (1<<3)
+#define NAME_MASK_PIPE (1<<4)
+#define NAME_MASK_NUMBER (1<<5)
+#define NAME_MASK_WARN (1<<6)
+#define NAME_MASK_IGNORE (1<<7)
+
+#define NAME_MASK_REQUIRED \
+ (NAME_MASK_FATAL | NAME_MASK_RETURN | NAME_MASK_WARN | NAME_MASK_IGNORE)
+#define STR_NAME_MASK_REQUIRED (NAME_MASK_REQUIRED | NAME_MASK_NUMBER)
+
+#define NAME_MASK_MATCH_REQ NAME_MASK_FATAL
+
+#define NAME_MASK_NONE 0
+#define NAME_MASK_DEFAULT (NAME_MASK_FATAL)
+#define NAME_MASK_DEFAULT_DELIM ", \t\r\n"
+
+#define name_mask_opt(tag, table, str, flags) \
+ name_mask_delim_opt((tag), (table), (str), \
+ NAME_MASK_DEFAULT_DELIM, (flags))
+#define name_mask(tag, table, str) \
+ name_mask_opt((tag), (table), (str), NAME_MASK_DEFAULT)
+#define str_name_mask(tag, table, mask) \
+ str_name_mask_opt(((VSTRING *) 0), (tag), (table), (mask), NAME_MASK_DEFAULT)
+
+extern int name_mask_delim_opt(const char *, const NAME_MASK *, const char *, const char *, int);
+extern const char *str_name_mask_opt(VSTRING *, const char *, const NAME_MASK *, int, int);
+
+ /*
+ * "long" API
+ */
+typedef struct {
+ const char *name;
+ long mask;
+} LONG_NAME_MASK;
+
+#define long_name_mask_opt(tag, table, str, flags) \
+ long_name_mask_delim_opt((tag), (table), (str), NAME_MASK_DEFAULT_DELIM, (flags))
+#define long_name_mask(tag, table, str) \
+ long_name_mask_opt((tag), (table), (str), NAME_MASK_DEFAULT)
+#define str_long_name_mask(tag, table, mask) \
+ str_long_name_mask_opt(((VSTRING *) 0), (tag), (table), (mask), NAME_MASK_DEFAULT)
+
+extern long long_name_mask_delim_opt(const char *, const LONG_NAME_MASK *, const char *, const char *, int);
+extern const char *str_long_name_mask_opt(VSTRING *, const char *, const LONG_NAME_MASK *, long, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/name_mask.in b/src/util/name_mask.in
new file mode 100644
index 0000000..d4c9b16
--- /dev/null
+++ b/src/util/name_mask.in
@@ -0,0 +1,9 @@
+zero
+one
+two
+three
+four
+zero one two three four
+0xff
+0xffffffff
+0xffffffffffffffff
diff --git a/src/util/name_mask.ref0 b/src/util/name_mask.ref0
new file mode 100644
index 0000000..603dc26
--- /dev/null
+++ b/src/util/name_mask.ref0
@@ -0,0 +1,9 @@
+zero -> 0x1 -> zero
+one -> 0x2 -> one
+two -> 0x4 -> two
+three -> 0x8 -> three
+four -> 0x0 ->
+zero one two three four -> 0xf -> zero one two three
+0xff -> 0x0 ->
+0xffffffff -> 0x0 ->
+0xffffffffffffffff -> 0x0 ->
diff --git a/src/util/name_mask.ref1 b/src/util/name_mask.ref1
new file mode 100644
index 0000000..1ae0fca
--- /dev/null
+++ b/src/util/name_mask.ref1
@@ -0,0 +1,12 @@
+zero -> 0x1 -> zero
+one -> 0x2 -> one
+two -> 0x4 -> two
+three -> 0x8 -> three
+unknown: warning: unknown name value "four" in "four"
+four -> 0x0 ->
+unknown: warning: unknown name value "four" in "zero one two three four"
+zero one two three four -> 0xf -> zero one two three
+0xff -> 0xff -> zero one two three 0xf0
+0xffffffff -> 0xffffffff -> zero one two three 0xfffffff0
+unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff"
+0xffffffffffffffff -> 0x0 ->
diff --git a/src/util/name_mask.ref2 b/src/util/name_mask.ref2
new file mode 100644
index 0000000..1ac716a
--- /dev/null
+++ b/src/util/name_mask.ref2
@@ -0,0 +1,12 @@
+zero -> 0x1 -> zero
+one -> 0x2 -> one
+two -> 0x4 -> two
+three -> 0x8 -> three
+unknown: warning: unknown name value "four" in "four"
+four -> 0x0 ->
+unknown: warning: unknown name value "four" in "zero one two three four"
+zero one two three four -> 0x0 ->
+0xff -> 0xff -> zero one two three 0xf0
+0xffffffff -> 0xffffffff -> zero one two three 0xfffffff0
+unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff"
+0xffffffffffffffff -> 0x0 ->
diff --git a/src/util/name_mask.ref3 b/src/util/name_mask.ref3
new file mode 100644
index 0000000..82e8850
--- /dev/null
+++ b/src/util/name_mask.ref3
@@ -0,0 +1,14 @@
+zero -> 0x1 -> zero
+one -> 0x2 -> one
+two -> 0x4 -> two
+three -> 0x8 -> three
+unknown: warning: unknown name value "four" in "four"
+four -> 0x0 ->
+unknown: warning: unknown name value "four" in "zero one two three four"
+zero one two three four -> 0xf -> zero one two three
+unknown: warning: unknown name value "0xff" in "0xff"
+0xff -> 0x0 ->
+unknown: warning: unknown name value "0xffffffff" in "0xffffffff"
+0xffffffff -> 0x0 ->
+unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff"
+0xffffffffffffffff -> 0x0 ->
diff --git a/src/util/name_mask.ref4 b/src/util/name_mask.ref4
new file mode 100644
index 0000000..4ec996b
--- /dev/null
+++ b/src/util/name_mask.ref4
@@ -0,0 +1,14 @@
+zero -> 0x1 -> zero
+one -> 0x2 -> one
+two -> 0x4 -> two
+three -> 0x8 -> three
+unknown: warning: unknown name value "four" in "four"
+four -> 0x0 ->
+unknown: warning: unknown name value "four" in "zero one two three four"
+zero one two three four -> 0x0 ->
+unknown: warning: unknown name value "0xff" in "0xff"
+0xff -> 0x0 ->
+unknown: warning: unknown name value "0xffffffff" in "0xffffffff"
+0xffffffff -> 0x0 ->
+unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff"
+0xffffffffffffffff -> 0x0 ->
diff --git a/src/util/name_mask.ref5 b/src/util/name_mask.ref5
new file mode 100644
index 0000000..68c1c30
--- /dev/null
+++ b/src/util/name_mask.ref5
@@ -0,0 +1,14 @@
+zero -> 0x1 -> zero
+one -> 0x2 -> one
+two -> 0x4 -> two
+three -> 0x8 -> three
+unknown: warning: unknown name value "four" in "four"
+four -> 0x0 ->
+unknown: warning: unknown name value "four" in "zero one two three four"
+zero one two three four -> 0xf -> zero one two three
+unknown: warning: name_mask: unknown mask bit in mask: 0xf0
+0xff -> 0xff -> (null)
+unknown: warning: name_mask: unknown mask bit in mask: 0xfffffff0
+0xffffffff -> 0xffffffff -> (null)
+unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff"
+0xffffffffffffffff -> 0x0 ->
diff --git a/src/util/name_mask.ref6 b/src/util/name_mask.ref6
new file mode 100644
index 0000000..c86a532
--- /dev/null
+++ b/src/util/name_mask.ref6
@@ -0,0 +1,14 @@
+zero -> 0x1 -> zero
+one -> 0x2 -> one
+two -> 0x4 -> two
+three -> 0x8 -> three
+unknown: warning: unknown name value "four" in "four"
+four -> 0x0 ->
+unknown: warning: unknown name value "four" in "zero one two three four"
+zero one two three four -> 0xf -> zero one two three
+unknown: warning: name_mask: unknown mask bit in mask: 0xf0
+0xff -> 0xff -> zero one two three
+unknown: warning: name_mask: unknown mask bit in mask: 0xfffffff0
+0xffffffff -> 0xffffffff -> zero one two three
+unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff"
+0xffffffffffffffff -> 0x0 ->
diff --git a/src/util/name_mask.ref7 b/src/util/name_mask.ref7
new file mode 100644
index 0000000..156d7aa
--- /dev/null
+++ b/src/util/name_mask.ref7
@@ -0,0 +1,12 @@
+zero -> 0x1 -> zero
+one -> 0x2 -> one
+two -> 0x4 -> two
+three -> 0x8 -> three
+unknown: warning: unknown name value "four" in "four"
+four -> 0x0 ->
+unknown: warning: unknown name value "four" in "zero one two three four"
+zero one two three four -> 0xf -> zero one two three
+0xff -> 0xff -> zero one two three
+0xffffffff -> 0xffffffff -> zero one two three
+unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff"
+0xffffffffffffffff -> 0x0 ->
diff --git a/src/util/name_mask.ref8 b/src/util/name_mask.ref8
new file mode 100644
index 0000000..7463fb7
--- /dev/null
+++ b/src/util/name_mask.ref8
@@ -0,0 +1,12 @@
+zero -> 0x1 -> zero
+one -> 0x2 -> one
+two -> 0x4 -> two
+three -> 0x8 -> three
+unknown: warning: unknown name value "four" in "four"
+four -> 0x0 ->
+unknown: warning: unknown name value "four" in "zero one two three four"
+zero one two three four -> 0xf -> zero,one,two,three
+0xff -> 0xff -> zero,one,two,three,0xf0
+0xffffffff -> 0xffffffff -> zero,one,two,three,0xfffffff0
+unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff"
+0xffffffffffffffff -> 0x0 ->
diff --git a/src/util/name_mask.ref9 b/src/util/name_mask.ref9
new file mode 100644
index 0000000..e50a73a
--- /dev/null
+++ b/src/util/name_mask.ref9
@@ -0,0 +1,12 @@
+zero -> 0x1 -> zero
+one -> 0x2 -> one
+two -> 0x4 -> two
+three -> 0x8 -> three
+unknown: warning: unknown name value "four" in "four"
+four -> 0x0 ->
+unknown: warning: unknown name value "four" in "zero one two three four"
+zero one two three four -> 0xf -> zero|one|two|three
+0xff -> 0xff -> zero|one|two|three|0xf0
+0xffffffff -> 0xffffffff -> zero|one|two|three|0xfffffff0
+unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff"
+0xffffffffffffffff -> 0x0 ->
diff --git a/src/util/nbbio.c b/src/util/nbbio.c
new file mode 100644
index 0000000..e9ccc38
--- /dev/null
+++ b/src/util/nbbio.c
@@ -0,0 +1,382 @@
+/*++
+/* NAME
+/* nbbio 3
+/* SUMMARY
+/* non-blocking buffered I/O
+/* SYNOPSIS
+/* #include <nbbio.h>
+/*
+/* NBBIO *nbbio_create(fd, bufsize, label, action, context)
+/* int fd;
+/* ssize_t bufsize;
+/* const char *label;
+/* void (*action)(int event, void *context);
+/* char *context;
+/*
+/* void nbbio_free(np)
+/* NBBIO *np;
+/*
+/* void nbbio_enable_read(np, timeout)
+/* NBBIO *np;
+/* int timeout;
+/*
+/* void nbbio_enable_write(np, timeout)
+/* NBBIO *np;
+/* int timeout;
+/*
+/* void nbbio_disable_readwrite(np)
+/* NBBIO *np;
+/*
+/* void nbbio_slumber(np, timeout)
+/* NBBIO *np;
+/* int timeout;
+/*
+/* int NBBIO_ACTIVE_FLAGS(np)
+/* NBBIO *np;
+/*
+/* int NBBIO_ERROR_FLAGS(np)
+/* NBBIO *np;
+/*
+/* const ssize_t NBBIO_BUFSIZE(np)
+/* NBBIO *np;
+/*
+/* ssize_t NBBIO_READ_PEND(np)
+/* NBBIO *np;
+/*
+/* char *NBBIO_READ_BUF(np)
+/* NBBIO *np;
+/*
+/* const ssize_t NBBIO_WRITE_PEND(np)
+/* NBBIO *np;
+/*
+/* char *NBBIO_WRITE_BUF(np)
+/* NBBIO *np;
+/* DESCRIPTION
+/* This module implements low-level support for event-driven
+/* I/O on a full-duplex stream. Read/write events are handled
+/* by pseudothreads that run under control by the events(5)
+/* module. After each I/O operation, the application is
+/* notified via a call-back routine.
+/*
+/* It is up to the call-back routine to turn on/off read/write
+/* events as appropriate. It is an error to leave read events
+/* enabled for a buffer that is full, or to leave write events
+/* enabled for a buffer that is empty.
+/*
+/* nbbio_create() creates a pair of buffers of the named size
+/* for the named stream. The label specifies the purpose of
+/* the stream, and is used for diagnostic messages. The
+/* nbbio(3) event handler invokes the application call-back
+/* routine with the current event type (EVENT_READ etc.) and
+/* with the application-specified context.
+/*
+/* nbbio_free() terminates any pseudothreads associated with
+/* the named buffer pair, closes the stream, and destroys the
+/* buffer pair.
+/*
+/* nbbio_enable_read() enables a read pseudothread (if one
+/* does not already exist) for the named buffer pair, and
+/* (re)starts the buffer pair's timer. It is an error to enable
+/* a read pseudothread while the read buffer is full, or while
+/* a write pseudothread is still enabled.
+/*
+/* nbbio_enable_write() enables a write pseudothread (if one
+/* does not already exist) for the named buffer pair, and
+/* (re)starts the buffer pair's timer. It is an error to enable
+/* a write pseudothread while the write buffer is empty, or
+/* while a read pseudothread is still enabled.
+/*
+/* nbbio_disable_readwrite() disables any read/write pseudothreads
+/* for the named buffer pair, including timeouts. To ensure
+/* buffer liveness, use nbbio_slumber() instead of
+/* nbbio_disable_readwrite(). It is no error to call this
+/* function while no read/write pseudothread is enabled.
+/*
+/* nbbio_slumber() disables any read/write pseudothreads for
+/* the named buffer pair, but keeps the timer active to ensure
+/* buffer liveness. It is no error to call this function while
+/* no read/write pseudothread is enabled.
+/*
+/* NBBIO_ERROR_FLAGS() returns the error flags for the named buffer
+/* pair: zero or more of NBBIO_FLAG_EOF (read EOF), NBBIO_FLAG_ERROR
+/* (read/write error) or NBBIO_FLAG_TIMEOUT (time limit
+/* exceeded).
+/*
+/* NBBIO_ACTIVE_FLAGS() returns the pseudothread flags for the
+/* named buffer pair: NBBIO_FLAG_READ (read pseudothread is
+/* active), NBBIO_FLAG_WRITE (write pseudothread is active),
+/* or zero (no pseudothread is active).
+/*
+/* NBBIO_WRITE_PEND() and NBBIO_WRITE_BUF() evaluate to the
+/* number of to-be-written bytes and the write buffer for the
+/* named buffer pair. NBBIO_WRITE_PEND() must be updated by
+/* the application code that fills the write buffer; no more
+/* than NBBIO_BUFSIZE() bytes may be filled.
+/*
+/* NBBIO_READ_PEND() and NBBIO_READ_BUF() evaluate to the
+/* number of unread bytes and the read buffer for the named
+/* buffer pair. NBBIO_READ_PEND() and NBBIO_READ_BUF() must
+/* be updated by the application code that drains the read
+/* buffer.
+/* SEE ALSO
+/* events(3) event manager
+/* DIAGNOSTICS
+/* Panic: interface violation.
+/*
+/* Fatal: out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h> /* memmove() */
+
+ /*
+ * Utility library.
+ */
+#include <mymalloc.h>
+#include <msg.h>
+#include <events.h>
+#include <nbbio.h>
+
+/* nbbio_event - non-blocking event handler */
+
+static void nbbio_event(int event, void *context)
+{
+ const char *myname = "nbbio_event";
+ NBBIO *np = (NBBIO *) context;
+ ssize_t count;
+
+ switch (event) {
+
+ /*
+ * Read data into the read buffer. Leave it up to the application to
+ * drain the buffer until it is empty.
+ */
+ case EVENT_READ:
+ if (np->read_pend == np->bufsize)
+ msg_panic("%s: socket fd=%d: read buffer is full",
+ myname, np->fd);
+ if (np->read_pend < 0 || np->read_pend > np->bufsize)
+ msg_panic("%s: socket fd=%d: bad pending read count %ld",
+ myname, np->fd, (long) np->read_pend);
+ count = read(np->fd, np->read_buf + np->read_pend,
+ np->bufsize - np->read_pend);
+ if (count > 0) {
+ np->read_pend += count;
+ if (msg_verbose)
+ msg_info("%s: read %ld on %s fd=%d",
+ myname, (long) count, np->label, np->fd);
+ } else if (count == 0) {
+ np->flags |= NBBIO_FLAG_EOF;
+ if (msg_verbose)
+ msg_info("%s: read EOF on %s fd=%d",
+ myname, np->label, np->fd);
+ } else {
+ if (errno == EAGAIN)
+ msg_warn("%s: read() returns EAGAIN on readable descriptor",
+ myname);
+ np->flags |= NBBIO_FLAG_ERROR;
+ if (msg_verbose)
+ msg_info("%s: read %s fd=%d: %m", myname, np->label, np->fd);
+ }
+ break;
+
+ /*
+ * Drain data from the output buffer. Notify the application
+ * whenever some bytes are written.
+ *
+ * XXX Enforce a total time limit to ensure liveness when a hostile
+ * receiver sets a very small TCP window size.
+ */
+ case EVENT_WRITE:
+ if (np->write_pend == 0)
+ msg_panic("%s: socket fd=%d: empty write buffer", myname, np->fd);
+ if (np->write_pend < 0 || np->write_pend > np->bufsize)
+ msg_panic("%s: socket fd=%d: bad pending write count %ld",
+ myname, np->fd, (long) np->write_pend);
+ count = write(np->fd, np->write_buf, np->write_pend);
+ if (count > 0) {
+ np->write_pend -= count;
+ if (np->write_pend > 0)
+ memmove(np->write_buf, np->write_buf + count, np->write_pend);
+ } else {
+ if (errno == EAGAIN)
+ msg_warn("%s: write() returns EAGAIN on writable descriptor",
+ myname);
+ np->flags |= NBBIO_FLAG_ERROR;
+ if (msg_verbose)
+ msg_info("%s: write %s fd=%d: %m", myname, np->label, np->fd);
+ }
+ break;
+
+ /*
+ * Something bad happened.
+ */
+ case EVENT_XCPT:
+ np->flags |= NBBIO_FLAG_ERROR;
+ if (msg_verbose)
+ msg_info("%s: error on %s fd=%d: %m", myname, np->label, np->fd);
+ break;
+
+ /*
+ * Something good didn't happen.
+ */
+ case EVENT_TIME:
+ np->flags |= NBBIO_FLAG_TIMEOUT;
+ if (msg_verbose)
+ msg_info("%s: %s timeout on %s fd=%d",
+ myname, NBBIO_OP_NAME(np), np->label, np->fd);
+ break;
+
+ default:
+ msg_panic("%s: unknown event %d", myname, event);
+ }
+
+ /*
+ * Application notification. The application will check for any error
+ * flags, copy application data from or to our buffer pair, and decide
+ * what I/O happens next.
+ */
+ np->action(event, np->context);
+}
+
+/* nbbio_enable_read - enable reading from socket into buffer */
+
+void nbbio_enable_read(NBBIO *np, int timeout)
+{
+ const char *myname = "nbbio_enable_read";
+
+ /*
+ * Sanity checks.
+ */
+ if (np->flags & (NBBIO_MASK_ACTIVE & ~NBBIO_FLAG_READ))
+ msg_panic("%s: socket fd=%d is enabled for %s",
+ myname, np->fd, NBBIO_OP_NAME(np));
+ if (timeout <= 0)
+ msg_panic("%s: socket fd=%d: bad timeout %d",
+ myname, np->fd, timeout);
+ if (np->read_pend >= np->bufsize)
+ msg_panic("%s: socket fd=%d: read buffer is full",
+ myname, np->fd);
+
+ /*
+ * Enable events.
+ */
+ if ((np->flags & NBBIO_FLAG_READ) == 0) {
+ event_enable_read(np->fd, nbbio_event, (void *) np);
+ np->flags |= NBBIO_FLAG_READ;
+ }
+ event_request_timer(nbbio_event, (void *) np, timeout);
+}
+
+/* nbbio_enable_write - enable writing from buffer to socket */
+
+void nbbio_enable_write(NBBIO *np, int timeout)
+{
+ const char *myname = "nbbio_enable_write";
+
+ /*
+ * Sanity checks.
+ */
+ if (np->flags & (NBBIO_MASK_ACTIVE & ~NBBIO_FLAG_WRITE))
+ msg_panic("%s: socket fd=%d is enabled for %s",
+ myname, np->fd, NBBIO_OP_NAME(np));
+ if (timeout <= 0)
+ msg_panic("%s: socket fd=%d: bad timeout %d",
+ myname, np->fd, timeout);
+ if (np->write_pend <= 0)
+ msg_panic("%s: socket fd=%d: empty write buffer",
+ myname, np->fd);
+
+ /*
+ * Enable events.
+ */
+ if ((np->flags & NBBIO_FLAG_WRITE) == 0) {
+ event_enable_write(np->fd, nbbio_event, (void *) np);
+ np->flags |= NBBIO_FLAG_WRITE;
+ }
+ event_request_timer(nbbio_event, (void *) np, timeout);
+}
+
+/* nbbio_disable_readwrite - disable read/write/timer events */
+
+void nbbio_disable_readwrite(NBBIO *np)
+{
+ np->flags &= ~NBBIO_MASK_ACTIVE;
+ event_disable_readwrite(np->fd);
+ event_cancel_timer(nbbio_event, (void *) np);
+}
+
+/* nbbio_slumber - disable read/write events, keep timer */
+
+void nbbio_slumber(NBBIO *np, int timeout)
+{
+ np->flags &= ~NBBIO_MASK_ACTIVE;
+ event_disable_readwrite(np->fd);
+ event_request_timer(nbbio_event, (void *) np, timeout);
+}
+
+/* nbbio_create - create socket buffer */
+
+NBBIO *nbbio_create(int fd, ssize_t bufsize, const char *label,
+ NBBIO_ACTION action, void *context)
+{
+ NBBIO *np;
+
+ /*
+ * Sanity checks.
+ */
+ if (fd < 0)
+ msg_panic("nbbio_create: bad file descriptor: %d", fd);
+ if (bufsize <= 0)
+ msg_panic("nbbio_create: bad buffer size: %ld", (long) bufsize);
+
+ /*
+ * Create a new buffer pair.
+ */
+ np = (NBBIO *) mymalloc(sizeof(*np));
+ np->fd = fd;
+ np->bufsize = bufsize;
+ np->label = mystrdup(label);
+ np->action = action;
+ np->context = context;
+ np->flags = 0;
+
+ np->read_buf = mymalloc(bufsize);
+ np->read_pend = 0;
+
+ np->write_buf = mymalloc(bufsize);
+ np->write_pend = 0;
+
+ return (np);
+}
+
+/* nbbio_free - destroy socket buffer */
+
+void nbbio_free(NBBIO *np)
+{
+ nbbio_disable_readwrite(np);
+ (void) close(np->fd);
+ myfree(np->label);
+ myfree(np->read_buf);
+ myfree(np->write_buf);
+ myfree((void *) np);
+}
diff --git a/src/util/nbbio.h b/src/util/nbbio.h
new file mode 100644
index 0000000..d1d2b0e
--- /dev/null
+++ b/src/util/nbbio.h
@@ -0,0 +1,85 @@
+#ifndef _NBBIO_H_INCLUDED_
+#define _NBBIO_H_INCLUDED_
+
+/*++
+/* NAME
+/* nbbio 3h
+/* SUMMARY
+/* non-blocking buffered I/O
+/* SYNOPSIS
+/* #include "nbbio.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <events.h> /* Needed for EVENT_READ etc. */
+
+ /*
+ * External interface. All structure members are private.
+ */
+typedef void (*NBBIO_ACTION) (int, void *);
+
+typedef struct {
+ int fd; /* socket file descriptor */
+ ssize_t bufsize; /* read/write buffer size */
+ char *label; /* diagnostics */
+ NBBIO_ACTION action; /* call-back routine */
+ void *context; /* call-back context */
+ int flags; /* buffer-pair status */
+
+ char *read_buf; /* start of buffer */
+ ssize_t read_pend; /* nr of unread bytes */
+
+ char *write_buf; /* start of buffer */
+ ssize_t write_pend; /* nr of unwritten bytes */
+} NBBIO;
+
+#define NBBIO_FLAG_READ (1<<0)
+#define NBBIO_FLAG_WRITE (1<<1)
+#define NBBIO_FLAG_EOF (1<<2)
+#define NBBIO_FLAG_ERROR (1<<3)
+#define NBBIO_FLAG_TIMEOUT (1<<4)
+
+#define NBBIO_OP_NAME(np) \
+ (((np)->flags & NBBIO_FLAG_READ) ? "read" : \
+ ((np)->flags & NBBIO_FLAG_WRITE) ? "write" : \
+ "unknown")
+
+#define NBBIO_MASK_ACTIVE \
+ (NBBIO_FLAG_READ | NBBIO_FLAG_WRITE)
+
+#define NBBIO_MASK_ERROR \
+ (NBBIO_FLAG_EOF | NBBIO_FLAG_ERROR | NBBIO_FLAG_TIMEOUT)
+
+#define NBBIO_BUFSIZE(np) (((np)->bufsize) + 0) /* Read-only */
+
+#define NBBIO_READ_PEND(np) ((np)->read_pend)
+#define NBBIO_READ_BUF(np) ((np)->read_buf + 0) /* Read-only */
+
+#define NBBIO_WRITE_PEND(np) ((np)->write_pend)
+#define NBBIO_WRITE_BUF(np) ((np)->write_buf + 0) /* Read-only */
+
+#define NBBIO_ACTIVE_FLAGS(np) ((np)->flags & NBBIO_MASK_ACTIVE)
+#define NBBIO_ERROR_FLAGS(np) ((np)->flags & NBBIO_MASK_ERROR)
+
+extern NBBIO *nbbio_create(int, ssize_t, const char *, NBBIO_ACTION, void *);
+extern void nbbio_free(NBBIO *);
+extern void nbbio_enable_read(NBBIO *, int);
+extern void nbbio_enable_write(NBBIO *, int);
+extern void nbbio_disable_readwrite(NBBIO *);
+extern void nbbio_slumber(NBBIO *, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/netstring.c b/src/util/netstring.c
new file mode 100644
index 0000000..0edd80e
--- /dev/null
+++ b/src/util/netstring.c
@@ -0,0 +1,498 @@
+/*++
+/* NAME
+/* netstring 3
+/* SUMMARY
+/* netstring stream I/O support
+/* SYNOPSIS
+/* #include <netstring.h>
+/*
+/* void netstring_setup(stream, timeout)
+/* VSTREAM *stream;
+/* int timeout;
+/*
+/* void netstring_except(stream, exception)
+/* VSTREAM *stream;
+/* int exception;
+/*
+/* const char *netstring_strerror(err)
+/* int err;
+/*
+/* VSTRING *netstring_get(stream, buf, limit)
+/* VSTREAM *stream;
+/* VSTRING *buf;
+/* ssize_t limit;
+/*
+/* void netstring_put(stream, data, len)
+/* VSTREAM *stream;
+/* const char *data;
+/* ssize_t len;
+/*
+/* void netstring_put_multi(stream, data, len, data, len, ..., 0)
+/* VSTREAM *stream;
+/* const char *data;
+/* ssize_t len;
+/*
+/* void NETSTRING_PUT_BUF(stream, buf)
+/* VSTREAM *stream;
+/* VSTRING *buf;
+/*
+/* void netstring_fflush(stream)
+/* VSTREAM *stream;
+/*
+/* VSTRING *netstring_memcpy(buf, data, len)
+/* VSTRING *buf;
+/* const char *data;
+/* ssize_t len;
+/*
+/* VSTRING *netstring_memcat(buf, data, len)
+/* VSTRING *buf;
+/* const char *src;
+/* ssize_t len;
+/* AUXILIARY ROUTINES
+/* ssize_t netstring_get_length(stream)
+/* VSTREAM *stream;
+/*
+/* VSTRING *netstring_get_data(stream, buf, len)
+/* VSTREAM *stream;
+/* VSTRING *buf;
+/* ssize_t len;
+/*
+/* void netstring_get_terminator(stream)
+/* VSTREAM *stream;
+/* DESCRIPTION
+/* This module reads and writes netstrings with error detection:
+/* timeouts, unexpected end-of-file, or format errors. Netstring
+/* is a data format designed by Daniel Bernstein.
+/*
+/* netstring_setup() arranges for a time limit on the netstring
+/* read and write operations described below.
+/* This routine alters the behavior of streams as follows:
+/* .IP \(bu
+/* The read/write timeout is set to the specified value.
+/* .IP \(bu
+/* The stream is configured to enable exception handling.
+/* .PP
+/* netstring_except() raises the specified exception on the
+/* named stream. See the DIAGNOSTICS section below.
+/*
+/* netstring_strerror() converts an exception number to string.
+/*
+/* netstring_get() reads a netstring from the specified stream
+/* and extracts its content. The limit specifies a maximal size.
+/* Specify zero to disable the size limit. The result is not null
+/* terminated. The result value is the buf argument.
+/*
+/* netstring_put() encapsulates the specified string as a netstring
+/* and sends the result to the specified stream.
+/* The stream output buffer is not flushed.
+/*
+/* netstring_put_multi() encapsulates the content of multiple strings
+/* as one netstring and sends the result to the specified stream. The
+/* argument list must be terminated with a null data pointer.
+/* The stream output buffer is not flushed.
+/*
+/* NETSTRING_PUT_BUF() is a macro that provides a VSTRING-based
+/* wrapper for the netstring_put() routine.
+/*
+/* netstring_fflush() flushes the output buffer of the specified
+/* stream and handles any errors.
+/*
+/* netstring_memcpy() encapsulates the specified data as a netstring
+/* and copies the result over the specified buffer. The result
+/* value is the buffer.
+/*
+/* netstring_memcat() encapsulates the specified data as a netstring
+/* and appends the result to the specified buffer. The result
+/* value is the buffer.
+/*
+/* The following routines provide low-level access to a netstring
+/* stream.
+/*
+/* netstring_get_length() reads a length field from the specified
+/* stream, and absorbs the netstring length field terminator.
+/*
+/* netstring_get_data() reads the specified number of bytes from the
+/* specified stream into the specified buffer, and absorbs the
+/* netstring terminator. The result value is the buf argument.
+/*
+/* netstring_get_terminator() reads the netstring terminator from
+/* the specified stream.
+/* DIAGNOSTICS
+/* .fi
+/* .ad
+/* In case of error, a vstream_longjmp() call is performed to the
+/* caller-provided context specified with vstream_setjmp().
+/* Error codes passed along with vstream_longjmp() are:
+/* .IP NETSTRING_ERR_EOF
+/* An I/O error happened, or the peer has disconnected unexpectedly.
+/* .IP NETSTRING_ERR_TIME
+/* The time limit specified to netstring_setup() was exceeded.
+/* .IP NETSTRING_ERR_FORMAT
+/* The input contains an unexpected character value.
+/* .IP NETSTRING_ERR_SIZE
+/* The input is larger than acceptable.
+/* BUGS
+/* The timeout deadline affects all I/O on the named stream, not
+/* just the I/O done on behalf of this module.
+/*
+/* The timeout deadline overwrites any previously set up state on
+/* the named stream.
+/*
+/* netstrings are not null terminated, which makes printing them
+/* a bit awkward.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* SEE ALSO
+/* http://cr.yp.to/proto/netstrings.txt, netstring definition
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdarg.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <compat_va_copy.h>
+#include <netstring.h>
+
+/* Application-specific. */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* netstring_setup - initialize netstring stream */
+
+void netstring_setup(VSTREAM *stream, int timeout)
+{
+ vstream_control(stream,
+ CA_VSTREAM_CTL_TIMEOUT(timeout),
+ CA_VSTREAM_CTL_EXCEPT,
+ CA_VSTREAM_CTL_END);
+}
+
+/* netstring_except - process netstring stream exception */
+
+void netstring_except(VSTREAM *stream, int exception)
+{
+ vstream_longjmp(stream, exception);
+}
+
+/* netstring_get_length - read netstring length + terminator */
+
+ssize_t netstring_get_length(VSTREAM *stream)
+{
+ const char *myname = "netstring_get_length";
+ ssize_t len = 0;
+ int ch;
+ int digit;
+
+ for (;;) {
+ switch (ch = VSTREAM_GETC(stream)) {
+ case VSTREAM_EOF:
+ netstring_except(stream, vstream_ftimeout(stream) ?
+ NETSTRING_ERR_TIME : NETSTRING_ERR_EOF);
+ case ':':
+ if (msg_verbose > 1)
+ msg_info("%s: read netstring length %ld", myname, (long) len);
+ return (len);
+ default:
+ if (!ISDIGIT(ch))
+ netstring_except(stream, NETSTRING_ERR_FORMAT);
+ digit = ch - '0';
+ if (len > SSIZE_T_MAX / 10
+ || (len *= 10) > SSIZE_T_MAX - digit)
+ netstring_except(stream, NETSTRING_ERR_SIZE);
+ len += digit;
+ break;
+ }
+ }
+}
+
+/* netstring_get_data - read netstring payload + terminator */
+
+VSTRING *netstring_get_data(VSTREAM *stream, VSTRING *buf, ssize_t len)
+{
+ const char *myname = "netstring_get_data";
+
+ /*
+ * Read the payload and absorb the terminator.
+ */
+ if (vstream_fread_buf(stream, buf, len) != len)
+ netstring_except(stream, vstream_ftimeout(stream) ?
+ NETSTRING_ERR_TIME : NETSTRING_ERR_EOF);
+ if (msg_verbose > 1)
+ msg_info("%s: read netstring data %.*s",
+ myname, (int) (len < 30 ? len : 30), STR(buf));
+ netstring_get_terminator(stream);
+
+ /*
+ * Return the buffer.
+ */
+ return (buf);
+}
+
+/* netstring_get_terminator - absorb netstring terminator */
+
+void netstring_get_terminator(VSTREAM *stream)
+{
+ if (VSTREAM_GETC(stream) != ',')
+ netstring_except(stream, NETSTRING_ERR_FORMAT);
+}
+
+/* netstring_get - read string from netstring stream */
+
+VSTRING *netstring_get(VSTREAM *stream, VSTRING *buf, ssize_t limit)
+{
+ ssize_t len;
+
+ len = netstring_get_length(stream);
+ if (ENFORCING_SIZE_LIMIT(limit) && len > limit)
+ netstring_except(stream, NETSTRING_ERR_SIZE);
+ netstring_get_data(stream, buf, len);
+ return (buf);
+}
+
+/* netstring_put - send string as netstring */
+
+void netstring_put(VSTREAM *stream, const char *data, ssize_t len)
+{
+ const char *myname = "netstring_put";
+
+ if (msg_verbose > 1)
+ msg_info("%s: write netstring len %ld data %.*s",
+ myname, (long) len, (int) (len < 30 ? len : 30), data);
+ vstream_fprintf(stream, "%ld:", (long) len);
+ vstream_fwrite(stream, data, len);
+ VSTREAM_PUTC(',', stream);
+}
+
+/* netstring_put_multi - send multiple strings as one netstring */
+
+void netstring_put_multi(VSTREAM *stream,...)
+{
+ const char *myname = "netstring_put_multi";
+ ssize_t total;
+ char *data;
+ ssize_t data_len;
+ va_list ap;
+ va_list ap2;
+
+ /*
+ * Initialize argument lists.
+ */
+ va_start(ap, stream);
+ VA_COPY(ap2, ap);
+
+ /*
+ * Figure out the total result size.
+ */
+ for (total = 0; (data = va_arg(ap, char *)) != 0; total += data_len)
+ if ((data_len = va_arg(ap, ssize_t)) < 0)
+ msg_panic("%s: bad data length %ld", myname, (long) data_len);
+ va_end(ap);
+ if (total < 0)
+ msg_panic("%s: bad total length %ld", myname, (long) total);
+ if (msg_verbose > 1)
+ msg_info("%s: write total length %ld", myname, (long) total);
+
+ /*
+ * Send the length, content and terminator.
+ */
+ vstream_fprintf(stream, "%ld:", (long) total);
+ while ((data = va_arg(ap2, char *)) != 0) {
+ data_len = va_arg(ap2, ssize_t);
+ if (msg_verbose > 1)
+ msg_info("%s: write netstring len %ld data %.*s",
+ myname, (long) data_len,
+ (int) (data_len < 30 ? data_len : 30), data);
+ if (vstream_fwrite(stream, data, data_len) != data_len)
+ netstring_except(stream, vstream_ftimeout(stream) ?
+ NETSTRING_ERR_TIME : NETSTRING_ERR_EOF);
+ }
+ va_end(ap2);
+ vstream_fwrite(stream, ",", 1);
+}
+
+/* netstring_fflush - flush netstring stream */
+
+void netstring_fflush(VSTREAM *stream)
+{
+ if (vstream_fflush(stream) == VSTREAM_EOF)
+ netstring_except(stream, vstream_ftimeout(stream) ?
+ NETSTRING_ERR_TIME : NETSTRING_ERR_EOF);
+}
+
+/* netstring_memcpy - copy data as in-memory netstring */
+
+VSTRING *netstring_memcpy(VSTRING *buf, const char *src, ssize_t len)
+{
+ vstring_sprintf(buf, "%ld:", (long) len);
+ vstring_memcat(buf, src, len);
+ VSTRING_ADDCH(buf, ',');
+ return (buf);
+}
+
+/* netstring_memcat - append data as in-memory netstring */
+
+VSTRING *netstring_memcat(VSTRING *buf, const char *src, ssize_t len)
+{
+ vstring_sprintf_append(buf, "%ld:", (long) len);
+ vstring_memcat(buf, src, len);
+ VSTRING_ADDCH(buf, ',');
+ return (buf);
+}
+
+/* netstring_strerror - convert error number to string */
+
+const char *netstring_strerror(int err)
+{
+ switch (err) {
+ case NETSTRING_ERR_EOF:
+ return ("unexpected disconnect");
+ case NETSTRING_ERR_TIME:
+ return ("time limit exceeded");
+ case NETSTRING_ERR_FORMAT:
+ return ("input format error");
+ case NETSTRING_ERR_SIZE:
+ return ("input exceeds size limit");
+ default:
+ return ("unknown netstring error");
+ }
+}
+
+ /*
+ * Proof-of-concept netstring encoder/decoder.
+ *
+ * Usage: netstring command...
+ *
+ * Run the command as a child process. Then, convert between plain strings on
+ * our own stdin/stdout, and netstrings on the child program's stdin/stdout.
+ *
+ * Example (socketmap test server): netstring nc -l 9999
+ */
+#ifdef TEST
+#include <unistd.h>
+#include <stdlib.h>
+#include <events.h>
+
+static VSTRING *stdin_read_buf; /* stdin line buffer */
+static VSTRING *child_read_buf; /* child read buffer */
+static VSTREAM *child_stream; /* child stream (full-duplex) */
+
+/* stdin_read_event - line-oriented event handler */
+
+static void stdin_read_event(int event, void *context)
+{
+ int ch;
+
+ /*
+ * Send a netstring to the child when we have accumulated an entire line
+ * of input.
+ *
+ * Note: the first VSTREAM_GETCHAR() call implicitly fills the VSTREAM
+ * buffer. We must drain the entire VSTREAM buffer before requesting the
+ * next read(2) event.
+ */
+ do {
+ ch = VSTREAM_GETCHAR();
+ switch (ch) {
+ default:
+ VSTRING_ADDCH(stdin_read_buf, ch);
+ break;
+ case '\n':
+ NETSTRING_PUT_BUF(child_stream, stdin_read_buf);
+ vstream_fflush(child_stream);
+ VSTRING_RESET(stdin_read_buf);
+ break;
+ case VSTREAM_EOF:
+ /* Better: wait for child to terminate. */
+ sleep(1);
+ exit(0);
+ }
+ } while (vstream_peek(VSTREAM_IN) > 0);
+}
+
+/* child_read_event - netstring-oriented event handler */
+
+static void child_read_event(int event, void *context)
+{
+
+ /*
+ * Read an entire netstring from the child and send the result to stdout.
+ *
+ * This is a simplistic implementation that assumes a server will not
+ * trickle its data.
+ *
+ * Note: the first netstring_get() call implicitly fills the VSTREAM buffer.
+ * We must drain the entire VSTREAM buffer before requesting the next
+ * read(2) event.
+ */
+ do {
+ netstring_get(child_stream, child_read_buf, 10000);
+ vstream_fwrite(VSTREAM_OUT, STR(child_read_buf), LEN(child_read_buf));
+ VSTREAM_PUTC('\n', VSTREAM_OUT);
+ vstream_fflush(VSTREAM_OUT);
+ } while (vstream_peek(child_stream) > 0);
+}
+
+int main(int argc, char **argv)
+{
+ int err;
+
+ /*
+ * Sanity check.
+ */
+ if (argv[1] == 0)
+ msg_fatal("usage: %s command...", argv[0]);
+
+ /*
+ * Run the specified command as a child process with stdin and stdout
+ * connected to us.
+ */
+ child_stream = vstream_popen(O_RDWR, CA_VSTREAM_POPEN_ARGV(argv + 1),
+ CA_VSTREAM_POPEN_END);
+ vstream_control(child_stream, CA_VSTREAM_CTL_DOUBLE, CA_VSTREAM_CTL_END);
+ netstring_setup(child_stream, 10);
+
+ /*
+ * Buffer plumbing.
+ */
+ stdin_read_buf = vstring_alloc(100);
+ child_read_buf = vstring_alloc(100);
+
+ /*
+ * Monitor both the child's stdout stream and our own stdin stream. If
+ * there is activity on the child stdout stream, read an entire netstring
+ * or EOF. If there is activity on stdin, send a netstring to the child
+ * when we have read an entire line, or terminate in case of EOF.
+ */
+ event_enable_read(vstream_fileno(VSTREAM_IN), stdin_read_event, (void *) 0);
+ event_enable_read(vstream_fileno(child_stream), child_read_event,
+ (void *) 0);
+
+ if ((err = vstream_setjmp(child_stream)) == 0) {
+ for (;;)
+ event_loop(-1);
+ } else {
+ msg_fatal("%s: %s", argv[1], netstring_strerror(err));
+ }
+}
+
+#endif
diff --git a/src/util/netstring.h b/src/util/netstring.h
new file mode 100644
index 0000000..e8f418d
--- /dev/null
+++ b/src/util/netstring.h
@@ -0,0 +1,55 @@
+#ifndef _NETSTRING_H_INCLUDED_
+#define _NETSTRING_H_INCLUDED_
+
+/*++
+/* NAME
+/* netstring 3h
+/* SUMMARY
+/* netstring stream I/O support
+/* SYNOPSIS
+/* #include <netstring.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+#include <vstream.h>
+
+ /*
+ * External interface.
+ */
+#define NETSTRING_ERR_EOF 1 /* unexpected disconnect */
+#define NETSTRING_ERR_TIME 2 /* time out */
+#define NETSTRING_ERR_FORMAT 3 /* format error */
+#define NETSTRING_ERR_SIZE 4 /* netstring too large */
+
+extern void netstring_except(VSTREAM *, int);
+extern void netstring_setup(VSTREAM *, int);
+extern ssize_t netstring_get_length(VSTREAM *);
+extern VSTRING *netstring_get_data(VSTREAM *, VSTRING *, ssize_t);
+extern void netstring_get_terminator(VSTREAM *);
+extern VSTRING *netstring_get(VSTREAM *, VSTRING *, ssize_t);
+extern void netstring_put(VSTREAM *, const char *, ssize_t);
+extern void netstring_put_multi(VSTREAM *,...);
+extern void netstring_fflush(VSTREAM *);
+extern VSTRING *netstring_memcpy(VSTRING *, const char *, ssize_t);
+extern VSTRING *netstring_memcat(VSTRING *, const char *, ssize_t);
+extern const char *netstring_strerror(int);
+
+#define NETSTRING_PUT_BUF(str, buf) \
+ netstring_put((str), vstring_str(buf), VSTRING_LEN(buf))
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/neuter.c b/src/util/neuter.c
new file mode 100644
index 0000000..53576fb
--- /dev/null
+++ b/src/util/neuter.c
@@ -0,0 +1,56 @@
+/*++
+/* NAME
+/* neuter 3
+/* SUMMARY
+/* neutralize characters before they can explode
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* char *neuter(buffer, bad, replacement)
+/* char *buffer;
+/* const char *bad;
+/* int replacement;
+/* DESCRIPTION
+/* neuter() replaces bad characters in its input
+/* by the given replacement.
+/*
+/* Arguments:
+/* .IP buffer
+/* The null-terminated input string.
+/* .IP bad
+/* The null-terminated bad character string.
+/* .IP replacement
+/* Replacement value for characters in \fIbuffer\fR that do not
+/* pass the bad character test.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <stringops.h>
+
+/* neuter - neutralize bad characters */
+
+char *neuter(char *string, const char *bad, int replacement)
+{
+ char *cp;
+ int ch;
+
+ for (cp = string; (ch = *(unsigned char *) cp) != 0; cp++)
+ if (strchr(bad, ch) != 0)
+ *cp = replacement;
+ return (string);
+}
diff --git a/src/util/non_blocking.c b/src/util/non_blocking.c
new file mode 100644
index 0000000..6427cd8
--- /dev/null
+++ b/src/util/non_blocking.c
@@ -0,0 +1,66 @@
+/*++
+/* NAME
+/* non_blocking 3
+/* SUMMARY
+/* set/clear non-blocking flag
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* int non_blocking(int fd, int on)
+/* DESCRIPTION
+/* the \fInon_blocking\fR() function manipulates the non-blocking
+/* flag for the specified open file, and returns the old setting.
+/*
+/* Arguments:
+/* .IP fd
+/* A file descriptor.
+/* .IP on
+/* For non-blocking I/O, specify a non-zero value (or use the
+/* NON_BLOCKING constant); for blocking I/O, specify zero
+/* (or use the BLOCKING constant).
+/*
+/* The result is non-zero when the non-blocking flag was enabled.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System interfaces. */
+
+#include "sys_defs.h"
+#include <fcntl.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "iostuff.h"
+
+/* Backwards compatibility */
+#ifndef O_NONBLOCK
+#define PATTERN FNDELAY
+#else
+#define PATTERN O_NONBLOCK
+#endif
+
+/* non_blocking - set/clear non-blocking flag */
+
+int non_blocking(fd, on)
+int fd;
+int on;
+{
+ int flags;
+
+ if ((flags = fcntl(fd, F_GETFL, 0)) < 0)
+ msg_fatal("fcntl: get flags: %m");
+ if (fcntl(fd, F_SETFL, on ? flags | PATTERN : flags & ~PATTERN) < 0)
+ msg_fatal("fcntl: set non-blocking flag %s: %m", on ? "on" : "off");
+ return ((flags & PATTERN) != 0);
+}
diff --git a/src/util/nvtable.c b/src/util/nvtable.c
new file mode 100644
index 0000000..5238e29
--- /dev/null
+++ b/src/util/nvtable.c
@@ -0,0 +1,122 @@
+/*++
+/* NAME
+/* nvtable 3
+/* SUMMARY
+/* attribute list manager
+/* SYNOPSIS
+/* #include <nvtable.h>
+/*
+/* typedef struct {
+/* .in +4
+/* char *key;
+/* char *value;
+/* /* private fields... */
+/* .in -4
+/* } NVTABLE_INFO;
+/*
+/* NVTABLE *nvtable_create(size)
+/* int size;
+/*
+/* NVTABLE_INFO *nvtable_update(table, key, value)
+/* NVTABLE *table;
+/* const char *key;
+/* const char *value;
+/*
+/* char *nvtable_find(table, key)
+/* NVTABLE *table;
+/* const char *key;
+/*
+/* NVTABLE_INFO *nvtable_locate(table, key)
+/* NVTABLE *table;
+/* const char *key;
+/*
+/* void nvtable_delete(table, key)
+/* NVTABLE *table;
+/* const char *key;
+/*
+/* void nvtable_free(table)
+/* NVTABLE *table;
+/*
+/* void nvtable_walk(table, action, ptr)
+/* NVTABLE *table;
+/* void (*action)(NVTABLE_INFO *, char *ptr);
+/* char *ptr;
+/*
+/* NVTABLE_INFO **nvtable_list(table)
+/* NVTABLE *table;
+/* DESCRIPTION
+/* This module maintains one or more attribute lists. It provides a
+/* more convenient interface than hash tables, although it uses the
+/* same underlying implementation. Each attribute list entry consists
+/* of a unique string-valued lookup key and a string value.
+/*
+/* nvtable_create() creates a table of the specified size and returns a
+/* pointer to the result.
+/*
+/* nvtable_update() stores or updates a (key, value) pair in the specified
+/* table and returns a pointer to the resulting entry. The key and the
+/* value are copied.
+/*
+/* nvtable_find() returns the value that was stored under the given key,
+/* or a null pointer if it was not found. In order to distinguish
+/* a null value from a non-existent value, use nvtable_locate().
+/*
+/* nvtable_locate() returns a pointer to the entry that was stored
+/* for the given key, or a null pointer if it was not found.
+/*
+/* nvtable_delete() removes one entry that was stored under the given key.
+/*
+/* nvtable_free() destroys a hash table, including contents.
+/*
+/* nvtable_walk() invokes the action function for each table entry, with
+/* a pointer to the entry as its argument. The ptr argument is passed
+/* on to the action function.
+/*
+/* nvtable_list() returns a null-terminated list of pointers to
+/* all elements in the named table. The list should be passed to
+/* myfree().
+/* RESTRICTIONS
+/* A callback function should not modify the attribute list that is
+/* specified to its caller.
+/* DIAGNOSTICS
+/* The following conditions are reported and cause the program to
+/* terminate immediately: memory allocation failure; an attempt
+/* to delete a non-existent entry.
+/* SEE ALSO
+/* mymalloc(3) memory management wrapper
+/* htable(3) hash table manager
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* C library */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <htable.h>
+#include <nvtable.h>
+
+/* nvtable_update - update or enter (key, value) pair */
+
+NVTABLE_INFO *nvtable_update(NVTABLE * table, const char *key, const char *value)
+{
+ NVTABLE_INFO *ht;
+
+ if ((ht = htable_locate(table, key)) != 0) {
+ myfree(ht->value);
+ } else {
+ ht = htable_enter(table, key, (void *) 0);
+ }
+ ht->value = mystrdup(value);
+ return (ht);
+}
diff --git a/src/util/nvtable.h b/src/util/nvtable.h
new file mode 100644
index 0000000..fed366a
--- /dev/null
+++ b/src/util/nvtable.h
@@ -0,0 +1,44 @@
+#ifndef _NVTABLE_H_INCLUDED_
+#define _NVTABLE_H_INCLUDED_
+
+/*++
+/* NAME
+/* nvtable 3h
+/* SUMMARY
+/* attribute list manager
+/* SYNOPSIS
+/* #include <nvtable.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <htable.h>
+#include <mymalloc.h>
+
+typedef struct HTABLE NVTABLE;
+typedef struct HTABLE_INFO NVTABLE_INFO;
+
+#define nvtable_create(size) htable_create(size)
+#define nvtable_locate(table, key) htable_locate((table), (key))
+#define nvtable_walk(table, action, ptr) htable_walk((table), HTABLE_ACTION_FN_CAST(action), (ptr))
+#define nvtable_list(table) htable_list(table)
+#define nvtable_find(table, key) htable_find((table), (key))
+#define nvtable_delete(table, key) htable_delete((table), (key), myfree)
+#define nvtable_free(table) htable_free((table), myfree)
+
+extern NVTABLE_INFO *nvtable_update(NVTABLE *, const char *, const char *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/open_as.c b/src/util/open_as.c
new file mode 100644
index 0000000..0fa84b7
--- /dev/null
+++ b/src/util/open_as.c
@@ -0,0 +1,70 @@
+/*++
+/* NAME
+/* open_as 3
+/* SUMMARY
+/* open file as user
+/* SYNOPSIS
+/* #include <fcntl.h>
+/* #include <open_as.h>
+/*
+/* int open_as(path, flags, mode, euid, egid)
+/* const char *path;
+/* int mode;
+/* uid_t euid;
+/* gid_t egid;
+/* DESCRIPTION
+/* open_as() opens the named \fIpath\fR with the named \fIflags\fR
+/* and \fImode\fR, and with the effective rights specified by \fIeuid\fR
+/* and \fIegid\fR. A -1 result means the open failed.
+/* DIAGNOSTICS
+/* Fatal error: no permission to change privilege level.
+/* SEE ALSO
+/* set_eugid(3) switch effective rights
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "set_eugid.h"
+#include "open_as.h"
+
+/* open_as - open file as user */
+
+int open_as(const char *path, int flags, int mode, uid_t euid, gid_t egid)
+{
+ uid_t saved_euid = geteuid();
+ gid_t saved_egid = getegid();
+ int fd;
+
+ /*
+ * Switch to the target user privileges.
+ */
+ set_eugid(euid, egid);
+
+ /*
+ * Open that file.
+ */
+ fd = open(path, flags, mode);
+
+ /*
+ * Restore saved privileges.
+ */
+ set_eugid(saved_euid, saved_egid);
+
+ return (fd);
+}
diff --git a/src/util/open_as.h b/src/util/open_as.h
new file mode 100644
index 0000000..308e009
--- /dev/null
+++ b/src/util/open_as.h
@@ -0,0 +1,30 @@
+#ifndef _OPEN_H_INCLUDED_
+#define _OPEN_H_INCLUDED_
+
+/*++
+/* NAME
+/* open_as 3h
+/* SUMMARY
+/* open file as user
+/* SYNOPSIS
+/* #include <fcntl.h>
+/* #include <open_as.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern int open_as(const char *, int, int, uid_t, gid_t);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/open_limit.c b/src/util/open_limit.c
new file mode 100644
index 0000000..8d49433
--- /dev/null
+++ b/src/util/open_limit.c
@@ -0,0 +1,100 @@
+/*++
+/* NAME
+/* open_limit 3
+/* SUMMARY
+/* set/get open file limit
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* int open_limit(int limit)
+/* DESCRIPTION
+/* The \fIopen_limit\fR() routine attempts to change the maximum
+/* number of open files to the specified limit. Specify a null
+/* argument to effect no change. The result is the actual open file
+/* limit for the current process. The number can be smaller or larger
+/* than the requested limit.
+/* DIAGNOSTICS
+/* open_limit() returns -1 in case of problems. The errno
+/* variable gives hints about the nature of the problem.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System libraries. */
+
+#include "sys_defs.h"
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <errno.h>
+
+#ifdef USE_MAX_FILES_PER_PROC
+#include <sys/sysctl.h>
+#define MAX_FILES_PER_PROC "kern.maxfilesperproc"
+#endif
+
+/* Application-specific. */
+
+#include "iostuff.h"
+
+ /*
+ * 44BSD compatibility.
+ */
+#ifndef RLIMIT_NOFILE
+#ifdef RLIMIT_OFILE
+#define RLIMIT_NOFILE RLIMIT_OFILE
+#endif
+#endif
+
+/* open_limit - set/query file descriptor limit */
+
+int open_limit(int limit)
+{
+#ifdef RLIMIT_NOFILE
+ struct rlimit rl;
+#endif
+
+ if (limit < 0) {
+ errno = EINVAL;
+ return (-1);
+ }
+#ifdef RLIMIT_NOFILE
+ if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
+ return (-1);
+ if (limit > 0) {
+
+ /*
+ * MacOSX incorrectly reports rlim_max as RLIM_INFINITY. The true
+ * hard limit is finite and equals the kern.maxfilesperproc value.
+ */
+#ifdef USE_MAX_FILES_PER_PROC
+ int max_files_per_proc;
+ size_t len = sizeof(max_files_per_proc);
+
+ if (sysctlbyname(MAX_FILES_PER_PROC, &max_files_per_proc, &len,
+ (void *) 0, (size_t) 0) < 0)
+ return (-1);
+ if (limit > max_files_per_proc)
+ limit = max_files_per_proc;
+#endif
+ if (limit > rl.rlim_max)
+ rl.rlim_cur = rl.rlim_max;
+ else
+ rl.rlim_cur = limit;
+ if (setrlimit(RLIMIT_NOFILE, &rl) < 0)
+ return (-1);
+ }
+ return (rl.rlim_cur);
+#endif
+
+#ifndef RLIMIT_NOFILE
+ return (getdtablesize());
+#endif
+}
+
diff --git a/src/util/open_lock.c b/src/util/open_lock.c
new file mode 100644
index 0000000..87e852d
--- /dev/null
+++ b/src/util/open_lock.c
@@ -0,0 +1,76 @@
+/*++
+/* NAME
+/* open_lock 3
+/* SUMMARY
+/* open or create file and lock it for exclusive access
+/* SYNOPSIS
+/* #include <open_lock.h>
+/*
+/* VSTREAM *open_lock(path, flags, mode, why)
+/* const char *path;
+/* int flags;
+/* mode_t mode;
+/* VSTRING *why;
+/* DESCRIPTION
+/* This module opens or creates the named file and attempts to
+/* acquire an exclusive lock. The lock is lost when the last
+/* process closes the file.
+/*
+/* Arguments:
+/* .IP "path, flags, mode"
+/* These are passed on to safe_open().
+/* .IP why
+/* storage for diagnostics.
+/* SEE ALSO
+/* safe_open(3) carefully open or create file
+/* myflock(3) get exclusive lock on file
+/* DIAGNOSTICS
+/* In case of problems the result is a null pointer and a problem
+/* description is returned via the global \fIerrno\fR variable.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <safe_open.h>
+#include <myflock.h>
+#include <open_lock.h>
+
+/* open_lock - open file and lock it for exclusive access */
+
+VSTREAM *open_lock(const char *path, int flags, mode_t mode, VSTRING *why)
+{
+ VSTREAM *fp;
+
+ /*
+ * Carefully create or open the file, and lock it down. Some systems
+ * don't have the O_LOCK open() flag, or the flag does not do what we
+ * want, so we roll our own lock.
+ */
+ if ((fp = safe_open(path, flags, mode, (struct stat *) 0, -1, -1, why)) == 0)
+ return (0);
+ if (myflock(vstream_fileno(fp), INTERNAL_LOCK,
+ MYFLOCK_OP_EXCLUSIVE | MYFLOCK_OP_NOWAIT) < 0) {
+ vstring_sprintf(why, "unable to set exclusive lock: %m");
+ vstream_fclose(fp);
+ return (0);
+ }
+ return (fp);
+}
diff --git a/src/util/open_lock.h b/src/util/open_lock.h
new file mode 100644
index 0000000..ada9f4d
--- /dev/null
+++ b/src/util/open_lock.h
@@ -0,0 +1,41 @@
+#ifndef _OPEN_LOCK_H_INCLUDED_
+#define _OPEN_LOCK_H_INCLUDED_
+
+/*++
+/* NAME
+/* open_lock 3h
+/* SUMMARY
+/* open or create file and lock it for exclusive access
+/* SYNOPSIS
+/* #include <open_lock.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <fcntl.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern VSTREAM *open_lock(const char *, int, mode_t, VSTRING *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/pass_accept.c b/src/util/pass_accept.c
new file mode 100644
index 0000000..d06926f
--- /dev/null
+++ b/src/util/pass_accept.c
@@ -0,0 +1,106 @@
+/*++
+/* NAME
+/* pass_accept 3
+/* SUMMARY
+/* start UNIX-domain file descriptor listener
+/* SYNOPSIS
+/* #include <listen.h>
+/*
+/* int pass_accept(listen_fd)
+/* int listen_fd;
+/*
+/* int pass_accept_attr(listen_fd, attr)
+/* int listen_fd;
+/* HTABLE **attr;
+/* DESCRIPTION
+/* This module implements a listener that receives one attribute list
+/* and file descriptor over each a local connection that is made to it.
+/*
+/* Arguments:
+/* .IP attr
+/* Pointer to attribute list pointer. In case of error, or
+/* no attributes, the attribute list pointer is set to null.
+/* .IP listen_fd
+/* File descriptor returned by LOCAL_LISTEN().
+/* DIAGNOSTICS
+/* Warnings: I/O errors, timeout.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <errno.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <listen.h>
+#include <attr.h>
+
+#define PASS_ACCEPT_TMOUT 100
+
+/* pass_accept - accept descriptor */
+
+int pass_accept(int listen_fd)
+{
+ const char *myname = "pass_accept";
+ int accept_fd;
+ int recv_fd = -1;
+
+ accept_fd = LOCAL_ACCEPT(listen_fd);
+ if (accept_fd < 0) {
+ if (errno != EAGAIN)
+ msg_warn("%s: cannot accept connection: %m", myname);
+ return (-1);
+ } else {
+ if (read_wait(accept_fd, PASS_ACCEPT_TMOUT) < 0)
+ msg_warn("%s: timeout receiving file descriptor: %m", myname);
+ else if ((recv_fd = LOCAL_RECV_FD(accept_fd)) < 0)
+ msg_warn("%s: cannot receive file descriptor: %m", myname);
+ if (close(accept_fd) < 0)
+ msg_warn("%s: close: %m", myname);
+ return (recv_fd);
+ }
+}
+
+/* pass_accept_attr - accept descriptor and attribute list */
+
+int pass_accept_attr(int listen_fd, HTABLE **attr)
+{
+ const char *myname = "pass_accept_attr";
+ int accept_fd;
+ int recv_fd = -1;
+
+ *attr = 0;
+ accept_fd = LOCAL_ACCEPT(listen_fd);
+ if (accept_fd < 0) {
+ if (errno != EAGAIN)
+ msg_warn("%s: cannot accept connection: %m", myname);
+ return (-1);
+ } else {
+ if (read_wait(accept_fd, PASS_ACCEPT_TMOUT) < 0)
+ msg_warn("%s: timeout receiving file descriptor: %m", myname);
+ else if ((recv_fd = LOCAL_RECV_FD(accept_fd)) < 0)
+ msg_warn("%s: cannot receive file descriptor: %m", myname);
+ else if (read_wait(accept_fd, PASS_ACCEPT_TMOUT) < 0
+ || recv_pass_attr(accept_fd, attr, PASS_ACCEPT_TMOUT, 0) < 0) {
+ msg_warn("%s: cannot receive connection attributes: %m", myname);
+ if (close(recv_fd) < 0)
+ msg_warn("%s: close: %m", myname);
+ recv_fd = -1;
+ }
+ if (close(accept_fd) < 0)
+ msg_warn("%s: close: %m", myname);
+ return (recv_fd);
+ }
+}
diff --git a/src/util/pass_trigger.c b/src/util/pass_trigger.c
new file mode 100644
index 0000000..d7d16f2
--- /dev/null
+++ b/src/util/pass_trigger.c
@@ -0,0 +1,151 @@
+/*++
+/* NAME
+/* pass_trigger 3
+/* SUMMARY
+/* trigger file descriptor listener
+/* SYNOPSIS
+/* #include <trigger.h>
+/*
+/* int pass_trigger(service, buf, len, timeout)
+/* const char *service;
+/* const char *buf;
+/* ssize_t len;
+/* int timeout;
+/* DESCRIPTION
+/* pass_trigger() connects to the named local server by sending
+/* a file descriptor to it and writing the named buffer.
+/*
+/* The connection is closed by a background thread. Some kernels
+/* cannot handle client-side disconnect before the server has
+/* received the message.
+/*
+/* Arguments:
+/* .IP service
+/* Name of the communication endpoint.
+/* .IP buf
+/* Address of data to be written.
+/* .IP len
+/* Amount of data to be written.
+/* .IP timeout
+/* Deadline in seconds. Specify a value <= 0 to disable
+/* the time limit.
+/* DIAGNOSTICS
+/* The result is zero in case of success, -1 in case of problems.
+/* SEE ALSO
+/* unix_connect(3), local client
+/* stream_connect(3), streams-based client
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <connect.h>
+#include <iostuff.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <trigger.h>
+
+struct pass_trigger {
+ int connect_fd;
+ char *service;
+ int pass_fd[2];
+};
+
+/* pass_trigger_event - disconnect from peer */
+
+static void pass_trigger_event(int event, void *context)
+{
+ struct pass_trigger *pp = (struct pass_trigger *) context;
+ static const char *myname = "pass_trigger_event";
+
+ /*
+ * Disconnect.
+ */
+ if (event == EVENT_TIME)
+ msg_warn("%s: read timeout for service %s", myname, pp->service);
+ event_disable_readwrite(pp->connect_fd);
+ event_cancel_timer(pass_trigger_event, context);
+ /* Don't combine multiple close() calls into one boolean expression. */
+ if (close(pp->connect_fd) < 0)
+ msg_warn("%s: close %s: %m", myname, pp->service);
+ if (close(pp->pass_fd[0]) < 0)
+ msg_warn("%s: close pipe: %m", myname);
+ if (close(pp->pass_fd[1]) < 0)
+ msg_warn("%s: close pipe: %m", myname);
+ myfree(pp->service);
+ myfree((void *) pp);
+}
+
+/* pass_trigger - wakeup local server */
+
+int pass_trigger(const char *service, const char *buf, ssize_t len, int timeout)
+{
+ const char *myname = "pass_trigger";
+ int pass_fd[2];
+ struct pass_trigger *pp;
+ int connect_fd;
+
+ if (msg_verbose > 1)
+ msg_info("%s: service %s", myname, service);
+
+ /*
+ * Connect...
+ */
+ if ((connect_fd = LOCAL_CONNECT(service, BLOCKING, timeout)) < 0) {
+ if (msg_verbose)
+ msg_warn("%s: connect to %s: %m", myname, service);
+ return (-1);
+ }
+ close_on_exec(connect_fd, CLOSE_ON_EXEC);
+
+ /*
+ * Create a pipe, and send one pipe end to the server.
+ */
+ if (pipe(pass_fd) < 0)
+ msg_fatal("%s: pipe: %m", myname);
+ close_on_exec(pass_fd[0], CLOSE_ON_EXEC);
+ close_on_exec(pass_fd[1], CLOSE_ON_EXEC);
+ if (LOCAL_SEND_FD(connect_fd, pass_fd[0]) < 0)
+ msg_fatal("%s: send file descriptor: %m", myname);
+
+ /*
+ * Stash away context.
+ */
+ pp = (struct pass_trigger *) mymalloc(sizeof(*pp));
+ pp->connect_fd = connect_fd;
+ pp->service = mystrdup(service);
+ pp->pass_fd[0] = pass_fd[0];
+ pp->pass_fd[1] = pass_fd[1];
+
+ /*
+ * Write the request...
+ */
+ if (write_buf(pass_fd[1], buf, len, timeout) < 0
+ || write_buf(pass_fd[1], "", 1, timeout) < 0)
+ if (msg_verbose)
+ msg_warn("%s: write to %s: %m", myname, service);
+
+ /*
+ * Wakeup when the peer disconnects, or when we lose patience.
+ */
+ if (timeout > 0)
+ event_request_timer(pass_trigger_event, (void *) pp, timeout + 100);
+ event_enable_read(connect_fd, pass_trigger_event, (void *) pp);
+ return (0);
+}
diff --git a/src/util/peekfd.c b/src/util/peekfd.c
new file mode 100644
index 0000000..e9480a2
--- /dev/null
+++ b/src/util/peekfd.c
@@ -0,0 +1,89 @@
+/*++
+/* NAME
+/* peekfd 3
+/* SUMMARY
+/* determine amount of data ready to read
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* ssize_t peekfd(fd)
+/* int fd;
+/* DESCRIPTION
+/* peekfd() attempts to find out how many bytes are available to
+/* be read from the named file descriptor. The result value is
+/* the number of available bytes.
+/* DIAGNOSTICS
+/* peekfd() returns -1 in case of trouble. The global \fIerrno\fR
+/* variable reflects the nature of the problem.
+/* BUGS
+/* On some systems, non-blocking read() may fail even after a
+/* positive return from peekfd(). The smtp-sink program works
+/* around this by using the readable() function instead.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/ioctl.h>
+#ifdef FIONREAD_IN_SYS_FILIO_H
+#include <sys/filio.h>
+#endif
+#ifdef FIONREAD_IN_TERMIOS_H
+#include <termios.h>
+#endif
+#include <unistd.h>
+
+#ifndef SHUT_RDWR
+#define SHUT_RDWR 2
+#endif
+
+/* Utility library. */
+
+#include "iostuff.h"
+
+/* peekfd - return amount of data ready to read */
+
+ssize_t peekfd(int fd)
+{
+
+ /*
+ * Anticipate a series of system-dependent code fragments.
+ */
+#ifdef FIONREAD
+ int count;
+
+#ifdef SUNOS5
+
+ /*
+ * With Solaris10, write_wait() hangs in poll() until timeout, when
+ * invoked after peekfd() has received an ECONNRESET error indication.
+ * This happens when a client sends QUIT and closes the connection
+ * immediately.
+ */
+ if (ioctl(fd, FIONREAD, (char *) &count) < 0) {
+ (void) shutdown(fd, SHUT_RDWR);
+ return (-1);
+ } else {
+ return (count);
+ }
+#else /* SUNOS5 */
+ return (ioctl(fd, FIONREAD, (char *) &count) < 0 ? -1 : count);
+#endif /* SUNOS5 */
+#else
+#error "don't know how to look ahead"
+#endif
+}
diff --git a/src/util/poll_fd.c b/src/util/poll_fd.c
new file mode 100644
index 0000000..80cd0f6
--- /dev/null
+++ b/src/util/poll_fd.c
@@ -0,0 +1,269 @@
+/*++
+/* NAME
+/* poll_fd 3
+/* SUMMARY
+/* wait until file descriptor becomes readable or writable
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* int readable(fd)
+/* int fd;
+/*
+/* int writable(fd)
+/* int fd;
+/*
+/* int read_wait(fd, time_limit)
+/* int fd;
+/* int time_limit;
+/*
+/* int write_wait(fd, time_limit)
+/* int fd;
+/* int time_limit;
+/*
+/* int poll_fd(fd, request, time_limit, true_res, false_res)
+/* int fd;
+/* int request;
+/* int time_limit;
+/* int true_res;
+/* int false_res;
+/* DESCRIPTION
+/* The read*() and write*() functions in this module are macros
+/* that provide a convenient interface to poll_fd().
+/*
+/* readable() asks the kernel if the specified file descriptor
+/* is readable, i.e. a read operation would not block.
+/*
+/* writable() asks the kernel if the specified file descriptor
+/* is writable, i.e. a write operation would not block.
+/*
+/* read_wait() waits until the specified file descriptor becomes
+/* readable, or until the time limit is reached.
+/*
+/* write_wait() waits until the specified file descriptor
+/* becomes writable, or until the time limit is reached.
+/*
+/* poll_fd() waits until the specified file descriptor becomes
+/* readable or writable, or until the time limit is reached.
+/*
+/* Arguments:
+/* .IP fd
+/* File descriptor. With implementations based on select(), a
+/* best effort is made to handle descriptors >=FD_SETSIZE.
+/* .IP request
+/* POLL_FD_READ (wait until readable) or POLL_FD_WRITE (wait
+/* until writable).
+/* .IP time_limit
+/* A positive value specifies a time limit in seconds. A zero
+/* value effects a poll (return immediately). A negative value
+/* means wait until the requested POLL_FD_READ or POLL_FD_WRITE
+/* condition becomes true.
+/* .IP true_res
+/* Result value when the requested POLL_FD_READ or POLL_FD_WRITE
+/* condition is true.
+/* .IP false_res
+/* Result value when the requested POLL_FD_READ or POLL_FD_WRITE
+/* condition is false.
+/* DIAGNOSTICS
+/* Panic: interface violation. All system call errors are fatal
+/* unless specified otherwise.
+/*
+/* readable() and writable() return 1 when the requested
+/* POLL_FD_READ or POLL_FD_WRITE condition is true, zero when
+/* it is false. They never return an error indication.
+/*
+/* read_wait() and write_wait() return zero when the requested
+/* POLL_FD_READ or POLL_FD_WRITE condition is true, -1 (with
+/* errno set to ETIMEDOUT) when it is false.
+/*
+/* poll_fd() returns true_res when the requested POLL_FD_READ
+/* or POLL_FD_WRITE condition is true, false_res when it is
+/* false. When poll_fd() returns a false_res value < 0, it
+/* also sets errno to ETIMEDOUT.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/time.h>
+#include <signal.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+
+ /*
+ * Use poll() with fall-back to select(). MacOSX needs this for devices.
+ */
+#if defined(USE_SYSV_POLL_THEN_SELECT)
+#define poll_fd_sysv poll_fd
+#define USE_SYSV_POLL
+#define USE_BSD_SELECT
+int poll_fd_bsd(int, int, int, int, int);
+
+ /*
+ * Use select() only.
+ */
+#elif defined(USE_BSD_SELECT)
+#define poll_fd_bsd poll_fd
+#undef USE_SYSV_POLL
+
+ /*
+ * Use poll() only.
+ */
+#elif defined(USE_SYSV_POLL)
+#define poll_fd_sysv poll_fd
+
+ /*
+ * Sanity check.
+ */
+#else
+#error "specify USE_SYSV_POLL, USE_BSD_SELECT or USE_SYSV_POLL_THEN_SELECT"
+#endif
+
+#ifdef USE_SYSV_POLL
+#include <poll.h>
+#endif
+
+#ifdef USE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+#ifdef USE_BSD_SELECT
+
+/* poll_fd_bsd - block with time_limit until file descriptor is ready */
+
+int poll_fd_bsd(int fd, int request, int time_limit,
+ int true_res, int false_res)
+{
+ fd_set req_fds;
+ fd_set *read_fds;
+ fd_set *write_fds;
+ fd_set except_fds;
+ struct timeval tv;
+ struct timeval *tp;
+ int temp_fd = -1;
+
+ /*
+ * Sanity checks.
+ */
+ if (FD_SETSIZE <= fd) {
+ if ((temp_fd = dup(fd)) < 0 || temp_fd >= FD_SETSIZE)
+ msg_fatal("descriptor %d does not fit FD_SETSIZE %d", fd, FD_SETSIZE);
+ fd = temp_fd;
+ }
+
+ /*
+ * Use select() so we do not depend on alarm() and on signal() handlers.
+ * Restart select() when interrupted by some signal. Some select()
+ * implementations reduce the time to wait when interrupted, which is
+ * exactly what we want.
+ */
+ FD_ZERO(&req_fds);
+ FD_SET(fd, &req_fds);
+ except_fds = req_fds;
+ if (request == POLL_FD_READ) {
+ read_fds = &req_fds;
+ write_fds = 0;
+ } else if (request == POLL_FD_WRITE) {
+ read_fds = 0;
+ write_fds = &req_fds;
+ } else {
+ msg_panic("poll_fd: bad request %d", request);
+ }
+
+ if (time_limit >= 0) {
+ tv.tv_usec = 0;
+ tv.tv_sec = time_limit;
+ tp = &tv;
+ } else {
+ tp = 0;
+ }
+
+ for (;;) {
+ switch (select(fd + 1, read_fds, write_fds, &except_fds, tp)) {
+ case -1:
+ if (errno != EINTR)
+ msg_fatal("select: %m");
+ continue;
+ case 0:
+ if (temp_fd != -1)
+ (void) close(temp_fd);
+ if (false_res < 0)
+ errno = ETIMEDOUT;
+ return (false_res);
+ default:
+ if (temp_fd != -1)
+ (void) close(temp_fd);
+ return (true_res);
+ }
+ }
+}
+
+#endif
+
+#ifdef USE_SYSV_POLL
+
+#ifdef USE_SYSV_POLL_THEN_SELECT
+#define HANDLE_SYSV_POLL_ERROR(fd, req, time_limit, true_res, false_res) \
+ return (poll_fd_bsd((fd), (req), (time_limit), (true_res), (false_res)))
+#else
+#define HANDLE_SYSV_POLL_ERROR(fd, req, time_limit, true_res, false_res) \
+ msg_fatal("poll: %m")
+#endif
+
+/* poll_fd_sysv - block with time_limit until file descriptor is ready */
+
+int poll_fd_sysv(int fd, int request, int time_limit,
+ int true_res, int false_res)
+{
+ struct pollfd pollfd;
+
+ /*
+ * System-V poll() is optimal for polling a few descriptors.
+ */
+#define WAIT_FOR_EVENT (-1)
+
+ pollfd.fd = fd;
+ if (request == POLL_FD_READ) {
+ pollfd.events = POLLIN;
+ } else if (request == POLL_FD_WRITE) {
+ pollfd.events = POLLOUT;
+ } else {
+ msg_panic("poll_fd: bad request %d", request);
+ }
+
+ for (;;) {
+ switch (poll(&pollfd, 1, time_limit < 0 ?
+ WAIT_FOR_EVENT : time_limit * 1000)) {
+ case -1:
+ if (errno != EINTR)
+ HANDLE_SYSV_POLL_ERROR(fd, request, time_limit,
+ true_res, false_res);
+ continue;
+ case 0:
+ if (false_res < 0)
+ errno = ETIMEDOUT;
+ return (false_res);
+ default:
+ if (pollfd.revents & POLLNVAL)
+ HANDLE_SYSV_POLL_ERROR(fd, request, time_limit,
+ true_res, false_res);
+ return (true_res);
+ }
+ }
+}
+
+#endif
diff --git a/src/util/posix_signals.c b/src/util/posix_signals.c
new file mode 100644
index 0000000..8ccddf0
--- /dev/null
+++ b/src/util/posix_signals.c
@@ -0,0 +1,126 @@
+/*++
+/* NAME
+/* posix_signals 3
+/* SUMMARY
+/* POSIX signal handling compatibility
+/* SYNOPSIS
+/* #include <posix_signals.h>
+/*
+/* int sigemptyset(m)
+/* sigset_t *m;
+/*
+/* int sigaddset(set, signum)
+/* sigset_t *set;
+/* int signum;
+/*
+/* int sigprocmask(how, set, old)
+/* int how;
+/* sigset_t *set;
+/* sigset_t *old;
+/*
+/* int sigaction(sig, act, oact)
+/* int sig;
+/* struct sigaction *act;
+/* struct sigaction *oact;
+/* DESCRIPTION
+/* These routines emulate the POSIX signal handling interface.
+/* AUTHOR(S)
+/* Pieter Schoenmakers
+/* Eindhoven University of Technology
+/* P.O. Box 513
+/* 5600 MB Eindhoven
+/* The Netherlands
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <signal.h>
+#include <errno.h>
+
+/* Utility library.*/
+
+#include "posix_signals.h"
+
+#ifdef MISSING_SIGSET_T
+
+int sigemptyset(sigset_t *m)
+{
+ return *m = 0;
+}
+
+int sigaddset(sigset_t *set, int signum)
+{
+ *set |= sigmask(signum);
+ return 0;
+}
+
+int sigprocmask(int how, sigset_t *set, sigset_t *old)
+{
+ int previous;
+
+ if (how == SIG_BLOCK)
+ previous = sigblock(*set);
+ else if (how == SIG_SETMASK)
+ previous = sigsetmask(*set);
+ else if (how == SIG_UNBLOCK) {
+ int m = sigblock(0);
+
+ previous = sigsetmask(m & ~*set);
+ } else {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (old)
+ *old = previous;
+ return 0;
+}
+
+#endif
+
+#ifdef MISSING_SIGACTION
+
+static struct sigaction actions[NSIG] = {};
+
+static int sighandle(int signum)
+{
+ if (signum == SIGCHLD) {
+ /* XXX If the child is just stopped, don't invoke the handler. */
+ }
+ actions[signum].sa_handler(signum);
+}
+
+int sigaction(int sig, struct sigaction *act, struct sigaction *oact)
+{
+ static int initialized = 0;
+
+ if (!initialized) {
+ int i;
+
+ for (i = 0; i < NSIG; i++)
+ actions[i].sa_handler = SIG_DFL;
+ initialized = 1;
+ }
+ if (sig <= 0 || sig >= NSIG) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (oact)
+ *oact = actions[sig];
+
+ {
+ struct sigvec mine = {
+ sighandle, act->sa_mask,
+ act->sa_flags & SA_RESTART ? SV_INTERRUPT : 0
+ };
+
+ if (sigvec(sig, &mine, NULL))
+ return -1;
+ }
+
+ actions[sig] = *act;
+ return 0;
+}
+
+#endif
diff --git a/src/util/posix_signals.h b/src/util/posix_signals.h
new file mode 100644
index 0000000..12c1664
--- /dev/null
+++ b/src/util/posix_signals.h
@@ -0,0 +1,59 @@
+#ifndef _POSIX_SIGNALS_H_INCLUDED_
+#define _POSIX_SIGNALS_H_INCLUDED_
+/*++
+/* NAME
+/* posix_signals 3h
+/* SUMMARY
+/* POSIX signal handling compatibility
+/* SYNOPSIS
+/* #include <posix_signals.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Compatibility interface.
+ */
+
+#ifdef MISSING_SIGSET_T
+
+typedef int sigset_t;
+
+enum {
+ SIG_BLOCK,
+ SIG_UNBLOCK,
+ SIG_SETMASK
+};
+
+extern int sigemptyset(sigset_t *);
+extern int sigaddset(sigset_t *, int);
+extern int sigprocmask(int, sigset_t *, sigset_t *);
+
+#endif
+
+#ifdef MISSING_SIGACTION
+
+struct sigaction {
+ void (*sa_handler) ();
+ sigset_t sa_mask;
+ int sa_flags;
+};
+
+ /* Possible values for sa_flags. Or them to set multiple. */
+enum {
+ SA_RESTART,
+ SA_NOCLDSTOP = 4 /* drop the = 4. */
+};
+
+extern int sigaction(int, struct sigaction *, struct sigaction *);
+
+#endif
+
+/* AUTHOR(S)
+/* Pieter Schoenmakers
+/* Eindhoven University of Technology
+/* P.O. Box 513
+/* 5600 MB Eindhoven
+/* The Netherlands
+/*--*/
+
+#endif
diff --git a/src/util/printable.c b/src/util/printable.c
new file mode 100644
index 0000000..6c148fd
--- /dev/null
+++ b/src/util/printable.c
@@ -0,0 +1,100 @@
+/*++
+/* NAME
+/* printable 3
+/* SUMMARY
+/* mask non-printable characters
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* int util_utf8_enable;
+/*
+/* char *printable(buffer, replacement)
+/* char *buffer;
+/* int replacement;
+/*
+/* char *printable_except(buffer, replacement, except)
+/* char *buffer;
+/* int replacement;
+/* const char *except;
+/* DESCRIPTION
+/* printable() replaces non-printable characters
+/* in its input with the given replacement.
+/*
+/* util_utf8_enable controls whether UTF8 is considered printable.
+/* With util_utf8_enable equal to zero, non-ASCII text is replaced.
+/*
+/* Arguments:
+/* .IP buffer
+/* The null-terminated input string.
+/* .IP replacement
+/* Replacement value for characters in \fIbuffer\fR that do not
+/* pass the ASCII isprint(3) test or that are not valid UTF8.
+/* .IP except
+/* Null-terminated sequence of non-replaced ASCII characters.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <ctype.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include "stringops.h"
+
+int util_utf8_enable = 0;
+
+/* printable - binary compatibility */
+
+#undef printable
+
+char *printable(char *, int);
+
+char *printable(char *string, int replacement)
+{
+ return (printable_except(string, replacement, (char *) 0));
+}
+
+/* printable_except - pass through printable or other preserved characters */
+
+char *printable_except(char *string, int replacement, const char *except)
+{
+ unsigned char *cp;
+ int ch;
+
+ /*
+ * XXX Replace invalid UTF8 sequences (too short, over-long encodings,
+ * out-of-range code points, etc). See valid_utf8_string.c.
+ */
+ cp = (unsigned char *) string;
+ while ((ch = *cp) != 0) {
+ if (ISASCII(ch) && (ISPRINT(ch) || (except && strchr(except, ch)))) {
+ /* ok */
+ } else if (util_utf8_enable && ch >= 194 && ch <= 254
+ && cp[1] >= 128 && cp[1] < 192) {
+ /* UTF8; skip the rest of the bytes in the character. */
+ while (cp[1] >= 128 && cp[1] < 192)
+ cp++;
+ } else {
+ /* Not ASCII and not UTF8. */
+ *cp = replacement;
+ }
+ cp++;
+ }
+ return (string);
+}
diff --git a/src/util/rand_sleep.c b/src/util/rand_sleep.c
new file mode 100644
index 0000000..69a8cc8
--- /dev/null
+++ b/src/util/rand_sleep.c
@@ -0,0 +1,89 @@
+/*++
+/* NAME
+/* rand_sleep 3
+/* SUMMARY
+/* sleep for randomized interval
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* void rand_sleep(delay, variation)
+/* unsigned delay;
+/* unsigned variation;
+/* DESCRIPTION
+/* rand_sleep() blocks the current process for an amount of time
+/* pseudo-randomly chosen from the interval (delay +- variation/2).
+/*
+/* Arguments:
+/* .IP delay
+/* Time to sleep in microseconds.
+/* .IP variation
+/* Variation in microseconds; must not be larger than delay.
+/* DIAGNOSTICS
+/* Panic: interface violation. All system call errors are fatal.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <myrand.h>
+#include <iostuff.h>
+
+/* rand_sleep - block for random time */
+
+void rand_sleep(unsigned delay, unsigned variation)
+{
+ const char *myname = "rand_sleep";
+ unsigned usec;
+
+ /*
+ * Sanity checks.
+ */
+ if (delay == 0)
+ msg_panic("%s: bad delay %d", myname, delay);
+ if (variation > delay)
+ msg_panic("%s: bad variation %d", myname, variation);
+
+ /*
+ * Use the semi-crappy random number generator.
+ */
+ usec = (delay - variation / 2) + variation * (double) myrand() / RAND_MAX;
+ doze(usec);
+}
+
+#ifdef TEST
+
+#include <msg_vstream.h>
+
+int main(int argc, char **argv)
+{
+ int delay;
+ int variation;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ if (argc != 3)
+ msg_fatal("usage: %s delay variation", argv[0]);
+ if ((delay = atoi(argv[1])) <= 0)
+ msg_fatal("bad delay: %s", argv[1]);
+ if ((variation = atoi(argv[2])) < 0)
+ msg_fatal("bad variation: %s", argv[2]);
+ rand_sleep(delay * 1000000, variation * 1000000);
+ exit(0);
+}
+
+#endif
diff --git a/src/util/readlline.c b/src/util/readlline.c
new file mode 100644
index 0000000..015877a
--- /dev/null
+++ b/src/util/readlline.c
@@ -0,0 +1,138 @@
+/*++
+/* NAME
+/* readlline 3
+/* SUMMARY
+/* read logical line
+/* SYNOPSIS
+/* #include <readlline.h>
+/*
+/* VSTRING *readllines(buf, fp, lineno, first_line)
+/* VSTRING *buf;
+/* VSTREAM *fp;
+/* int *lineno;
+/* int *first_line;
+/*
+/* VSTRING *readlline(buf, fp, lineno)
+/* VSTRING *buf;
+/* VSTREAM *fp;
+/* int *lineno;
+/* DESCRIPTION
+/* readllines() reads one logical line from the named stream.
+/* .IP "blank lines and comments"
+/* Empty lines and whitespace-only lines are ignored, as
+/* are lines whose first non-whitespace character is a `#'.
+/* .IP "multi-line text"
+/* A logical line starts with non-whitespace text. A line that
+/* starts with whitespace continues a logical line.
+/* .PP
+/* The result value is the input buffer argument or a null pointer
+/* when no input is found.
+/*
+/* readlline() is a backwards-compatibility wrapper.
+/*
+/* Arguments:
+/* .IP buf
+/* A variable-length buffer for input. The result is null terminated.
+/* .IP fp
+/* Handle to an open stream.
+/* .IP lineno
+/* A null pointer, or a pointer to an integer that is incremented
+/* after reading a physical line.
+/* .IP first_line
+/* A null pointer, or a pointer to an integer that will contain
+/* the line number of the first non-blank, non-comment line
+/* in the result logical line.
+/* DIAGNOSTICS
+/* Warning: a continuation line that does not continue preceding text.
+/* The invalid input is ignored, to avoid complicating caller code.
+/* SECURITY
+/* .ad
+/* .fi
+/* readlline() imposes no logical line length limit therefore it
+/* should be used for reading trusted information only.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "vstream.h"
+#include "vstring.h"
+#include "readlline.h"
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+#define END(x) vstring_end(x)
+
+/* readllines - read one logical line */
+
+VSTRING *readllines(VSTRING *buf, VSTREAM *fp, int *lineno, int *first_line)
+{
+ int ch;
+ int next;
+ ssize_t start;
+ char *cp;
+
+ VSTRING_RESET(buf);
+
+ /*
+ * Ignore comment lines, all whitespace lines, and empty lines. Terminate
+ * at EOF or at the beginning of the next logical line.
+ */
+ for (;;) {
+ /* Read one line, possibly not newline terminated. */
+ start = LEN(buf);
+ while ((ch = VSTREAM_GETC(fp)) != VSTREAM_EOF && ch != '\n')
+ VSTRING_ADDCH(buf, ch);
+ if (lineno != 0 && (ch == '\n' || LEN(buf) > start))
+ *lineno += 1;
+ /* Ignore comment line, all whitespace line, or empty line. */
+ for (cp = STR(buf) + start; cp < END(buf) && ISSPACE(*cp); cp++)
+ /* void */ ;
+ if (cp == END(buf) || *cp == '#')
+ vstring_truncate(buf, start);
+ else if (start == 0 && lineno != 0 && first_line != 0)
+ *first_line = *lineno;
+ /* Terminate at EOF or at the beginning of the next logical line. */
+ if (ch == VSTREAM_EOF)
+ break;
+ if (LEN(buf) > 0) {
+ if ((next = VSTREAM_GETC(fp)) != VSTREAM_EOF)
+ vstream_ungetc(fp, next);
+ if (next != '#' && !ISSPACE(next))
+ break;
+ }
+ }
+ VSTRING_TERMINATE(buf);
+
+ /*
+ * Invalid input: continuing text without preceding text. Allowing this
+ * would complicate "postconf -e", which implements its own multi-line
+ * parsing routine. Do not abort, just warn, so that critical programs
+ * like postmap do not leave behind a truncated table.
+ */
+ if (LEN(buf) > 0 && ISSPACE(*STR(buf))) {
+ msg_warn("%s: logical line must not start with whitespace: \"%.30s%s\"",
+ VSTREAM_PATH(fp), STR(buf),
+ LEN(buf) > 30 ? "..." : "");
+ return (readllines(buf, fp, lineno, first_line));
+ }
+
+ /*
+ * Done.
+ */
+ return (LEN(buf) > 0 ? buf : 0);
+}
diff --git a/src/util/readlline.h b/src/util/readlline.h
new file mode 100644
index 0000000..d63cf7d
--- /dev/null
+++ b/src/util/readlline.h
@@ -0,0 +1,38 @@
+#ifndef _READLINE_H_INCLUDED_
+#define _READLINE_H_INCLUDED_
+
+/*++
+/* NAME
+/* readlline 3h
+/* SUMMARY
+/* read logical line
+/* SYNOPSIS
+/* #include <readlline.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern VSTRING *readllines(VSTRING *, VSTREAM *, int *, int *);
+
+#define readlline(bp, fp, lp) readllines((bp), (fp), (lp), (int *) 0)
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/recv_pass_attr.c b/src/util/recv_pass_attr.c
new file mode 100644
index 0000000..0d6c647
--- /dev/null
+++ b/src/util/recv_pass_attr.c
@@ -0,0 +1,98 @@
+/*++
+/* NAME
+/* recv_pass_attr 3
+/* SUMMARY
+/* predicate if string is all numerical
+/* SYNOPSIS
+/* #include <listen.h>
+/*
+/* int recv_pass_attr(fd, attr, timeout, bufsize)
+/* int fd;
+/* HTABLE **attr;
+/* int timeout;
+/* ssize_t bufsize;
+/* DESCRIPTION
+/* recv_pass_attr() receives named attributes over the specified
+/* descriptor. The result value is zero for success, -1 for error.
+/*
+/* Arguments:
+/* .IP fd
+/* The file descriptor to read from.
+/* .IP attr
+/* Pointer to attribute list pointer. The target is set to
+/* zero on error or when the received attribute list is empty,
+/* otherwise it is assigned a pointer to non-empty attribute
+/* list.
+/* .IP timeout
+/* The deadline for receiving all attributes.
+/* .IP bufsize
+/* The read buffer size. Specify 1 to avoid reading past the
+/* end of the attribute list.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <iostuff.h>
+#include <htable.h>
+#include <vstream.h>
+#include <attr.h>
+#include <mymalloc.h>
+#include <listen.h>
+
+/* recv_pass_attr - receive connection attributes */
+
+int recv_pass_attr(int fd, HTABLE **attr, int timeout, ssize_t bufsize)
+{
+ VSTREAM *fp;
+ int stream_err;
+
+ /*
+ * Set up a temporary VSTREAM to receive the attributes.
+ *
+ * XXX We use one-character reads to simplify the implementation.
+ */
+ fp = vstream_fdopen(fd, O_RDWR);
+ vstream_control(fp,
+ CA_VSTREAM_CTL_BUFSIZE(bufsize),
+ CA_VSTREAM_CTL_TIMEOUT(timeout),
+ CA_VSTREAM_CTL_START_DEADLINE,
+ CA_VSTREAM_CTL_END);
+ stream_err = (attr_scan(fp, ATTR_FLAG_NONE,
+ ATTR_TYPE_HASH, *attr = htable_create(1),
+ ATTR_TYPE_END) < 0
+ || vstream_feof(fp) || vstream_ferror(fp));
+ vstream_fdclose(fp);
+
+ /*
+ * Error reporting and recovery.
+ */
+ if (stream_err) {
+ htable_free(*attr, myfree);
+ *attr = 0;
+ return (-1);
+ } else {
+ if ((*attr)->used == 0) {
+ htable_free(*attr, myfree);
+ *attr = 0;
+ }
+ return (0);
+ }
+}
diff --git a/src/util/ring.c b/src/util/ring.c
new file mode 100644
index 0000000..d4c5f82
--- /dev/null
+++ b/src/util/ring.c
@@ -0,0 +1,121 @@
+/*++
+/* NAME
+/* ring 3
+/* SUMMARY
+/* circular list management
+/* SYNOPSIS
+/* #include <ring.h>
+/*
+/* void ring_init(list)
+/* RING *list;
+/*
+/* void ring_prepend(list, element)
+/* RING *list;
+/* RING *element;
+/*
+/* void ring_append(list, element)
+/* RING *list;
+/* RING *element;
+/*
+/* RING *ring_pred(element)
+/* RING *element;
+/*
+/* RING *ring_succ(element)
+/* RING *element;
+/*
+/* void ring_detach(element)
+/* RING *element;
+/*
+/* RING_FOREACH(RING *element, RING *head)
+/* DESCRIPTION
+/* This module manages circular, doubly-linked, lists. It provides
+/* operations to initialize a list, to add or remove an element,
+/* and to iterate over a list. Although the documentation appears
+/* to emphasize the special role of the list head, each operation
+/* can be applied to each list member.
+/*
+/* Examples of applications: any sequence of objects such as queue,
+/* unordered list, or stack. Typically, an application embeds a RING
+/* structure into its own data structure, and uses the RING primitives
+/* to maintain the linkage between application-specific data objects.
+/*
+/* ring_init() initializes its argument to a list of just one element.
+/*
+/* ring_append() appends the named element to the named list head.
+/*
+/* ring_prepend() prepends the named element to the named list head.
+/*
+/* ring_succ() returns the list element that follows its argument.
+/*
+/* ring_pred() returns the list element that precedes its argument.
+/*
+/* ring_detach() disconnects a list element from its neighbors
+/* and closes the hole. This routine performs no implicit ring_init()
+/* on the removed element.
+/*
+/* RING_FOREACH() is a macro that expands to a for (... ; ... ; ...)
+/* statement that iterates over each list element in forward order.
+/* Upon completion, the \fIelement\fR variable is set equal to
+/* \fIhead\fR. The list head itself is not treated as a list member.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System libraries. */
+
+/* Application-specific. */
+
+#include "ring.h"
+
+/* ring_init - initialize ring head */
+
+void ring_init(ring)
+RING *ring;
+{
+ ring->pred = ring->succ = ring;
+}
+
+/* ring_append - insert entry after ring head */
+
+void ring_append(ring, entry)
+RING *ring;
+RING *entry;
+{
+ entry->succ = ring->succ;
+ entry->pred = ring;
+ ring->succ->pred = entry;
+ ring->succ = entry;
+}
+
+/* ring_prepend - insert new entry before ring head */
+
+void ring_prepend(ring, entry)
+RING *ring;
+RING *entry;
+{
+ entry->pred = ring->pred;
+ entry->succ = ring;
+ ring->pred->succ = entry;
+ ring->pred = entry;
+}
+
+/* ring_detach - remove entry from ring */
+
+void ring_detach(entry)
+RING *entry;
+{
+ RING *succ = entry->succ;
+ RING *pred = entry->pred;
+
+ pred->succ = succ;
+ succ->pred = pred;
+
+ entry->succ = entry->pred = 0;
+}
diff --git a/src/util/ring.h b/src/util/ring.h
new file mode 100644
index 0000000..47749e2
--- /dev/null
+++ b/src/util/ring.h
@@ -0,0 +1,59 @@
+#ifndef _RING_H_INCLUDED_
+#define _RING_H_INCLUDED_
+
+/*++
+/* NAME
+/* ring 3h
+/* SUMMARY
+/* circular list management
+/* SYNOPSIS
+/* #include <ring.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+typedef struct RING RING;
+
+struct RING {
+ RING *succ; /* successor */
+ RING *pred; /* predecessor */
+};
+
+extern void ring_init(RING *);
+extern void ring_prepend(RING *, RING *);
+extern void ring_append(RING *, RING *);
+extern void ring_detach(RING *);
+
+#define ring_succ(c) ((c)->succ)
+#define ring_pred(c) ((c)->pred)
+
+#define RING_FOREACH(entry, head) \
+ for (entry = ring_succ(head); entry != (head); entry = ring_succ(entry))
+
+ /*
+ * Typically, an application will embed a RING structure into a larger
+ * structure that also contains application-specific members. This approach
+ * gives us the best of both worlds. The application can still use the
+ * generic RING primitives for manipulating RING structures. The macro below
+ * transforms a pointer from RING structure to the structure that contains
+ * it.
+ */
+#define RING_TO_APPL(ring_ptr,app_type,ring_member) \
+ ((app_type *) (((char *) (ring_ptr)) - offsetof(app_type,ring_member)))
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/* LAST MODIFICATION
+/* Tue Jan 28 16:50:20 EST 1997
+/*--*/
+
+#endif
diff --git a/src/util/safe.h b/src/util/safe.h
new file mode 100644
index 0000000..8b75bf4
--- /dev/null
+++ b/src/util/safe.h
@@ -0,0 +1,30 @@
+#ifndef _SAFE_H_INCLUDED_
+#define _SAFE_H_INCLUDED_
+
+/*++
+/* NAME
+/* safe 3h
+/* SUMMARY
+/* miscellaneous taint checks
+/* SYNOPSIS
+/* #include <safe.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern int unsafe(void);
+extern char *safe_getenv(const char *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/safe_getenv.c b/src/util/safe_getenv.c
new file mode 100644
index 0000000..04ca659
--- /dev/null
+++ b/src/util/safe_getenv.c
@@ -0,0 +1,41 @@
+/*++
+/* NAME
+/* safe_getenv 3
+/* SUMMARY
+/* guarded getenv()
+/* SYNOPSIS
+/* #include <safe.h>
+/*
+/* char *safe_getenv(const name)
+/* char *name;
+/* DESCRIPTION
+/* The \fBsafe_getenv\fR() routine reads the named variable from the
+/* environment, provided that the unsafe() routine agrees.
+/* SEE ALSO
+/* unsafe(3), detect non-user privileges
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include "safe.h"
+
+/* safe_getenv - read environment variable with guard */
+
+char *safe_getenv(const char *name)
+{
+ return (unsafe() == 0 ? getenv(name) : 0);
+}
diff --git a/src/util/safe_open.c b/src/util/safe_open.c
new file mode 100644
index 0000000..c7a80cf
--- /dev/null
+++ b/src/util/safe_open.c
@@ -0,0 +1,283 @@
+/*++
+/* NAME
+/* safe_open 3
+/* SUMMARY
+/* safely open or create regular file
+/* SYNOPSIS
+/* #include <safe_open.h>
+/*
+/* VSTREAM *safe_open(path, flags, mode, st, user, group, why)
+/* const char *path;
+/* int flags;
+/* mode_t mode;
+/* struct stat *st;
+/* uid_t user;
+/* gid_t group;
+/* VSTRING *why;
+/* DESCRIPTION
+/* safe_open() carefully opens or creates a file in a directory
+/* that may be writable by untrusted users. If a file is created
+/* it is given the specified ownership and permission attributes.
+/* If an existing file is opened it must not be a symbolic link,
+/* it must not be a directory, and it must have only one hard link.
+/*
+/* Arguments:
+/* .IP "path, flags, mode"
+/* These arguments are the same as with open(2). The O_EXCL flag
+/* must appear either in combination with O_CREAT, or not at all.
+/* .sp
+/* No change is made to the permissions of an existing file.
+/* .IP st
+/* Null pointer, or pointer to storage for the attributes of the
+/* opened file.
+/* .IP "user, group"
+/* File ownership for a file created by safe_open(). Specify -1
+/* in order to disable user and/or group ownership change.
+/* .sp
+/* No change is made to the ownership of an existing file.
+/* .IP why
+/* A VSTRING pointer for diagnostics.
+/* DIAGNOSTICS
+/* Panic: interface violations.
+/*
+/* A null result means there was a problem. The nature of the
+/* problem is returned via the \fIwhy\fR buffer; when an error
+/* cannot be reported via \fIerrno\fR, the generic value EPERM
+/* (operation not permitted) is used instead.
+/* HISTORY
+/* .fi
+/* .ad
+/* A safe open routine was discussed by Casper Dik in article
+/* <2rdb0s$568@mail.fwi.uva.nl>, posted to comp.security.unix
+/* (May 18, 1994).
+/*
+/* Olaf Kirch discusses how the lstat()/open()+fstat() test can
+/* be fooled by delaying the open() until the inode found with
+/* lstat() has been re-used for a sensitive file (article
+/* <20000103212443.A5807@monad.swb.de> posted to bugtraq on
+/* Jan 3, 2000). This can be a concern for a set-ugid process
+/* that runs under the control of a user and that can be
+/* manipulated with start/stop signals.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <safe_open.h>
+#include <warn_stat.h>
+
+/* safe_open_exist - open existing file */
+
+static VSTREAM *safe_open_exist(const char *path, int flags,
+ struct stat * fstat_st, VSTRING *why)
+{
+ struct stat local_statbuf;
+ struct stat lstat_st;
+ int saved_errno;
+ VSTREAM *fp;
+
+ /*
+ * Open an existing file.
+ */
+ if ((fp = vstream_fopen(path, flags & ~(O_CREAT | O_EXCL), 0)) == 0) {
+ saved_errno = errno;
+ vstring_sprintf(why, "cannot open file: %m");
+ errno = saved_errno;
+ return (0);
+ }
+
+ /*
+ * Examine the modes from the open file: it must have exactly one hard
+ * link (so that someone can't lure us into clobbering a sensitive file
+ * by making a hard link to it), and it must be a non-symlink file.
+ */
+ if (fstat_st == 0)
+ fstat_st = &local_statbuf;
+ if (fstat(vstream_fileno(fp), fstat_st) < 0) {
+ msg_fatal("%s: bad open file status: %m", path);
+ } else if (fstat_st->st_nlink != 1) {
+ vstring_sprintf(why, "file has %d hard links",
+ (int) fstat_st->st_nlink);
+ errno = EPERM;
+ } else if (S_ISDIR(fstat_st->st_mode)) {
+ vstring_sprintf(why, "file is a directory");
+ errno = EISDIR;
+ }
+
+ /*
+ * Look up the file again, this time using lstat(). Compare the fstat()
+ * (open file) modes with the lstat() modes. If there is any difference,
+ * either we followed a symlink while opening an existing file, someone
+ * quickly changed the number of hard links, or someone replaced the file
+ * after the open() call. The link and mode tests aren't really necessary
+ * in daemon processes. Set-uid programs, on the other hand, can be
+ * slowed down by arbitrary amounts, and there it would make sense to
+ * compare even more file attributes, such as the inode generation number
+ * on systems that have one.
+ *
+ * Grr. Solaris /dev/whatever is a symlink. We'll have to make an exception
+ * for symlinks owned by root. NEVER, NEVER, make exceptions for symlinks
+ * owned by a non-root user. This would open a security hole when
+ * delivering mail to a world-writable mailbox directory.
+ *
+ * Sebastian Krahmer of SuSE brought to my attention that some systems have
+ * changed their semantics of link(symlink, newpath), such that the
+ * result is a hardlink to the symlink. For this reason, we now also
+ * require that the symlink's parent directory is writable only by root.
+ */
+ else if (lstat(path, &lstat_st) < 0) {
+ vstring_sprintf(why, "file status changed unexpectedly: %m");
+ errno = EPERM;
+ } else if (S_ISLNK(lstat_st.st_mode)) {
+ if (lstat_st.st_uid == 0) {
+ VSTRING *parent_buf = vstring_alloc(100);
+ const char *parent_path = sane_dirname(parent_buf, path);
+ struct stat parent_st;
+ int parent_ok;
+
+ parent_ok = (stat(parent_path, &parent_st) == 0 /* not lstat */
+ && parent_st.st_uid == 0
+ && (parent_st.st_mode & (S_IWGRP | S_IWOTH)) == 0);
+ vstring_free(parent_buf);
+ if (parent_ok)
+ return (fp);
+ }
+ vstring_sprintf(why, "file is a symbolic link");
+ errno = EPERM;
+ } else if (fstat_st->st_dev != lstat_st.st_dev
+ || fstat_st->st_ino != lstat_st.st_ino
+#ifdef HAS_ST_GEN
+ || fstat_st->st_gen != lstat_st.st_gen
+#endif
+ || fstat_st->st_nlink != lstat_st.st_nlink
+ || fstat_st->st_mode != lstat_st.st_mode) {
+ vstring_sprintf(why, "file status changed unexpectedly");
+ errno = EPERM;
+ }
+
+ /*
+ * We are almost there...
+ */
+ else {
+ return (fp);
+ }
+
+ /*
+ * End up here in case of fstat()/lstat() problems or inconsistencies.
+ */
+ vstream_fclose(fp);
+ return (0);
+}
+
+/* safe_open_create - create new file */
+
+static VSTREAM *safe_open_create(const char *path, int flags, mode_t mode,
+ struct stat * st, uid_t user, gid_t group, VSTRING *why)
+{
+ VSTREAM *fp;
+
+ /*
+ * Create a non-existing file. This relies on O_CREAT | O_EXCL to not
+ * follow symbolic links.
+ */
+ if ((fp = vstream_fopen(path, flags | (O_CREAT | O_EXCL), mode)) == 0) {
+ vstring_sprintf(why, "cannot create file exclusively: %m");
+ return (0);
+ }
+
+ /*
+ * Optionally look up the file attributes.
+ */
+ if (st != 0 && fstat(vstream_fileno(fp), st) < 0)
+ msg_fatal("%s: bad open file status: %m", path);
+
+ /*
+ * Optionally change ownership after creating a new file. If there is a
+ * problem we should not attempt to delete the file. Something else may
+ * have opened the file in the mean time.
+ */
+#define CHANGE_OWNER(user, group) (user != (uid_t) -1 || group != (gid_t) -1)
+
+ if (CHANGE_OWNER(user, group)
+ && fchown(vstream_fileno(fp), user, group) < 0) {
+ msg_warn("%s: cannot change file ownership: %m", path);
+ }
+
+ /*
+ * We are almost there...
+ */
+ else {
+ return (fp);
+ }
+
+ /*
+ * End up here in case of trouble.
+ */
+ vstream_fclose(fp);
+ return (0);
+}
+
+/* safe_open - safely open or create file */
+
+VSTREAM *safe_open(const char *path, int flags, mode_t mode,
+ struct stat * st, uid_t user, gid_t group, VSTRING *why)
+{
+ VSTREAM *fp;
+
+ switch (flags & (O_CREAT | O_EXCL)) {
+
+ /*
+ * Open an existing file, carefully.
+ */
+ case 0:
+ return (safe_open_exist(path, flags, st, why));
+
+ /*
+ * Create a new file, carefully.
+ */
+ case O_CREAT | O_EXCL:
+ return (safe_open_create(path, flags, mode, st, user, group, why));
+
+ /*
+ * Open an existing file or create a new one, carefully. When opening
+ * an existing file, we are prepared to deal with "no file" errors
+ * only. When creating a file, we are prepared for "file exists"
+ * errors only. Any other error means we better give up trying.
+ */
+ case O_CREAT:
+ fp = safe_open_exist(path, flags, st, why);
+ if (fp == 0 && errno == ENOENT) {
+ fp = safe_open_create(path, flags, mode, st, user, group, why);
+ if (fp == 0 && errno == EEXIST)
+ fp = safe_open_exist(path, flags, st, why);
+ }
+ return (fp);
+
+ /*
+ * Interface violation. Sorry, but we must be strict.
+ */
+ default:
+ msg_panic("safe_open: O_EXCL flag without O_CREAT flag");
+ }
+}
diff --git a/src/util/safe_open.h b/src/util/safe_open.h
new file mode 100644
index 0000000..88b5344
--- /dev/null
+++ b/src/util/safe_open.h
@@ -0,0 +1,42 @@
+#ifndef _SAFE_OPEN_H_INCLUDED_
+#define _SAFE_OPEN_H_INCLUDED_
+
+/*++
+/* NAME
+/* safe_open 3h
+/* SUMMARY
+/* safely open or create regular file
+/* SYNOPSIS
+/* #include <safe_open.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <sys/stat.h>
+#include <fcntl.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern VSTREAM *safe_open(const char *, int, mode_t, struct stat *, uid_t, gid_t, VSTRING *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/sane_accept.c b/src/util/sane_accept.c
new file mode 100644
index 0000000..86e3d34
--- /dev/null
+++ b/src/util/sane_accept.c
@@ -0,0 +1,125 @@
+/*++
+/* NAME
+/* sane_accept 3
+/* SUMMARY
+/* sanitize accept() error returns
+/* SYNOPSIS
+/* #include <sane_accept.h>
+/*
+/* int sane_accept(sock, buf, len)
+/* int sock;
+/* struct sockaddr *buf;
+/* SOCKADDR_SIZE *len;
+/* DESCRIPTION
+/* sane_accept() implements the accept(2) socket call, and maps
+/* known harmless error results to EAGAIN.
+/*
+/* If the buf and len arguments are not null, then additional
+/* workarounds may be enabled that depend on the socket type.
+/* BUGS
+/* Bizarre systems may have other harmless error results. Such
+/* systems encourage programmers to ignore error results, and
+/* penalize programmers who code defensively.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <sys/socket.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "sane_accept.h"
+
+/* sane_accept - sanitize accept() error returns */
+
+int sane_accept(int sock, struct sockaddr *sa, SOCKADDR_SIZE *len)
+{
+ static int accept_ok_errors[] = {
+ EAGAIN,
+ ECONNREFUSED,
+ ECONNRESET,
+ EHOSTDOWN,
+ EHOSTUNREACH,
+ EINTR,
+ ENETDOWN,
+ ENETUNREACH,
+ ENOTCONN,
+ EWOULDBLOCK,
+ ENOBUFS, /* HPUX11 */
+ ECONNABORTED,
+#ifdef EPROTO
+ EPROTO, /* SunOS 5.5.1 */
+#endif
+ 0,
+ };
+ int count;
+ int err;
+ int fd;
+
+ /*
+ * XXX Solaris 2.4 accept() returns EPIPE when a UNIX-domain client has
+ * disconnected in the mean time. From then on, UNIX-domain sockets are
+ * hosed beyond recovery. There is no point treating this as a beneficial
+ * error result because the program would go into a tight loop.
+ *
+ * XXX Solaris 2.5.1 accept() returns EPROTO when a TCP client has
+ * disconnected in the mean time. Since there is no connection, it is
+ * safe to map the error code onto EAGAIN.
+ *
+ * XXX LINUX < 2.1 accept() wakes up before the three-way handshake is
+ * complete, so it can fail with ECONNRESET and other "false alarm"
+ * indications.
+ *
+ * XXX FreeBSD 4.2-STABLE accept() returns ECONNABORTED when a UNIX-domain
+ * client has disconnected in the mean time. The data that was sent with
+ * connect() write() close() is lost, even though the write() and close()
+ * reported successful completion. This was fixed shortly before FreeBSD
+ * 4.3.
+ *
+ * XXX HP-UX 11 returns ENOBUFS when the client has disconnected in the mean
+ * time.
+ */
+ if ((fd = accept(sock, sa, len)) < 0) {
+ for (count = 0; (err = accept_ok_errors[count]) != 0; count++) {
+ if (errno == err) {
+ errno = EAGAIN;
+ break;
+ }
+ }
+ }
+
+ /*
+ * XXX Solaris select() produces false read events, so that read() blocks
+ * forever on a blocking socket, and fails with EAGAIN on a non-blocking
+ * socket. Turning on keepalives will fix a blocking socket provided that
+ * the kernel's keepalive timer expires before the Postfix watchdog
+ * timer.
+ *
+ * XXX Work around NAT induced damage by sending a keepalive before an idle
+ * connection is expired. This requires that the kernel keepalive timer
+ * is set to a short time, like 100s.
+ */
+ else if (sa && (sa->sa_family == AF_INET
+#ifdef HAS_IPV6
+ || sa->sa_family == AF_INET6
+#endif
+ )) {
+ int on = 1;
+
+ (void) setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
+ (void *) &on, sizeof(on));
+ }
+ return (fd);
+}
diff --git a/src/util/sane_accept.h b/src/util/sane_accept.h
new file mode 100644
index 0000000..84cc360
--- /dev/null
+++ b/src/util/sane_accept.h
@@ -0,0 +1,29 @@
+#ifndef _SANE_ACCEPT_H_
+#define _SANE_ACCEPT_H_
+
+/*++
+/* NAME
+/* sane_accept 3h
+/* SUMMARY
+/* sanitize accept() error returns
+/* SYNOPSIS
+/* #include <sane_accept.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern int sane_accept(int, struct sockaddr *, SOCKADDR_SIZE *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/sane_basename.c b/src/util/sane_basename.c
new file mode 100644
index 0000000..6c3a4c1
--- /dev/null
+++ b/src/util/sane_basename.c
@@ -0,0 +1,181 @@
+/*++
+/* NAME
+/* sane_basename 3
+/* SUMMARY
+/* split pathname into last component and parent directory
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* char *sane_basename(buf, path)
+/* VSTRING *buf;
+/* const char *path;
+/*
+/* char *sane_dirname(buf, path)
+/* VSTRING *buf;
+/* const char *path;
+/* DESCRIPTION
+/* These functions split a pathname into its last component
+/* and its parent directory, excluding any trailing "/"
+/* characters from the input. The result is a pointer to "/"
+/* when the input is all "/" characters, or a pointer to "."
+/* when the input is a null pointer or zero-length string.
+/*
+/* sane_basename() and sane_dirname() differ as follows
+/* from standard basename() and dirname() implementations:
+/* .IP \(bu
+/* They can use caller-provided storage or private storage.
+/* .IP \(bu
+/* They never modify their input.
+/* .PP
+/* sane_basename() returns a pointer to string with the last
+/* pathname component.
+/*
+/* sane_dirname() returns a pointer to string with the parent
+/* directory. The result is a pointer to "." when the input
+/* contains no '/' character.
+/*
+/* Arguments:
+/* .IP buf
+/* Result storage. If a null pointer is specified, each function
+/* uses its own private memory that is overwritten upon each call.
+/* .IP path
+/* The input pathname.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this
+/* software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <vstring.h>
+#include <stringops.h>
+
+#define STR(x) vstring_str(x)
+
+/* sane_basename - skip directory prefix */
+
+char *sane_basename(VSTRING *bp, const char *path)
+{
+ static VSTRING *buf;
+ const char *first;
+ const char *last;
+
+ /*
+ * Your buffer or mine?
+ */
+ if (bp == 0) {
+ bp = buf;
+ if (bp == 0)
+ bp = buf = vstring_alloc(10);
+ }
+
+ /*
+ * Special case: return "." for null or zero-length input.
+ */
+ if (path == 0 || *path == 0)
+ return (STR(vstring_strcpy(bp, ".")));
+
+ /*
+ * Remove trailing '/' characters from input. Return "/" if input is all
+ * '/' characters.
+ */
+ last = path + strlen(path) - 1;
+ while (*last == '/') {
+ if (last == path)
+ return (STR(vstring_strcpy(bp, "/")));
+ last--;
+ }
+
+ /*
+ * The pathname does not end in '/'. Skip to last '/' character if any.
+ */
+ first = last - 1;
+ while (first >= path && *first != '/')
+ first--;
+
+ return (STR(vstring_strncpy(bp, first + 1, last - first)));
+}
+
+/* sane_dirname - keep directory prefix */
+
+char *sane_dirname(VSTRING *bp, const char *path)
+{
+ static VSTRING *buf;
+ const char *last;
+
+ /*
+ * Your buffer or mine?
+ */
+ if (bp == 0) {
+ bp = buf;
+ if (bp == 0)
+ bp = buf = vstring_alloc(10);
+ }
+
+ /*
+ * Special case: return "." for null or zero-length input.
+ */
+ if (path == 0 || *path == 0)
+ return (STR(vstring_strcpy(bp, ".")));
+
+ /*
+ * Remove trailing '/' characters from input. Return "/" if input is all
+ * '/' characters.
+ */
+ last = path + strlen(path) - 1;
+ while (*last == '/') {
+ if (last == path)
+ return (STR(vstring_strcpy(bp, "/")));
+ last--;
+ }
+
+ /*
+ * This pathname does not end in '/'. Skip to last '/' character if any.
+ */
+ while (last >= path && *last != '/')
+ last--;
+ if (last < path) /* no '/' */
+ return (STR(vstring_strcpy(bp, ".")));
+
+ /*
+ * Strip trailing '/' characters from dirname (not strictly needed).
+ */
+ while (last > path && *last == '/')
+ last--;
+
+ return (STR(vstring_strncpy(bp, path, last - path + 1)));
+}
+
+#ifdef TEST
+#include <vstring_vstream.h>
+
+int main(int argc, char **argv)
+{
+ VSTRING *buf = vstring_alloc(10);
+ char *dir;
+ char *base;
+
+ while (vstring_get_nonl(buf, VSTREAM_IN) > 0) {
+ dir = sane_dirname((VSTRING *) 0, STR(buf));
+ base = sane_basename((VSTRING *) 0, STR(buf));
+ vstream_printf("input=\"%s\" dir=\"%s\" base=\"%s\"\n",
+ STR(buf), dir, base);
+ }
+ vstream_fflush(VSTREAM_OUT);
+ vstring_free(buf);
+ return (0);
+}
+
+#endif
diff --git a/src/util/sane_basename.in b/src/util/sane_basename.in
new file mode 100644
index 0000000..75d613d
--- /dev/null
+++ b/src/util/sane_basename.in
@@ -0,0 +1,18 @@
+///
+/
+fo2///
+fo2/
+fo2
+///fo2
+/fo2
+///fo2///bar///
+fo2///bar///
+fo2///bar/
+fo2/bar
+
+/usr/lib
+/usr/
+usr
+/
+.
+..
diff --git a/src/util/sane_basename.ref b/src/util/sane_basename.ref
new file mode 100644
index 0000000..8edcfcd
--- /dev/null
+++ b/src/util/sane_basename.ref
@@ -0,0 +1,18 @@
+input="///" dir="/" base="/"
+input="/" dir="/" base="/"
+input="fo2///" dir="." base="fo2"
+input="fo2/" dir="." base="fo2"
+input="fo2" dir="." base="fo2"
+input="///fo2" dir="/" base="fo2"
+input="/fo2" dir="/" base="fo2"
+input="///fo2///bar///" dir="///fo2" base="bar"
+input="fo2///bar///" dir="fo2" base="bar"
+input="fo2///bar/" dir="fo2" base="bar"
+input="fo2/bar" dir="fo2" base="bar"
+input="" dir="." base="."
+input="/usr/lib" dir="/usr" base="lib"
+input="/usr/" dir="/" base="usr"
+input="usr" dir="." base="usr"
+input="/" dir="/" base="/"
+input="." dir="." base="."
+input=".." dir="." base=".."
diff --git a/src/util/sane_connect.c b/src/util/sane_connect.c
new file mode 100644
index 0000000..a15204b
--- /dev/null
+++ b/src/util/sane_connect.c
@@ -0,0 +1,65 @@
+/*++
+/* NAME
+/* sane_connect 3
+/* SUMMARY
+/* sanitize connect() results
+/* SYNOPSIS
+/* #include <sane_connect.h>
+/*
+/* int sane_connect(sock, buf, len)
+/* int sock;
+/* struct sockaddr *buf;
+/* SOCKADDR_SIZE *len;
+/* DESCRIPTION
+/* sane_connect() implements the connect(2) socket call, and maps
+/* known harmless error results to EAGAIN.
+/* BUGS
+/* Bizarre systems may have other harmless error results. Such
+/* systems encourage programmers to ignore error results, and
+/* penalize programmers who code defensively.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <sys/socket.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "sane_connect.h"
+
+/* sane_connect - sanitize connect() results */
+
+int sane_connect(int sock, struct sockaddr *sa, SOCKADDR_SIZE len)
+{
+
+ /*
+ * XXX Solaris select() produces false read events, so that read() blocks
+ * forever on a blocking socket, and fails with EAGAIN on a non-blocking
+ * socket. Turning on keepalives will fix a blocking socket provided that
+ * the kernel's keepalive timer expires before the Postfix watchdog
+ * timer.
+ *
+ * XXX Work around NAT induced damage by sending a keepalive before an idle
+ * connection is expired. This requires that the kernel keepalive timer
+ * is set to a short time, like 100s.
+ */
+ if (sa->sa_family == AF_INET) {
+ int on = 1;
+
+ (void) setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE,
+ (void *) &on, sizeof(on));
+ }
+ return (connect(sock, sa, len));
+}
diff --git a/src/util/sane_connect.h b/src/util/sane_connect.h
new file mode 100644
index 0000000..1f023b0
--- /dev/null
+++ b/src/util/sane_connect.h
@@ -0,0 +1,29 @@
+#ifndef _SANE_CONNECT_H_
+#define _SANE_CONNECT_H_
+
+/*++
+/* NAME
+/* sane_connect 3h
+/* SUMMARY
+/* sanitize connect() results
+/* SYNOPSIS
+/* #include <sane_connect.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern int sane_connect(int, struct sockaddr *, SOCKADDR_SIZE);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/sane_fsops.h b/src/util/sane_fsops.h
new file mode 100644
index 0000000..28e7f67
--- /dev/null
+++ b/src/util/sane_fsops.h
@@ -0,0 +1,35 @@
+#ifndef _SANE_FSOPS_H_
+#define _SANE_FSOPS_H_
+
+/*++
+/* NAME
+/* sane_rename 3h
+/* SUMMARY
+/* sanitize rename() error returns
+/* SYNOPSIS
+/* #include <sane_rename.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern int WARN_UNUSED_RESULT sane_rename(const char *, const char *);
+extern int WARN_UNUSED_RESULT sane_link(const char *, const char *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/sane_link.c b/src/util/sane_link.c
new file mode 100644
index 0000000..40fd56d
--- /dev/null
+++ b/src/util/sane_link.c
@@ -0,0 +1,72 @@
+/*++
+/* NAME
+/* sane_link 3
+/* SUMMARY
+/* sanitize link() error returns
+/* SYNOPSIS
+/* #include <sane_fsops.h>
+/*
+/* int sane_link(from, to)
+/* const char *from;
+/* const char *to;
+/* DESCRIPTION
+/* sane_link() implements the link(2) system call, and works
+/* around some errors that are possible with NFS file systems.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "sane_fsops.h"
+#include "warn_stat.h"
+
+/* sane_link - sanitize link() error returns */
+
+int sane_link(const char *from, const char *to)
+{
+ const char *myname = "sane_link";
+ int saved_errno;
+ struct stat from_st;
+ struct stat to_st;
+
+ /*
+ * Normal case: link() succeeds.
+ */
+ if (link(from, to) >= 0)
+ return (0);
+
+ /*
+ * Woops. Save errno, and see if the error is an NFS artifact. If it is,
+ * pretend the error never happened.
+ */
+ saved_errno = errno;
+ if (stat(from, &from_st) >= 0 && stat(to, &to_st) >= 0
+ && from_st.st_dev == to_st.st_dev
+ && from_st.st_ino == to_st.st_ino) {
+ msg_info("%s(%s,%s): worked around spurious NFS error",
+ myname, from, to);
+ return (0);
+ }
+
+ /*
+ * Nope, it didn't. Restore errno and report the error.
+ */
+ errno = saved_errno;
+ return (-1);
+}
diff --git a/src/util/sane_rename.c b/src/util/sane_rename.c
new file mode 100644
index 0000000..3b301bd
--- /dev/null
+++ b/src/util/sane_rename.c
@@ -0,0 +1,69 @@
+/*++
+/* NAME
+/* sane_rename 3
+/* SUMMARY
+/* sanitize rename() error returns
+/* SYNOPSIS
+/* #include <sane_fsops.h>
+/*
+/* int sane_rename(old, new)
+/* const char *from;
+/* const char *to;
+/* DESCRIPTION
+/* sane_rename() implements the rename(2) system call, and works
+/* around some errors that are possible with NFS file systems.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <sys/stat.h>
+#include <errno.h>
+#include <stdio.h> /* rename(2) syscall in stdio.h? */
+
+/* Utility library. */
+
+#include "msg.h"
+#include "sane_fsops.h"
+#include "warn_stat.h"
+
+/* sane_rename - sanitize rename() error returns */
+
+int sane_rename(const char *from, const char *to)
+{
+ const char *myname = "sane_rename";
+ int saved_errno;
+ struct stat st;
+
+ /*
+ * Normal case: rename() succeeds.
+ */
+ if (rename(from, to) >= 0)
+ return (0);
+
+ /*
+ * Woops. Save errno, and see if the error is an NFS artifact. If it is,
+ * pretend the error never happened.
+ */
+ saved_errno = errno;
+ if (stat(from, &st) < 0 && stat(to, &st) >= 0) {
+ msg_info("%s(%s,%s): worked around spurious NFS error",
+ myname, from, to);
+ return (0);
+ }
+
+ /*
+ * Nope, it didn't. Restore errno and report the error.
+ */
+ errno = saved_errno;
+ return (-1);
+}
diff --git a/src/util/sane_socketpair.c b/src/util/sane_socketpair.c
new file mode 100644
index 0000000..a889934
--- /dev/null
+++ b/src/util/sane_socketpair.c
@@ -0,0 +1,71 @@
+/*++
+/* NAME
+/* sane_socketpair 3
+/* SUMMARY
+/* sanitize socketpair() error returns
+/* SYNOPSIS
+/* #include <sane_socketpair.h>
+/*
+/* int sane_socketpair(domain, type, protocol, result)
+/* int domain;
+/* int type;
+/* int protocol;
+/* int *result;
+/* DESCRIPTION
+/* sane_socketpair() implements the socketpair(2) socket call, and
+/* skips over silly error results such as EINTR.
+/* BUGS
+/* Bizarre systems may have other harmless error results. Such
+/* systems encourage programmers to ignore error results, and
+/* penalize programmers who code defensively.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <sys/socket.h>
+#include <unistd.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "sane_socketpair.h"
+
+/* sane_socketpair - sanitize socketpair() error returns */
+
+int sane_socketpair(int domain, int type, int protocol, int *result)
+{
+ static int socketpair_ok_errors[] = {
+ EINTR,
+ 0,
+ };
+ int count;
+ int err;
+ int ret;
+
+ /*
+ * Solaris socketpair() can fail with EINTR.
+ */
+ while ((ret = socketpair(domain, type, protocol, result)) < 0) {
+ for (count = 0; /* void */ ; count++) {
+ if ((err = socketpair_ok_errors[count]) == 0)
+ return (ret);
+ if (errno == err) {
+ msg_warn("socketpair: %m (trying again)");
+ sleep(1);
+ break;
+ }
+ }
+ }
+ return (ret);
+}
diff --git a/src/util/sane_socketpair.h b/src/util/sane_socketpair.h
new file mode 100644
index 0000000..d54a73d
--- /dev/null
+++ b/src/util/sane_socketpair.h
@@ -0,0 +1,34 @@
+#ifndef _SANE_SOCKETPAIR_H_
+#define _SANE_SOCKETPAIR_H_
+
+/*++
+/* NAME
+/* sane_socketpair 3h
+/* SUMMARY
+/* sanitize socketpair() error returns
+/* SYNOPSIS
+/* #include <sane_socketpair.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern int WARN_UNUSED_RESULT sane_socketpair(int, int, int, int *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/sane_strtol.c b/src/util/sane_strtol.c
new file mode 100644
index 0000000..b7435dd
--- /dev/null
+++ b/src/util/sane_strtol.c
@@ -0,0 +1,59 @@
+/*++
+/* NAME
+/* sane_strtol 3
+/* SUMMARY
+/* strtol() with mandatory errno reset
+/* SYNOPSIS
+/* #include <sane_strtol.h>
+/*
+/* long sane_strtol(
+/* const char *start,
+/* char **restrict end,
+/* int base)
+/*
+/* unsigned long sane_strtoul(
+/* const char *start,
+/* char **restrict end,
+/* int base)
+/* DESCRIPTION
+/* These functions are wrappers around the strtol() and strtoul()
+/* standard library functions that reset errno first, so that a
+/* prior ERANGE error won't cause false errors.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <stdlib.h>
+#include <errno.h>
+
+ /*
+ * Utility library.
+ */
+#include <sane_strtol.h>
+
+/* sane_strtol - strtol() with mandatory initialization */
+
+long sane_strtol(const char *start, char **end, int base)
+{
+ errno = 0;
+ return (strtol(start, end, base));
+}
+
+/* sane_strtoul - strtoul() with mandatory initialization */
+
+unsigned long sane_strtoul(const char *start, char **end, int base)
+{
+ errno = 0;
+ return (strtoul(start, end, base));
+}
diff --git a/src/util/sane_strtol.h b/src/util/sane_strtol.h
new file mode 100644
index 0000000..ac08316
--- /dev/null
+++ b/src/util/sane_strtol.h
@@ -0,0 +1,26 @@
+/*++
+/* NAME
+/* sane_strtol 3h
+/* SUMMARY
+/* strtol() with mandatory errno reset
+/* SYNOPSIS
+/* #include <sane_strtol.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External API.
+ */
+extern long sane_strtol(const char *start, char **end, int);
+extern unsigned long sane_strtoul(const char *start, char **end, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
diff --git a/src/util/sane_time.c b/src/util/sane_time.c
new file mode 100644
index 0000000..cc86de2
--- /dev/null
+++ b/src/util/sane_time.c
@@ -0,0 +1,127 @@
+/*++
+/* NAME
+/* sane_time 3
+/* SUMMARY
+/* time(2) with backward time jump protection.
+/* SYNOPSIS
+/* #include <sane_time.h>
+/*
+/* time_t sane_time(void)
+/*
+/* DESCRIPTION
+/* This module provides time(2) like call for applications
+/* which need monotonically increasing time function rather
+/* than the real exact time. It eliminates the need for various
+/* workarounds all over the application which would handle
+/* potential problems if time suddenly jumps backward.
+/* Instead we choose to deal with this problem inside this
+/* module and let the application focus on its own tasks.
+/*
+/* sane_time() returns the current timestamp as obtained from
+/* time(2) call, at least most of the time. In case this routine
+/* detects that time has jumped backward, it keeps returning
+/* whatever timestamp it returned before, until this timestamp
+/* and the time(2) timestamp become synchronized again.
+/* Additionally, the returned timestamp is slowly increased to
+/* prevent the faked clock from freezing for too long.
+/* SEE ALSO
+/* time(2) get current time
+/* DIAGNOSTICS
+/* Warning message is logged if backward time jump is detected.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Patrik Rak
+/* Modra 6
+/* 155 00, Prague, Czech Republic
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+
+/* Application-specific. */
+
+#include "sane_time.h"
+
+/*
+ * How many times shall we slow down the real clock when recovering from
+ * time jump.
+ */
+#define SLEW_FACTOR 2
+
+/* sane_time - get current time, protected against time warping */
+
+time_t sane_time(void)
+{
+ time_t now;
+ static time_t last_time, last_real;
+ long delta;
+ static int fraction;
+ static int warned;
+
+ now = time((time_t *) 0);
+
+ if ((delta = now - last_time) < 0 && last_time != 0) {
+ if ((delta = now - last_real) < 0) {
+ msg_warn("%sbackward time jump detected -- slewing clock",
+ warned++ ? "another " : "");
+ } else {
+ delta += fraction;
+ last_time += delta / SLEW_FACTOR;
+ fraction = delta % SLEW_FACTOR;
+ }
+ } else {
+ if (warned) {
+ warned = 0;
+ msg_warn("backward time jump recovered -- back to normality");
+ fraction = 0;
+ }
+ last_time = now;
+ }
+ last_real = now;
+
+ return (last_time);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program. Repeatedly print current system time and
+ * time returned by sane_time(). Meanwhile, try stepping your system clock
+ * back and forth to see what happens.
+ */
+
+#include <stdlib.h>
+#include <msg_vstream.h>
+#include <iostuff.h> /* doze() */
+
+int main(int argc, char **argv)
+{
+ int delay = 1000000;
+ time_t now;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ if (argc == 2 && (delay = atol(argv[1]) * 1000) > 0)
+ /* void */ ;
+ else if (argc != 1)
+ msg_fatal("usage: %s [delay in ms (default 1 second)]", argv[0]);
+
+ for (;;) {
+ now = time((time_t *) 0);
+ vstream_printf("real: %s", ctime(&now));
+ now = sane_time();
+ vstream_printf("fake: %s\n", ctime(&now));
+ vstream_fflush(VSTREAM_OUT);
+ doze(delay);
+ }
+}
+
+#endif
diff --git a/src/util/sane_time.h b/src/util/sane_time.h
new file mode 100644
index 0000000..0fe50d9
--- /dev/null
+++ b/src/util/sane_time.h
@@ -0,0 +1,34 @@
+#ifndef _SANE_TIME_H_
+#define _SANE_TIME_H_
+
+/*++
+/* NAME
+/* sane_time 3h
+/* SUMMARY
+/* time(2) with backward time jump protection
+/* SYNOPSIS
+/* #include <sane_time.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <time.h>
+
+ /*
+ * External interface.
+ */
+extern time_t sane_time(void);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Patrik Rak
+/* Modra 6
+/* 155 00, Prague, Czech Republic
+/*--*/
+
+#endif
diff --git a/src/util/scan_dir.c b/src/util/scan_dir.c
new file mode 100644
index 0000000..d94c674
--- /dev/null
+++ b/src/util/scan_dir.c
@@ -0,0 +1,216 @@
+/*++
+/* NAME
+/* scan_dir 3
+/* SUMMARY
+/* directory scanning
+/* SYNOPSIS
+/* #include <scan_dir.h>
+/*
+/* SCAN_DIR *scan_dir_open(path)
+/* const char *path;
+/*
+/* char *scan_dir_next(scan)
+/* SCAN_DIR *scan;
+/*
+/* char *scan_dir_path(scan)
+/* SCAN_DIR *scan;
+/*
+/* void scan_push(scan, entry)
+/* SCAN_DIR *scan;
+/* const char *entry;
+/*
+/* SCAN_DIR *scan_pop(scan)
+/* SCAN_DIR *scan;
+/*
+/* SCAN_DIR *scan_dir_close(scan)
+/* SCAN_DIR *scan;
+/* DESCRIPTION
+/* These functions scan directories for names. The "." and
+/* ".." names are skipped. Essentially, this is <dirent>
+/* extended with error handling and with knowledge of the
+/* name of the directory being scanned.
+/*
+/* scan_dir_open() opens the named directory and
+/* returns a handle for subsequent use.
+/*
+/* scan_dir_close() terminates the directory scan, cleans up
+/* and returns a null pointer.
+/*
+/* scan_dir_next() returns the next requested object in the specified
+/* directory. It skips the "." and ".." entries.
+/*
+/* scan_dir_path() returns the name of the directory being scanned.
+/*
+/* scan_dir_push() causes the specified directory scan to enter the
+/* named subdirectory.
+/*
+/* scan_dir_pop() leaves the directory being scanned and returns
+/* to the previous one. The result is the argument, null if no
+/* previous directory information is available.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#ifdef HAVE_DIRENT_H
+#include <dirent.h>
+#else
+#define dirent direct
+#ifdef HAVE_SYS_NDIR_H
+#include <sys/ndir.h>
+#endif
+#ifdef HAVE_SYS_DIR_H
+#include <sys/dir.h>
+#endif
+#ifdef HAVE_NDIR_H
+#include <ndir.h>
+#endif
+#endif
+#include <string.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "mymalloc.h"
+#include "stringops.h"
+#include "vstring.h"
+#include "scan_dir.h"
+
+ /*
+ * The interface is based on an opaque structure, so we don't have to expose
+ * the user to the guts. Subdirectory info sits in front of parent directory
+ * info: a simple last-in, first-out list.
+ */
+typedef struct SCAN_INFO SCAN_INFO;
+
+struct SCAN_INFO {
+ char *path; /* directory name */
+ DIR *dir; /* directory structure */
+ SCAN_INFO *parent; /* linkage */
+};
+struct SCAN_DIR {
+ SCAN_INFO *current; /* current scan */
+};
+
+#define SCAN_DIR_PATH(scan) (scan->current->path)
+#define STR(x) vstring_str(x)
+
+/* scan_dir_path - return the path of the directory being read. */
+
+char *scan_dir_path(SCAN_DIR *scan)
+{
+ return (SCAN_DIR_PATH(scan));
+}
+
+/* scan_dir_push - enter directory */
+
+void scan_dir_push(SCAN_DIR *scan, const char *path)
+{
+ const char *myname = "scan_dir_push";
+ SCAN_INFO *info;
+
+ info = (SCAN_INFO *) mymalloc(sizeof(*info));
+ if (scan->current)
+ info->path = concatenate(SCAN_DIR_PATH(scan), "/", path, (char *) 0);
+ else
+ info->path = mystrdup(path);
+ if ((info->dir = opendir(info->path)) == 0)
+ msg_fatal("%s: open directory %s: %m", myname, info->path);
+ if (msg_verbose > 1)
+ msg_info("%s: open %s", myname, info->path);
+ info->parent = scan->current;
+ scan->current = info;
+}
+
+/* scan_dir_pop - leave directory */
+
+SCAN_DIR *scan_dir_pop(SCAN_DIR *scan)
+{
+ const char *myname = "scan_dir_pop";
+ SCAN_INFO *info = scan->current;
+ SCAN_INFO *parent;
+
+ if (info == 0)
+ return (0);
+ parent = info->parent;
+ if (closedir(info->dir))
+ msg_fatal("%s: close directory %s: %m", myname, info->path);
+ if (msg_verbose > 1)
+ msg_info("%s: close %s", myname, info->path);
+ myfree(info->path);
+ myfree((void *) info);
+ scan->current = parent;
+ return (parent ? scan : 0);
+}
+
+/* scan_dir_open - start directory scan */
+
+SCAN_DIR *scan_dir_open(const char *path)
+{
+ SCAN_DIR *scan;
+
+ scan = (SCAN_DIR *) mymalloc(sizeof(*scan));
+ scan->current = 0;
+ scan_dir_push(scan, path);
+ return (scan);
+}
+
+/* scan_dir_next - find next entry */
+
+char *scan_dir_next(SCAN_DIR *scan)
+{
+ const char *myname = "scan_dir_next";
+ SCAN_INFO *info = scan->current;
+ struct dirent *dp;
+
+#define STREQ(x,y) (strcmp((x),(y)) == 0)
+
+ if (info) {
+
+ /*
+ * Fix 20150421: readdir() does not reset errno after reaching the
+ * end-of-directory. This dates back all the way to the initial
+ * implementation of 19970309.
+ */
+ errno = 0;
+ while ((dp = readdir(info->dir)) != 0) {
+ if (STREQ(dp->d_name, ".") || STREQ(dp->d_name, "..")) {
+ if (msg_verbose > 1)
+ msg_info("%s: skip %s", myname, dp->d_name);
+ continue;
+ } else {
+ if (msg_verbose > 1)
+ msg_info("%s: found %s", myname, dp->d_name);
+ return (dp->d_name);
+ }
+ }
+ }
+ return (0);
+}
+
+/* scan_dir_close - terminate directory scan */
+
+SCAN_DIR *scan_dir_close(SCAN_DIR *scan)
+{
+ while (scan->current)
+ scan_dir_pop(scan);
+ myfree((void *) scan);
+ return (0);
+}
diff --git a/src/util/scan_dir.h b/src/util/scan_dir.h
new file mode 100644
index 0000000..8f3bf8b
--- /dev/null
+++ b/src/util/scan_dir.h
@@ -0,0 +1,37 @@
+#ifndef _SCAN_DIR_H_INCLUDED_
+#define _SCAN_DIR_H_INCLUDED_
+
+/*++
+/* NAME
+/* scan_dir 3h
+/* SUMMARY
+/* directory scanner
+/* SYNOPSIS
+/* #include <scan_dir.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * The directory scanner interface.
+ */
+typedef struct SCAN_DIR SCAN_DIR;
+
+extern SCAN_DIR *scan_dir_open(const char *);
+extern char *scan_dir_next(SCAN_DIR *);
+extern char *scan_dir_path(SCAN_DIR *);
+extern void scan_dir_push(SCAN_DIR *, const char *);
+extern SCAN_DIR *scan_dir_pop(SCAN_DIR *);
+extern SCAN_DIR *scan_dir_close(SCAN_DIR *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/select_bug.c b/src/util/select_bug.c
new file mode 100644
index 0000000..9b479ca
--- /dev/null
+++ b/src/util/select_bug.c
@@ -0,0 +1,95 @@
+/*++
+/* NAME
+/* select_bug 1
+/* SUMMARY
+/* select test program
+/* SYNOPSIS
+/* select_bug
+/* DESCRIPTION
+/* select_bug forks child processes that perform select()
+/* on a shared socket, and sees if a wakeup affects other
+/* processes selecting on a different socket or stdin.
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h> /* bzero() prototype for 44BSD */
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+
+static pid_t fork_and_read_select(const char *what, int delay, int fd)
+{
+ struct timeval tv;
+ pid_t pid;
+ fd_set readfds;
+
+ switch (pid = fork()) {
+ case -1:
+ msg_fatal("fork: %m");
+ case 0:
+ tv.tv_sec = delay;
+ tv.tv_usec = 0;
+ FD_ZERO(&readfds);
+ FD_SET(fd, &readfds);
+ switch (select(fd + 1, &readfds, (fd_set *) 0, &readfds, &tv)) {
+ case -1:
+ msg_fatal("select: %m");
+ case 0:
+ msg_info("%s select timed out", what);
+ exit(0);
+ default:
+ msg_info("%s select wakeup", what);
+ exit(0);
+ }
+ default:
+ return (pid);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int pair1[2];
+ int pair2[2];
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+#define DELAY 1
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair1) < 0)
+ msg_fatal("socketpair: %m");
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair2) < 0)
+ msg_fatal("socketpair: %m");
+
+ vstream_printf("Doing multiple select on socket1, then write to it...\n");
+ vstream_fflush(VSTREAM_OUT);
+ fork_and_read_select("socket1", DELAY, pair1[0]); /* one */
+ fork_and_read_select("socket1", DELAY, pair1[0]); /* two */
+ fork_and_read_select("socket2", DELAY, pair2[0]);
+ fork_and_read_select("stdin", DELAY, 0);
+ if (write(pair1[1], "", 1) != 1)
+ msg_fatal("write: %m");
+ while (wait((int *) 0) >= 0)
+ /* void */ ;
+ return (0);
+}
diff --git a/src/util/set_eugid.c b/src/util/set_eugid.c
new file mode 100644
index 0000000..ef35380
--- /dev/null
+++ b/src/util/set_eugid.c
@@ -0,0 +1,70 @@
+/*++
+/* NAME
+/* set_eugid 3
+/* SUMMARY
+/* set effective user and group attributes
+/* SYNOPSIS
+/* #include <set_eugid.h>
+/*
+/* void set_eugid(euid, egid)
+/* uid_t euid;
+/* gid_t egid;
+/*
+/* void SAVE_AND_SET_EUGID(uid, gid)
+/* uid_t uid;
+/* gid_t gid;
+/*
+/* void RESTORE_SAVED_EUGID()
+/* DESCRIPTION
+/* set_eugid() sets the effective user and group process attributes
+/* and updates the process group access list to be just the specified
+/* effective group id.
+/*
+/* SAVE_AND_SET_EUGID() opens a block that executes with the
+/* specified privilege. RESTORE_SAVED_EUGID() closes the block.
+/* DIAGNOSTICS
+/* All system call errors are fatal.
+/* SEE ALSO
+/* seteuid(2), setegid(2), setgroups(2)
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <unistd.h>
+#include <grp.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "set_eugid.h"
+
+/* set_eugid - set effective user and group attributes */
+
+void set_eugid(uid_t euid, gid_t egid)
+{
+ int saved_errno = errno;
+
+ if (geteuid() != 0)
+ if (seteuid(0))
+ msg_fatal("set_eugid: seteuid(0): %m");
+ if (setegid(egid) < 0)
+ msg_fatal("set_eugid: setegid(%ld): %m", (long) egid);
+ if (setgroups(1, &egid) < 0)
+ msg_fatal("set_eugid: setgroups(%ld): %m", (long) egid);
+ if (euid != 0 && seteuid(euid) < 0)
+ msg_fatal("set_eugid: seteuid(%ld): %m", (long) euid);
+ if (msg_verbose)
+ msg_info("set_eugid: euid %ld egid %ld", (long) euid, (long) egid);
+ errno = saved_errno;
+}
diff --git a/src/util/set_eugid.h b/src/util/set_eugid.h
new file mode 100644
index 0000000..97a523e
--- /dev/null
+++ b/src/util/set_eugid.h
@@ -0,0 +1,43 @@
+#ifndef _SET_EUGID_H_INCLUDED_
+#define _SET_EUGID_H_INCLUDED_
+
+/*++
+/* NAME
+/* set_eugid 3h
+/* SUMMARY
+/* set effective user and group attributes
+/* SYNOPSIS
+/* #include <set_eugid.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern void set_eugid(uid_t, gid_t);
+
+ /*
+ * The following macros open and close a block that runs at a different
+ * privilege level. To make mistakes with stray curly braces less likely, we
+ * shape the macros below as the head and tail of a do-while loop.
+ */
+#define SAVE_AND_SET_EUGID(uid, gid) do { \
+ uid_t __set_eugid_uid = geteuid(); \
+ gid_t __set_eugid_gid = getegid(); \
+ set_eugid((uid), (gid));
+
+#define RESTORE_SAVED_EUGID() \
+ set_eugid(__set_eugid_uid, __set_eugid_gid); \
+ } while (0)
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/set_ugid.c b/src/util/set_ugid.c
new file mode 100644
index 0000000..bbcb901
--- /dev/null
+++ b/src/util/set_ugid.c
@@ -0,0 +1,61 @@
+/*++
+/* NAME
+/* set_ugid 3
+/* SUMMARY
+/* set real, effective and saved user and group attributes
+/* SYNOPSIS
+/* #include <set_ugid.h>
+/*
+/* void set_ugid(uid, gid)
+/* uid_t uid;
+/* gid_t gid;
+/* DESCRIPTION
+/* set_ugid() sets the real, effective and saved user and group process
+/* attributes and updates the process group access list to be just the
+/* user's primary group. This operation is irreversible.
+/* DIAGNOSTICS
+/* All system call errors are fatal.
+/* SEE ALSO
+/* setuid(2), setgid(2), setgroups(2)
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <unistd.h>
+#include <grp.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "set_ugid.h"
+
+/* set_ugid - set real, effective and saved user and group attributes */
+
+void set_ugid(uid_t uid, gid_t gid)
+{
+ int saved_errno = errno;
+
+ if (geteuid() != 0)
+ if (seteuid(0) < 0)
+ msg_fatal("seteuid(0): %m");
+ if (setgid(gid) < 0)
+ msg_fatal("setgid(%ld): %m", (long) gid);
+ if (setgroups(1, &gid) < 0)
+ msg_fatal("setgroups(1, &%ld): %m", (long) gid);
+ if (setuid(uid) < 0)
+ msg_fatal("setuid(%ld): %m", (long) uid);
+ if (msg_verbose > 1)
+ msg_info("setugid: uid %ld gid %ld", (long) uid, (long) gid);
+ errno = saved_errno;
+}
diff --git a/src/util/set_ugid.h b/src/util/set_ugid.h
new file mode 100644
index 0000000..e752beb
--- /dev/null
+++ b/src/util/set_ugid.h
@@ -0,0 +1,29 @@
+#ifndef _SET_UGID_H_INCLUDED_
+#define _SET_UGID_H_INCLUDED_
+
+/*++
+/* NAME
+/* set_ugid 3h
+/* SUMMARY
+/* set real, effective and saved user and group attributes
+/* SYNOPSIS
+/* #include <set_ugid.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern void set_ugid(uid_t, gid_t);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/sigdelay.c b/src/util/sigdelay.c
new file mode 100644
index 0000000..6f07a22
--- /dev/null
+++ b/src/util/sigdelay.c
@@ -0,0 +1,118 @@
+/*++
+/* NAME
+/* sigdelay 3
+/* SUMMARY
+/* delay/resume signal delivery
+/* SYNOPSIS
+/* #include <sigdelay.h>
+/*
+/* void sigdelay()
+/*
+/* void sigresume()
+/* DESCRIPTION
+/* sigdelay() delays delivery of signals. Signals that
+/* arrive in the mean time will be queued.
+/*
+/* sigresume() resumes delivery of signals. Signals that have
+/* arrived in the mean time will be delivered.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* BUGS
+/* The signal queue may be really short (as in: one per signal type).
+/*
+/* Some signals such as SIGKILL cannot be blocked.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <signal.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "posix_signals.h"
+#include "sigdelay.h"
+
+/* Application-specific. */
+
+static sigset_t saved_sigmask;
+static sigset_t block_sigmask;
+static int suspending;
+static int siginit_done;
+
+/* siginit - compute signal mask only once */
+
+static void siginit(void)
+{
+ int sig;
+
+ siginit_done = 1;
+ sigemptyset(&block_sigmask);
+ for (sig = 1; sig < NSIG; sig++)
+ sigaddset(&block_sigmask, sig);
+}
+
+/* sigresume - deliver delayed signals and disable signal delay */
+
+void sigresume(void)
+{
+ if (suspending != 0) {
+ suspending = 0;
+ if (sigprocmask(SIG_SETMASK, &saved_sigmask, (sigset_t *) 0) < 0)
+ msg_fatal("sigresume: sigprocmask: %m");
+ }
+}
+
+/* sigdelay - save signal mask and block all signals */
+
+void sigdelay(void)
+{
+ if (siginit_done == 0)
+ siginit();
+ if (suspending == 0) {
+ suspending = 1;
+ if (sigprocmask(SIG_BLOCK, &block_sigmask, &saved_sigmask) < 0)
+ msg_fatal("sigdelay: sigprocmask: %m");
+ }
+}
+
+#ifdef TEST
+
+ /*
+ * Test program - press Ctrl-C twice while signal delivery is delayed, and
+ * see how many signals are delivered when signal delivery is resumed.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+static void gotsig(int sig)
+{
+ printf("Got signal %d\n", sig);
+}
+
+int main(int unused_argc, char **unused_argv)
+{
+ signal(SIGINT, gotsig);
+ signal(SIGQUIT, gotsig);
+
+ printf("Delaying signal delivery\n");
+ sigdelay();
+ sleep(5);
+ printf("Resuming signal delivery\n");
+ sigresume();
+ exit(0);
+}
+
+#endif
diff --git a/src/util/sigdelay.h b/src/util/sigdelay.h
new file mode 100644
index 0000000..d3b4ea3
--- /dev/null
+++ b/src/util/sigdelay.h
@@ -0,0 +1,31 @@
+#ifndef _SIGDELAY_H_INCLUDED_
+#define _SIGDELAY_H_INCLUDED_
+
+/*++
+/* NAME
+/* sigdelay 3h
+/* SUMMARY
+/* delay/resume signal delivery
+/* SYNOPSIS
+/* #include <sigdelay.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern void sigdelay(void);
+extern void sigresume(void);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/skipblanks.c b/src/util/skipblanks.c
new file mode 100644
index 0000000..fc19284
--- /dev/null
+++ b/src/util/skipblanks.c
@@ -0,0 +1,43 @@
+/*++
+/* NAME
+/* skipblanks 3
+/* SUMMARY
+/* skip leading whitespace
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* char *skipblanks(string)
+/* const char *string;
+/* DESCRIPTION
+/* skipblanks() returns a pointer to the first non-whitespace
+/* character in the specified string, or a pointer to the string
+/* terminator when the string contains all white-space characters.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <ctype.h>
+
+/* Utility library. */
+
+#include "stringops.h"
+
+char *skipblanks(const char *string)
+{
+ const char *cp;
+
+ for (cp = string; *cp != 0; cp++)
+ if (!ISSPACE(*cp))
+ break;
+ return ((char *) cp);
+}
diff --git a/src/util/slmdb.c b/src/util/slmdb.c
new file mode 100644
index 0000000..499589d
--- /dev/null
+++ b/src/util/slmdb.c
@@ -0,0 +1,938 @@
+/*++
+/* NAME
+/* slmdb 3
+/* SUMMARY
+/* Simplified LMDB API
+/* SYNOPSIS
+/* #include <slmdb.h>
+/*
+/* int slmdb_init(slmdb, curr_limit, size_incr, hard_limit)
+/* SLMDB *slmdb;
+/* size_t curr_limit;
+/* int size_incr;
+/* size_t hard_limit;
+/*
+/* int slmdb_open(slmdb, path, open_flags, lmdb_flags, slmdb_flags)
+/* SLMDB *slmdb;
+/* const char *path;
+/* int open_flags;
+/* int lmdb_flags;
+/* int slmdb_flags;
+/*
+/* int slmdb_close(slmdb)
+/* SLMDB *slmdb;
+/*
+/* int slmdb_get(slmdb, mdb_key, mdb_value)
+/* SLMDB *slmdb;
+/* MDB_val *mdb_key;
+/* MDB_val *mdb_value;
+/*
+/* int slmdb_put(slmdb, mdb_key, mdb_value, flags)
+/* SLMDB *slmdb;
+/* MDB_val *mdb_key;
+/* MDB_val *mdb_value;
+/* int flags;
+/*
+/* int slmdb_del(slmdb, mdb_key)
+/* SLMDB *slmdb;
+/* MDB_val *mdb_key;
+/*
+/* int slmdb_cursor_get(slmdb, mdb_key, mdb_value, op)
+/* SLMDB *slmdb;
+/* MDB_val *mdb_key;
+/* MDB_val *mdb_value;
+/* MDB_cursor_op op;
+/* AUXILIARY FUNCTIONS
+/* int slmdb_fd(slmdb)
+/* SLMDB *slmdb;
+/*
+/* size_t slmdb_curr_limit(slmdb)
+/* SLMDB *slmdb;
+/*
+/* int slmdb_control(slmdb, request, ...)
+/* SLMDB *slmdb;
+/* int request;
+/* DESCRIPTION
+/* This module simplifies the LMDB API by hiding recoverable
+/* errors from the application. Details are given in the
+/* section "ERROR RECOVERY".
+/*
+/* slmdb_init() performs mandatory initialization before opening
+/* an LMDB database. The result value is an LMDB status code
+/* (zero in case of success).
+/*
+/* slmdb_open() opens an LMDB database. The result value is
+/* an LMDB status code (zero in case of success).
+/*
+/* slmdb_close() finalizes an optional bulk-mode transaction
+/* and closes a successfully-opened LMDB database. The result
+/* value is an LMDB status code (zero in case of success).
+/*
+/* slmdb_get() is an mdb_get() wrapper with automatic error
+/* recovery. The result value is an LMDB status code (zero
+/* in case of success).
+/*
+/* slmdb_put() is an mdb_put() wrapper with automatic error
+/* recovery. The result value is an LMDB status code (zero
+/* in case of success).
+/*
+/* slmdb_del() is an mdb_del() wrapper with automatic error
+/* recovery. The result value is an LMDB status code (zero
+/* in case of success).
+/*
+/* slmdb_cursor_get() is an mdb_cursor_get() wrapper with
+/* automatic error recovery. The result value is an LMDB
+/* status code (zero in case of success). This wrapper supports
+/* only one cursor per database.
+/*
+/* slmdb_fd() returns the file descriptor for the specified
+/* database. This may be used for file status queries or
+/* application-controlled locking.
+/*
+/* slmdb_curr_limit() returns the current database size limit
+/* for the specified database.
+/*
+/* slmdb_control() specifies optional features. The result is
+/* an LMDB status code (zero in case of success).
+/*
+/* Arguments:
+/* .IP slmdb
+/* Pointer to caller-provided storage.
+/* .IP curr_limit
+/* The initial memory mapping size limit. This limit is
+/* automatically increased when the database becomes full.
+/* .IP size_incr
+/* An integer factor by which the memory mapping size limit
+/* is increased when the database becomes full.
+/* .IP hard_limit
+/* The upper bound for the memory mapping size limit.
+/* .IP path
+/* LMDB database pathname.
+/* .IP open_flags
+/* Flags that control file open operations. Do not specify
+/* locking flags here.
+/* .IP lmdb_flags
+/* Flags that control the LMDB environment. If MDB_NOLOCK is
+/* specified, then each slmdb_get() or slmdb_cursor_get() call
+/* must be protected with a shared (or exclusive) external lock,
+/* and each slmdb_put() or slmdb_del() call must be protected
+/* with an exclusive external lock. A lock may be released
+/* after the call returns. A writer may atomically downgrade
+/* an exclusive lock to shared, but it must obtain an exclusive
+/* lock before making another slmdb(3) write request.
+/* .sp
+/* Note: when a database is opened with MDB_NOLOCK, external
+/* locks such as fcntl() do not protect slmdb(3) requests
+/* within the same process against each other. If a program
+/* cannot avoid making simultaneous slmdb(3) requests, then
+/* it must synchronize these requests with in-process locks,
+/* in addition to the per-process fcntl(2) locks.
+/* .IP slmdb_flags
+/* Bit-wise OR of zero or more of the following:
+/* .RS
+/* .IP SLMDB_FLAG_BULK
+/* Open the database and create a "bulk" transaction that is
+/* committed when the database is closed. If MDB_NOLOCK is
+/* specified, then the entire transaction must be protected
+/* with a persistent external lock. All slmdb_get(), slmdb_put()
+/* and slmdb_del() requests will be directed to the "bulk"
+/* transaction.
+/* .RE
+/* .IP mdb_key
+/* Pointer to caller-provided lookup key storage.
+/* .IP mdb_value
+/* Pointer to caller-provided value storage.
+/* .IP op
+/* LMDB cursor operation.
+/* .IP request
+/* The start of a list of (name, value) pairs, terminated with
+/* CA_SLMDB_CTL_END. The following text enumerates the symbolic
+/* request names and the corresponding argument types.
+/* .RS
+/* .IP "CA_SLMDB_CTL_LONGJMP_FN(void (*)(void *, int))"
+/* Call-back function pointer. The function is called to repeat
+/* a failed bulk-mode transaction from the start. The arguments
+/* are the application context and the setjmp() or sigsetjmp()
+/* result value.
+/* .IP "CA_SLMDB_CTL_NOTIFY_FN(void (*)(void *, int, ...))"
+/* Call-back function pointer. The function is called to report
+/* successful error recovery. The arguments are the application
+/* context, the MDB error code, and additional arguments that
+/* depend on the error code. Details are given in the section
+/* "ERROR RECOVERY".
+/* .IP "CA_SLMDB_CTL_ASSERT_FN(void (*)(void *, const char *))"
+/* Call-back function pointer. The function is called to
+/* report an LMDB internal assertion failure. The arguments
+/* are the application context, and text that describes the
+/* problem.
+/* .IP "CA_SLMDB_CTL_CB_CONTEXT(void *)"
+/* Application context that is passed in call-back function
+/* calls.
+/* .IP "CA_SLMDB_CTL_API_RETRY_LIMIT(int)"
+/* How many times to recover from LMDB errors within the
+/* execution of a single slmdb(3) API call before giving up.
+/* .IP "CA_SLMDB_CTL_BULK_RETRY_LIMIT(int)"
+/* How many times to recover from a bulk-mode transaction
+/* before giving up.
+/* .RE
+/* ERROR RECOVERY
+/* .ad
+/* .fi
+/* This module automatically repeats failed requests after
+/* recoverable errors, up to the limits specified with
+/* slmdb_control().
+/*
+/* Recoverable errors are reported through an optional
+/* notification function specified with slmdb_control(). With
+/* recoverable MDB_MAP_FULL and MDB_MAP_RESIZED errors, the
+/* additional argument is a size_t value with the updated
+/* current database size limit; with recoverable MDB_READERS_FULL
+/* errors there is no additional argument.
+/* BUGS
+/* Recovery from MDB_MAP_FULL involves resizing the database
+/* memory mapping. According to LMDB documentation this
+/* requires that there is no concurrent activity in the same
+/* database by other threads in the same memory address space.
+/* SEE ALSO
+/* lmdb(3) API manpage (currently, non-existent).
+/* AUTHOR(S)
+/* Howard Chu
+/* Symas Corporation
+/*
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * DO NOT include other Postfix-specific header files. This LMDB wrapper
+ * must be usable outside Postfix.
+ */
+
+#ifdef HAS_LMDB
+
+/* System library. */
+
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* Application-specific. */
+
+#include <slmdb.h>
+
+ /*
+ * Minimum LMDB patchlevel.
+ *
+ * LMDB 0.9.11 allows Postfix daemons to log an LMDB error message instead of
+ * falling out of the sky without any explanation. Without such logging,
+ * Postfix with LMDB would be too hard to support.
+ *
+ * LMDB 0.9.10 fixes an information leak where LMDB wrote chunks of up to 4096
+ * bytes of uninitialized heap memory to a database. This was a security
+ * violation because it made information persistent that was not meant to be
+ * persisted, or it was sharing information that was not meant to be shared.
+ *
+ * LMDB 0.9.9 allows Postfix to use external (fcntl()-based) locks, instead of
+ * having to use world-writable LMDB lock files.
+ *
+ * LMDB 0.9.8 allows Postfix to update the database size limit on-the-fly, so
+ * that it can recover from an MDB_MAP_FULL error without having to close
+ * the database. It also allows an application to "pick up" a new database
+ * size limit on-the-fly, so that it can recover from an MDB_MAP_RESIZED
+ * error without having to close the database.
+ *
+ * The database size limit that remains is imposed by the hardware memory
+ * address space (31 or 47 bits, typically) or file system. The LMDB
+ * implementation is supposed to handle databases larger than physical
+ * memory. However, this is not necessarily guaranteed for (bulk)
+ * transactions larger than physical memory.
+ */
+#if MDB_VERSION_FULL < MDB_VERINT(0, 9, 11)
+#error "This Postfix version requires LMDB version 0.9.11 or later"
+#endif
+
+ /*
+ * Error recovery.
+ *
+ * The purpose of the slmdb(3) API is to hide LMDB quirks (recoverable
+ * MAP_FULL, MAP_RESIZED, or MDB_READERS_FULL errors). With these out of the
+ * way, applications can pretend that those quirks don't exist, and focus on
+ * their own job.
+ *
+ * - To recover from a single-transaction LMDB error, each wrapper function
+ * uses tail recursion instead of goto. Since LMDB errors are rare, code
+ * clarity is more important than speed.
+ *
+ * - To recover from a bulk-transaction LMDB error, the error-recovery code
+ * triggers a long jump back into the caller to some pre-arranged point (the
+ * closest thing that C has to exception handling). The application is then
+ * expected to repeat the bulk transaction from scratch.
+ *
+ * When any code aborts a bulk transaction, it must reset slmdb->txn to null
+ * to avoid a use-after-free problem in slmdb_close().
+ */
+
+ /*
+ * Our default retry attempt limits. We allow a few retries per slmdb(3) API
+ * call for non-bulk transactions. We allow a number of bulk-transaction
+ * retries that is proportional to the memory address space.
+ */
+#define SLMDB_DEF_API_RETRY_LIMIT 30 /* Retries per slmdb(3) API call */
+#define SLMDB_DEF_BULK_RETRY_LIMIT \
+ (2 * sizeof(size_t) * CHAR_BIT) /* Retries per bulk-mode transaction */
+
+ /*
+ * We increment the recursion counter each time we try to recover from
+ * error, and reset the recursion counter when returning to the application
+ * from the slmdb(3) API.
+ */
+#define SLMDB_API_RETURN(slmdb, status) do { \
+ (slmdb)->api_retry_count = 0; \
+ return (status); \
+ } while (0)
+
+ /*
+ * With MDB_NOLOCK, the application uses an external lock for inter-process
+ * synchronization. Because the caller may release the external lock after
+ * an SLMDB API call, each SLMDB API function must use a short-lived
+ * transaction unless the transaction is a bulk-mode transaction.
+ */
+
+/* slmdb_cursor_close - close cursor and its read transaction */
+
+static void slmdb_cursor_close(SLMDB *slmdb)
+{
+ MDB_txn *txn;
+
+ /*
+ * Close the cursor and its read transaction. We can restore it later
+ * from the saved key information.
+ */
+ txn = mdb_cursor_txn(slmdb->cursor);
+ mdb_cursor_close(slmdb->cursor);
+ slmdb->cursor = 0;
+ mdb_txn_abort(txn);
+}
+
+/* slmdb_saved_key_init - initialize saved key info */
+
+static void slmdb_saved_key_init(SLMDB *slmdb)
+{
+ slmdb->saved_key.mv_data = 0;
+ slmdb->saved_key.mv_size = 0;
+ slmdb->saved_key_size = 0;
+}
+
+/* slmdb_saved_key_free - destroy saved key info */
+
+static void slmdb_saved_key_free(SLMDB *slmdb)
+{
+ free(slmdb->saved_key.mv_data);
+ slmdb_saved_key_init(slmdb);
+}
+
+#define HAVE_SLMDB_SAVED_KEY(s) ((s)->saved_key.mv_data != 0)
+
+/* slmdb_saved_key_assign - copy the saved key */
+
+static int slmdb_saved_key_assign(SLMDB *slmdb, MDB_val *key_val)
+{
+
+ /*
+ * Extend the buffer to fit the key, so that we can avoid malloc()
+ * overhead most of the time.
+ */
+ if (slmdb->saved_key_size < key_val->mv_size) {
+ if (slmdb->saved_key.mv_data == 0)
+ slmdb->saved_key.mv_data = malloc(key_val->mv_size);
+ else
+ slmdb->saved_key.mv_data =
+ realloc(slmdb->saved_key.mv_data, key_val->mv_size);
+ if (slmdb->saved_key.mv_data == 0) {
+ slmdb_saved_key_init(slmdb);
+ return (ENOMEM);
+ } else {
+ slmdb->saved_key_size = key_val->mv_size;
+ }
+ }
+
+ /*
+ * Copy the key under the cursor.
+ */
+ memcpy(slmdb->saved_key.mv_data, key_val->mv_data, key_val->mv_size);
+ slmdb->saved_key.mv_size = key_val->mv_size;
+ return (0);
+}
+
+/* slmdb_prepare - LMDB-specific (re)initialization before actual access */
+
+static int slmdb_prepare(SLMDB *slmdb)
+{
+ int status = 0;
+
+ /*
+ * This is called before accessing the database, or after recovery from
+ * an LMDB error. Note: this code cannot recover from errors itself.
+ * slmdb->txn is either the database open() transaction or a
+ * freshly-created bulk-mode transaction. When slmdb_prepare() commits or
+ * aborts commits a transaction, it must set slmdb->txn to null to avoid
+ * a use-after-free error in slmdb_close().
+ *
+ * - With O_TRUNC we make a "drop" request before updating the database.
+ *
+ * - With a bulk-mode transaction we commit when the database is closed.
+ */
+ if (slmdb->open_flags & O_TRUNC) {
+ if ((status = mdb_drop(slmdb->txn, slmdb->dbi, 0)) != 0) {
+ mdb_txn_abort(slmdb->txn);
+ slmdb->txn = 0;
+ return (status);
+ }
+ if ((slmdb->slmdb_flags & SLMDB_FLAG_BULK) == 0) {
+ status = mdb_txn_commit(slmdb->txn);
+ slmdb->txn = 0;
+ if (status != 0)
+ return (status);
+ }
+ } else if ((slmdb->slmdb_flags & SLMDB_FLAG_BULK) == 0) {
+ mdb_txn_abort(slmdb->txn);
+ slmdb->txn = 0;
+ }
+ slmdb->api_retry_count = 0;
+ return (status);
+}
+
+/* slmdb_recover - recover from LMDB errors */
+
+static int slmdb_recover(SLMDB *slmdb, int status)
+{
+ MDB_envinfo info;
+ int original_status = status;
+
+ /*
+ * This may be needed in non-MDB_NOLOCK mode. Recovery is rare enough
+ * that we don't care about a few wasted cycles.
+ */
+ if (slmdb->cursor != 0)
+ slmdb_cursor_close(slmdb);
+
+ /*
+ * Limit the number of recovery attempts per slmdb(3) API request.
+ */
+ if ((slmdb->api_retry_count += 1) >= slmdb->api_retry_limit)
+ return (status);
+
+ /*
+ * Limit the number of bulk transaction recovery attempts.
+ */
+ if ((slmdb->slmdb_flags & SLMDB_FLAG_BULK) != 0
+ && (slmdb->bulk_retry_count += 1) > slmdb->bulk_retry_limit)
+ return (status);
+
+ /*
+ * Try to clear the error condition.
+ */
+ switch (status) {
+
+ /*
+ * As of LMDB 0.9.8 when a non-bulk update runs into a "map full"
+ * error, we can resize the environment's memory map and clear the
+ * error condition. The caller should retry immediately.
+ */
+ case MDB_MAP_FULL:
+ /* Can we increase the memory map? Give up if we can't. */
+ if (slmdb->curr_limit < slmdb->hard_limit / slmdb->size_incr) {
+ slmdb->curr_limit = slmdb->curr_limit * slmdb->size_incr;
+ } else if (slmdb->curr_limit < slmdb->hard_limit) {
+ slmdb->curr_limit = slmdb->hard_limit;
+ } else {
+ /* Sorry, we are already maxed out. */
+ break;
+ }
+ if (slmdb->notify_fn)
+ slmdb->notify_fn(slmdb->cb_context, MDB_MAP_FULL,
+ slmdb->curr_limit);
+ status = mdb_env_set_mapsize(slmdb->env, slmdb->curr_limit);
+ break;
+
+ /*
+ * When a writer resizes the database, read-only applications must
+ * increase their LMDB memory map size limit, too. Otherwise, they
+ * won't be able to read a table after it grows.
+ *
+ * As of LMDB 0.9.8 we can import the new memory map size limit into the
+ * database environment by calling mdb_env_set_mapsize() with a zero
+ * size argument. Then we extract the map size limit for later use.
+ * The caller should retry immediately.
+ */
+ case MDB_MAP_RESIZED:
+ if ((status = mdb_env_set_mapsize(slmdb->env, 0)) == 0) {
+ /* Do not panic. Maps may shrink after bulk update. */
+ mdb_env_info(slmdb->env, &info);
+ slmdb->curr_limit = info.me_mapsize;
+ if (slmdb->notify_fn)
+ slmdb->notify_fn(slmdb->cb_context, MDB_MAP_RESIZED,
+ slmdb->curr_limit);
+ }
+ break;
+
+ /*
+ * What is it with these built-in hard limits that cause systems to
+ * stop when demand is at its highest? When the system is under
+ * stress it should slow down and keep making progress.
+ */
+ case MDB_READERS_FULL:
+ if (slmdb->notify_fn)
+ slmdb->notify_fn(slmdb->cb_context, MDB_READERS_FULL);
+ sleep(1);
+ status = 0;
+ break;
+
+ /*
+ * We can't solve this problem. The application should terminate with
+ * a fatal run-time error and the program should be re-run later.
+ */
+ default:
+ break;
+ }
+
+ /*
+ * If we cleared the error condition for a non-bulk transaction, return a
+ * success status. The caller should retry the failed operation
+ * immediately.
+ */
+ if (status == 0 && (slmdb->slmdb_flags & SLMDB_FLAG_BULK) != 0) {
+
+ /*
+ * We cleared the error condition for a bulk transaction. If the
+ * transaction is not restartable, return the original error. The
+ * caller should terminate with a fatal run-time error, and the
+ * program should be re-run later.
+ */
+ if (slmdb->longjmp_fn == 0)
+ return (original_status);
+
+ /*
+ * Rebuild a bulk transaction from scratch, by making a long jump
+ * back into the caller at some pre-arranged point. In MDB_NOLOCK
+ * mode, there is no need to upgrade a lock to "exclusive", because a
+ * failed write transaction has no side effects.
+ */
+ if ((status = mdb_txn_begin(slmdb->env, (MDB_txn *) 0,
+ slmdb->lmdb_flags & MDB_RDONLY,
+ &slmdb->txn)) == 0
+ && (status = slmdb_prepare(slmdb)) == 0)
+ slmdb->longjmp_fn(slmdb->cb_context, 1);
+ }
+ return (status);
+}
+
+/* slmdb_txn_begin - mdb_txn_begin() wrapper with LMDB error recovery */
+
+static int slmdb_txn_begin(SLMDB *slmdb, int rdonly, MDB_txn **txn)
+{
+ int status;
+
+ if ((status = mdb_txn_begin(slmdb->env, (MDB_txn *) 0, rdonly, txn)) != 0
+ && (status = slmdb_recover(slmdb, status)) == 0)
+ status = slmdb_txn_begin(slmdb, rdonly, txn);
+
+ return (status);
+}
+
+/* slmdb_get - mdb_get() wrapper with LMDB error recovery */
+
+int slmdb_get(SLMDB *slmdb, MDB_val *mdb_key, MDB_val *mdb_value)
+{
+ MDB_txn *txn;
+ int status;
+
+ /*
+ * Start a read transaction if there's no bulk-mode txn.
+ */
+ if (slmdb->txn)
+ txn = slmdb->txn;
+ else if ((status = slmdb_txn_begin(slmdb, MDB_RDONLY, &txn)) != 0)
+ SLMDB_API_RETURN(slmdb, status);
+
+ /*
+ * Do the lookup.
+ */
+ if ((status = mdb_get(txn, slmdb->dbi, mdb_key, mdb_value)) != 0
+ && status != MDB_NOTFOUND) {
+ mdb_txn_abort(txn);
+ if (txn == slmdb->txn)
+ slmdb->txn = 0;
+ if ((status = slmdb_recover(slmdb, status)) == 0)
+ status = slmdb_get(slmdb, mdb_key, mdb_value);
+ SLMDB_API_RETURN(slmdb, status);
+ }
+
+ /*
+ * Close the read txn if it's not the bulk-mode txn.
+ */
+ if (slmdb->txn == 0)
+ mdb_txn_abort(txn);
+
+ SLMDB_API_RETURN(slmdb, status);
+}
+
+/* slmdb_put - mdb_put() wrapper with LMDB error recovery */
+
+int slmdb_put(SLMDB *slmdb, MDB_val *mdb_key,
+ MDB_val *mdb_value, int flags)
+{
+ MDB_txn *txn;
+ int status;
+
+ /*
+ * Start a write transaction if there's no bulk-mode txn.
+ */
+ if (slmdb->txn)
+ txn = slmdb->txn;
+ else if ((status = slmdb_txn_begin(slmdb, 0, &txn)) != 0)
+ SLMDB_API_RETURN(slmdb, status);
+
+ /*
+ * Do the update.
+ */
+ if ((status = mdb_put(txn, slmdb->dbi, mdb_key, mdb_value, flags)) != 0) {
+ if (status != MDB_KEYEXIST) {
+ mdb_txn_abort(txn);
+ if (txn == slmdb->txn)
+ slmdb->txn = 0;
+ if ((status = slmdb_recover(slmdb, status)) == 0)
+ status = slmdb_put(slmdb, mdb_key, mdb_value, flags);
+ SLMDB_API_RETURN(slmdb, status);
+ } else {
+ /* Abort non-bulk transaction only. */
+ if (slmdb->txn == 0)
+ mdb_txn_abort(txn);
+ }
+ }
+
+ /*
+ * Commit the transaction if it's not the bulk-mode txn.
+ */
+ if (status == 0 && slmdb->txn == 0 && (status = mdb_txn_commit(txn)) != 0
+ && (status = slmdb_recover(slmdb, status)) == 0)
+ status = slmdb_put(slmdb, mdb_key, mdb_value, flags);
+
+ SLMDB_API_RETURN(slmdb, status);
+}
+
+/* slmdb_del - mdb_del() wrapper with LMDB error recovery */
+
+int slmdb_del(SLMDB *slmdb, MDB_val *mdb_key)
+{
+ MDB_txn *txn;
+ int status;
+
+ /*
+ * Start a write transaction if there's no bulk-mode txn.
+ */
+ if (slmdb->txn)
+ txn = slmdb->txn;
+ else if ((status = slmdb_txn_begin(slmdb, 0, &txn)) != 0)
+ SLMDB_API_RETURN(slmdb, status);
+
+ /*
+ * Do the update.
+ */
+ if ((status = mdb_del(txn, slmdb->dbi, mdb_key, (MDB_val *) 0)) != 0) {
+ if (status != MDB_NOTFOUND) {
+ mdb_txn_abort(txn);
+ if (txn == slmdb->txn)
+ slmdb->txn = 0;
+ if ((status = slmdb_recover(slmdb, status)) == 0)
+ status = slmdb_del(slmdb, mdb_key);
+ SLMDB_API_RETURN(slmdb, status);
+ } else {
+ /* Abort non-bulk transaction only. */
+ if (slmdb->txn == 0)
+ mdb_txn_abort(txn);
+ }
+ }
+
+ /*
+ * Commit the transaction if it's not the bulk-mode txn.
+ */
+ if (status == 0 && slmdb->txn == 0 && (status = mdb_txn_commit(txn)) != 0
+ && (status = slmdb_recover(slmdb, status)) == 0)
+ status = slmdb_del(slmdb, mdb_key);
+
+ SLMDB_API_RETURN(slmdb, status);
+}
+
+/* slmdb_cursor_get - mdb_cursor_get() wrapper with LMDB error recovery */
+
+int slmdb_cursor_get(SLMDB *slmdb, MDB_val *mdb_key,
+ MDB_val *mdb_value, MDB_cursor_op op)
+{
+ MDB_txn *txn;
+ int status = 0;
+
+ /*
+ * TODO: figure how we would recover a failing bulk transaction.
+ */
+ if ((slmdb->slmdb_flags & SLMDB_FLAG_BULK) != 0) {
+ if (slmdb->assert_fn)
+ slmdb->assert_fn(slmdb->cb_context,
+ "slmdb_cursor_get: bulk transaction is not supported");
+ return (MDB_PANIC);
+ }
+
+ /*
+ * Open a read transaction and cursor if needed.
+ */
+ if (slmdb->cursor == 0) {
+ if ((status = slmdb_txn_begin(slmdb, MDB_RDONLY, &txn)) != 0)
+ SLMDB_API_RETURN(slmdb, status);
+ if ((status = mdb_cursor_open(txn, slmdb->dbi, &slmdb->cursor)) != 0) {
+ mdb_txn_abort(txn);
+ if ((status = slmdb_recover(slmdb, status)) == 0)
+ status = slmdb_cursor_get(slmdb, mdb_key, mdb_value, op);
+ SLMDB_API_RETURN(slmdb, status);
+ }
+
+ /*
+ * Restore the cursor position from the saved key information.
+ */
+ if (HAVE_SLMDB_SAVED_KEY(slmdb) && op != MDB_FIRST)
+ status = mdb_cursor_get(slmdb->cursor, &slmdb->saved_key,
+ (MDB_val *) 0, MDB_SET);
+ }
+
+ /*
+ * Database lookup.
+ */
+ if (status == 0)
+ status = mdb_cursor_get(slmdb->cursor, mdb_key, mdb_value, op);
+
+ /*
+ * Save the cursor position if successful. This can fail only with
+ * ENOMEM.
+ *
+ * Close the cursor read transaction if in MDB_NOLOCK mode, because the
+ * caller may release the external lock after we return.
+ */
+ if (status == 0) {
+ status = slmdb_saved_key_assign(slmdb, mdb_key);
+ if (slmdb->lmdb_flags & MDB_NOLOCK)
+ slmdb_cursor_close(slmdb);
+ }
+
+ /*
+ * Handle end-of-database or other error.
+ */
+ else {
+ /* Do not hand-optimize out the slmdb_cursor_close() calls below. */
+ if (status == MDB_NOTFOUND) {
+ slmdb_cursor_close(slmdb);
+ if (HAVE_SLMDB_SAVED_KEY(slmdb))
+ slmdb_saved_key_free(slmdb);
+ } else {
+ slmdb_cursor_close(slmdb);
+ if ((status = slmdb_recover(slmdb, status)) == 0)
+ status = slmdb_cursor_get(slmdb, mdb_key, mdb_value, op);
+ SLMDB_API_RETURN(slmdb, status);
+ /* Do not hand-optimize out the above return statement. */
+ }
+ }
+ SLMDB_API_RETURN(slmdb, status);
+}
+
+/* slmdb_assert_cb - report LMDB assertion failure */
+
+static void slmdb_assert_cb(MDB_env *env, const char *text)
+{
+ SLMDB *slmdb = (SLMDB *) mdb_env_get_userctx(env);
+
+ if (slmdb->assert_fn)
+ slmdb->assert_fn(slmdb->cb_context, text);
+}
+
+/* slmdb_control - control optional settings */
+
+int slmdb_control(SLMDB *slmdb, int first,...)
+{
+ va_list ap;
+ int status = 0;
+ int reqno;
+ int rc;
+
+ va_start(ap, first);
+ for (reqno = first; status == 0 && reqno != SLMDB_CTL_END; reqno = va_arg(ap, int)) {
+ switch (reqno) {
+ case SLMDB_CTL_LONGJMP_FN:
+ slmdb->longjmp_fn = va_arg(ap, SLMDB_LONGJMP_FN);
+ break;
+ case SLMDB_CTL_NOTIFY_FN:
+ slmdb->notify_fn = va_arg(ap, SLMDB_NOTIFY_FN);
+ break;
+ case SLMDB_CTL_ASSERT_FN:
+ slmdb->assert_fn = va_arg(ap, SLMDB_ASSERT_FN);
+ if ((rc = mdb_env_set_userctx(slmdb->env, (void *) slmdb)) != 0
+ || (rc = mdb_env_set_assert(slmdb->env, slmdb_assert_cb)) != 0)
+ status = rc;
+ break;
+ case SLMDB_CTL_CB_CONTEXT:
+ slmdb->cb_context = va_arg(ap, void *);
+ break;
+ case SLMDB_CTL_API_RETRY_LIMIT:
+ slmdb->api_retry_limit = va_arg(ap, int);
+ break;
+ case SLMDB_CTL_BULK_RETRY_LIMIT:
+ slmdb->bulk_retry_limit = va_arg(ap, int);
+ break;
+ default:
+ status = errno = EINVAL;
+ break;
+ }
+ }
+ va_end(ap);
+ return (status);
+}
+
+/* slmdb_close - wrapper with LMDB error recovery */
+
+int slmdb_close(SLMDB *slmdb)
+{
+ int status = 0;
+
+ /*
+ * Finish an open bulk transaction. If slmdb_recover() returns after a
+ * bulk-transaction error, then it was unable to clear the error
+ * condition, or unable to restart the bulk transaction.
+ */
+ if ((slmdb->slmdb_flags & SLMDB_FLAG_BULK) != 0 && slmdb->txn != 0
+ && (status = mdb_txn_commit(slmdb->txn)) != 0)
+ status = slmdb_recover(slmdb, status);
+
+ /*
+ * Clean up after an unfinished sequence() operation.
+ */
+ if (slmdb->cursor != 0)
+ slmdb_cursor_close(slmdb);
+
+ mdb_env_close(slmdb->env);
+
+ /*
+ * Clean up the saved key information.
+ */
+ if (HAVE_SLMDB_SAVED_KEY(slmdb))
+ slmdb_saved_key_free(slmdb);
+
+ SLMDB_API_RETURN(slmdb, status);
+}
+
+/* slmdb_init - mandatory initialization */
+
+int slmdb_init(SLMDB *slmdb, size_t curr_limit, int size_incr,
+ size_t hard_limit)
+{
+
+ /*
+ * This is a separate operation to keep the slmdb_open() API simple.
+ * Don't allocate resources here. Just store control information,
+ */
+ slmdb->curr_limit = curr_limit;
+ slmdb->size_incr = size_incr;
+ slmdb->hard_limit = hard_limit;
+
+ return (MDB_SUCCESS);
+}
+
+/* slmdb_open - open wrapped LMDB database */
+
+int slmdb_open(SLMDB *slmdb, const char *path, int open_flags,
+ int lmdb_flags, int slmdb_flags)
+{
+ struct stat st;
+ MDB_env *env;
+ MDB_txn *txn;
+ MDB_dbi dbi;
+ int db_fd;
+ int status;
+
+ /*
+ * Create LMDB environment.
+ */
+ if ((status = mdb_env_create(&env)) != 0)
+ return (status);
+
+ /*
+ * Make sure that the memory map has room to store and commit an initial
+ * "drop" transaction as well as fixed database metadata. We have no way
+ * to recover from errors before the first application-level I/O request.
+ */
+#define SLMDB_FUDGE 10240
+
+ if (slmdb->curr_limit < SLMDB_FUDGE)
+ slmdb->curr_limit = SLMDB_FUDGE;
+ if (stat(path, &st) == 0
+ && st.st_size > slmdb->curr_limit - SLMDB_FUDGE) {
+ if (st.st_size > slmdb->hard_limit)
+ slmdb->hard_limit = st.st_size;
+ if (st.st_size < slmdb->hard_limit - SLMDB_FUDGE)
+ slmdb->curr_limit = st.st_size + SLMDB_FUDGE;
+ else
+ slmdb->curr_limit = slmdb->hard_limit;
+ }
+
+ /*
+ * mdb_open() requires a txn, but since the default DB always exists in
+ * an LMDB environment, we usually don't need to do anything else with
+ * the txn. It is currently used for truncate and for bulk transactions.
+ */
+ if ((status = mdb_env_set_mapsize(env, slmdb->curr_limit)) != 0
+ || (status = mdb_env_open(env, path, lmdb_flags, 0644)) != 0
+ || (status = mdb_txn_begin(env, (MDB_txn *) 0,
+ lmdb_flags & MDB_RDONLY, &txn)) != 0
+ || (status = mdb_open(txn, (const char *) 0, 0, &dbi)) != 0
+ || (status = mdb_env_get_fd(env, &db_fd)) != 0) {
+ mdb_env_close(env);
+ return (status);
+ }
+
+ /*
+ * Bundle up.
+ */
+ slmdb->open_flags = open_flags;
+ slmdb->lmdb_flags = lmdb_flags;
+ slmdb->slmdb_flags = slmdb_flags;
+ slmdb->env = env;
+ slmdb->dbi = dbi;
+ slmdb->db_fd = db_fd;
+ slmdb->cursor = 0;
+ slmdb_saved_key_init(slmdb);
+ slmdb->api_retry_count = 0;
+ slmdb->bulk_retry_count = 0;
+ slmdb->api_retry_limit = SLMDB_DEF_API_RETRY_LIMIT;
+ slmdb->bulk_retry_limit = SLMDB_DEF_BULK_RETRY_LIMIT;
+ slmdb->longjmp_fn = 0;
+ slmdb->notify_fn = 0;
+ slmdb->assert_fn = 0;
+ slmdb->cb_context = 0;
+ slmdb->txn = txn;
+
+ if ((status = slmdb_prepare(slmdb)) != 0)
+ mdb_env_close(env);
+
+ return (status);
+}
+
+#endif
diff --git a/src/util/slmdb.h b/src/util/slmdb.h
new file mode 100644
index 0000000..9469c0f
--- /dev/null
+++ b/src/util/slmdb.h
@@ -0,0 +1,117 @@
+#ifndef _SLMDB_H_INCLUDED_
+#define _SLMDB_H_INCLUDED_
+
+/*++
+/* NAME
+/* slmdb 3h
+/* SUMMARY
+/* LMDB API wrapper
+/* SYNOPSIS
+/* #include <slmdb.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <setjmp.h>
+
+#ifdef PATH_LMDB_H
+#include PATH_LMDB_H
+#else
+#include <lmdb.h>
+#endif
+
+ /*
+ * Utility library.
+ */
+#include <check_arg.h>
+
+ /*
+ * External interface.
+ */
+#ifdef NO_SIGSETJMP
+#define SLMDB_JMP_BUF jmp_buf
+#else
+#define SLMDB_JMP_BUF sigjmp_buf
+#endif
+
+ /*
+ * All data structure members are private.
+ */
+typedef struct {
+ size_t curr_limit; /* database soft size limit */
+ int size_incr; /* database expansion factor */
+ size_t hard_limit; /* database hard size limit */
+ int open_flags; /* open() flags */
+ int lmdb_flags; /* LMDB-specific flags */
+ int slmdb_flags; /* bulk-mode flag */
+ MDB_env *env; /* database environment */
+ MDB_dbi dbi; /* database instance */
+ MDB_txn *txn; /* bulk transaction */
+ int db_fd; /* database file handle */
+ MDB_cursor *cursor; /* iterator */
+ MDB_val saved_key; /* saved cursor key buffer */
+ size_t saved_key_size; /* saved cursor key buffer size */
+ void (*longjmp_fn) (void *, int);/* exception handling */
+ void (*notify_fn) (void *, int,...); /* workaround notification */
+ void (*assert_fn) (void *, const char *); /* assert notification */
+ void *cb_context; /* call-back context */
+ int api_retry_count; /* slmdb(3) API call retry count */
+ int bulk_retry_count; /* bulk_mode retry count */
+ int api_retry_limit; /* slmdb(3) API call retry limit */
+ int bulk_retry_limit; /* bulk_mode retry limit */
+} SLMDB;
+
+#define SLMDB_FLAG_BULK (1 << 0)
+
+extern int slmdb_init(SLMDB *, size_t, int, size_t);
+extern int slmdb_open(SLMDB *, const char *, int, int, int);
+extern int slmdb_get(SLMDB *, MDB_val *, MDB_val *);
+extern int slmdb_put(SLMDB *, MDB_val *, MDB_val *, int);
+extern int slmdb_del(SLMDB *, MDB_val *);
+extern int slmdb_cursor_get(SLMDB *, MDB_val *, MDB_val *, MDB_cursor_op);
+extern int slmdb_control(SLMDB *, int,...);
+extern int slmdb_close(SLMDB *);
+
+#define slmdb_fd(slmdb) ((slmdb)->db_fd)
+#define slmdb_curr_limit(slmdb) ((slmdb)->curr_limit)
+
+/* Legacy API: type-unchecked arguments, internal use. */
+#define SLMDB_CTL_END 0
+#define SLMDB_CTL_LONGJMP_FN 1 /* exception handling */
+#define SLMDB_CTL_NOTIFY_FN 2 /* debug logging function */
+#define SLMDB_CTL_CB_CONTEXT 3 /* call-back context */
+#define SLMDB_CTL_API_RETRY_LIMIT 5 /* per slmdb(3) API call */
+#define SLMDB_CTL_BULK_RETRY_LIMIT 6 /* per bulk update */
+#define SLMDB_CTL_ASSERT_FN 7 /* report assertion failure */
+
+/* Safer API: type-checked arguments, external use. */
+#define CA_SLMDB_CTL_END SLMDB_CTL_END
+#define CA_SLMDB_CTL_LONGJMP_FN(v) SLMDB_CTL_LONGJMP_FN, CHECK_VAL(SLMDB_CTL, SLMDB_LONGJMP_FN, (v))
+#define CA_SLMDB_CTL_NOTIFY_FN(v) SLMDB_CTL_NOTIFY_FN, CHECK_VAL(SLMDB_CTL, SLMDB_NOTIFY_FN, (v))
+#define CA_SLMDB_CTL_CB_CONTEXT(v) SLMDB_CTL_CB_CONTEXT, CHECK_PTR(SLMDB_CTL, void, (v))
+#define CA_SLMDB_CTL_API_RETRY_LIMIT(v) SLMDB_CTL_API_RETRY_LIMIT, CHECK_VAL(SLMDB_CTL, int, (v))
+#define CA_SLMDB_CTL_BULK_RETRY_LIMIT(v) SLMDB_CTL_BULK_RETRY_LIMIT, CHECK_VAL(SLMDB_CTL, int, (v))
+#define CA_SLMDB_CTL_ASSERT_FN(v) SLMDB_CTL_ASSERT_FN, CHECK_VAL(SLMDB_CTL, SLMDB_ASSERT_FN, (v))
+
+typedef void (*SLMDB_NOTIFY_FN) (void *, int,...);
+typedef void (*SLMDB_LONGJMP_FN) (void *, int);
+typedef void (*SLMDB_ASSERT_FN) (void *, const char *);
+
+CHECK_VAL_HELPER_DCL(SLMDB_CTL, int);
+CHECK_VAL_HELPER_DCL(SLMDB_CTL, SLMDB_NOTIFY_FN);
+CHECK_VAL_HELPER_DCL(SLMDB_CTL, SLMDB_LONGJMP_FN);
+CHECK_VAL_HELPER_DCL(SLMDB_CTL, SLMDB_ASSERT_FN);
+CHECK_PTR_HELPER_DCL(SLMDB_CTL, void);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Howard Chu
+/* Symas Corporation
+/*--*/
+
+#endif
diff --git a/src/util/sock_addr.c b/src/util/sock_addr.c
new file mode 100644
index 0000000..38ccc91
--- /dev/null
+++ b/src/util/sock_addr.c
@@ -0,0 +1,175 @@
+/*++
+/* NAME
+/* sock_addr 3
+/* SUMMARY
+/* sockaddr utilities
+/* SYNOPSIS
+/* #include <sock_addr.h>
+/*
+/* int sock_addr_cmp_addr(sa, sb)
+/* const struct sockaddr *sa;
+/* const struct sockaddr *sb;
+/*
+/* int sock_addr_cmp_port(sa, sb)
+/* const struct sockaddr *sa;
+/* const struct sockaddr *sb;
+/*
+/* int SOCK_ADDR_EQ_ADDR(sa, sb)
+/* const struct sockaddr *sa;
+/* const struct sockaddr *sb;
+/*
+/* int SOCK_ADDR_EQ_PORT(sa, sb)
+/* const struct sockaddr *sa;
+/* const struct sockaddr *sb;
+/*
+/* int sock_addr_in_loopback(sa)
+/* const struct sockaddr *sa;
+/* AUXILIARY MACROS
+/* struct sockaddr *SOCK_ADDR_PTR(ptr)
+/* unsigned char SOCK_ADDR_FAMILY(ptr)
+/* unsigned char SOCK_ADDR_LEN(ptr)
+/* void *SOCK_ADDR_ADDRP(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. SOCKADDR_ADDRP() returns a pointer to the
+/* IPv4 or IPv6 address. SOCK_ADDR_PORT() returns the IPv4 or IPv6
+/* port number, in network byte order; it must not be used as
+/* lvalue. SOCK_ADDR_PORTP() returns a pointer to the same.
+/*
+/* The macros SOCK_ADDR_IN{,6}_{PTR,FAMILY,PORT,ADDR}() cast
+/* a generic pointer to a specific socket address structure
+/* pointer, or access a specific socket address structure
+/* member. These can be used as lvalues.
+/*
+/* The unsafe INADDR() and IN6_ADDR() macros dereference a
+/* generic pointer to a specific address structure.
+/* DIAGNOSTICS
+/* Panic: unsupported address family.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <sock_addr.h>
+
+/* sock_addr_cmp_addr - compare addresses for equality */
+
+int sock_addr_cmp_addr(const struct sockaddr *sa,
+ const struct sockaddr *sb)
+{
+ if (sa->sa_family != sb->sa_family)
+ return (sa->sa_family - sb->sa_family);
+
+ /*
+ * With IPv6 address structures, assume a non-hostile implementation that
+ * stores the address as a contiguous sequence of bits. Any holes in the
+ * sequence would invalidate the use of memcmp().
+ */
+ if (sa->sa_family == AF_INET) {
+ return (SOCK_ADDR_IN_ADDR(sa).s_addr - SOCK_ADDR_IN_ADDR(sb).s_addr);
+#ifdef HAS_IPV6
+ } else if (sa->sa_family == AF_INET6) {
+ return (memcmp((void *) &(SOCK_ADDR_IN6_ADDR(sa)),
+ (void *) &(SOCK_ADDR_IN6_ADDR(sb)),
+ sizeof(SOCK_ADDR_IN6_ADDR(sa))));
+#endif
+ } else {
+ msg_panic("sock_addr_cmp_addr: unsupported address family %d",
+ sa->sa_family);
+ }
+}
+
+/* sock_addr_cmp_port - compare ports for equality */
+
+int sock_addr_cmp_port(const struct sockaddr *sa,
+ const struct sockaddr *sb)
+{
+ if (sa->sa_family != sb->sa_family)
+ return (sa->sa_family - sb->sa_family);
+
+ if (sa->sa_family == AF_INET) {
+ return (SOCK_ADDR_IN_PORT(sa) - SOCK_ADDR_IN_PORT(sb));
+#ifdef HAS_IPV6
+ } else if (sa->sa_family == AF_INET6) {
+ return (SOCK_ADDR_IN6_PORT(sa) - SOCK_ADDR_IN6_PORT(sb));
+#endif
+ } else {
+ msg_panic("sock_addr_cmp_port: unsupported address family %d",
+ sa->sa_family);
+ }
+}
+
+/* sock_addr_in_loopback - determine if address is loopback */
+
+int sock_addr_in_loopback(const struct sockaddr *sa)
+{
+ unsigned long inaddr;
+
+ if (sa->sa_family == AF_INET) {
+ inaddr = ntohl(SOCK_ADDR_IN_ADDR(sa).s_addr);
+ return (IN_CLASSA(inaddr)
+ && ((inaddr & IN_CLASSA_NET) >> IN_CLASSA_NSHIFT)
+ == IN_LOOPBACKNET);
+#ifdef HAS_IPV6
+ } else if (sa->sa_family == AF_INET6) {
+ return (IN6_IS_ADDR_LOOPBACK(&SOCK_ADDR_IN6_ADDR(sa)));
+#endif
+ } else {
+ msg_panic("sock_addr_in_loopback: unsupported address family %d",
+ sa->sa_family);
+ }
+}
diff --git a/src/util/sock_addr.h b/src/util/sock_addr.h
new file mode 100644
index 0000000..92b6835
--- /dev/null
+++ b/src/util/sock_addr.h
@@ -0,0 +1,109 @@
+#ifndef _SOCK_ADDR_EQ_H_INCLUDED_
+#define _SOCK_ADDR_EQ_H_INCLUDED_
+
+/*++
+/* NAME
+/* sock_addr 3h
+/* SUMMARY
+/* socket address utilities
+/* SYNOPSIS
+/* #include <sock_addr.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <string.h>
+
+ /*
+ * External interface.
+ */
+#define SOCK_ADDR_PTR(ptr) ((struct sockaddr *)(ptr))
+#define SOCK_ADDR_FAMILY(ptr) SOCK_ADDR_PTR(ptr)->sa_family
+#ifdef HAS_SA_LEN
+#define SOCK_ADDR_LEN(ptr) SOCK_ADDR_PTR(ptr)->sa_len
+#endif
+
+#define SOCK_ADDR_IN_PTR(sa) ((struct sockaddr_in *)(sa))
+#define SOCK_ADDR_IN_FAMILY(sa) SOCK_ADDR_IN_PTR(sa)->sin_family
+#define SOCK_ADDR_IN_PORT(sa) SOCK_ADDR_IN_PTR(sa)->sin_port
+#define SOCK_ADDR_IN_ADDR(sa) SOCK_ADDR_IN_PTR(sa)->sin_addr
+#define IN_ADDR(ia) (*((struct in_addr *) (ia)))
+
+extern int sock_addr_cmp_addr(const struct sockaddr *, const struct sockaddr *);
+extern int sock_addr_cmp_port(const struct sockaddr *, const struct sockaddr *);
+extern int sock_addr_in_loopback(const struct sockaddr *);
+
+#ifdef HAS_IPV6
+
+#ifndef HAS_SA_LEN
+#define SOCK_ADDR_LEN(sa) \
+ (SOCK_ADDR_PTR(sa)->sa_family == AF_INET6 ? \
+ sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in))
+#endif
+
+#define SOCK_ADDR_ADDRP(sa) \
+ (SOCK_ADDR_FAMILY(sa) == AF_INET ? \
+ (void *) &SOCK_ADDR_IN_ADDR(sa) : (void *) &SOCK_ADDR_IN6_ADDR(sa))
+#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_ADDRP(sa) (&SOCK_ADDR_IN_ADDR(sa))
+#define SOCK_ADDR_PORT(sa) SOCK_ADDR_IN_PORT(sa)
+#define SOCK_ADDR_PORTP(sa) (&SOCK_ADDR_IN_PORT(sa))
+
+#define SOCK_ADDR_EQ_ADDR(sa, sb) \
+ (SOCK_ADDR_FAMILY(sa) == AF_INET && SOCK_ADDR_FAMILY(sb) == AF_INET \
+ && SOCK_ADDR_IN_ADDR(sa).s_addr == SOCK_ADDR_IN_ADDR(sb).s_addr)
+
+#define SOCK_ADDR_EQ_PORT(sa, sb) \
+ (SOCK_ADDR_FAMILY(sa) == AF_INET && SOCK_ADDR_FAMILY(sb) == AF_INET \
+ && SOCK_ADDR_IN_PORT(sa) == SOCK_ADDR_IN_PORT(sb))
+
+#endif
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/spawn_command.c b/src/util/spawn_command.c
new file mode 100644
index 0000000..739e012
--- /dev/null
+++ b/src/util/spawn_command.c
@@ -0,0 +1,313 @@
+/*++
+/* NAME
+/* spawn_command 3
+/* SUMMARY
+/* run external command
+/* SYNOPSIS
+/* #include <spawn_command.h>
+/*
+/* WAIT_STATUS_T spawn_command(key, value, ...)
+/* int key;
+/* DESCRIPTION
+/* spawn_command() runs a command in a child process and returns
+/* the command exit status.
+/*
+/* Arguments:
+/* .IP key
+/* spawn_command() takes a list of macros with arguments,
+/* terminated by CA_SPAWN_CMD_END which has no arguments. The
+/* following is a listing of macros and expected argument
+/* types.
+/* .RS
+/* .IP "CA_SPAWN_CMD_COMMAND(const char *)"
+/* Specifies the command to execute as a string. The string is
+/* passed to the shell when it contains shell meta characters
+/* or when it appears to be a shell built-in command, otherwise
+/* the command is executed without invoking a shell.
+/* One of CA_SPAWN_CMD_COMMAND or CA_SPAWN_CMD_ARGV must be specified.
+/* See also the SPAWN_CMD_SHELL attribute below.
+/* .IP "CA_SPAWN_CMD_ARGV(char **)"
+/* The command is specified as an argument vector. This vector is
+/* passed without further inspection to the \fIexecvp\fR() routine.
+/* One of CA_SPAWN_CMD_COMMAND or CA_SPAWN_CMD_ARGV must be specified.
+/* .IP "CA_SPAWN_CMD_ENV(char **)"
+/* Additional environment information, in the form of a null-terminated
+/* list of name, value, name, value, ... elements. By default only the
+/* command search path is initialized to _PATH_DEFPATH.
+/* .IP "CA_SPAWN_CMD_EXPORT(char **)"
+/* Null-terminated array of names of environment parameters that can
+/* be exported. By default, everything is exported.
+/* .IP "CA_SPAWN_CMD_STDIN(int)"
+/* .IP "CA_SPAWN_CMD_STDOUT(int)"
+/* .IP "CA_SPAWN_CMD_STDERR(int)"
+/* Each of these specifies I/O redirection of one of the standard file
+/* descriptors for the command.
+/* .IP "CA_SPAWN_CMD_UID(uid_t)"
+/* The user ID to execute the command as. The value -1 is reserved
+/* and cannot be specified.
+/* .IP "CA_SPAWN_CMD_GID(gid_t)"
+/* The group ID to execute the command as. The value -1 is reserved
+/* and cannot be specified.
+/* .IP "CA_SPAWN_CMD_TIME_LIMIT(int)"
+/* The amount of time in seconds the command is allowed to run before
+/* it is terminated with SIGKILL. The default is no time limit.
+/* .IP "CA_SPAWN_CMD_SHELL(const char *)"
+/* The shell to use when executing the command specified with
+/* CA_SPAWN_CMD_COMMAND. This shell is invoked regardless of the
+/* command content.
+/* .RE
+/* DIAGNOSTICS
+/* Panic: interface violations (for example, a missing command).
+/*
+/* Fatal error: fork() failure, other system call failures.
+/*
+/* spawn_command() returns the exit status as defined by wait(2).
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* SEE ALSO
+/* exec_command(3) execute command
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#ifdef USE_PATHS_H
+#include <paths.h>
+#endif
+#include <syslog.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <timed_wait.h>
+#include <set_ugid.h>
+#include <argv.h>
+#include <spawn_command.h>
+#include <exec_command.h>
+#include <clean_env.h>
+
+/* Application-specific. */
+
+struct spawn_args {
+ char **argv; /* argument vector */
+ char *command; /* or a plain string */
+ int stdin_fd; /* read stdin here */
+ int stdout_fd; /* write stdout here */
+ int stderr_fd; /* write stderr here */
+ uid_t uid; /* privileges */
+ gid_t gid; /* privileges */
+ char **env; /* extra environment */
+ char **export; /* exportable environment */
+ char *shell; /* command shell */
+ int time_limit; /* command time limit */
+};
+
+/* get_spawn_args - capture the variadic argument list */
+
+static void get_spawn_args(struct spawn_args * args, int init_key, va_list ap)
+{
+ const char *myname = "get_spawn_args";
+ int key;
+
+ /*
+ * First, set the default values.
+ */
+ args->argv = 0;
+ args->command = 0;
+ args->stdin_fd = -1;
+ args->stdout_fd = -1;
+ args->stderr_fd = -1;
+ args->uid = (uid_t) - 1;
+ args->gid = (gid_t) - 1;
+ args->env = 0;
+ args->export = 0;
+ args->shell = 0;
+ args->time_limit = 0;
+
+ /*
+ * Then, override the defaults with user-supplied inputs.
+ */
+ for (key = init_key; key != SPAWN_CMD_END; key = va_arg(ap, int)) {
+ switch (key) {
+ case SPAWN_CMD_ARGV:
+ if (args->command)
+ msg_panic("%s: specify SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND",
+ myname);
+ args->argv = va_arg(ap, char **);
+ break;
+ case SPAWN_CMD_COMMAND:
+ if (args->argv)
+ msg_panic("%s: specify SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND",
+ myname);
+ args->command = va_arg(ap, char *);
+ break;
+ case SPAWN_CMD_STDIN:
+ args->stdin_fd = va_arg(ap, int);
+ break;
+ case SPAWN_CMD_STDOUT:
+ args->stdout_fd = va_arg(ap, int);
+ break;
+ case SPAWN_CMD_STDERR:
+ args->stderr_fd = va_arg(ap, int);
+ break;
+ case SPAWN_CMD_UID:
+ args->uid = va_arg(ap, uid_t);
+ if (args->uid == (uid_t) (-1))
+ msg_panic("spawn_command: request with reserved user ID: -1");
+ break;
+ case SPAWN_CMD_GID:
+ args->gid = va_arg(ap, gid_t);
+ if (args->gid == (gid_t) (-1))
+ msg_panic("spawn_command: request with reserved group ID: -1");
+ break;
+ case SPAWN_CMD_TIME_LIMIT:
+ args->time_limit = va_arg(ap, int);
+ break;
+ case SPAWN_CMD_ENV:
+ args->env = va_arg(ap, char **);
+ break;
+ case SPAWN_CMD_EXPORT:
+ args->export = va_arg(ap, char **);
+ break;
+ case SPAWN_CMD_SHELL:
+ args->shell = va_arg(ap, char *);
+ break;
+ default:
+ msg_panic("%s: unknown key: %d", myname, key);
+ }
+ }
+ if (args->command == 0 && args->argv == 0)
+ msg_panic("%s: missing SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND", myname);
+ if (args->command == 0 && args->shell != 0)
+ msg_panic("%s: SPAWN_CMD_ARGV cannot be used with SPAWN_CMD_SHELL",
+ myname);
+}
+
+/* spawn_command - execute command with extreme prejudice */
+
+WAIT_STATUS_T spawn_command(int key,...)
+{
+ const char *myname = "spawn_comand";
+ va_list ap;
+ pid_t pid;
+ WAIT_STATUS_T wait_status;
+ struct spawn_args args;
+ char **cpp;
+ ARGV *argv;
+ int err;
+
+ /*
+ * Process the variadic argument list. This also does sanity checks on
+ * what data the caller is passing to us.
+ */
+ va_start(ap, key);
+ get_spawn_args(&args, key, ap);
+ va_end(ap);
+
+ /*
+ * For convenience...
+ */
+ if (args.command == 0)
+ args.command = args.argv[0];
+
+ /*
+ * Spawn off a child process and irrevocably change privilege to the
+ * user. This includes revoking all rights on open files (via the close
+ * on exec flag). If we cannot run the command now, try again some time
+ * later.
+ */
+ switch (pid = fork()) {
+
+ /*
+ * Error. Instead of trying again right now, back off, give the
+ * system a chance to recover, and try again later.
+ */
+ case -1:
+ msg_fatal("fork: %m");
+
+ /*
+ * Child. Run the child in a separate process group so that the
+ * parent can kill not just the child but also its offspring.
+ */
+ case 0:
+ if (args.uid != (uid_t) - 1 || args.gid != (gid_t) - 1)
+ set_ugid(args.uid, args.gid);
+ setsid();
+
+ /*
+ * Pipe plumbing.
+ */
+ if ((args.stdin_fd >= 0 && DUP2(args.stdin_fd, STDIN_FILENO) < 0)
+ || (args.stdout_fd >= 0 && DUP2(args.stdout_fd, STDOUT_FILENO) < 0)
+ || (args.stderr_fd >= 0 && DUP2(args.stderr_fd, STDERR_FILENO) < 0))
+ msg_fatal("%s: dup2: %m", myname);
+
+ /*
+ * Environment plumbing. Always reset the command search path. XXX
+ * That should probably be done by clean_env().
+ */
+ if (args.export)
+ clean_env(args.export);
+ if (setenv("PATH", _PATH_DEFPATH, 1))
+ msg_fatal("%s: setenv: %m", myname);
+ if (args.env)
+ for (cpp = args.env; *cpp; cpp += 2)
+ if (setenv(cpp[0], cpp[1], 1))
+ msg_fatal("setenv: %m");
+
+ /*
+ * Process plumbing. If possible, avoid running a shell.
+ */
+ closelog();
+ if (args.argv) {
+ execvp(args.argv[0], args.argv);
+ msg_fatal("%s: execvp %s: %m", myname, args.argv[0]);
+ } else if (args.shell && *args.shell) {
+ argv = argv_split(args.shell, CHARS_SPACE);
+ argv_add(argv, args.command, (char *) 0);
+ argv_terminate(argv);
+ execvp(argv->argv[0], argv->argv);
+ msg_fatal("%s: execvp %s: %m", myname, argv->argv[0]);
+ } else {
+ exec_command(args.command);
+ }
+ /* NOTREACHED */
+
+ /*
+ * Parent.
+ */
+ default:
+
+ /*
+ * Be prepared for the situation that the child does not terminate.
+ * Make sure that the child terminates before the parent attempts to
+ * retrieve its exit status, otherwise the parent could become stuck,
+ * and the mail system would eventually run out of exec daemons. Do a
+ * thorough job, and kill not just the child process but also its
+ * offspring.
+ */
+ if ((err = timed_waitpid(pid, &wait_status, 0, args.time_limit)) < 0
+ && errno == ETIMEDOUT) {
+ msg_warn("%s: process id %lu: command time limit exceeded",
+ args.command, (unsigned long) pid);
+ kill(-pid, SIGKILL);
+ err = waitpid(pid, &wait_status, 0);
+ }
+ if (err < 0)
+ msg_fatal("wait: %m");
+ return (wait_status);
+ }
+}
diff --git a/src/util/spawn_command.h b/src/util/spawn_command.h
new file mode 100644
index 0000000..7d16413
--- /dev/null
+++ b/src/util/spawn_command.h
@@ -0,0 +1,66 @@
+#ifndef _SPAWN_COMMAND_H_INCLUDED_
+#define _SPAWN_COMMAND_H_INCLUDED_
+
+/*++
+/* NAME
+/* spawn_command 3h
+/* SUMMARY
+/* run external command
+/* SYNOPSIS
+/* #include <spawn_command.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <check_arg.h>
+
+/* Legacy API: type-unchecked arguments, internal use. */
+#define SPAWN_CMD_END 0 /* terminator */
+#define SPAWN_CMD_ARGV 1 /* command is array */
+#define SPAWN_CMD_COMMAND 2 /* command is string */
+#define SPAWN_CMD_STDIN 3 /* mail_copy() flags */
+#define SPAWN_CMD_STDOUT 4 /* mail_copy() sender */
+#define SPAWN_CMD_STDERR 5 /* mail_copy() recipient */
+#define SPAWN_CMD_UID 6 /* privileges */
+#define SPAWN_CMD_GID 7 /* privileges */
+#define SPAWN_CMD_TIME_LIMIT 8 /* time limit */
+#define SPAWN_CMD_ENV 9 /* extra environment */
+#define SPAWN_CMD_SHELL 10 /* alternative shell */
+#define SPAWN_CMD_EXPORT 11 /* exportable parameters */
+
+/* Safer API: type-checked arguments, external use. */
+#define CA_SPAWN_CMD_END SPAWN_CMD_END
+#define CA_SPAWN_CMD_ARGV(v) SPAWN_CMD_ARGV, CHECK_PPTR(CA_SPAWN_CMD, char, (v))
+#define CA_SPAWN_CMD_COMMAND(v) SPAWN_CMD_COMMAND, CHECK_CPTR(CA_SPAWN_CMD, char, (v))
+#define CA_SPAWN_CMD_STDIN(v) SPAWN_CMD_STDIN, CHECK_VAL(CA_SPAWN_CMD, int, (v))
+#define CA_SPAWN_CMD_STDOUT(v) SPAWN_CMD_STDOUT, CHECK_VAL(CA_SPAWN_CMD, int, (v))
+#define CA_SPAWN_CMD_STDERR(v) SPAWN_CMD_STDERR, CHECK_VAL(CA_SPAWN_CMD, int, (v))
+#define CA_SPAWN_CMD_UID(v) SPAWN_CMD_UID, CHECK_VAL(CA_SPAWN_CMD, uid_t, (v))
+#define CA_SPAWN_CMD_GID(v) SPAWN_CMD_GID, CHECK_VAL(CA_SPAWN_CMD, gid_t, (v))
+#define CA_SPAWN_CMD_TIME_LIMIT(v) SPAWN_CMD_TIME_LIMIT, CHECK_VAL(CA_SPAWN_CMD, int, (v))
+#define CA_SPAWN_CMD_ENV(v) SPAWN_CMD_ENV, CHECK_PPTR(CA_SPAWN_CMD, char, (v))
+#define CA_SPAWN_CMD_SHELL(v) SPAWN_CMD_SHELL, CHECK_CPTR(CA_SPAWN_CMD, char, (v))
+#define CA_SPAWN_CMD_EXPORT(v) SPAWN_CMD_EXPORT, CHECK_PPTR(CA_SPAWN_CMD, char, (v))
+
+CHECK_VAL_HELPER_DCL(CA_SPAWN_CMD, uid_t);
+CHECK_VAL_HELPER_DCL(CA_SPAWN_CMD, int);
+CHECK_VAL_HELPER_DCL(CA_SPAWN_CMD, gid_t);
+CHECK_PPTR_HELPER_DCL(CA_SPAWN_CMD, char);
+CHECK_CPTR_HELPER_DCL(CA_SPAWN_CMD, char);
+
+extern WAIT_STATUS_T spawn_command(int,...);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/split_at.c b/src/util/split_at.c
new file mode 100644
index 0000000..c75e902
--- /dev/null
+++ b/src/util/split_at.c
@@ -0,0 +1,71 @@
+/*++
+/* NAME
+/* split_at 3
+/* SUMMARY
+/* trivial token splitter
+/* SYNOPSIS
+/* #include <split_at.h>
+/*
+/* char *split_at(string, delimiter)
+/* char *string;
+/* int delimiter
+/*
+/* char *split_at_right(string, delimiter)
+/* char *string;
+/* int delimiter
+/* DESCRIPTION
+/* split_at() null-terminates the \fIstring\fR at the first
+/* occurrence of the \fIdelimiter\fR character found, and
+/* returns a pointer to the remainder.
+/*
+/* split_at_right() looks for the rightmost delimiter
+/* occurrence, but is otherwise identical to split_at().
+/* DIAGNOSTICS
+/* The result is a null pointer when the delimiter character
+/* was not found.
+/* HISTORY
+/* .ad
+/* .fi
+/* A split_at() routine appears in the TCP Wrapper software
+/* by Wietse Venema.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System libraries */
+
+#include <sys_defs.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include "split_at.h"
+
+/* split_at - break string at first delimiter, return remainder */
+
+char *split_at(char *string, int delimiter)
+{
+ char *cp;
+
+ if ((cp = strchr(string, delimiter)) != 0)
+ *cp++ = 0;
+ return (cp);
+}
+
+/* split_at_right - break string at last delimiter, return remainder */
+
+char *split_at_right(char *string, int delimiter)
+{
+ char *cp;
+
+ if ((cp = strrchr(string, delimiter)) != 0)
+ *cp++ = 0;
+ return (cp);
+}
diff --git a/src/util/split_at.h b/src/util/split_at.h
new file mode 100644
index 0000000..2d03ebb
--- /dev/null
+++ b/src/util/split_at.h
@@ -0,0 +1,35 @@
+#ifndef _SPLIT_AT_H_INCLUDED_
+#define _SPLIT_AT_H_INCLUDED_
+
+/*++
+/* NAME
+/* split_at 3h
+/* SUMMARY
+/* trivial token splitter
+/* SYNOPSIS
+/* #include <split_at.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern char *split_at(char *, int);
+extern char *split_at_right(char *, int);
+
+/* HISTORY
+/* .ad
+/* .fi
+/* A split_at() routine appears in the TCP Wrapper software
+/* by Wietse Venema.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/split_nameval.c b/src/util/split_nameval.c
new file mode 100644
index 0000000..da74fcf
--- /dev/null
+++ b/src/util/split_nameval.c
@@ -0,0 +1,97 @@
+/*++
+/* NAME
+/* split_nameval 3
+/* SUMMARY
+/* name-value splitter
+/* SYNOPSIS
+/* #include <split_nameval.h>
+/*
+/* const char *split_nameval(buf, name, value)
+/* char *buf;
+/* char **name;
+/* char **value;
+/* DESCRIPTION
+/* split_nameval() takes a logical line from readlline() and expects
+/* text of the form "name = value" or "name =". The buffer
+/* argument is broken up into name and value substrings.
+/*
+/* Arguments:
+/* .IP buf
+/* Result from readlline() or equivalent. The buffer is modified.
+/* .IP name
+/* Upon successful completion, this is set to the name
+/* substring.
+/* .IP value
+/* Upon successful completion, this is set to the value
+/* substring.
+/* FEATURES
+/* SEE ALSO
+/* dict(3) mid-level dictionary routines
+/* BUGS
+/* DIAGNOSTICS
+/* Fatal errors: out of memory.
+/*
+/* The result is a null pointer in case of success, a string
+/* describing the error otherwise: missing '=' after attribute
+/* name; missing attribute name.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System libraries. */
+
+#include "sys_defs.h"
+#include <ctype.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <stringops.h>
+
+/* split_nameval - split text into name and value */
+
+const char *split_nameval(char *buf, char **name, char **value)
+{
+ char *np; /* name substring */
+ char *vp; /* value substring */
+ char *cp;
+ char *ep;
+
+ /*
+ * Ugly macros to make complex expressions less unreadable.
+ */
+#define SKIP(start, var, cond) do { \
+ for (var = start; *var && (cond); var++) \
+ /* void */; \
+ } while (0)
+
+#define TRIM(s) do { \
+ char *p; \
+ for (p = (s) + strlen(s); p > (s) && ISSPACE(p[-1]); p--) \
+ /* void */; \
+ *p = 0; \
+ } while (0)
+
+ SKIP(buf, np, ISSPACE(*np)); /* find name begin */
+ if (*np == 0 || *np == '=')
+ return ("missing attribute name");
+ SKIP(np, ep, !ISSPACE(*ep) && *ep != '='); /* find name end */
+ SKIP(ep, cp, ISSPACE(*cp)); /* skip blanks before '=' */
+ if (*cp != '=') /* need '=' */
+ return ("missing '=' after attribute name");
+ *ep = 0; /* terminate name */
+ cp++; /* skip over '=' */
+ SKIP(cp, vp, ISSPACE(*vp)); /* skip leading blanks */
+ TRIM(vp); /* trim trailing blanks */
+ *name = np;
+ *value = vp;
+ return (0);
+}
diff --git a/src/util/split_qnameval.c b/src/util/split_qnameval.c
new file mode 100644
index 0000000..b166125
--- /dev/null
+++ b/src/util/split_qnameval.c
@@ -0,0 +1,168 @@
+/*++
+/* NAME
+/* split_qnameval 3
+/* SUMMARY
+/* name-value splitter
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* const char *split_qnameval(buf, name, value)
+/* char *buf;
+/* char **name;
+/* char **value;
+/* DESCRIPTION
+/* split_qnameval() expects text of the form "key = value"
+/* or "key =", where the key may be quoted with backslash or
+/* double quotes. The buffer argument is broken up into the key
+/* and value substrings.
+/*
+/* Arguments:
+/* .IP buf
+/* Result from readlline() or equivalent. The buffer is modified.
+/* .IP key
+/* Upon successful completion, this is set to the key
+/* substring.
+/* .IP value
+/* Upon successful completion, this is set to the value
+/* substring.
+/* SEE ALSO
+/* split_nameval(3) name-value splitter
+/* BUGS
+/* DIAGNOSTICS
+/* The result is a null pointer in case of success, a string
+/* describing the error otherwise: missing '=' after attribute
+/* name; missing attribute name.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System libraries. */
+
+#include <sys_defs.h>
+#include <ctype.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <stringops.h>
+
+/* split_qnameval - split "key = value", support quoted key */
+
+const char *split_qnameval(char *buf, char **pkey, char **pvalue)
+{
+ int in_quotes = 0;
+ char *key;
+ char *key_end;
+ char *value;
+
+ for (key = buf; *key && ISSPACE(*key); key++)
+ /* void */ ;
+ if (*key == 0)
+ return ("no key found; expected format: key = value");
+
+ for (key_end = key; *key_end; key_end++) {
+ if (*key_end == '\\') {
+ if (*++key_end == 0)
+ break;
+ } else if (ISSPACE(*key_end) || *key_end == '=') {
+ if (!in_quotes)
+ break;
+ } else if (*key_end == '"') {
+ in_quotes = !in_quotes;
+ }
+ }
+ if (in_quotes) {
+ return ("unbalanced '\"\'");
+ }
+ value = key_end;
+ while (ISSPACE(*value))
+ value++;
+ if (*value != '=')
+ return ("missing '=' after attribute name");
+ *key_end = 0;
+ do {
+ value++;
+ } while (ISSPACE(*value));
+ trimblanks(value, 0)[0] = 0;
+ *pkey = key;
+ *pvalue = value;
+ return (0);
+}
+
+#ifdef TEST
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <mymalloc.h>
+
+static int compare(int test_number, const char *what,
+ const char *expect, const char *real)
+{
+ if ((expect == 0 && real == 0)
+ || (expect != 0 && real != 0 && strcmp(expect, real) == 0)) {
+ return (0);
+ } else {
+ msg_warn("test %d: %s mis-match: expect='%s', real='%s'",
+ test_number, what, expect ? expect : "(null)",
+ real ? real : "(null)");
+ return (1);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ struct test_info {
+ const char *input;
+ const char *expect_result;
+ const char *expect_key;
+ const char *expect_value;
+ };
+ static const struct test_info test_info[] = {
+ /* Unquoted keys. */
+ {"xx = yy", 0, "xx", "yy"},
+ {"xx=yy", 0, "xx", "yy"},
+ {"xx =", 0, "xx", ""},
+ {"xx=", 0, "xx", ""},
+ {"xx", "missing '=' after attribute name", 0, 0},
+ /* Quoted keys. */
+ {"\"xx \" = yy", 0, "\"xx \"", "yy"},
+ {"\"xx \"= yy", 0, "\"xx \"", "yy"},
+ {"\"xx \" =", 0, "\"xx \"", ""},
+ {"\"xx \"=", 0, "\"xx \"", ""},
+ {"\"xx \"", "missing '=' after attribute name", 0, 0},
+ {"\"xx ", "unbalanced '\"'", 0, 0},
+ /* Backslash escapes. */
+ {"\"\\\"xx \" = yy", 0, "\"\\\"xx \"", "yy"},
+ {0,},
+ };
+
+ int errs = 0;
+ const struct test_info *tp;
+
+ for (tp = test_info; tp->input != 0; tp++) {
+ const char *result;
+ char *key = 0;
+ char *value = 0;
+ char *buf = mystrdup(tp->input);
+ int test_number = (int) (tp - test_info);
+
+ result = split_qnameval(buf, &key, &value);
+ errs += compare(test_number, "result", tp->expect_result, result);
+ errs += compare(test_number, "key", tp->expect_key, key);
+ errs += compare(test_number, "value", tp->expect_value, value);
+ myfree(buf);
+ }
+ exit(errs);
+}
+
+#endif
diff --git a/src/util/stat_as.c b/src/util/stat_as.c
new file mode 100644
index 0000000..3e05ff7
--- /dev/null
+++ b/src/util/stat_as.c
@@ -0,0 +1,73 @@
+/*++
+/* NAME
+/* stat_as 3
+/* SUMMARY
+/* stat file as user
+/* SYNOPSIS
+/* #include <sys/stat.h>
+/* #include <stat_as.h>
+/*
+/* int stat_as(path, st, euid, egid)
+/* const char *path;
+/* struct stat *st;
+/* uid_t euid;
+/* gid_t egid;
+/* DESCRIPTION
+/* stat_as() looks up the file status of the named \fIpath\fR,
+/* using the effective rights specified by \fIeuid\fR
+/* and \fIegid\fR, and stores the result into the structure pointed
+/* to by \fIst\fR. A -1 result means the lookup failed.
+/* This call follows symbolic links.
+/* DIAGNOSTICS
+/* Fatal error: no permission to change privilege level.
+/* SEE ALSO
+/* set_eugid(3) switch effective rights
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "set_eugid.h"
+#include "stat_as.h"
+#include "warn_stat.h"
+
+/* stat_as - stat file as user */
+
+int stat_as(const char *path, struct stat * st, uid_t euid, gid_t egid)
+{
+ uid_t saved_euid = geteuid();
+ gid_t saved_egid = getegid();
+ int status;
+
+ /*
+ * Switch to the target user privileges.
+ */
+ set_eugid(euid, egid);
+
+ /*
+ * Stat that file.
+ */
+ status = stat(path, st);
+
+ /*
+ * Restore saved privileges.
+ */
+ set_eugid(saved_euid, saved_egid);
+
+ return (status);
+}
diff --git a/src/util/stat_as.h b/src/util/stat_as.h
new file mode 100644
index 0000000..90bc05d
--- /dev/null
+++ b/src/util/stat_as.h
@@ -0,0 +1,35 @@
+#ifndef _STAT_AS_H_INCLUDED_
+#define _STAT_AS_H_INCLUDED_
+
+/*++
+/* NAME
+/* stat_as 3h
+/* SUMMARY
+/* stat file as user
+/* SYNOPSIS
+/* #include <sys/stat.h>
+/* #include <stat_as.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern int WARN_UNUSED_RESULT stat_as(const char *, struct stat *, uid_t, gid_t);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/strcasecmp.c b/src/util/strcasecmp.c
new file mode 100644
index 0000000..07e9381
--- /dev/null
+++ b/src/util/strcasecmp.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 1987, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if defined(LIBC_SCCS) && !defined(lint)
+static char sccsid[] = "@(#)strcasecmp.c 8.1 (Berkeley) 6/4/93";
+#endif /* LIBC_SCCS and not lint */
+
+#include <sys_defs.h>
+#include <ctype.h>
+
+int strcasecmp(const char *s1, const char *s2)
+{
+ const unsigned char *us1 = (const unsigned char *) s1;
+ const unsigned char *us2 = (const unsigned char *) s2;
+
+ while (tolower(*us1) == tolower(*us2++))
+ if (*us1++ == '\0')
+ return (0);
+ return (tolower(*us1) - tolower(*--us2));
+}
+
+int strncasecmp(const char *s1, const char *s2, size_t n)
+{
+ if (n != 0) {
+ const unsigned char *us1 = (const unsigned char *) s1;
+ const unsigned char *us2 = (const unsigned char *) s2;
+
+ do {
+ if (tolower(*us1) != tolower(*us2++))
+ return (tolower(*us1) - tolower(*--us2));
+ if (*us1++ == '\0')
+ break;
+ } while (--n != 0);
+ }
+ return (0);
+}
diff --git a/src/util/strcasecmp_utf8.c b/src/util/strcasecmp_utf8.c
new file mode 100644
index 0000000..e3f20df
--- /dev/null
+++ b/src/util/strcasecmp_utf8.c
@@ -0,0 +1,216 @@
+/*++
+/* NAME
+/* strcasecmp_utf8 3
+/* SUMMARY
+/* caseless string comparison
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* int strcasecmp_utf8(
+/* const char *s1,
+/* const char *s2)
+/*
+/* int strncasecmp_utf8(
+/* const char *s1,
+/* const char *s2,
+/* ssize_t len)
+/* AUXILIARY FUNCTIONS
+/* int strcasecmp_utf8x(
+/* int flags,
+/* const char *s1,
+/* const char *s2)
+/*
+/* int strncasecmp_utf8x(
+/* int flags,
+/* const char *s1,
+/* const char *s2,
+/* ssize_t len)
+/* DESCRIPTION
+/* strcasecmp_utf8() implements caseless string comparison for
+/* UTF-8 text, with an API similar to strcasecmp(). Only ASCII
+/* characters are casefolded when the code is compiled without
+/* EAI support or when util_utf8_enable is zero.
+/*
+/* strncasecmp_utf8() implements caseless string comparison
+/* for UTF-8 text, with an API similar to strncasecmp(). Only
+/* ASCII characters are casefolded when the code is compiled
+/* without EAI support or when util_utf8_enable is zero.
+/*
+/* strcasecmp_utf8x() and strncasecmp_utf8x() implement a more
+/* complex API that provides the above functionality and more.
+/*
+/* Arguments:
+/* .IP "s1, s2"
+/* Null-terminated strings to be compared.
+/* .IP len
+/* String length before casefolding.
+/* .IP flags
+/* Zero or CASEF_FLAG_UTF8. The latter flag enables UTF-8 case
+/* folding instead of folding only ASCII characters. This flag
+/* is ignored when compiled without EAI support.
+/* SEE ALSO
+/* casefold(), casefold text for caseless comparison.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+ /*
+ * Utility library.
+ */
+#include <stringops.h>
+
+#define STR(x) vstring_str(x)
+
+static VSTRING *f1; /* casefold result for s1 */
+static VSTRING *f2; /* casefold result for s2 */
+
+/* strcasecmp_utf8_init - initialize */
+
+static void strcasecmp_utf8_init(void)
+{
+ f1 = vstring_alloc(100);
+ f2 = vstring_alloc(100);
+}
+
+/* strcasecmp_utf8x - caseless string comparison */
+
+int strcasecmp_utf8x(int flags, const char *s1, const char *s2)
+{
+
+ /*
+ * Short-circuit optimization for ASCII-only text. This may be slower
+ * than using a cache for all results. We must not expose strcasecmp(3)
+ * to non-ASCII text.
+ */
+ if (allascii(s1) && allascii(s2))
+ return (strcasecmp(s1, s2));
+
+ if (f1 == 0)
+ strcasecmp_utf8_init();
+
+ /*
+ * Cross our fingers and hope that strcmp() remains agnostic of
+ * charactersets and locales.
+ */
+ flags &= CASEF_FLAG_UTF8;
+ casefoldx(flags, f1, s1, -1);
+ casefoldx(flags, f2, s2, -1);
+ return (strcmp(STR(f1), STR(f2)));
+}
+
+/* strncasecmp_utf8x - caseless string comparison */
+
+int strncasecmp_utf8x(int flags, const char *s1, const char *s2,
+ ssize_t len)
+{
+
+ /*
+ * Consider using a cache for all results.
+ */
+ if (f1 == 0)
+ strcasecmp_utf8_init();
+
+ /*
+ * Short-circuit optimization for ASCII-only text. This may be slower
+ * than using a cache for all results. See comments above for limitations
+ * of strcasecmp().
+ */
+ if (allascii_len(s1, len) && allascii_len(s2, len))
+ return (strncasecmp(s1, s2, len));
+
+ /*
+ * Caution: casefolding may change the number of bytes. See comments
+ * above for concerns about strcmp().
+ */
+ flags &= CASEF_FLAG_UTF8;
+ casefoldx(flags, f1, s1, len);
+ casefoldx(flags, f2, s2, len);
+ return (strcmp(STR(f1), STR(f2)));
+}
+
+#ifdef TEST
+#include <stdio.h>
+#include <stdlib.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <msg_vstream.h>
+#include <argv.h>
+
+int main(int argc, char **argv)
+{
+ VSTRING *buffer = vstring_alloc(1);
+ ARGV *cmd;
+ char **args;
+ int len;
+ int flags;
+ int res;
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ flags = CASEF_FLAG_UTF8;
+ util_utf8_enable = 1;
+ while (vstring_fgets_nonl(buffer, VSTREAM_IN)) {
+ vstream_printf("> %s\n", STR(buffer));
+ cmd = argv_split(STR(buffer), CHARS_SPACE);
+ if (cmd->argc == 0 || cmd->argv[0][0] == '#')
+ continue;
+ args = cmd->argv;
+
+ /*
+ * Compare two strings.
+ */
+ if (strcmp(args[0], "compare") == 0 && cmd->argc == 3) {
+ res = strcasecmp_utf8x(flags, args[1], args[2]);
+ vstream_printf("\"%s\" %s \"%s\"\n",
+ args[1],
+ res < 0 ? "<" : res == 0 ? "==" : ">",
+ args[2]);
+ }
+
+ /*
+ * Compare two substrings.
+ */
+ else if (strcmp(args[0], "compare-len") == 0 && cmd->argc == 4
+ && sscanf(args[3], "%d", &len) == 1 && len >= 0) {
+ res = strncasecmp_utf8x(flags, args[1], args[2], len);
+ vstream_printf("\"%.*s\" %s \"%.*s\"\n",
+ len, args[1],
+ res < 0 ? "<" : res == 0 ? "==" : ">",
+ len, args[2]);
+ }
+
+ /*
+ * Usage.
+ */
+ else {
+ vstream_printf("Usage: %s compare <s1> <s2> | compare-len <s1> <s2> <len>\n",
+ argv[0]);
+ }
+ vstream_fflush(VSTREAM_OUT);
+ argv_free(cmd);
+ }
+ exit(0);
+}
+
+#endif /* TEST */
diff --git a/src/util/strcasecmp_utf8_test.in b/src/util/strcasecmp_utf8_test.in
new file mode 100644
index 0000000..8bafbf0
--- /dev/null
+++ b/src/util/strcasecmp_utf8_test.in
@@ -0,0 +1,10 @@
+compare Δημοσθένους.example.com δημοσθένουσ.example.com
+compare Δημοσθένους.example.com ηδμοσθένουσ.example.com
+compare ηδμοσθένουσ.example.com Δημοσθένους.example.com
+compare HeLlO.ExAmPlE.CoM hello.example.com
+compare HeLlO hellp
+compare hellp HeLlO
+compare-len HeLlO hellp 4
+compare-len HeLO help 4
+compare abcde abcdf
+compare YYY€€€XXX yyy€€€xxx
diff --git a/src/util/strcasecmp_utf8_test.ref b/src/util/strcasecmp_utf8_test.ref
new file mode 100644
index 0000000..8c0e1a5
--- /dev/null
+++ b/src/util/strcasecmp_utf8_test.ref
@@ -0,0 +1,20 @@
+> compare Δημοσθένους.example.com δημοσθένουσ.example.com
+"Δημοσθένους.example.com" == "δημοσθένουσ.example.com"
+> compare Δημοσθένους.example.com ηδμοσθένουσ.example.com
+"Δημοσθένους.example.com" < "ηδμοσθένουσ.example.com"
+> compare ηδμοσθένουσ.example.com Δημοσθένους.example.com
+"ηδμοσθένουσ.example.com" > "Δημοσθένους.example.com"
+> compare HeLlO.ExAmPlE.CoM hello.example.com
+"HeLlO.ExAmPlE.CoM" == "hello.example.com"
+> compare HeLlO hellp
+"HeLlO" < "hellp"
+> compare hellp HeLlO
+"hellp" > "HeLlO"
+> compare-len HeLlO hellp 4
+"HeLl" == "hell"
+> compare-len HeLO help 4
+"HeLO" < "help"
+> compare abcde abcdf
+"abcde" < "abcdf"
+> compare YYY€€€XXX yyy€€€xxx
+"YYY€€€XXX" == "yyy€€€xxx"
diff --git a/src/util/stream_connect.c b/src/util/stream_connect.c
new file mode 100644
index 0000000..b8cc624
--- /dev/null
+++ b/src/util/stream_connect.c
@@ -0,0 +1,109 @@
+/*++
+/* NAME
+/* stream_connect 3
+/* SUMMARY
+/* connect to stream listener
+/* SYNOPSIS
+/* #include <connect.h>
+/*
+/* int stream_connect(path, block_mode, timeout)
+/* const char *path;
+/* int block_mode;
+/* int timeout;
+/* DESCRIPTION
+/* stream_connect() connects to a stream listener for the specified
+/* pathname, and returns the resulting file descriptor.
+/*
+/* Arguments:
+/* .IP path
+/* Null-terminated string with listener endpoint name.
+/* .IP block_mode
+/* Either NON_BLOCKING for a non-blocking stream, or BLOCKING for
+/* blocking mode. However, a stream connection succeeds or fails
+/* immediately.
+/* .IP timeout
+/* This argument is ignored; it is present for compatibility with
+/* other interfaces. Stream connections succeed or fail immediately.
+/* DIAGNOSTICS
+/* The result is -1 in case the connection could not be made.
+/* Fatal errors: other system call failures.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+#ifdef STREAM_CONNECTIONS
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stropts.h>
+
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <connect.h>
+
+/* stream_connect - connect to stream listener */
+
+int stream_connect(const char *path, int block_mode, int unused_timeout)
+{
+#ifdef STREAM_CONNECTIONS
+ const char *myname = "stream_connect";
+ int pair[2];
+ int fifo;
+
+ /*
+ * The requested file system object must exist, otherwise we can't reach
+ * the server.
+ */
+ if ((fifo = open(path, O_WRONLY | O_NONBLOCK, 0)) < 0)
+ return (-1);
+
+ /*
+ * This is for {unix,inet}_connect() compatibility.
+ */
+ if (block_mode == BLOCKING)
+ non_blocking(fifo, BLOCKING);
+
+ /*
+ * Create a pipe, and send one pipe end to the server.
+ */
+ if (pipe(pair) < 0)
+ msg_fatal("%s: pipe: %m", myname);
+ if (ioctl(fifo, I_SENDFD, pair[1]) < 0)
+ msg_fatal("%s: send file descriptor: %m", myname);
+ close(pair[1]);
+
+ /*
+ * This is for {unix,inet}_connect() compatibility.
+ */
+ if (block_mode == NON_BLOCKING)
+ non_blocking(pair[0], NON_BLOCKING);
+
+ /*
+ * Cleanup.
+ */
+ close(fifo);
+
+ /*
+ * Keep the other end of the pipe.
+ */
+ return (pair[0]);
+#else
+ msg_fatal("stream connections are not implemented");
+#endif
+}
diff --git a/src/util/stream_listen.c b/src/util/stream_listen.c
new file mode 100644
index 0000000..b522b76
--- /dev/null
+++ b/src/util/stream_listen.c
@@ -0,0 +1,102 @@
+/*++
+/* NAME
+/* stream_listen 3
+/* SUMMARY
+/* start stream listener
+/* SYNOPSIS
+/* #include <listen.h>
+/*
+/* int stream_listen(path, backlog, block_mode)
+/* const char *path;
+/* int backlog;
+/* int block_mode;
+/*
+/* int stream_accept(fd)
+/* int fd;
+/* DESCRIPTION
+/* This module implements a substitute local IPC for systems that do
+/* not have properly-working UNIX-domain sockets.
+/*
+/* stream_listen() creates a listener endpoint with the specified
+/* permissions, and returns a file descriptor to be used for accepting
+/* connections.
+/*
+/* stream_accept() accepts a connection.
+/*
+/* Arguments:
+/* .IP path
+/* Null-terminated string with connection destination.
+/* .IP backlog
+/* This argument exists for compatibility and is ignored.
+/* .IP block_mode
+/* Either NON_BLOCKING or BLOCKING. This does not affect the
+/* mode of accepted connections.
+/* .IP fd
+/* File descriptor returned by stream_listen().
+/* DIAGNOSTICS
+/* Fatal errors: stream_listen() aborts upon any system call failure.
+/* stream_accept() leaves all error handling up to the caller.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System interfaces. */
+
+#include <sys_defs.h>
+
+#ifdef STREAM_CONNECTIONS
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stropts.h>
+#include <fcntl.h>
+
+#endif
+
+/* Utility library. */
+
+#include "msg.h"
+#include "listen.h"
+
+/* stream_listen - create stream listener */
+
+int stream_listen(const char *path, int unused_backlog, int block_mode)
+{
+#ifdef STREAM_CONNECTIONS
+
+ /*
+ * We can't specify a listen backlog, however, sending file descriptors
+ * across a FIFO gives us a backlog buffer of 460 on Solaris 2.4/SPARC.
+ */
+ return (fifo_listen(path, 0622, block_mode));
+#else
+ msg_fatal("stream connections are not implemented");
+#endif
+}
+
+/* stream_accept - accept stream connection */
+
+int stream_accept(int fd)
+{
+#ifdef STREAM_CONNECTIONS
+ struct strrecvfd fdinfo;
+
+ /*
+ * This will return EAGAIN on a non-blocking stream when someone else
+ * snatched the connection from us.
+ */
+ if (ioctl(fd, I_RECVFD, &fdinfo) < 0)
+ return (-1);
+ return (fdinfo.fd);
+#else
+ msg_fatal("stream connections are not implemented");
+#endif
+}
diff --git a/src/util/stream_recv_fd.c b/src/util/stream_recv_fd.c
new file mode 100644
index 0000000..5be02e4
--- /dev/null
+++ b/src/util/stream_recv_fd.c
@@ -0,0 +1,120 @@
+/*++
+/* NAME
+/* stream_recv_fd 3
+/* SUMMARY
+/* receive file descriptor
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* int stream_recv_fd(fd)
+/* int fd;
+/* DESCRIPTION
+/* stream_recv_fd() receives a file descriptor via the specified
+/* stream. The result value is the received descriptor.
+/*
+/* Arguments:
+/* .IP fd
+/* File descriptor that connects the sending and receiving processes.
+/* DIAGNOSTICS
+/* stream_recv_fd() returns -1 upon failure.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h> /* includes <sys/types.h> */
+
+#ifdef STREAM_CONNECTIONS
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stropts.h>
+#include <fcntl.h>
+
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+/* stream_recv_fd - receive file descriptor */
+
+int stream_recv_fd(int fd)
+{
+#ifdef STREAM_CONNECTIONS
+ struct strrecvfd fdinfo;
+
+ /*
+ * This will return EAGAIN on a non-blocking stream when someone else
+ * snatched the connection from us.
+ */
+ if (ioctl(fd, I_RECVFD, &fdinfo) < 0)
+ return (-1);
+ return (fdinfo.fd);
+#else
+ msg_fatal("stream connections are not implemented");
+#endif
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept program. Receive a descriptor (presumably from the
+ * stream_send_fd test program) and copy its content until EOF.
+ */
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <split_at.h>
+#include <listen.h>
+
+int main(int argc, char **argv)
+{
+ char *transport;
+ char *endpoint;
+ int listen_sock;
+ int client_sock;
+ int client_fd;
+ ssize_t read_count;
+ char buf[1024];
+
+ if (argc != 2
+ || (endpoint = split_at(transport = argv[1], ':')) == 0
+ || *endpoint == 0 || *transport == 0)
+ msg_fatal("usage: %s transport:endpoint", argv[0]);
+
+ if (strcmp(transport, "stream") == 0) {
+ listen_sock = stream_listen(endpoint, BLOCKING, 0);
+ } else {
+ msg_fatal("invalid transport name: %s", transport);
+ }
+ if (listen_sock < 0)
+ msg_fatal("listen %s:%s: %m", transport, endpoint);
+
+ client_sock = stream_accept(listen_sock);
+ if (client_sock < 0)
+ msg_fatal("stream_accept: %m");
+
+ while ((client_fd = stream_recv_fd(client_sock)) >= 0) {
+ msg_info("client_fd = %d", client_fd);
+ while ((read_count = read(client_fd, buf, sizeof(buf))) > 0)
+ write(1, buf, read_count);
+ if (read_count < 0)
+ msg_fatal("read: %m");
+ if (close(client_fd) != 0)
+ msg_fatal("close(%d): %m", client_fd);
+ }
+ exit(0);
+}
+
+#endif
diff --git a/src/util/stream_send_fd.c b/src/util/stream_send_fd.c
new file mode 100644
index 0000000..0a9aebf
--- /dev/null
+++ b/src/util/stream_send_fd.c
@@ -0,0 +1,115 @@
+/*++
+/* NAME
+/* stream_send_fd 3
+/* SUMMARY
+/* send file descriptor
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* int stream_send_fd(fd, sendfd)
+/* int fd;
+/* int sendfd;
+/* DESCRIPTION
+/* stream_send_fd() sends a file descriptor over the specified
+/* stream.
+/*
+/* Arguments:
+/* .IP fd
+/* File descriptor that connects the sending and receiving processes.
+/* .IP sendfd
+/* The file descriptor to be sent.
+/* DIAGNOSTICS
+/* stream_send_fd() returns -1 upon failure.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h> /* includes <sys/types.h> */
+
+#ifdef STREAM_CONNECTIONS
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stropts.h>
+
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+/* stream_send_fd - send file descriptor */
+
+int stream_send_fd(int fd, int sendfd)
+{
+#ifdef STREAM_CONNECTIONS
+ const char *myname = "stream_send_fd";
+
+ if (ioctl(fd, I_SENDFD, sendfd) < 0)
+ msg_fatal("%s: send file descriptor %d: %m", myname, sendfd);
+ return (0);
+#else
+ msg_fatal("stream connections are not implemented");
+#endif
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept program. Open a file and send the descriptor, presumably
+ * to the stream_recv_fd test program.
+ */
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdlib.h>
+#include <split_at.h>
+#include <connect.h>
+
+int main(int argc, char **argv)
+{
+ char *transport;
+ char *endpoint;
+ char *path;
+ int server_sock;
+ int client_fd;
+
+ if (argc < 3
+ || (endpoint = split_at(transport = argv[1], ':')) == 0
+ || *endpoint == 0 || *transport == 0)
+ msg_fatal("usage: %s transport:endpoint file...", argv[0]);
+
+ if (strcmp(transport, "stream") == 0) {
+ server_sock = stream_connect(endpoint, BLOCKING, 0);
+ } else {
+ msg_fatal("invalid transport name: %s", transport);
+ }
+ if (server_sock < 0)
+ msg_fatal("connect %s:%s: %m", transport, endpoint);
+
+ argv += 2;
+ while ((path = *argv++) != 0) {
+ if ((client_fd = open(path, O_RDONLY, 0)) < 0)
+ msg_fatal("open %s: %m", path);
+ msg_info("path=%s client_fd=%d", path, client_fd);
+ if (stream_send_fd(server_sock, client_fd) < 0)
+ msg_fatal("send file descriptor: %m");
+ if (close(client_fd) != 0)
+ msg_fatal("close(%d): %m", client_fd);
+ }
+ exit(0);
+}
+
+#endif
diff --git a/src/util/stream_test.c b/src/util/stream_test.c
new file mode 100644
index 0000000..5c8f82f
--- /dev/null
+++ b/src/util/stream_test.c
@@ -0,0 +1,111 @@
+#include "sys_defs.h"
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <fcntl.h>
+
+#include "iostuff.h"
+#include "msg.h"
+#include "msg_vstream.h"
+#include "listen.h"
+#include "connect.h"
+
+#ifdef SUNOS5
+#include <stropts.h>
+
+#define FIFO "/tmp/test-fifo"
+
+static const char *progname;
+
+static void print_fstat(int fd)
+{
+ struct stat st;
+
+ if (fstat(fd, &st) < 0)
+ msg_fatal("fstat: %m");
+ vstream_printf("fd %d\n", fd);
+ vstream_printf("dev %ld\n", (long) st.st_dev);
+ vstream_printf("ino %ld\n", (long) st.st_ino);
+ vstream_fflush(VSTREAM_OUT);
+}
+
+static NORETURN usage(void)
+{
+ msg_fatal("usage: %s [-p] [-n count] [-v]", progname);
+}
+
+int main(int argc, char **argv)
+{
+ int server_fd;
+ int client_fd;
+ int fd;
+ int print_fstats = 0;
+ int count = 1;
+ int ch;
+ int i;
+
+ progname = argv[0];
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ /*
+ * Parse JCL.
+ */
+ while ((ch = GETOPT(argc, argv, "pn:v")) > 0) {
+ switch (ch) {
+ default:
+ usage();
+ case 'p':
+ print_fstats = 1;
+ break;
+ case 'n':
+ if ((count = atoi(optarg)) < 1)
+ usage();
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ }
+ }
+ server_fd = stream_listen(FIFO, 0, 0);
+ if (readable(server_fd))
+ msg_fatal("server fd is readable after create");
+
+ /*
+ * Connect in client.
+ */
+ for (i = 0; i < count; i++) {
+ msg_info("connect attempt %d", i);
+ if ((client_fd = stream_connect(FIFO, 0, 0)) < 0)
+ msg_fatal("open %s as client: %m", FIFO);
+ if (readable(server_fd))
+ msg_info("server fd is readable after client open");
+ if (close(client_fd) < 0)
+ msg_fatal("close client fd: %m");
+ }
+
+ /*
+ * Accept in server.
+ */
+ for (i = 0; i < count; i++) {
+ msg_info("receive attempt %d", i);
+ if (!readable(server_fd)) {
+ msg_info("wait for server fd to become readable");
+ read_wait(server_fd, -1);
+ }
+ if ((fd = stream_accept(server_fd)) < 0)
+ msg_fatal("receive fd: %m");
+ if (print_fstats)
+ print_fstat(fd);
+ if (close(fd) < 0)
+ msg_fatal("close received fd: %m");
+ }
+ if (close(server_fd) < 0)
+ msg_fatal("close server fd");
+ return (0);
+}
+#else
+int main(int argc, char **argv)
+{
+ return (0);
+}
+#endif
diff --git a/src/util/stream_trigger.c b/src/util/stream_trigger.c
new file mode 100644
index 0000000..4feea5f
--- /dev/null
+++ b/src/util/stream_trigger.c
@@ -0,0 +1,130 @@
+/*++
+/* NAME
+/* stream_trigger 3
+/* SUMMARY
+/* wakeup stream server
+/* SYNOPSIS
+/* #include <trigger.h>
+/*
+/* int stream_trigger(service, buf, len, timeout)
+/* const char *service;
+/* const char *buf;
+/* ssize_t len;
+/* int timeout;
+/* DESCRIPTION
+/* stream_trigger() wakes up the named stream server by making
+/* a brief connection to it and writing the named buffer.
+/*
+/* The connection is closed by a background thread. Some kernels
+/* cannot handle client-side disconnect before the server has
+/* received the message.
+/*
+/* Arguments:
+/* .IP service
+/* Name of the communication endpoint.
+/* .IP buf
+/* Address of data to be written.
+/* .IP len
+/* Amount of data to be written.
+/* .IP timeout
+/* Deadline in seconds. Specify a value <= 0 to disable
+/* the time limit.
+/* DIAGNOSTICS
+/* The result is zero in case of success, -1 in case of problems.
+/* SEE ALSO
+/* stream_connect(3), stream client
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <unistd.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <connect.h>
+#include <iostuff.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <trigger.h>
+
+struct stream_trigger {
+ int fd;
+ char *service;
+};
+
+/* stream_trigger_event - disconnect from peer */
+
+static void stream_trigger_event(int event, void *context)
+{
+ struct stream_trigger *sp = (struct stream_trigger *) context;
+ static const char *myname = "stream_trigger_event";
+
+ /*
+ * Disconnect.
+ */
+ if (event == EVENT_TIME)
+ msg_warn("%s: read timeout for service %s", myname, sp->service);
+ event_disable_readwrite(sp->fd);
+ event_cancel_timer(stream_trigger_event, context);
+ if (close(sp->fd) < 0)
+ msg_warn("%s: close %s: %m", myname, sp->service);
+ myfree(sp->service);
+ myfree((void *) sp);
+}
+
+/* stream_trigger - wakeup stream server */
+
+int stream_trigger(const char *service, const char *buf, ssize_t len, int timeout)
+{
+ const char *myname = "stream_trigger";
+ struct stream_trigger *sp;
+ int fd;
+
+ if (msg_verbose > 1)
+ msg_info("%s: service %s", myname, service);
+
+ /*
+ * Connect...
+ */
+ if ((fd = stream_connect(service, BLOCKING, timeout)) < 0) {
+ if (msg_verbose)
+ msg_warn("%s: connect to %s: %m", myname, service);
+ return (-1);
+ }
+ close_on_exec(fd, CLOSE_ON_EXEC);
+
+ /*
+ * Stash away context.
+ */
+ sp = (struct stream_trigger *) mymalloc(sizeof(*sp));
+ sp->fd = fd;
+ sp->service = mystrdup(service);
+
+ /*
+ * Write the request...
+ */
+ if (write_buf(fd, buf, len, timeout) < 0
+ || write_buf(fd, "", 1, timeout) < 0)
+ if (msg_verbose)
+ msg_warn("%s: write to %s: %m", myname, service);
+
+ /*
+ * Wakeup when the peer disconnects, or when we lose patience.
+ */
+ if (timeout > 0)
+ event_request_timer(stream_trigger_event, (void *) sp, timeout + 100);
+ event_enable_read(fd, stream_trigger_event, (void *) sp);
+ return (0);
+}
diff --git a/src/util/stringops.h b/src/util/stringops.h
new file mode 100644
index 0000000..97aa597
--- /dev/null
+++ b/src/util/stringops.h
@@ -0,0 +1,114 @@
+#ifndef _STRINGOPS_H_INCLUDED_
+#define _STRINGOPS_H_INCLUDED_
+
+/*++
+/* NAME
+/* stringops 3h
+/* SUMMARY
+/* string operations
+/* SYNOPSIS
+/* #include <stringops.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern int util_utf8_enable;
+extern char *printable_except(char *, int, const char *);
+extern char *neuter(char *, const char *, int);
+extern char *lowercase(char *);
+extern char *casefoldx(int, VSTRING *, const char *, ssize_t);
+extern char *uppercase(char *);
+extern char *skipblanks(const char *);
+extern char *trimblanks(char *, ssize_t);
+extern char *concatenate(const char *,...);
+extern char *mystrtok(char **, const char *);
+extern char *mystrtokq(char **, const char *, const char *);
+extern char *mystrtokdq(char **, const char *);
+extern char *mystrtok_cw(char **, const char *, const char *);
+extern char *mystrtokq_cw(char **, const char *, const char *, const char *);
+extern char *mystrtokdq_cw(char **, const char *, const char *);
+extern char *translit(char *, const char *, const char *);
+
+#define mystrtok(cp, sp) mystrtok_cw((cp), (sp), (char *) 0)
+#define mystrtokq(cp, sp, pp) mystrtokq_cw((cp), (sp), (pp), (char *) 0)
+#define mystrtokdq(cp, sp) mystrtokdq_cw((cp), (sp), (char *) 0)
+
+#define printable(string, replacement) \
+ printable_except((string), (replacement), (char *) 0)
+
+#ifndef HAVE_BASENAME
+#define basename postfix_basename
+extern char *basename(const char *);
+
+#endif
+extern char *sane_basename(VSTRING *, const char *);
+extern char *sane_dirname(VSTRING *, const char *);
+extern VSTRING *unescape(VSTRING *, const char *);
+extern VSTRING *escape(VSTRING *, const char *, ssize_t);
+extern int alldig(const char *);
+extern int allalnum(const char *);
+extern int allprint(const char *);
+extern int allspace(const char *);
+extern int allascii_len(const char *, ssize_t);
+extern const char *WARN_UNUSED_RESULT split_nameval(char *, char **, char **);
+extern const char *WARN_UNUSED_RESULT split_qnameval(char *, char **, char **);
+extern int valid_utf8_string(const char *, ssize_t);
+extern size_t balpar(const char *, const char *);
+extern char *WARN_UNUSED_RESULT extpar(char **, const char *, int);
+extern int strcasecmp_utf8x(int, const char *, const char *);
+extern int strncasecmp_utf8x(int, const char *, const char *, ssize_t);
+
+#define EXTPAR_FLAG_NONE (0)
+#define EXTPAR_FLAG_STRIP (1<<0) /* "{ text }" -> "text" */
+#define EXTPAR_FLAG_EXTRACT (1<<1) /* hint from caller's caller */
+
+#define CASEF_FLAG_UTF8 (1<<0)
+#define CASEF_FLAG_APPEND (1<<1)
+
+ /*
+ * Convenience wrappers for most-common use cases.
+ */
+#define allascii(s) allascii_len((s), -1)
+#define casefold(dst, src) \
+ casefoldx(util_utf8_enable ? CASEF_FLAG_UTF8 : 0, (dst), (src), -1)
+#define casefold_len(dst, src, len) \
+ casefoldx(util_utf8_enable ? CASEF_FLAG_UTF8 : 0, (dst), (src), (len))
+#define casefold_append(dst, src) \
+ casefoldx((util_utf8_enable ? CASEF_FLAG_UTF8 : 0) | CASEF_FLAG_APPEND, \
+ (dst), (src), -1)
+
+#define strcasecmp_utf8(s1, s2) \
+ strcasecmp_utf8x(util_utf8_enable ? CASEF_FLAG_UTF8 : 0, (s1), (s2))
+#define strncasecmp_utf8(s1, s2, l) \
+ strncasecmp_utf8x(util_utf8_enable ? CASEF_FLAG_UTF8 : 0, (s1), (s2), (l))
+
+ /*
+ * Use STRREF(x) instead of x, to shut up compiler warnings when the operand
+ * is a string literal.
+ */
+#define STRREF(x) (&x[0])
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/surrogate.ref b/src/util/surrogate.ref
new file mode 100644
index 0000000..b8ecd8e
--- /dev/null
+++ b/src/util/surrogate.ref
@@ -0,0 +1,55 @@
+./dict_open: error: cidr:/xx map requires O_RDONLY access mode
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: cidr:/xx is unavailable. cidr:/xx map requires O_RDONLY access mode
+foo: error
+./dict_open: error: open /xx: No such file or directory
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: cidr:/xx is unavailable. open /xx: No such file or directory
+foo: error
+./dict_open: error: pcre:/xx map requires O_RDONLY access mode
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: pcre:/xx is unavailable. pcre:/xx map requires O_RDONLY access mode
+foo: error
+./dict_open: error: open /xx: No such file or directory
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: pcre:/xx is unavailable. open /xx: No such file or directory
+foo: error
+./dict_open: error: regexp:/xx map requires O_RDONLY access mode
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: regexp:/xx is unavailable. regexp:/xx map requires O_RDONLY access mode
+foo: error
+./dict_open: error: open /xx: No such file or directory
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: regexp:/xx is unavailable. open /xx: No such file or directory
+foo: error
+./dict_open: error: unix:xx map requires O_RDONLY access mode
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: unix:xx is unavailable. unix:xx map requires O_RDONLY access mode
+foo: error
+./dict_open: error: unknown table: unix:xx
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: unix:xx is unavailable. unknown table: unix:xx
+foo: error
+./dict_open: error: texthash:/xx map requires O_RDONLY access mode
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: texthash:/xx is unavailable. texthash:/xx map requires O_RDONLY access mode
+foo: error
+./dict_open: error: open database /xx: No such file or directory
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: texthash:/xx is unavailable. open database /xx: No such file or directory
+foo: error
+./dict_open: error: open database /xx.db: No such file or directory
+owner=trusted (uid=2147483647)
+> get foo
+./dict_open: warning: hash:/xx is unavailable. open database /xx.db: No such file or directory
+foo: error
diff --git a/src/util/sys_compat.c b/src/util/sys_compat.c
new file mode 100644
index 0000000..8bf8e58
--- /dev/null
+++ b/src/util/sys_compat.c
@@ -0,0 +1,389 @@
+/*++
+/* NAME
+/* sys_compat 3
+/* SUMMARY
+/* compatibility routines
+/* SYNOPSIS
+/* #include <sys_defs.h>
+/*
+/* void closefrom(int lowfd)
+/* int lowfd;
+/*
+/* const char *strerror(err)
+/* int err;
+/*
+/* int setenv(name, value, clobber)
+/* const char *name;
+/* const char *value;
+/* int clobber;
+/*
+/* int unsetenv(name)
+/* const char *name;
+/*
+/* int seteuid(euid)
+/* uid_t euid;
+/*
+/* int setegid(egid)
+/* gid_t euid;
+/*
+/* int mkfifo(path, mode)
+/* char *path;
+/* int mode;
+/*
+/* int waitpid(pid, statusp, options)
+/* int pid;
+/* WAIT_STATUS_T *statusp;
+/* int options;
+/*
+/* int setsid()
+/*
+/* void dup2_pass_on_exec(int oldd, int newd)
+/*
+/* char *inet_ntop(af, src, dst, size)
+/* int af;
+/* const void *src;
+/* char *dst;
+/* SOCKADDR_SIZE size;
+/*
+/* int inet_pton(af, src, dst)
+/* int af;
+/* const char *src;
+/* void *dst;
+/* DESCRIPTION
+/* These routines are compiled for platforms that lack the functionality
+/* or that have broken versions that we prefer to stay away from.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+
+ /*
+ * ANSI strerror() emulation
+ */
+#ifdef MISSING_STRERROR
+
+extern int errno;
+extern char *sys_errlist[];
+extern int sys_nerr;
+
+#include <vstring.h>
+
+/* strerror - print text corresponding to error */
+
+const char *strerror(int err)
+{
+ static VSTRING *buf;
+
+ if (err < 0 || err >= sys_nerr) {
+ if (buf == 0)
+ buf = vstring_alloc(10);
+ vstring_sprintf(buf, "Unknown error %d", err);
+ return (vstring_str(buf));
+ } else {
+ return (sys_errlist[errno]);
+ }
+}
+
+#endif
+
+ /*
+ * setenv() emulation on top of putenv().
+ */
+#ifdef MISSING_SETENV
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* setenv - update or insert environment (name,value) pair */
+
+int setenv(const char *name, const char *value, int clobber)
+{
+ char *cp;
+
+ if (clobber == 0 && getenv(name) != 0)
+ return (0);
+ if ((cp = malloc(strlen(name) + strlen(value) + 2)) == 0)
+ return (1);
+ sprintf(cp, "%s=%s", name, value);
+ return (putenv(cp));
+}
+
+/* unsetenv - remove all instances of the name */
+
+int unsetenv(const char *name)
+{
+ extern char **environ;
+ ssize_t name_len = strlen(name);
+ char **src_pp;
+ char **dst_pp;
+
+ for (dst_pp = src_pp = environ; *src_pp; src_pp++, dst_pp++) {
+ if (strncmp(*src_pp, name, name_len) == 0
+ && *(*src_pp + name_len) == '=') {
+ dst_pp--;
+ } else if (dst_pp != src_pp) {
+ *dst_pp = *src_pp;
+ }
+ }
+ *dst_pp = 0;
+ return (0);
+}
+
+#endif
+
+ /*
+ * seteuid() and setegid() emulation, the HP-UX way
+ */
+#ifdef MISSING_SETEUID
+#ifdef HAVE_SETRESUID
+#include <unistd.h>
+
+int seteuid(uid_t euid)
+{
+ return setresuid(-1, euid, -1);
+}
+
+#else
+#error MISSING_SETEUID
+#endif
+
+#endif
+
+#ifdef MISSING_SETEGID
+#ifdef HAVE_SETRESGID
+#include <unistd.h>
+
+int setegid(gid_t egid)
+{
+ return setresgid(-1, egid, -1);
+}
+
+#else
+#error MISSING_SETEGID
+#endif
+
+#endif
+
+ /*
+ * mkfifo() emulation - requires superuser privileges
+ */
+#ifdef MISSING_MKFIFO
+
+#include <sys/stat.h>
+
+int mkfifo(char *path, int mode)
+{
+ return mknod(path, (mode & ~_S_IFMT) | _S_IFIFO, 0);
+}
+
+#endif
+
+ /*
+ * waitpid() emulation on top of Berkeley UNIX wait4()
+ */
+#ifdef MISSING_WAITPID
+#ifdef HAS_WAIT4
+
+#include <sys/wait.h>
+#include <errno.h>
+
+int waitpid(int pid, WAIT_STATUS_T *status, int options)
+{
+ if (pid == -1)
+ pid = 0;
+ return wait4(pid, status, options, (struct rusage *) 0);
+}
+
+#else
+#error MISSING_WAITPID
+#endif
+
+#endif
+
+ /*
+ * setsid() emulation, the Berkeley UNIX way
+ */
+#ifdef MISSING_SETSID
+
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#ifdef TIOCNOTTY
+
+#include <msg.h>
+
+int setsid(void)
+{
+ int p = getpid();
+ int fd;
+
+ if (setpgrp(p, p))
+ return -1;
+
+ fd = open("/dev/tty", O_RDONLY, 0);
+ if (fd >= 0 || errno != ENXIO) {
+ if (fd < 0) {
+ msg_warn("open /dev/tty: %m");
+ return -1;
+ }
+ if (ioctl(fd, TIOCNOTTY, 0)) {
+ msg_warn("ioctl TIOCNOTTY: %m");
+ return -1;
+ }
+ close(fd);
+ }
+ return 0;
+}
+
+#else
+#error MISSING_SETSID
+#endif
+
+#endif
+
+ /*
+ * dup2_pass_on_exec() - dup2() and clear close-on-exec flag on the result
+ */
+#ifdef DUP2_DUPS_CLOSE_ON_EXEC
+
+#include "iostuff.h"
+
+int dup2_pass_on_exec(int oldd, int newd)
+{
+ int res;
+
+ if ((res = dup2(oldd, newd)) >= 0)
+ close_on_exec(newd, PASS_ON_EXEC);
+
+ return res;
+}
+
+#endif
+
+#ifndef HAS_CLOSEFROM
+
+#include <unistd.h>
+#include <errno.h>
+#include <iostuff.h>
+
+/* closefrom() - closes all file descriptors from the given one up */
+
+int closefrom(int lowfd)
+{
+ int fd_limit = open_limit(0);
+ int fd;
+
+ /*
+ * lowfrom does not have an easy to determine upper limit. A process may
+ * have files open that were inherited from a parent process with a less
+ * restrictive resource limit.
+ */
+ if (lowfd < 0) {
+ errno = EBADF;
+ return (-1);
+ }
+ if (fd_limit > 500)
+ fd_limit = 500;
+ for (fd = lowfd; fd < fd_limit; fd++)
+ (void) close(fd);
+
+ return (0);
+}
+
+#endif
+
+#ifdef MISSING_INET_NTOP
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+/* inet_ntop - convert binary address to printable address */
+
+const char *inet_ntop(int af, const void *src, char *dst, SOCKADDR_SIZE size)
+{
+ const unsigned char *addr;
+ char buffer[sizeof("255.255.255.255")];
+ int len;
+
+ if (af != AF_INET) {
+ errno = EAFNOSUPPORT;
+ return (0);
+ }
+ addr = (const unsigned char *) src;
+#if (CHAR_BIT > 8)
+ sprintf(buffer, "%d.%d.%d.%d", addr[0] & 0xff,
+ addr[1] & 0xff, addr[2] & 0xff, addr[3] & 0xff);
+#else
+ sprintf(buffer, "%d.%d.%d.%d", addr[0], addr[1], addr[2], addr[3]);
+#endif
+ if ((len = strlen(buffer)) >= size) {
+ errno = ENOSPC;
+ return (0);
+ } else {
+ memcpy(dst, buffer, len + 1);
+ return (dst);
+ }
+}
+
+#endif
+
+#ifdef MISSING_INET_PTON
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <errno.h>
+
+#ifndef INADDR_NONE
+#define INADDR_NONE 0xffffffff
+#endif
+
+/* inet_pton - convert printable address to binary address */
+
+int inet_pton(int af, const char *src, void *dst)
+{
+ struct in_addr addr;
+
+ /*
+ * inet_addr() accepts a wider range of input formats than inet_pton();
+ * the former accepts 1-, 2-, or 3-part dotted addresses, while the
+ * latter requires dotted quad form.
+ */
+ if (af != AF_INET) {
+ errno = EAFNOSUPPORT;
+ return (-1);
+ } else if ((addr.s_addr = inet_addr(src)) == INADDR_NONE
+ && strcmp(src, "255.255.255.255") != 0) {
+ return (0);
+ } else {
+ memcpy(dst, (void *) &addr, sizeof(addr));
+ return (1);
+ }
+}
+
+#endif
diff --git a/src/util/sys_defs.h b/src/util/sys_defs.h
new file mode 100644
index 0000000..9247185
--- /dev/null
+++ b/src/util/sys_defs.h
@@ -0,0 +1,1801 @@
+#ifndef _SYS_DEFS_H_INCLUDED_
+#define _SYS_DEFS_H_INCLUDED_
+
+/*++
+/* NAME
+/* sys_defs 3h
+/* SUMMARY
+/* portability header
+/* SYNOPSIS
+/* #include <sys_defs.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Specific platforms. Major release numbers differ for a good reason. So be
+ * a good girl, plan for the future, and at least include the major release
+ * number in the system type (for example, SUNOS5 or FREEBSD2). The system
+ * type is determined by the makedefs shell script in the top-level
+ * directory. Adding support for a new system type means updating the
+ * makedefs script, and adding a section below for the new system.
+ */
+#ifdef SUNOS5
+#define _SVID_GETTOD /* Solaris 2.5, XSH4.2 versus SVID */
+#endif
+#include <sys/types.h>
+
+ /*
+ * 4.4BSD and close derivatives.
+ */
+#if defined(FREEBSD2) || defined(FREEBSD3) || defined(FREEBSD4) \
+ || defined(FREEBSD5) || defined(FREEBSD6) || defined(FREEBSD7) \
+ || defined(FREEBSD8) || defined(FREEBSD9) || defined(FREEBSD10) \
+ || defined(FREEBSD11) || defined(FREEBSD12) || defined(FREEBSD13) \
+ || defined(FREEBSD14) \
+ || defined(BSDI2) || defined(BSDI3) || defined(BSDI4) \
+ || defined(OPENBSD2) || defined(OPENBSD3) || defined(OPENBSD4) \
+ || defined(OPENBSD5) || defined(OPENBSD6) || defined(OPENBSD7) \
+ || defined(NETBSD1) || defined(NETBSD2) || defined(NETBSD3) \
+ || defined(NETBSD4) || defined(NETBSD5) || defined(NETBSD6) \
+ || defined(NETBSD7) | defined(NETBSD8) || defined(NETBSD9) \
+ || defined(NETBSD10) \
+ || defined(EKKOBSD1) || defined(DRAGONFLY)
+#define SUPPORTED
+#include <sys/param.h>
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define USE_PATHS_H
+#define HAS_FLOCK_LOCK
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK
+#define DEF_MAILBOX_LOCK "flock, dotlock"
+#define HAS_SUN_LEN
+#define HAS_FSYNC
+#define HAS_DB
+#define HAS_SA_LEN
+#define NATIVE_DB_TYPE "hash"
+#if (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 104250000)
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases" /* sendmail 8.10 */
+#endif
+#if (defined(OpenBSD) && OpenBSD >= 200006)
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases" /* OpenBSD 2.7 */
+#endif
+#ifndef ALIAS_DB_MAP
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases"
+#endif
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin"
+#if (defined(__NetBSD_Version__) && __NetBSD_Version__ > 299000900)
+#define USE_STATVFS
+#define STATVFS_IN_SYS_STATVFS_H
+#else
+#define USE_STATFS
+#define STATFS_IN_SYS_MOUNT_H
+#endif
+#define HAS_POSIX_REGEXP
+#define HAS_ST_GEN /* struct stat contains inode
+ * generation number */
+#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail"
+#define NATIVE_MAILQ_PATH "/usr/bin/mailq"
+#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases"
+#define NATIVE_COMMAND_DIR "/usr/sbin"
+#define NATIVE_DAEMON_DIR "/usr/libexec/postfix"
+#define HAS_DLOPEN
+#endif
+
+#ifdef FREEBSD2
+#define getsid(p) getpgrp()
+#ifndef CMSG_SPACE
+#define CMSG_SPACE(len) (CMSG_ALIGN(sizeof(struct cmsghdr)) + CMSG_ALIGN(len))
+#endif
+#ifndef CMSG_LEN
+#define CMSG_LEN(len) (CMSG_ALIGN(sizeof(struct cmsghdr)) + (len))
+#endif
+#ifndef CMSG_ALIGN
+#define CMSG_ALIGN(n) ALIGN(n)
+#endif
+#endif /* FREEBSD2 */
+
+#ifdef BSDI4
+/* #define HAS_IPV6 find out interface lookup method */
+#endif
+
+/* __FreeBSD_version version is major+minor */
+
+#if __FreeBSD_version >= 220000
+#define PREFERRED_RAND_SOURCE "dev:/dev/urandom" /* introduced 2.1.5 */
+#endif
+
+#if __FreeBSD_version >= 300000
+#define HAS_ISSETUGID
+#define HAS_FUTIMES
+#endif
+
+#if __FreeBSD_version >= 400000
+#define SOCKADDR_SIZE socklen_t
+#define SOCKOPT_SIZE socklen_t
+#endif
+
+#if __FreeBSD_version >= 420000
+#define HAS_DUPLEX_PIPE /* 4.1 breaks with kqueue(2) */
+#endif
+
+#if (__FreeBSD_version >= 702104 && __FreeBSD_version <= 800000) \
+ || __FreeBSD_version >= 800100
+#define HAS_CLOSEFROM
+#endif
+
+/* OpenBSD version is year+month */
+
+#if OpenBSD >= 199805 /* XXX */
+#define HAS_FUTIMES /* XXX maybe earlier */
+#endif
+
+#if (defined(OpenBSD) && OpenBSD >= 199608 && OpenBSD < 201105)
+#define PREFERRED_RAND_SOURCE "dev:/dev/arandom" /* XXX earlier */
+#endif
+
+#if OpenBSD >= 200000 /* XXX */
+#define HAS_ISSETUGID
+#endif
+
+#if OpenBSD >= 200200 /* XXX */
+#define SOCKADDR_SIZE socklen_t
+#define SOCKOPT_SIZE socklen_t
+#endif
+
+#if OpenBSD >= 200405 /* 3.5 */
+#define HAS_CLOSEFROM
+#endif
+
+/* __NetBSD_Version__ is major+minor */
+
+#if __NetBSD_Version__ >= 103000000 /* XXX maybe earlier */
+#undef DEF_MAILBOX_LOCK
+#define DEF_MAILBOX_LOCK "flock, dotlock"
+#define PREFERRED_RAND_SOURCE "dev:/dev/urandom" /* XXX maybe earlier */
+#endif
+
+#if __NetBSD_Version__ >= 105000000
+#define HAS_ISSETUGID /* XXX maybe earlier */
+#endif
+
+#if __NetBSD_Version__ >= 106000000 /* XXX maybe earlier */
+#define SOCKADDR_SIZE socklen_t
+#define SOCKOPT_SIZE socklen_t
+#endif
+
+#if __NetBSD_Version__ >= 299000900 /* 2.99.9 */
+#define HAS_CLOSEFROM
+#endif
+
+#if (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 102000000)
+#define HAS_FUTIMES
+#endif
+
+#if defined(__DragonFly__)
+#define HAS_DEV_URANDOM
+#define HAS_ISSETUGID
+#define HAS_FUTIMES
+#define SOCKADDR_SIZE socklen_t
+#define SOCKOPT_SIZE socklen_t
+#define HAS_DUPLEX_PIPE
+#endif
+
+#if (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 105000000) \
+ || (defined(__FreeBSD__) && __FreeBSD__ >= 4) \
+ || (defined(OpenBSD) && OpenBSD >= 200003) \
+ || defined(__DragonFly__) \
+ || defined(USAGI_LIBINET6)
+#ifndef NO_IPV6
+#define HAS_IPV6
+#define HAVE_GETIFADDRS
+#endif
+
+#if (defined(__FreeBSD_version) && __FreeBSD_version >= 300000) \
+ || (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 103000000) \
+ || (defined(OpenBSD) && OpenBSD >= 199700) /* OpenBSD 2.0?? */ \
+ || defined(__DragonFly__)
+#define USE_SYSV_POLL
+#endif
+
+#ifndef NO_KQUEUE
+#if (defined(__FreeBSD_version) && __FreeBSD_version >= 410000) \
+ || (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 200000000) \
+ || (defined(OpenBSD) && OpenBSD >= 200105) /* OpenBSD 2.9 */ \
+ || defined(__DragonFly__)
+#define EVENTS_STYLE EVENTS_STYLE_KQUEUE
+#endif
+#endif
+
+#ifndef NO_POSIX_GETPW_R
+#if (defined(__FreeBSD_version) && __FreeBSD_version >= 510000) \
+ || (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 300000000) \
+ || (defined(OpenBSD) && OpenBSD >= 200811) /* OpenBSD 4.4 */
+#define HAVE_POSIX_GETPW_R
+#endif
+#endif
+
+#endif
+
+ /*
+ * UNIX on MAC.
+ */
+#if defined(RHAPSODY5) || defined(MACOSX)
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define USE_PATHS_H
+#define HAS_FLOCK_LOCK
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK
+#define DEF_MAILBOX_LOCK "flock, dotlock"
+#define HAS_SUN_LEN
+#define HAS_FSYNC
+#define HAS_DB
+#define HAS_SA_LEN
+#define NATIVE_DB_TYPE "hash"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases"
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin"
+#define USE_STATFS
+#define STATFS_IN_SYS_MOUNT_H
+#define HAS_POSIX_REGEXP
+#ifndef NO_NETINFO
+#define HAS_NETINFO
+#endif
+#ifndef NO_IPV6
+#define HAS_IPV6
+#define HAVE_GETIFADDRS
+#endif
+#define HAS_FUTIMES /* XXX Guessing */
+#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail"
+#define NATIVE_MAILQ_PATH "/usr/bin/mailq"
+#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases"
+#define NATIVE_COMMAND_DIR "/usr/sbin"
+#define NATIVE_DAEMON_DIR "/usr/libexec/postfix"
+#define SOCKADDR_SIZE socklen_t
+#define SOCKOPT_SIZE socklen_t
+#ifndef NO_KQUEUE
+#define EVENTS_STYLE EVENTS_STYLE_KQUEUE
+#define USE_SYSV_POLL_THEN_SELECT
+#endif
+#define USE_MAX_FILES_PER_PROC
+#ifndef NO_POSIX_GETPW_R
+#define HAVE_POSIX_GETPW_R
+#endif
+#define HAS_DLOPEN
+#define PREFERRED_RAND_SOURCE "dev:/dev/urandom"
+#endif
+
+ /*
+ * Ultrix 4.x, a sort of 4.[1-2] BSD system with System V.2 compatibility
+ * and POSIX.
+ */
+#ifdef ULTRIX4
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+/* Ultrix by default has only 64 descriptors per process */
+#ifndef FD_SETSIZE
+#define FD_SETSIZE 96
+#endif
+#define _PATH_MAILDIR "/var/spool/mail"
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_DEFPATH "/bin:/usr/bin:/usr/ucb"
+#define _PATH_STDPATH "/bin:/usr/bin:/usr/etc:/usr/ucb"
+#define HAS_FLOCK_LOCK
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK
+#define DEF_MAILBOX_LOCK "flock, dotlock"
+#define HAS_FSYNC
+/* might be set by makedef */
+#ifdef HAS_DB
+#define NATIVE_DB_TYPE "hash"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases"
+#else
+#define HAS_DBM
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases"
+#endif
+extern int optind;
+extern char *optarg;
+extern int opterr;
+extern int h_errno;
+
+#define MISSING_STRFTIME_E
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define ROOT_PATH "/bin:/usr/bin:/etc:/usr/etc:/usr/ucb"
+#define USE_STATFS
+#define USE_STRUCT_FS_DATA
+#define STATFS_IN_SYS_MOUNT_H
+/* Ultrix misses just S_ISSOCK, the others are there */
+#define S_ISSOCK(mode) (((mode) & (S_IFMT)) == (S_IFSOCK))
+#define DUP2_DUPS_CLOSE_ON_EXEC
+#define MISSING_USLEEP
+#define NO_HERRNO
+#define NATIVE_SENDMAIL_PATH "/usr/lib/sendmail"
+#define NATIVE_COMMAND_DIR "/usr/etc"
+#define NATIVE_DAEMON_DIR "/usr/libexec/postfix"
+#endif
+
+ /*
+ * OSF, then Digital UNIX, then Compaq. A BSD-flavored hybrid.
+ */
+#ifdef OSF1
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define MISSING_SETENV
+#define USE_PATHS_H
+#define _PATH_DEFPATH "/usr/bin:/usr/ucb"
+#define HAS_FLOCK_LOCK
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK
+#define DEF_MAILBOX_LOCK "flock, dotlock"
+#define HAS_FSYNC
+#define HAVE_BASENAME
+#define HAS_DBM
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/var/adm/sendmail/aliases"
+extern int optind; /* XXX use <getopt.h> */
+extern char *optarg; /* XXX use <getopt.h> */
+extern int opterr; /* XXX use <getopt.h> */
+
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb"
+#define USE_STATFS
+#define STATFS_IN_SYS_MOUNT_H
+#define HAS_POSIX_REGEXP
+#define BROKEN_WRITE_SELECT_ON_NON_BLOCKING_PIPE
+#define NO_MSGHDR_MSG_CONTROL
+#ifndef NO_IPV6
+#define HAS_IPV6
+#endif
+
+#endif
+
+ /*
+ * SunOS 4.x, a mostly 4.[2-3] BSD system with System V.2 compatibility and
+ * POSIX support.
+ */
+#ifdef SUNOS4
+#define SUPPORTED
+#include <memory.h>
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define UNSAFE_CTYPE
+#define fpos_t long
+#define MISSING_SETENV
+#define MISSING_STRERROR
+#define MISSING_STRTOUL
+#define _PATH_MAILDIR "/var/spool/mail"
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_DEFPATH "/usr/bin:/usr/ucb"
+#define _PATH_STDPATH "/usr/bin:/usr/etc:/usr/ucb"
+#define HAS_FLOCK_LOCK
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK
+#define DEF_MAILBOX_LOCK "flock, dotlock"
+#define HAS_FSYNC
+#define HAS_DBM
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases"
+extern int optind;
+extern char *optarg;
+extern int opterr;
+
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define ROOT_PATH "/bin:/usr/bin:/etc:/usr/etc:/usr/ucb"
+#define USE_STATFS
+#define STATFS_IN_SYS_VFS_H
+#define memmove(d,s,l) bcopy(s,d,l)
+#define NO_HERRNO
+#define NATIVE_SENDMAIL_PATH "/usr/lib/sendmail"
+#define NATIVE_MAILQ_PATH "/usr/ucb/mailq"
+#define NATIVE_NEWALIAS_PATH "/usr/ucb/newaliases"
+#define NATIVE_COMMAND_DIR "/usr/etc"
+#define NATIVE_DAEMON_DIR "/usr/libexec/postfix"
+#define STRCASECMP_IN_STRINGS_H
+#define OCTAL_TO_UNSIGNED(res, str) sscanf((str), "%o", &(res))
+#define size_t unsigned
+#define ssize_t int
+#define getsid getpgrp
+#define NO_SNPRINTF
+#endif
+
+ /*
+ * SunOS 5.x, mostly System V Release 4.
+ */
+#ifdef SUNOS5
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define _PATH_MAILDIR "/var/mail"
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_DEFPATH "/usr/bin:/usr/ucb"
+#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/ucb"
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL
+#define DEF_MAILBOX_LOCK "fcntl, dotlock"
+#define HAS_FSYNC
+#define HAS_DBM
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases"
+#ifndef NO_NIS
+#define HAS_NIS
+#ifndef NO_NISPLUS
+#define HAS_NISPLUS
+#endif /* NO_NISPLUS */
+#endif
+#define USE_SYS_SOCKIO_H /* Solaris 2.5, changed sys/ioctl.h */
+#define GETTIMEOFDAY(t) gettimeofday(t)
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb"
+#define FIONREAD_IN_SYS_FILIO_H
+#define USE_STATVFS
+#define STATVFS_IN_SYS_STATVFS_H
+#define INT_MAX_IN_LIMITS_H
+#ifdef STREAM_CONNECTIONS /* avoid UNIX-domain sockets */
+#define LOCAL_LISTEN stream_listen
+#define LOCAL_ACCEPT stream_accept
+#define LOCAL_CONNECT stream_connect
+#define LOCAL_TRIGGER stream_trigger
+#define LOCAL_SEND_FD stream_send_fd
+#define LOCAL_RECV_FD stream_recv_fd
+#endif
+#define HAS_VOLATILE_LOCKS
+#define BROKEN_READ_SELECT_ON_TCP_SOCKET
+#define CANT_WRITE_BEFORE_SENDING_FD
+#ifndef NO_POSIX_REGEXP
+#define HAS_POSIX_REGEXP
+#endif
+#ifndef NO_IPV6
+#define HAS_IPV6
+#define HAS_SIOCGLIF
+#endif
+#ifndef NO_CLOSEFROM
+#define HAS_CLOSEFROM
+#endif
+#ifndef NO_DEV_URANDOM
+#define PREFERRED_RAND_SOURCE "dev:/dev/urandom"
+#endif
+#ifndef NO_FUTIMESAT
+#define HAS_FUTIMESAT
+#endif
+#define USE_SYSV_POLL
+#ifndef NO_DEVPOLL
+#define EVENTS_STYLE EVENTS_STYLE_DEVPOLL
+#endif
+#ifndef NO_POSIX_GETPW_R
+#define HAVE_POSIX_GETPW_R
+#define GETPW_R_NEEDS_POSIX_PTHREAD_SEMANTICS
+#endif
+
+/*
+ * Allow build environment to override paths.
+ */
+#define NATIVE_SENDMAIL_PATH "/usr/lib/sendmail"
+#define NATIVE_MAILQ_PATH "/usr/bin/mailq"
+#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases"
+#define NATIVE_COMMAND_DIR "/usr/sbin"
+#define NATIVE_DAEMON_DIR "/usr/libexec/postfix"
+
+#define HAS_DLOPEN
+#endif
+
+ /*
+ * UnixWare, System Release 4.
+ */
+#ifdef UW7 /* UnixWare 7 */
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define _PATH_MAILDIR "/var/mail"
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_DEFPATH "/usr/bin:/usr/ucb"
+#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/ucb"
+#define MISSING_SETENV
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL
+#define DEF_MAILBOX_LOCK "fcntl, dotlock"
+#define HAS_FSYNC
+#define HAS_DBM
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases"
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define USE_SYS_SOCKIO_H
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb"
+#define FIONREAD_IN_SYS_FILIO_H
+#define DBM_NO_TRAILING_NULL
+#define USE_STATVFS
+#define STATVFS_IN_SYS_STATVFS_H
+#define STRCASECMP_IN_STRINGS_H
+#define USE_SET_H_ERRNO
+#endif
+
+#ifdef UW21 /* UnixWare 2.1.x */
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define _PATH_MAILDIR "/var/mail"
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_DEFPATH "/usr/bin:/usr/ucb"
+#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/ucb"
+#define MISSING_SETENV
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL
+#define DEF_MAILBOX_LOCK "fcntl, dotlock"
+#define HAS_FSYNC
+#define HAS_DBM
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases"
+#ifndef NO_NIS
+#define HAS_NIS */
+#endif
+#define USE_SYS_SOCKIO_H
+#define GETTIMEOFDAY(t) gettimeofday(t,NULL)
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb"
+#define FIONREAD_IN_SYS_FILIO_H
+#define DBM_NO_TRAILING_NULL
+#define USE_STATVFS
+#define STATVFS_IN_SYS_STATVFS_H
+#endif
+
+ /*
+ * AIX: a SYSV-flavored hybrid. NB: fcntl() and flock() access the same
+ * underlying locking primitives.
+ */
+#if defined(AIX5) || defined(AIX6)
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define MISSING_SETENV
+#define USE_PATHS_H
+#ifndef _PATH_BSHELL
+#define _PATH_BSHELL "/bin/sh"
+#endif
+#ifndef _PATH_MAILDIR
+#define _PATH_MAILDIR "/var/spool/mail" /* paths.h lies */
+#endif
+#ifndef _PATH_DEFPATH
+#define _PATH_DEFPATH "/usr/bin:/usr/ucb"
+#endif
+#ifndef _PATH_STDPATH
+#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/ucb"
+#endif
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL
+#define DEF_MAILBOX_LOCK "fcntl, dotlock"
+#define USE_SYS_SELECT_H
+#define HAS_FSYNC
+#define HAS_DBM
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases"
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define HAS_SA_LEN
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb"
+#define SOCKADDR_SIZE socklen_t
+#define SOCKOPT_SIZE socklen_t
+#define USE_STATVFS
+#define STATVFS_IN_SYS_STATVFS_H
+#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail"
+#define NATIVE_MAILQ_PATH "/usr/sbin/mailq"
+#define NATIVE_NEWALIAS_PATH "/usr/sbin/newaliases"
+#define NATIVE_COMMAND_DIR "/usr/sbin"
+#define NATIVE_DAEMON_DIR "/usr/libexec/postfix"
+
+ /*
+ * XXX Need CMSG_SPACE() and CMSG_LEN() but don't want to drag in everything
+ * that comes with _LINUX_SOURCE_COMPAT.
+ */
+#include <sys/socket.h>
+#ifndef CMSG_SPACE
+#define CMSG_SPACE(len) (_CMSG_ALIGN(sizeof(struct cmsghdr)) + _CMSG_ALIGN(len))
+#endif
+#ifndef CMSG_LEN
+#define CMSG_LEN(len) (_CMSG_ALIGN(sizeof(struct cmsghdr)) + (len))
+#endif
+#ifndef NO_IPV6
+#define HAS_IPV6
+#endif
+#define BROKEN_AI_PASSIVE_NULL_HOST
+#define BROKEN_AI_NULL_SERVICE
+#define USE_SYSV_POLL
+#define MYMALLOC_FUZZ 1
+#endif
+
+#ifdef AIX4
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define MISSING_SETENV
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_MAILDIR "/var/spool/mail" /* paths.h lies */
+#define _PATH_DEFPATH "/usr/bin:/usr/ucb"
+#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/ucb"
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL
+#define DEF_MAILBOX_LOCK "fcntl, dotlock"
+#define USE_SYS_SELECT_H
+#define HAS_FSYNC
+#define HAS_DBM
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases"
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define HAS_SA_LEN
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define RESOLVE_H_NEEDS_STDIO_H
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb"
+#define SOCKADDR_SIZE size_t
+#define SOCKOPT_SIZE size_t
+#define USE_STATVFS
+#define STATVFS_IN_SYS_STATVFS_H
+#define STRCASECMP_IN_STRINGS_H
+#if 0
+extern time_t time(time_t *);
+extern int seteuid(uid_t);
+extern int setegid(gid_t);
+extern int initgroups(const char *, int);
+
+#endif
+#define NATIVE_SENDMAIL_PATH "/usr/lib/sendmail"
+#define NATIVE_MAILQ_PATH "/usr/sbin/mailq"
+#define NATIVE_NEWALIAS_PATH "/usr/sbin/newaliases"
+#define NATIVE_COMMAND_DIR "/usr/sbin"
+#define NATIVE_DAEMON_DIR "/usr/libexec/postfix"
+
+#define CANT_USE_SEND_RECV_MSG
+#endif
+
+#ifdef AIX3
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define MISSING_SETENV
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_MAILDIR "/var/spool/mail" /* paths.h lies */
+#define _PATH_DEFPATH "/usr/bin:/usr/ucb"
+#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/ucb"
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL
+#define DEF_MAILBOX_LOCK "fcntl, dotlock"
+#define USE_SYS_SELECT_H
+#define HAS_FSYNC
+#define HAS_DBM
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases"
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define HAS_SA_LEN
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define RESOLVE_H_NEEDS_STDIO_H
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb"
+#define SOCKADDR_SIZE size_t
+#define SOCKOPT_SIZE size_t
+#define USE_STATFS
+#define STATFS_IN_SYS_STATFS_H
+#define STRCASECMP_IN_STRINGS_H
+extern time_t time(time_t *);
+extern int seteuid(uid_t);
+extern int setegid(gid_t);
+extern int initgroups(const char *, int);
+
+#define NATIVE_SENDMAIL_PATH "/usr/lib/sendmail"
+
+#define CANT_USE_SEND_RECV_MSG
+#endif
+
+ /*
+ * IRIX, a mix of System V Releases.
+ */
+#if defined(IRIX5) || defined(IRIX6)
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define MISSING_SETENV
+#define _PATH_MAILDIR "/var/mail"
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_DEFPATH "/usr/bin:/usr/bsd"
+#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/bsd"
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL
+#define DEF_MAILBOX_LOCK "fcntl, dotlock"
+#define HAS_FSYNC
+#define HAS_DBM
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases"
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define USE_SYS_SOCKIO_H /* XXX check */
+#define GETTIMEOFDAY(t) gettimeofday(t)
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/bsd"
+#define FIONREAD_IN_SYS_FILIO_H /* XXX check */
+#define DBM_NO_TRAILING_NULL /* XXX check */
+#define USE_STATVFS
+#define STATVFS_IN_SYS_STATVFS_H
+#define BROKEN_WRITE_SELECT_ON_NON_BLOCKING_PIPE
+#define CANT_USE_SEND_RECV_MSG
+#endif
+
+#if defined(IRIX5)
+#define MISSING_USLEEP
+#endif
+
+#if defined(IRIX6)
+#ifndef NO_IPV6
+#define HAS_IPV6
+#endif
+#define HAS_POSIX_REGEXP
+#define PIPES_CANT_FIONREAD
+#endif
+
+ /*
+ * GLIBC, mainly, but not exclusively Linux
+ */
+#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
+
+ /*
+ * LINUX.
+ */
+#if defined(LINUX2) || defined(LINUX3) || defined(LINUX4) || defined(LINUX5) \
+ || defined(LINUX6)
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#include <features.h>
+#define USE_PATHS_H
+#define HAS_FLOCK_LOCK
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK
+#define DEF_MAILBOX_LOCK "fcntl, dotlock" /* RedHat >= 4.x */
+#define HAS_FSYNC
+#define HAS_DB
+#define NATIVE_DB_TYPE "hash"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases"
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin"
+#define FIONREAD_IN_TERMIOS_H
+#define USE_STATFS
+#define STATFS_IN_SYS_VFS_H
+#define PREPEND_PLUS_TO_OPTSTRING
+#define HAS_POSIX_REGEXP
+#define HAS_DLOPEN
+#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail"
+#define NATIVE_MAILQ_PATH "/usr/bin/mailq"
+#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases"
+#define NATIVE_COMMAND_DIR "/usr/sbin"
+#define NATIVE_DAEMON_DIR "/usr/libexec/postfix"
+#if HAVE_GLIBC_API_VERSION_SUPPORT(2, 1)
+#define SOCKADDR_SIZE socklen_t
+#define SOCKOPT_SIZE socklen_t
+#else
+#define NO_SNPRINTF
+#endif
+#ifndef NO_IPV6
+#define HAS_IPV6
+#if HAVE_GLIBC_API_VERSION_SUPPORT(2, 4)
+/* Really 2.3.3 or later, but there's no __GLIBC_MICRO version macro. */
+#define HAVE_GETIFADDRS
+#else
+#define HAS_PROCNET_IFINET6
+#define _PATH_PROCNET_IFINET6 "/proc/net/if_inet6"
+#endif
+#endif
+#include <linux/version.h>
+#if !defined(KERNEL_VERSION)
+#define KERNEL_VERSION(a,b,c) (LINUX_VERSION_CODE + 1)
+#endif
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,2,0)) \
+ || (defined(__GLIBC__) && __GLIBC__ < 2)
+#define CANT_USE_SEND_RECV_MSG
+#define DEF_SMTP_CACHE_DEMAND 0
+#else
+#define CANT_WRITE_BEFORE_SENDING_FD
+#endif
+#define PREFERRED_RAND_SOURCE "dev:/dev/urandom" /* introduced in 1.1 */
+#ifndef NO_EPOLL
+#define EVENTS_STYLE EVENTS_STYLE_EPOLL /* introduced in 2.5 */
+#endif
+#define USE_SYSV_POLL
+#ifndef NO_POSIX_GETPW_R
+#if (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 1) \
+ || (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 1) \
+ || (defined(_BSD_SOURCE) && _BSD_SOURCE >= 1) \
+ || (defined(_SVID_SOURCE) && _SVID_SOURCE >= 1) \
+ || (defined(_POSIX_SOURCE) && _POSIX_SOURCE >= 1)
+#define HAVE_POSIX_GETPW_R
+#endif
+#endif
+#if HAVE_GLIBC_API_VERSION_SUPPORT(2, 34)
+#define HAS_CLOSEFROM
+#endif
+
+#endif
+
+#ifdef LINUX1
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define USE_PATHS_H
+#define HAS_FLOCK_LOCK
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK
+#define DEF_MAILBOX_LOCK "dotlock" /* verified RedHat 3.03 */
+#define HAS_FSYNC
+#define HAS_DB
+#define NATIVE_DB_TYPE "hash"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases"
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin"
+#define FIONREAD_IN_TERMIOS_H /* maybe unnecessary */
+#define USE_STATFS
+#define STATFS_IN_SYS_VFS_H
+#define PREPEND_PLUS_TO_OPTSTRING
+#define HAS_POSIX_REGEXP
+#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail"
+#define NATIVE_MAILQ_PATH "/usr/bin/mailq"
+#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases"
+#define NATIVE_COMMAND_DIR "/usr/sbin"
+#define NATIVE_DAEMON_DIR "/usr/libexec/postfix"
+#define CANT_USE_SEND_RECV_MSG
+#define DEF_SMTP_CACHE_DEMAND 0
+#endif
+
+ /*
+ * GNU.
+ */
+#ifdef GNU0
+#define SUPPORTED
+#include <features.h>
+#define USE_PATHS_H
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL
+#define DEF_MAILBOX_LOCK "fcntl, dotlock" /* RedHat >= 4.x */
+#define HAS_FSYNC
+#define HAS_DB
+#define NATIVE_DB_TYPE "hash"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases"
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin"
+#define FIONREAD_IN_TERMIOS_H
+#define USE_STATFS
+#define STATFS_IN_SYS_VFS_H
+#define UNIX_DOMAIN_CONNECT_BLOCKS_FOR_ACCEPT
+#define PREPEND_PLUS_TO_OPTSTRING
+#define HAS_POSIX_REGEXP
+#define HAS_DLOPEN
+#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail"
+#define NATIVE_MAILQ_PATH "/usr/bin/mailq"
+#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases"
+#define NATIVE_COMMAND_DIR "/usr/sbin"
+#ifdef DEBIAN
+#define NATIVE_DAEMON_DIR "/usr/lib/postfix"
+#ifndef DEF_MANPAGE_DIR
+#define DEF_MANPAGE_DIR "/usr/share/man"
+#endif
+#ifndef DEF_SAMPLE_DIR
+#define DEF_SAMPLE_DIR "/usr/share/doc/postfix/examples"
+#endif
+#ifndef DEF_README_DIR
+#define DEF_README_DIR "/usr/share/doc/postfix"
+#endif
+#else
+#define NATIVE_DAEMON_DIR "/usr/libexec/postfix"
+#endif
+#define SOCKADDR_SIZE socklen_t
+#define SOCKOPT_SIZE socklen_t
+#ifdef __FreeBSD_kernel__
+#define HAS_DUPLEX_PIPE
+#define HAS_ISSETUGID
+#endif
+#ifndef NO_IPV6
+#define HAS_IPV6
+#ifdef __FreeBSD_kernel__
+#define HAVE_GETIFADDRS
+#else
+#define HAS_PROCNET_IFINET6
+#define _PATH_PROCNET_IFINET6 "/proc/net/if_inet6"
+#endif
+#endif
+#define CANT_USE_SEND_RECV_MSG
+#define DEF_SMTP_CACHE_DEMAND 0
+#define PREFERRED_RAND_SOURCE "dev:/dev/urandom"
+#endif
+
+ /*
+ * HPUX11 was copied from HPUX10, but can perhaps be trimmed down a bit.
+ */
+#ifdef HPUX11
+#define SUPPORTED
+#define USE_SIG_RETURN
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define HAS_DBM
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL
+#define DEF_MAILBOX_LOCK "fcntl, dotlock"
+#define HAS_FSYNC
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases"
+#define ROOT_PATH "/usr/bin:/sbin:/usr/sbin"
+#define MISSING_SETENV
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_MAILDIR "/var/mail"
+#define _PATH_DEFPATH "/usr/bin"
+#define _PATH_STDPATH "/usr/bin:/sbin:/usr/sbin"
+#define MISSING_SETEUID
+#define HAVE_SETRESUID
+#define MISSING_SETEGID
+#define HAVE_SETRESGID
+extern int h_errno; /* <netdb.h> imports too much stuff */
+
+#define USE_STATFS
+#define STATFS_IN_SYS_VFS_H
+#define HAS_POSIX_REGEXP
+#define HAS_DLOPEN
+#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail"
+#define NATIVE_MAILQ_PATH "/usr/bin/mailq"
+#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases"
+#define NATIVE_COMMAND_DIR "/usr/sbin"
+#define NATIVE_DAEMON_DIR "/usr/libexec/postfix"
+#endif
+
+#ifdef HPUX10
+#define SUPPORTED
+#define USE_SIG_RETURN
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define HAS_DBM
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL
+#define DEF_MAILBOX_LOCK "fcntl, dotlock"
+#define HAS_FSYNC
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases"
+#define ROOT_PATH "/usr/bin:/sbin:/usr/sbin"
+#define MISSING_SETENV
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_MAILDIR "/var/mail"
+#define _PATH_DEFPATH "/usr/bin"
+#define _PATH_STDPATH "/usr/bin:/sbin:/usr/sbin"
+#define MISSING_SETEUID
+#define HAVE_SETRESUID
+#define MISSING_SETEGID
+#define HAVE_SETRESGID
+extern int h_errno; /* <netdb.h> imports too much stuff */
+
+#define USE_STATFS
+#define STATFS_IN_SYS_VFS_H
+#define HAS_POSIX_REGEXP
+#define HAS_SHL_LOAD
+#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail"
+#define NATIVE_MAILQ_PATH "/usr/bin/mailq"
+#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases"
+#define NATIVE_COMMAND_DIR "/usr/sbin"
+#define NATIVE_DAEMON_DIR "/usr/libexec/postfix"
+#endif
+
+#ifdef HPUX9
+#define SUPPORTED
+#define USE_SIG_RETURN
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define HAS_DBM
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL
+#define DEF_MAILBOX_LOCK "fcntl, dotlock"
+#define HAS_FSYNC
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define MISSING_SETENV
+#define MISSING_RLIMIT_FSIZE
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/usr/lib/aliases"
+#define ROOT_PATH "/bin:/usr/bin:/etc"
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_MAILDIR "/usr/mail"
+#define _PATH_DEFPATH "/bin:/usr/bin"
+#define _PATH_STDPATH "/bin:/usr/bin:/etc"
+#define MISSING_SETEUID
+#define HAVE_SETRESUID
+#define MISSING_SETEGID
+#define HAVE_SETRESGID
+extern int h_errno;
+
+#define USE_ULIMIT /* no setrlimit() */
+#define USE_STATFS
+#define STATFS_IN_SYS_VFS_H
+#define HAS_POSIX_REGEXP
+#define HAS_SHL_LOAD
+#define NATIVE_SENDMAIL_PATH "/usr/bin/sendmail"
+#define NATIVE_MAILQ_PATH "/usr/bin/mailq"
+#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases"
+#define NATIVE_DAEMON_DIR "/usr/libexec/postfix"
+#endif
+
+ /*
+ * NEXTSTEP3, without -lposix, because its naming service is broken.
+ */
+#ifdef NEXTSTEP3
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define HAS_DBM
+#define HAS_FLOCK_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK
+#define DEF_MAILBOX_LOCK "flock, dotlock"
+#define USE_STATFS
+#define HAVE_SYS_DIR_H
+#define STATFS_IN_SYS_VFS_H
+#define HAS_FSYNC
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define HAS_NETINFO
+#define MISSING_SETENV_PUTENV
+#define MISSING_MKFIFO
+#define MISSING_SIGSET_T
+#define MISSING_SIGACTION
+#define MISSING_STD_FILENOS
+#define MISSING_SETSID
+#define MISSING_WAITPID
+#define MISSING_UTIMBUF
+#define HAS_WAIT4
+#define WAIT_STATUS_T union wait
+#define NORMAL_EXIT_STATUS(x) (WIFEXITED(x) && !WEXITSTATUS (x))
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define _PATH_MAILDIR "/usr/spool/mail"
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_DEFPATH "/bin:/usr/bin:/usr/ucb"
+#define _PATH_STDPATH "/bin:/usr/bin:/usr/ucb"
+#define ROOT_PATH "/bin:/usr/bin:/usr/etc:/usr/ucb"
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP "netinfo:/aliases"
+#include <libc.h>
+#define MISSING_POSIX_S_IS
+#define MISSING_POSIX_S_MODES
+/* It's amazing what is all missing... */
+#define isascii(c) ((unsigned)(c)<=0177)
+extern int opterr;
+typedef unsigned short mode_t;
+
+#define MISSING_PID_T
+#define MISSING_STRFTIME_E
+#define FD_CLOEXEC 1
+#define O_NONBLOCK O_NDELAY
+#define WEXITSTATUS(x) ((x).w_retcode)
+#define WTERMSIG(x) ((x).w_termsig)
+#endif
+
+ /*
+ * OPENSTEP does not have posix (some fix...)
+ */
+#ifdef OPENSTEP4
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define HAS_DBM
+#define HAS_FLOCK_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK
+#define DEF_MAILBOX_LOCK "flock, dotlock"
+#define USE_STATFS
+#define HAVE_SYS_DIR_H
+#define STATFS_IN_SYS_VFS_H
+#define HAS_FSYNC
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define HAS_NETINFO
+#define MISSING_SETENV_PUTENV
+#define MISSING_MKFIFO
+#define MISSING_SIGSET_T
+#define MISSING_SIGACTION
+#define MISSING_STD_FILENOS
+#define MISSING_SETSID
+#define MISSING_WAITPID
+#define MISSING_UTIMBUF
+#define HAS_WAIT4
+#define WAIT_STATUS_T union wait
+#define NORMAL_EXIT_STATUS(x) (WIFEXITED(x) && !WEXITSTATUS (x))
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define _PATH_MAILDIR "/usr/spool/mail"
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_DEFPATH "/bin:/usr/bin:/usr/ucb"
+#define _PATH_STDPATH "/bin:/usr/bin:/usr/ucb"
+#define ROOT_PATH "/bin:/usr/bin:/usr/etc:/usr/ucb"
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP "netinfo:/aliases"
+#include <libc.h>
+#define MISSING_POSIX_S_IS
+#define MISSING_POSIX_S_MODES
+/* It's amazing what is all missing... */
+#define isascii(c) ((unsigned)(c)<=0177)
+extern int opterr;
+typedef unsigned short mode_t;
+
+#define MISSING_PID_T
+#define MISSING_STRFTIME_E
+#define FD_CLOEXEC 1
+#define O_NONBLOCK O_NDELAY
+#define WEXITSTATUS(x) ((x).w_retcode)
+#define WTERMSIG(x) ((x).w_termsig)
+#endif
+
+#ifdef ReliantUnix543
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define MISSING_SETENV
+#define _PATH_DEFPATH "/usr/bin:/usr/ucb"
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_MAILDIR "/var/spool/mail"
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL
+#define DEF_MAILBOX_LOCK "fcntl, dotlock"
+#define HAS_FSYNC
+#define FIONREAD_IN_SYS_FILIO_H
+#define USE_SYS_SOCKIO_H
+#define HAS_DBM
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/var/adm/sendmail/aliases"
+extern int optind; /* XXX use <getopt.h> */
+extern char *optarg; /* XXX use <getopt.h> */
+extern int opterr; /* XXX use <getopt.h> */
+
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define GETTIMEOFDAY(t) gettimeofday(t)
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb"
+#define USE_STATVFS
+#define STATVFS_IN_SYS_STATVFS_H
+#define MISSING_USLEEP
+#endif
+
+#ifdef DCOSX1 /* Siemens Pyramid */
+#define SUPPORTED
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define _PATH_MAILDIR "/var/mail"
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_DEFPATH "/usr/bin:/usr/ucb"
+#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/ucb"
+#define MISSING_SETENV
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL
+#define DEF_MAILBOX_LOCK "fcntl, dotlock"
+#define HAS_FSYNC
+#define NATIVE_DB_TYPE "hash"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases"
+/* Uncomment the following line if you have NIS package installed */
+/* #define HAS_NIS */
+#define USE_SYS_SOCKIO_H
+#define GETTIMEOFDAY(t) gettimeofday(t,NULL)
+#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb"
+#define FIONREAD_IN_SYS_FILIO_H
+#define DBM_NO_TRAILING_NULL
+#define USE_STATVFS
+#define STATVFS_IN_SYS_STATVFS_H
+#ifndef S_ISSOCK
+#define S_ISSOCK(mode) ((mode&0xF000) == 0xC000)
+#endif
+#endif
+
+#ifdef SCO5
+#define SUPPORTED
+#include <sys/socket.h>
+extern int h_errno;
+
+#define UINT32_TYPE unsigned int
+#define UINT16_TYPE unsigned short
+#define _PATH_MAILDIR "/usr/spool/mail"
+#define _PATH_BSHELL "/bin/sh"
+#define _PATH_DEFPATH "/bin:/usr/bin"
+#define USE_PATHS_H
+#define HAS_FCNTL_LOCK
+#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL
+#define DEF_MAILBOX_LOCK "fcntl, dotlock"
+#define HAS_FSYNC
+#define HAS_DBM
+#define NATIVE_DB_TYPE "dbm"
+#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases"
+#define DBM_NO_TRAILING_NULL
+#ifndef NO_NIS
+#define HAS_NIS
+#endif
+#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0)
+#define ROOT_PATH "/bin:/etc:/usr/bin:/tcb/bin"
+#define USE_STATVFS
+#define STATVFS_IN_SYS_STATVFS_H
+#define MISSING_SETENV
+#define STRCASECMP_IN_STRINGS_H
+/* SCO5 misses just S_ISSOCK, the others are there
+ * Use C_ISSOCK definition from cpio.h.
+ */
+#include <cpio.h>
+#define S_ISSOCK(mode) (((mode) & (S_IFMT)) == (C_ISSOCK))
+#define CANT_USE_SEND_RECV_MSG
+#define DEF_SMTP_CACHE_DEMAND 0
+#endif
+
+ /*
+ * We're not going to try to guess like configure does.
+ */
+#ifndef SUPPORTED
+#error "unsupported platform"
+#endif
+
+ /*
+ * Allow command line flags to override native settings
+ */
+#ifndef DEF_COMMAND_DIR
+#ifdef NATIVE_COMMAND_DIR
+#define DEF_COMMAND_DIR NATIVE_COMMAND_DIR
+#endif
+#endif
+
+#ifndef DEF_DAEMON_DIR
+#ifdef NATIVE_DAEMON_DIR
+#define DEF_DAEMON_DIR NATIVE_DAEMON_DIR
+#endif
+#endif
+
+#ifndef DEF_SENDMAIL_PATH
+#ifdef NATIVE_SENDMAIL_PATH
+#define DEF_SENDMAIL_PATH NATIVE_SENDMAIL_PATH
+#endif
+#endif
+
+#ifndef DEF_MAILQ_PATH
+#ifdef NATIVE_MAILQ_PATH
+#define DEF_MAILQ_PATH NATIVE_MAILQ_PATH
+#endif
+#endif
+
+#ifndef DEF_NEWALIAS_PATH
+#ifdef NATIVE_NEWALIAS_PATH
+#define DEF_NEWALIAS_PATH NATIVE_NEWALIAS_PATH
+#endif
+#endif
+
+#ifndef DEF_DB_TYPE
+#define DEF_DB_TYPE NATIVE_DB_TYPE
+#endif
+
+#define CAST_ANY_PTR_TO_INT(cptr) ((int) (long) (cptr))
+#define CAST_INT_TO_VOID_PTR(ival) ((void *) (long) (ival))
+
+#ifdef DUP2_DUPS_CLOSE_ON_EXEC
+/* dup2_pass_on_exec() can be found in util/sys_compat.c */
+extern int dup2_pass_on_exec(int oldd, int newd);
+
+#define DUP2 dup2_pass_on_exec
+#else
+#define DUP2 dup2
+#endif
+
+#ifdef PREPEND_PLUS_TO_OPTSTRING
+#define GETOPT(argc, argv, str) getopt((argc), (argv), "+" str)
+#else
+#define GETOPT(argc, argv, str) getopt((argc), (argv), (str))
+#endif
+#define OPTIND (optind > 0 ? optind : 1)
+
+#if !defined(__UCLIBC__) && !defined(NO_RES_SEND)
+#define HAVE_RES_SEND
+#else
+#undef HAVE_RES_SEND
+#endif
+
+ /*
+ * Check for required but missing definitions.
+ */
+#if !defined(HAS_FCNTL_LOCK) && !defined(HAS_FLOCK_LOCK)
+#error "define HAS_FCNTL_LOCK and/or HAS_FLOCK_LOCK"
+#endif
+
+#if !defined(DEF_MAILBOX_LOCK)
+#error "define DEF_MAILBOX_LOCK"
+#endif
+
+#if !defined(INTERNAL_LOCK)
+#error "define INTERNAL_LOCK"
+#endif
+
+#if defined(USE_STATFS) && defined(USE_STATVFS)
+#error "define USE_STATFS or USE_STATVFS, not both"
+#endif
+
+#if !defined(USE_STATFS) && !defined(USE_STATVFS)
+#error "define USE_STATFS or USE_STATVFS"
+#endif
+
+ /*
+ * Defaults for systems that pre-date IPv6 support.
+ */
+#ifndef HAS_IPV6
+#include <sys/socket.h>
+#define EMULATE_IPV4_ADDRINFO
+#define MISSING_INET_PTON
+#define MISSING_INET_NTOP
+extern const char *inet_ntop(int, const void *, char *, SOCKADDR_SIZE);
+extern int inet_pton(int, const char *, void *);
+
+#endif
+
+ /*
+ * Workaround: after a watchdog alarm signal, wake up from select/poll/etc.
+ * by writing to a pipe. Solaris needs this, and HP-UX apparently, too. The
+ * run-time cost is negligible so we just turn it on for all systems. As a
+ * side benefit, making this code system-independent will simplify the
+ * detection of bit-rot problems.
+ */
+#ifndef NO_WATCHDOG_PIPE
+#define USE_WATCHDOG_PIPE
+#endif
+
+ /*
+ * If we don't have defined a preferred random device above, but the system
+ * has /dev/urandom, then we use that.
+ */
+#if !defined(PREFERRED_RAND_SOURCE) && defined(HAS_DEV_URANDOM)
+#define PREFERRED_RAND_SOURCE "dev:/dev/urandom"
+#endif
+
+ /*
+ * Defaults for systems without kqueue, /dev/poll or epoll support.
+ * master/multi-server.c and *qmgr/qmgr_transport.c depend on this.
+ */
+#if !defined(EVENTS_STYLE)
+#define EVENTS_STYLE EVENTS_STYLE_SELECT
+#endif
+
+#define EVENTS_STYLE_SELECT 1 /* Traditional BSD select */
+#define EVENTS_STYLE_KQUEUE 2 /* FreeBSD kqueue */
+#define EVENTS_STYLE_DEVPOLL 3 /* Solaris /dev/poll */
+#define EVENTS_STYLE_EPOLL 4 /* Linux epoll */
+
+ /*
+ * We use poll() for read/write time limit enforcement on modern systems. We
+ * use select() on historical systems without poll() support. And on systems
+ * where poll() is not implemented for some file handle types, we try to use
+ * select() as a fall-back solution (MacOS X needs this).
+ */
+#if !defined(USE_SYSV_POLL) && !defined(USE_SYSV_POLL_THEN_SELECT)
+#define USE_BSD_SELECT
+#endif
+
+ /*
+ * The Postfix 2.9 post-install workaround assumes that the inet_protocols
+ * default value is "ipv4" when Postfix is compiled without IPv6 support.
+ */
+#ifndef DEF_INET_PROTOCOLS
+#ifdef HAS_IPV6
+#define DEF_INET_PROTOCOLS INET_PROTO_NAME_ALL
+#else
+#define DEF_INET_PROTOCOLS INET_PROTO_NAME_IPV4
+#endif
+#endif
+
+ /*
+ * Defaults for systems that pre-date POSIX socklen_t.
+ */
+#ifndef SOCKADDR_SIZE
+#define SOCKADDR_SIZE int
+#endif
+
+#ifndef SOCKOPT_SIZE
+#define SOCKOPT_SIZE int
+#endif
+
+ /*
+ * Defaults for normal systems.
+ */
+#ifndef LOCAL_LISTEN
+#define LOCAL_LISTEN unix_listen
+#define LOCAL_ACCEPT unix_accept
+#define LOCAL_CONNECT unix_connect
+#define LOCAL_TRIGGER unix_trigger
+#define LOCAL_SEND_FD unix_send_fd
+#define LOCAL_RECV_FD unix_recv_fd
+#endif
+
+#if !defined (HAVE_SYS_NDIR_H) && !defined (HAVE_SYS_DIR_H) \
+ && !defined (HAVE_NDIR_H)
+#define HAVE_DIRENT_H
+#endif
+
+#ifndef WAIT_STATUS_T
+typedef int WAIT_STATUS_T;
+
+#define NORMAL_EXIT_STATUS(status) ((status) == 0)
+#endif
+
+#ifdef NO_POSIX_GETPW_R
+#undef HAVE_POSIX_GETPW_R
+#endif
+
+#ifdef NO_DB
+#undef HAS_DB
+#endif
+
+#ifndef OCTAL_TO_UNSIGNED
+#define OCTAL_TO_UNSIGNED(res, str) ((res) = strtoul((str), (char **) 0, 8))
+#endif
+
+ /*
+ * Avoid useless type mis-matches when using sizeof in an integer context.
+ */
+#define INT_SIZEOF(foo) ((int) sizeof(foo))
+
+ /*
+ * Turn on the compatibility stuff.
+ */
+#ifdef MISSING_UTIMBUF
+struct utimbuf {
+ time_t actime;
+ time_t modtime;
+};
+
+#endif
+
+#ifdef MISSING_STRERROR
+extern const char *strerror(int);
+
+#endif
+
+#if defined (MISSING_SETENV) || defined (MISSING_SETENV_PUTENV)
+extern int setenv(const char *, const char *, int);
+
+#endif
+
+#ifdef MISSING_SETEUID
+extern int seteuid(uid_t euid);
+
+#endif
+
+#ifdef MISSING_SETEGID
+extern int setegid(gid_t egid);
+
+#endif
+
+#ifdef MISSING_MKFIFO
+extern int mkfifo(char *, int);
+
+#endif
+
+#ifdef MISSING_WAITPID
+extern int waitpid(int, WAIT_STATUS_T *status, int options);
+
+#endif
+
+#ifdef MISSING_SETSID
+extern int setsid(void);
+
+#endif
+
+#ifndef HAS_CLOSEFROM
+extern int closefrom(int);
+
+#endif
+
+#ifdef MISSING_STD_FILENOS
+#define STDIN_FILENO 0
+#define STDOUT_FILENO 1
+#define STDERR_FILENO 2
+#endif
+
+#ifdef MISSING_PID_T
+typedef int pid_t;
+
+#endif
+
+#ifdef MISSING_POSIX_S_IS
+#define S_ISBLK(mode) (((mode) & (_S_IFMT)) == (_S_IFBLK))
+#define S_ISCHR(mode) (((mode) & (_S_IFMT)) == (_S_IFCHR))
+#define S_ISDIR(mode) (((mode) & (_S_IFMT)) == (_S_IFDIR))
+#define S_ISSOCK(mode) (((mode) & (_S_IFMT)) == (_S_IFSOCK))
+#define S_ISFIFO(mode) (((mode) & (_S_IFMT)) == (_S_IFIFO))
+#define S_ISREG(mode) (((mode) & (_S_IFMT)) == (_S_IFREG))
+#define S_ISLNK(mode) (((mode) & (_S_IFMT)) == (_S_IFLNK))
+#endif
+
+#ifdef MISSING_POSIX_S_MODES
+#define S_IRUSR _S_IRUSR
+#define S_IRGRP 0000040
+#define S_IROTH 0000004
+#define S_IWUSR _S_IWUSR
+#define S_IWGRP 0000020
+#define S_IWOTH 0000002
+#define S_IXUSR _S_IXUSR
+#define S_IXGRP 0000010
+#define S_IXOTH 0000001
+#define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR)
+#endif
+
+ /*
+ * Memory alignment of memory allocator results. By default we align for
+ * doubles.
+ */
+#ifndef ALIGN_TYPE
+#if defined(__hpux) && defined(__ia64)
+#define ALIGN_TYPE __float80
+#elif defined(__ia64__)
+#define ALIGN_TYPE long double
+#else
+#define ALIGN_TYPE double
+#endif
+#endif
+
+ /*
+ * Clang-style attribute tests.
+ *
+ * XXX Without the unconditional test below, gcc 4.6 will barf on ``elif
+ * defined(__clang__) && __has_attribute(__whatever__)'' with error message
+ * ``missing binary operator before token "("''.
+ */
+#ifndef __has_attribute
+#define __has_attribute(x) 0
+#endif /* __has_attribute */
+
+ /*
+ * Need to specify what functions never return, so that the compiler can
+ * warn for missing initializations and other trouble. However, OPENSTEP4
+ * gcc 2.7.x cannot handle this so we define this only if NORETURN isn't
+ * already defined above.
+ *
+ * Data point: gcc 2.7.2 has __attribute__ (Wietse Venema) but gcc 2.6.3 does
+ * not (Clive Jones). So we'll set the threshold at 2.7.
+ */
+#ifndef NORETURN
+#if (__GNUC__ == 2 && __GNUC_MINOR__ >= 7) || __GNUC__ >= 3
+#define NORETURN void __attribute__((__noreturn__))
+#elif defined(__clang__) && __has_attribute(__noreturn__)
+#define NORETURN void __attribute__((__noreturn__))
+#else
+#define NORETURN void
+#endif
+#endif /* NORETURN */
+
+ /*
+ * Turn on format string argument checking. This is more accurate than
+ * printfck, but it misses #ifdef-ed code. XXX I am just guessing at what
+ * gcc versions support this. In order to turn this off for some platforms,
+ * specify #define PRINTFLIKE and #define SCANFLIKE in the system-dependent
+ * sections above.
+ */
+#ifndef PRINTFLIKE
+#if (__GNUC__ == 2 && __GNUC_MINOR__ >= 7) || __GNUC__ >= 3
+#define PRINTFLIKE(x,y) __attribute__ ((format (printf, (x), (y))))
+#elif defined(__clang__) && __has_attribute(__format__)
+#define PRINTFLIKE(x,y) __attribute__ ((__format__ (__printf__, (x), (y))))
+#else
+#define PRINTFLIKE(x,y)
+#endif
+#endif /* PRINTFLIKE */
+
+#ifndef SCANFLIKE
+#if (__GNUC__ == 2 && __GNUC_MINOR__ >= 7) || __GNUC__ >= 3
+#define SCANFLIKE(x,y) __attribute__ ((format (scanf, (x), (y))))
+#elif defined(__clang__) && __has_attribute(__format__)
+#define SCANFLIKE(x,y) __attribute__ ((__format__ (__scanf__, (x), (y))))
+#else
+#define SCANFLIKE(x,y)
+#endif
+#endif /* SCANFLIKE */
+
+ /*
+ * Some gcc implementations don't grok these attributes with pointer to
+ * function. Again, wild guess of what is supported. To override, specify
+ * #define PRINTFPTRLIKE in the system-dependent sections above.
+ */
+#ifndef PRINTFPTRLIKE
+#if (__GNUC__ >= 3) /* XXX Rough estimate */
+#define PRINTFPTRLIKE(x,y) PRINTFLIKE(x,y)
+#elif defined(__clang__) && __has_attribute(__format__)
+#define PRINTFPTRLIKE(x,y) __attribute__ ((__format__ (__printf__, (x), (y))))
+#else
+#define PRINTFPTRLIKE(x,y)
+#endif
+#endif
+
+ /*
+ * Compiler optimization hint. This makes sense only for code in a
+ * performance-critical loop.
+ */
+#ifndef EXPECTED
+#if defined(__GNUC__) && (__GNUC__ > 2)
+#define EXPECTED(x) __builtin_expect(!!(x), 1)
+#define UNEXPECTED(x) __builtin_expect(!!(x), 0)
+#else
+#define EXPECTED(x) (x)
+#define UNEXPECTED(x) (x)
+#endif
+#endif
+
+ /*
+ * Warn about ignored function result values that must never be ignored.
+ * Typically, this is for error results from "read" functions that normally
+ * write to output parameters (for example, stat- or scanf-like functions)
+ * or from functions that have other useful side effects (for example,
+ * fseek- or rename-like functions).
+ *
+ * DO NOT use this for functions that write to a stream; it is entirely
+ * legitimate to detect write errors with fflush() or fclose() only. On the
+ * other hand most (but not all) functions that read from a stream must
+ * never ignore result values.
+ *
+ * XXX Prepending "(void)" won't shut up GCC. Clang behaves as expected.
+ */
+#if ((__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || __GNUC__ > 3)
+#define WARN_UNUSED_RESULT __attribute__((warn_unused_result))
+#elif defined(__clang__) && __has_attribute(warn_unused_result)
+#define WARN_UNUSED_RESULT __attribute__((warn_unused_result))
+#else
+#define WARN_UNUSED_RESULT
+#endif
+
+ /*
+ * ISO C says that the "volatile" qualifier protects against optimizations
+ * that cause longjmp() to clobber local variables.
+ */
+#ifndef NOCLOBBER
+#define NOCLOBBER volatile
+#endif
+
+ /*
+ * Bit banging!! There is no official constant that defines the INT_MAX
+ * equivalent for off_t, ssize_t, etc. Wietse came up with the following
+ * macro that works as long as off_t, ssize_t, etc. use one's or two's
+ * complement logic (that is, the maximum value is binary 01...1). Don't use
+ * right-shift for signed types: the result is implementation-defined.
+ */
+#include <limits.h>
+#define __MAXINT__(T) ((T) ~(((T) 1) << ((sizeof(T) * CHAR_BIT) - 1)))
+#ifndef OFF_T_MAX
+#define OFF_T_MAX __MAXINT__(off_t)
+#endif
+
+#ifndef SSIZE_T_MAX
+#define SSIZE_T_MAX __MAXINT__(ssize_t)
+#endif
+
+ /*
+ * Consistent enforcement of size limits.
+ */
+#define ENFORCING_SIZE_LIMIT(param) ((param) > 0)
+
+ /*
+ * Don't mix socket message send/receive calls with socket stream read/write
+ * calls. The fact that you can get away with it only on some stacks implies
+ * that there is no long-term guarantee.
+ */
+#ifndef CAN_WRITE_BEFORE_SENDING_FD
+#define CANT_WRITE_BEFORE_SENDING_FD
+#endif
+
+ /*
+ * FreeBSD sendmsg(2) says that after sending a file descriptor, the sender
+ * must not immediately close the descriptor, otherwise it may close the
+ * descriptor before it is actually sent.
+ */
+#ifndef DONT_WAIT_AFTER_SENDING_FD
+#define MUST_READ_AFTER_SENDING_FD
+#endif
+
+ /*
+ * Hope for the best.
+ */
+#ifndef UINT32_TYPE
+#define UINT32_TYPE uint32_t
+#define UINT16_TYPE uint16_t
+#endif
+#define UINT32_SIZE 4
+#define UINT16_SIZE 2
+
+ /*
+ * For the sake of clarity.
+ */
+#ifndef HAVE_CONST_CHAR_STAR
+typedef const char *CONST_CHAR_STAR;
+
+#endif
+
+ /*
+ * Safety. On some systems, ctype.h misbehaves with non-ASCII or negative
+ * characters. More importantly, Postfix uses the ISXXX() macros to ensure
+ * protocol compliance, so we have to rule out non-ASCII characters.
+ *
+ * XXX The (unsigned char) casts in isalnum() etc arguments are unnecessary
+ * because the ISASCII() guard already ensures that the values are
+ * non-negative; the casts are done anyway to shut up chatty compilers.
+ */
+#define ISASCII(c) isascii(_UCHAR_(c))
+#define _UCHAR_(c) ((unsigned char)(c))
+#define ISALNUM(c) (ISASCII(c) && isalnum((unsigned char)(c)))
+#define ISALPHA(c) (ISASCII(c) && isalpha((unsigned char)(c)))
+#define ISCNTRL(c) (ISASCII(c) && iscntrl((unsigned char)(c)))
+#define ISDIGIT(c) (ISASCII(c) && isdigit((unsigned char)(c)))
+#define ISGRAPH(c) (ISASCII(c) && isgraph((unsigned char)(c)))
+#define ISLOWER(c) (ISASCII(c) && islower((unsigned char)(c)))
+#define ISPRINT(c) (ISASCII(c) && isprint((unsigned char)(c)))
+#define ISPUNCT(c) (ISASCII(c) && ispunct((unsigned char)(c)))
+#define ISSPACE(c) (ISASCII(c) && isspace((unsigned char)(c)))
+#define ISUPPER(c) (ISASCII(c) && isupper((unsigned char)(c)))
+#define TOLOWER(c) (ISUPPER(c) ? tolower((unsigned char)(c)) : (c))
+#define TOUPPER(c) (ISLOWER(c) ? toupper((unsigned char)(c)) : (c))
+
+ /*
+ * Character sets for parsing.
+ */
+#define CHARS_COMMA_SP ", \t\r\n" /* list separator */
+#define CHARS_SPACE " \t\r\n" /* word separator */
+#define CHARS_BRACE "{}" /* grouping */
+
+ /*
+ * Scaffolding. I don't want to lose messages while the program is under
+ * development.
+ */
+extern int REMOVE(const char *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/testdb b/src/util/testdb
new file mode 100644
index 0000000..097c6c0
--- /dev/null
+++ b/src/util/testdb
@@ -0,0 +1,2 @@
+foo fooval
+bar barval
diff --git a/src/util/timecmp.c b/src/util/timecmp.c
new file mode 100644
index 0000000..607a9ae
--- /dev/null
+++ b/src/util/timecmp.c
@@ -0,0 +1,93 @@
+/*++
+/* NAME
+/* timecmp 3
+/* SUMMARY
+/* compare two time_t values
+/* SYNOPSIS
+/* #include <timecmp.h>
+/*
+/* int timecmp(t1, t2)
+/* time_t t1;
+/* time_t t2;
+/* DESCRIPTION
+/* The timecmp() function return an integer greater than, equal to, or
+/* less than 0, according as the time t1 is greater than, equal to, or
+/* less than the time t2. The comparison is made in a manner that is
+/* insensitive to clock wrap-around, provided the underlying times are
+/* within half of the time interval between the smallest and largest
+/* representable time values.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Viktor Dukhovni
+/*--*/
+
+#include "timecmp.h"
+
+/* timecmp - wrap-safe time_t comparison */
+
+int timecmp(time_t t1, time_t t2)
+{
+ time_t delta = t1 - t2;
+
+ if (delta == 0)
+ return 0;
+
+#define UNSIGNED(type) ( ((type)-1) > ((type)0) )
+
+ /*
+ * With a constant switch value, the compiler will emit only the code for
+ * the correct case, so the signed/unsigned test happens at compile time.
+ */
+ switch (UNSIGNED(time_t) ? 0 : 1) {
+ case 0:
+ return ((2 * delta > delta) ? 1 : -1);
+ case 1:
+ return ((delta > (time_t) 0) ? 1 : -1);
+ }
+}
+
+#ifdef TEST
+#include <assert.h>
+
+ /*
+ * Bit banging!! There is no official constant that defines the INT_MAX
+ * equivalent of the off_t type. Wietse came up with the following macro
+ * that works as long as off_t is some two's complement number.
+ *
+ * Note, however, that C99 permits signed integer representations other than
+ * two's complement.
+ */
+#include <limits.h>
+#define __MAXINT__(T) ((T) (((((T) 1) << ((sizeof(T) * CHAR_BIT) - 1)) ^ ((T) -1))))
+
+int main(void)
+{
+ time_t now = time((time_t *) 0);
+
+ /* Test that it works for normal times */
+ assert(timecmp(now + 10, now) > 0);
+ assert(timecmp(now, now) == 0);
+ assert(timecmp(now - 10, now) < 0);
+
+ /* Test that it works at a boundary time */
+ if (UNSIGNED(time_t))
+ now = (time_t) -1;
+ else
+ now = __MAXINT__(time_t);
+
+ assert(timecmp(now + 10, now) > 0);
+ assert(timecmp(now, now) == 0);
+ assert(timecmp(now - 10, now) < 0);
+
+ return (0);
+}
+
+#endif
diff --git a/src/util/timecmp.h b/src/util/timecmp.h
new file mode 100644
index 0000000..b6efeab
--- /dev/null
+++ b/src/util/timecmp.h
@@ -0,0 +1,37 @@
+#ifndef _TIMECMP_H_INCLUDED_
+#define _TIMECMP_H_INCLUDED_
+
+#include <time.h>
+
+/*++
+/* NAME
+/* timecmp 3h
+/* SUMMARY
+/* compare two time_t values
+/* SYNOPSIS
+/* #include <timecmp.h>
+/*
+/* int timecmp(t1, t2)
+/* time_t t1;
+/* time_t t2;
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern int timecmp(time_t, time_t);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Viktor Dukhovni
+/*--*/
+
+#endif
diff --git a/src/util/timed_connect.c b/src/util/timed_connect.c
new file mode 100644
index 0000000..962545f
--- /dev/null
+++ b/src/util/timed_connect.c
@@ -0,0 +1,111 @@
+/*++
+/* NAME
+/* timed_connect 3
+/* SUMMARY
+/* connect operation with timeout
+/* SYNOPSIS
+/* #include <sys/socket.h>
+/* #include <timed_connect.h>
+/*
+/* int timed_connect(fd, buf, buf_len, timeout)
+/* int fd;
+/* struct sockaddr *buf;
+/* int buf_len;
+/* int timeout;
+/* DESCRIPTION
+/* timed_connect() implement a BSD socket connect() operation that is
+/* bounded in time.
+/*
+/* Arguments:
+/* .IP fd
+/* File descriptor in the range 0..FD_SETSIZE. This descriptor
+/* must be set to non-blocking mode prior to calling timed_connect().
+/* .IP buf
+/* Socket address buffer pointer.
+/* .IP buf_len
+/* Size of socket address buffer.
+/* .IP timeout
+/* The deadline in seconds. This must be a number > 0.
+/* DIAGNOSTICS
+/* Panic: interface violations.
+/* When the operation does not complete within the deadline, the
+/* result value is -1, and errno is set to ETIMEDOUT.
+/* All other returns are identical to those of a blocking connect(2)
+/* operation.
+/* WARNINGS
+/* .ad
+/* .fi
+/* A common error is to call timed_connect() without enabling
+/* non-blocking I/O on the socket. In that case, the \fItimeout\fR
+/* parameter takes no effect.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "iostuff.h"
+#include "sane_connect.h"
+#include "timed_connect.h"
+
+/* timed_connect - connect with deadline */
+
+int timed_connect(int sock, struct sockaddr *sa, int len, int timeout)
+{
+ int error;
+ SOCKOPT_SIZE error_len;
+
+ /*
+ * Sanity check. Just like with timed_wait(), the timeout must be a
+ * positive number.
+ */
+ if (timeout <= 0)
+ msg_panic("timed_connect: bad timeout: %d", timeout);
+
+ /*
+ * Start the connection, and handle all possible results.
+ */
+ if (sane_connect(sock, sa, len) == 0)
+ return (0);
+ if (errno != EINPROGRESS)
+ return (-1);
+
+ /*
+ * A connection is in progress. Wait for a limited amount of time for
+ * something to happen. If nothing happens, report an error.
+ */
+ if (write_wait(sock, timeout) < 0)
+ return (-1);
+
+ /*
+ * Something happened. Some Solaris 2 versions have getsockopt() itself
+ * return the error, instead of returning it via the parameter list.
+ */
+ error = 0;
+ error_len = sizeof(error);
+ if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void *) &error, &error_len) < 0)
+ return (-1);
+ if (error) {
+ errno = error;
+ return (-1);
+ }
+
+ /*
+ * No problems.
+ */
+ return (0);
+}
diff --git a/src/util/timed_connect.h b/src/util/timed_connect.h
new file mode 100644
index 0000000..76ac715
--- /dev/null
+++ b/src/util/timed_connect.h
@@ -0,0 +1,31 @@
+#ifndef _TIMED_CONNECT_H_INCLUDED_
+#define _TIMED_CONNECT_H_INCLUDED_
+
+/*++
+/* NAME
+/* timed_connect 3h
+/* SUMMARY
+/* connect operation with timeout
+/* SYNOPSIS
+/* #include <sys/socket.h>
+/* #include <timed_connect.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern int timed_connect(int, struct sockaddr *, int, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/timed_read.c b/src/util/timed_read.c
new file mode 100644
index 0000000..fdae8fa
--- /dev/null
+++ b/src/util/timed_read.c
@@ -0,0 +1,86 @@
+/*++
+/* NAME
+/* timed_read 3
+/* SUMMARY
+/* read operation with pre-read timeout
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* ssize_t timed_read(fd, buf, len, timeout, context)
+/* int fd;
+/* void *buf;
+/* size_t len;
+/* int timeout;
+/* void *context;
+/* DESCRIPTION
+/* timed_read() performs a read() operation when the specified
+/* descriptor becomes readable within a user-specified deadline.
+/*
+/* Arguments:
+/* .IP fd
+/* File descriptor in the range 0..FD_SETSIZE.
+/* .IP buf
+/* Read buffer pointer.
+/* .IP len
+/* Read buffer size.
+/* .IP timeout
+/* The deadline in seconds. If this is <= 0, the deadline feature
+/* is disabled.
+/* .IP context
+/* Application context. This parameter is unused. It exists only
+/* for the sake of VSTREAM compatibility.
+/* DIAGNOSTICS
+/* When the operation does not complete within the deadline, the
+/* result value is -1, and errno is set to ETIMEDOUT.
+/* All other returns are identical to those of a read(2) operation.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <unistd.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+/* timed_read - read with deadline */
+
+ssize_t timed_read(int fd, void *buf, size_t len,
+ int timeout, void *unused_context)
+{
+ ssize_t ret;
+
+ /*
+ * Wait for a limited amount of time for something to happen. If nothing
+ * happens, report an ETIMEDOUT error.
+ *
+ * XXX Solaris 8 read() fails with EAGAIN after read-select() returns
+ * success.
+ */
+ for (;;) {
+ if (timeout > 0 && read_wait(fd, timeout) < 0)
+ return (-1);
+ if ((ret = read(fd, buf, len)) < 0 && timeout > 0 && errno == EAGAIN) {
+ msg_warn("read() returns EAGAIN on a readable file descriptor!");
+ msg_warn("pausing to avoid going into a tight select/read loop!");
+ sleep(1);
+ continue;
+ } else if (ret < 0 && errno == EINTR) {
+ continue;
+ } else {
+ return (ret);
+ }
+ }
+}
diff --git a/src/util/timed_wait.c b/src/util/timed_wait.c
new file mode 100644
index 0000000..45fbb69
--- /dev/null
+++ b/src/util/timed_wait.c
@@ -0,0 +1,122 @@
+/*++
+/* NAME
+/* timed_wait 3
+/* SUMMARY
+/* wait operations with timeout
+/* SYNOPSIS
+/* #include <timed_wait.h>
+/*
+/* int timed_waitpid(pid, statusp, options, time_limit)
+/* pid_t pid;
+/* WAIT_STATUS_T *statusp;
+/* int options;
+/* int time_limit;
+/* DESCRIPTION
+/* \fItimed_waitpid\fR() waits at most \fItime_limit\fR seconds
+/* for process termination.
+/*
+/* Arguments:
+/* .IP "pid, statusp, options"
+/* The process ID, status pointer and options passed to waitpid(3).
+/* .IP time_limit
+/* The time in seconds that timed_waitpid() will wait.
+/* This must be a number > 0.
+/* DIAGNOSTICS
+/* Panic: interface violation.
+/*
+/* When the time limit is exceeded, the result is -1 and errno
+/* is set to ETIMEDOUT. Otherwise, the result value is the result
+/* from the underlying waitpid() routine.
+/* BUGS
+/* If there were a \fIportable\fR way to select() on process status
+/* information, these routines would not have to use a steenkeeng
+/* alarm() timer and signal() handler.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <posix_signals.h>
+#include <timed_wait.h>
+
+/* Application-specific. */
+
+static int timed_wait_expired;
+
+/* timed_wait_alarm - timeout handler */
+
+static void timed_wait_alarm(int unused_sig)
+{
+
+ /*
+ * WARNING WARNING WARNING.
+ *
+ * This code runs at unpredictable moments, as a signal handler. This code
+ * is here only so that we can break out of waitpid(). Don't put any code
+ * here other than for setting a global flag.
+ */
+ timed_wait_expired = 1;
+}
+
+/* timed_waitpid - waitpid with time limit */
+
+int timed_waitpid(pid_t pid, WAIT_STATUS_T *statusp, int options,
+ int time_limit)
+{
+ const char *myname = "timed_waitpid";
+ struct sigaction action;
+ struct sigaction old_action;
+ int time_left;
+ int wpid;
+
+ /*
+ * Sanity checks.
+ */
+ if (time_limit <= 0)
+ msg_panic("%s: bad time limit: %d", myname, time_limit);
+
+ /*
+ * Set up a timer.
+ */
+ sigemptyset(&action.sa_mask);
+ action.sa_flags = 0;
+ action.sa_handler = timed_wait_alarm;
+ if (sigaction(SIGALRM, &action, &old_action) < 0)
+ msg_fatal("%s: sigaction(SIGALRM): %m", myname);
+ timed_wait_expired = 0;
+ time_left = alarm(time_limit);
+
+ /*
+ * Wait for only a limited amount of time.
+ */
+ if ((wpid = waitpid(pid, statusp, options)) < 0 && timed_wait_expired)
+ errno = ETIMEDOUT;
+
+ /*
+ * Cleanup.
+ */
+ alarm(0);
+ if (sigaction(SIGALRM, &old_action, (struct sigaction *) 0) < 0)
+ msg_fatal("%s: sigaction(SIGALRM): %m", myname);
+ if (time_left)
+ alarm(time_left);
+
+ return (wpid);
+}
diff --git a/src/util/timed_wait.h b/src/util/timed_wait.h
new file mode 100644
index 0000000..6a153de
--- /dev/null
+++ b/src/util/timed_wait.h
@@ -0,0 +1,35 @@
+#ifndef _TIMED_WAIT_H_INCLUDED_
+#define _TIMED_WAIT_H_INCLUDED_
+
+/*++
+/* NAME
+/* timed_wait 3h
+/* SUMMARY
+/* wait operations with timeout
+/* SYNOPSIS
+/* #include <timed_wait.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern int WARN_UNUSED_RESULT timed_waitpid(pid_t, WAIT_STATUS_T *, int, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/timed_write.c b/src/util/timed_write.c
new file mode 100644
index 0000000..220b4c4
--- /dev/null
+++ b/src/util/timed_write.c
@@ -0,0 +1,92 @@
+/*++
+/* NAME
+/* timed_write 3
+/* SUMMARY
+/* write operation with pre-write timeout
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* ssize_t timed_write(fd, buf, len, timeout, context)
+/* int fd;
+/* const void *buf;
+/* size_t len;
+/* int timeout;
+/* void *context;
+/* DESCRIPTION
+/* timed_write() performs a write() operation when the specified
+/* descriptor becomes writable within a user-specified deadline.
+/*
+/* Arguments:
+/* .IP fd
+/* File descriptor in the range 0..FD_SETSIZE.
+/* .IP buf
+/* Write buffer pointer.
+/* .IP len
+/* Write buffer size.
+/* .IP timeout
+/* The deadline in seconds. If this is <= 0, the deadline feature
+/* is disabled.
+/* .IP context
+/* Application context. This parameter is unused. It exists only
+/* for the sake of VSTREAM compatibility.
+/* DIAGNOSTICS
+/* When the operation does not complete within the deadline, the
+/* result value is -1, and errno is set to ETIMEDOUT.
+/* All other returns are identical to those of a write(2) operation.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <unistd.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+/* timed_write - write with deadline */
+
+ssize_t timed_write(int fd, const void *buf, size_t len,
+ int timeout, void *unused_context)
+{
+ ssize_t ret;
+
+ /*
+ * Wait for a limited amount of time for something to happen. If nothing
+ * happens, report an ETIMEDOUT error.
+ *
+ * XXX Solaris 8 read() fails with EAGAIN after read-select() returns
+ * success. The code below exists just in case their write implementation
+ * is equally broken.
+ *
+ * This condition may also be found on systems where select() returns
+ * success on pipes with less than PIPE_BUF bytes of space, and with
+ * badly designed software where multiple writers are fighting for access
+ * to the same resource.
+ */
+ for (;;) {
+ if (timeout > 0 && write_wait(fd, timeout) < 0)
+ return (-1);
+ if ((ret = write(fd, buf, len)) < 0 && timeout > 0 && errno == EAGAIN) {
+ msg_warn("write() returns EAGAIN on a writable file descriptor!");
+ msg_warn("pausing to avoid going into a tight select/write loop!");
+ sleep(1);
+ continue;
+ } else if (ret < 0 && errno == EINTR) {
+ continue;
+ } else {
+ return (ret);
+ }
+ }
+}
diff --git a/src/util/translit.c b/src/util/translit.c
new file mode 100644
index 0000000..ba04bf2
--- /dev/null
+++ b/src/util/translit.c
@@ -0,0 +1,86 @@
+/*++
+/* NAME
+/* translit 3
+/* SUMMARY
+/* transliterate characters
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* char *translit(buf, original, replacement)
+/* char *buf;
+/* char *original;
+/* char *replacement;
+/* DESCRIPTION
+/* translit() takes a null-terminated string, and replaces characters
+/* given in its \fIoriginal\fR argument by the corresponding characters
+/* in the \fIreplacement\fR string. The result value is the \fIbuf\fR
+/* argument.
+/* BUGS
+/* Cannot replace null characters.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <string.h>
+
+/* Utility library. */
+
+#include "stringops.h"
+
+char *translit(char *string, const char *original, const char *replacement)
+{
+ char *cp;
+ const char *op;
+
+ /*
+ * For large inputs, should use a lookup table.
+ */
+ for (cp = string; *cp != 0; cp++) {
+ for (op = original; *op != 0; op++) {
+ if (*cp == *op) {
+ *cp = replacement[op - original];
+ break;
+ }
+ }
+ }
+ return (string);
+}
+
+#ifdef TEST
+
+ /*
+ * Usage: translit string1 string2
+ *
+ * test program to perform the most basic operation of the UNIX tr command.
+ */
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+
+#define STR vstring_str
+
+int main(int argc, char **argv)
+{
+ VSTRING *buf = vstring_alloc(100);
+
+ if (argc != 3)
+ msg_fatal("usage: %s string1 string2", argv[0]);
+ while (vstring_fgets(buf, VSTREAM_IN))
+ vstream_fputs(translit(STR(buf), argv[1], argv[2]), VSTREAM_OUT);
+ vstream_fflush(VSTREAM_OUT);
+ vstring_free(buf);
+ return (0);
+}
+
+#endif
diff --git a/src/util/trigger.h b/src/util/trigger.h
new file mode 100644
index 0000000..e716d53
--- /dev/null
+++ b/src/util/trigger.h
@@ -0,0 +1,34 @@
+#ifndef _TRIGGER_H_INCLUDED_
+#define _TRIGGER_H_INCLUDED_
+
+/*++
+/* NAME
+/* trigger 3h
+/* SUMMARY
+/* client interface file
+/* SYNOPSIS
+/* #include <trigger.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+extern int unix_trigger(const char *, const char *, ssize_t, int);
+extern int inet_trigger(const char *, const char *, ssize_t, int);
+extern int fifo_trigger(const char *, const char *, ssize_t, int);
+extern int stream_trigger(const char *, const char *, ssize_t, int);
+extern int pass_trigger(const char *, const char *, ssize_t, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/trimblanks.c b/src/util/trimblanks.c
new file mode 100644
index 0000000..a4d9cb2
--- /dev/null
+++ b/src/util/trimblanks.c
@@ -0,0 +1,50 @@
+/*++
+/* NAME
+/* trimblanks 3
+/* SUMMARY
+/* skip leading whitespace
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* char *trimblanks(string, len)
+/* char *string;
+/* ssize_t len;
+/* DESCRIPTION
+/* trimblanks() returns a pointer to the beginning of the trailing
+/* whitespace in \fIstring\fR, or a pointer to the string terminator
+/* when the string contains no trailing whitespace.
+/* The \fIlen\fR argument is either zero or the string length.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <ctype.h>
+
+/* Utility library. */
+
+#include "stringops.h"
+
+char *trimblanks(char *string, ssize_t len)
+{
+ char *curr;
+
+ if (len) {
+ curr = string + len;
+ } else {
+ for (curr = string; *curr != 0; curr++)
+ /* void */ ;
+ }
+ while (curr > string && ISSPACE(curr[-1]))
+ curr -= 1;
+ return (curr);
+}
diff --git a/src/util/unescape.c b/src/util/unescape.c
new file mode 100644
index 0000000..4eacbba
--- /dev/null
+++ b/src/util/unescape.c
@@ -0,0 +1,208 @@
+/*++
+/* NAME
+/* unescape 3
+/* SUMMARY
+/* translate C-like escape sequences
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* VSTRING *unescape(result, input)
+/* VSTRING *result;
+/* const char *input;
+/*
+/* VSTRING *escape(result, input, len)
+/* VSTRING *result;
+/* const char *input;
+/* ssize_t len;
+/* DESCRIPTION
+/* unescape() translates C-like escape sequences in the null-terminated
+/* string \fIinput\fR and places the result in \fIresult\fR. The result
+/* is null-terminated, and is the function result value.
+/*
+/* escape() does the reverse transformation.
+/*
+/* Escape sequences and their translations:
+/* .IP \ea
+/* Bell character.
+/* .IP \eb
+/* Backspace character.
+/* .IP \ef
+/* formfeed character.
+/* .IP \en
+/* newline character
+/* .IP \er
+/* Carriage-return character.
+/* .IP \et
+/* Horizontal tab character.
+/* .IP \ev
+/* Vertical tab character.
+/* .IP \e\e
+/* Backslash character.
+/* .IP \e\fInum\fR
+/* 8-bit character whose ASCII value is the 1..3 digit
+/* octal number \fInum\fR.
+/* .IP \e\fIother\fR
+/* The backslash character is discarded.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <vstring.h>
+#include <stringops.h>
+
+/* unescape - process escape sequences */
+
+VSTRING *unescape(VSTRING *result, const char *data)
+{
+ int ch;
+ int oval;
+ int i;
+
+#define UCHAR(cp) ((unsigned char *) (cp))
+#define ISOCTAL(ch) (ISDIGIT(ch) && (ch) != '8' && (ch) != '9')
+
+ VSTRING_RESET(result);
+
+ while ((ch = *UCHAR(data++)) != 0) {
+ if (ch == '\\') {
+ if ((ch = *UCHAR(data++)) == 0)
+ break;
+ switch (ch) {
+ case 'a': /* \a -> audible bell */
+ ch = '\a';
+ break;
+ case 'b': /* \b -> backspace */
+ ch = '\b';
+ break;
+ case 'f': /* \f -> formfeed */
+ ch = '\f';
+ break;
+ case 'n': /* \n -> newline */
+ ch = '\n';
+ break;
+ case 'r': /* \r -> carriagereturn */
+ ch = '\r';
+ break;
+ case 't': /* \t -> horizontal tab */
+ ch = '\t';
+ break;
+ case 'v': /* \v -> vertical tab */
+ ch = '\v';
+ break;
+ case '0': /* \nnn -> ASCII value */
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ for (oval = ch - '0', i = 0;
+ i < 2 && (ch = *UCHAR(data)) != 0 && ISOCTAL(ch);
+ i++, data++) {
+ oval = (oval << 3) | (ch - '0');
+ }
+ ch = oval;
+ break;
+ default: /* \any -> any */
+ break;
+ }
+ }
+ VSTRING_ADDCH(result, ch);
+ }
+ VSTRING_TERMINATE(result);
+ return (result);
+}
+
+/* escape - reverse transformation */
+
+VSTRING *escape(VSTRING *result, const char *data, ssize_t len)
+{
+ int ch;
+
+ VSTRING_RESET(result);
+ while (len-- > 0) {
+ ch = *UCHAR(data++);
+ if (ISASCII(ch)) {
+ if (ISPRINT(ch)) {
+ if (ch == '\\')
+ VSTRING_ADDCH(result, ch);
+ VSTRING_ADDCH(result, ch);
+ continue;
+ } else if (ch == '\a') { /* \a -> audible bell */
+ vstring_strcat(result, "\\a");
+ continue;
+ } else if (ch == '\b') { /* \b -> backspace */
+ vstring_strcat(result, "\\b");
+ continue;
+ } else if (ch == '\f') { /* \f -> formfeed */
+ vstring_strcat(result, "\\f");
+ continue;
+ } else if (ch == '\n') { /* \n -> newline */
+ vstring_strcat(result, "\\n");
+ continue;
+ } else if (ch == '\r') { /* \r -> carriagereturn */
+ vstring_strcat(result, "\\r");
+ continue;
+ } else if (ch == '\t') { /* \t -> horizontal tab */
+ vstring_strcat(result, "\\t");
+ continue;
+ } else if (ch == '\v') { /* \v -> vertical tab */
+ vstring_strcat(result, "\\v");
+ continue;
+ }
+ }
+ vstring_sprintf_append(result, "\\%03o", ch);
+ }
+ VSTRING_TERMINATE(result);
+ return (result);
+}
+
+#ifdef TEST
+
+#include <stdlib.h>
+#include <string.h>
+#include <msg.h>
+#include <vstring_vstream.h>
+
+int main(int argc, char **argv)
+{
+ VSTRING *in = vstring_alloc(10);
+ VSTRING *out = vstring_alloc(10);
+ int un_escape = 1;
+
+ if (argc > 2 || (argc > 1 && (un_escape = strcmp(argv[1], "-e"))) != 0)
+ msg_fatal("usage: %s [-e (escape)]", argv[0]);
+
+ if (un_escape) {
+ while (vstring_fgets_nonl(in, VSTREAM_IN)) {
+ unescape(out, vstring_str(in));
+ vstream_fwrite(VSTREAM_OUT, vstring_str(out), VSTRING_LEN(out));
+ VSTREAM_PUTC('\n', VSTREAM_OUT);
+ }
+ } else {
+ while (vstring_fgets_nonl(in, VSTREAM_IN)) {
+ escape(out, vstring_str(in), VSTRING_LEN(in));
+ vstream_fwrite(VSTREAM_OUT, vstring_str(out), VSTRING_LEN(out));
+ VSTREAM_PUTC('\n', VSTREAM_OUT);
+ }
+ }
+ vstream_fflush(VSTREAM_OUT);
+ exit(0);
+}
+
+#endif
diff --git a/src/util/unescape.in b/src/util/unescape.in
new file mode 100644
index 0000000..41f24a7
--- /dev/null
+++ b/src/util/unescape.in
@@ -0,0 +1,4 @@
+\a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z
+\1\2\3\4\5\6\7\8\9
+\1234\2345\3456\4567
+rcpt to:<wietse@\317\200.porcupine.org>
diff --git a/src/util/unescape.ref b/src/util/unescape.ref
new file mode 100644
index 0000000..db16fa8
--- /dev/null
+++ b/src/util/unescape.ref
@@ -0,0 +1,11 @@
+0000000 \a \b c d e \f g h i j k l m \n o p
+ 007 010 143 144 145 014 147 150 151 152 153 154 155 012 157 160
+0000020 q \r s \t u \v w x y z \n 001 002 003 004 005
+ 161 015 163 011 165 013 167 170 171 172 012 001 002 003 004 005
+0000040 006 \a 8 9 \n S 4 234 5 345 6 . 7 \n r c
+ 006 007 070 071 012 123 064 234 065 345 066 056 067 012 162 143
+0000060 p t t o : < w i e t s e @ π **
+ 160 164 040 164 157 072 074 167 151 145 164 163 145 100 317 200
+0000100 . p o r c u p i n e . o r g > \n
+ 056 160 157 162 143 165 160 151 156 145 056 157 162 147 076 012
+0000120
diff --git a/src/util/unix_connect.c b/src/util/unix_connect.c
new file mode 100644
index 0000000..cbd8c0d
--- /dev/null
+++ b/src/util/unix_connect.c
@@ -0,0 +1,110 @@
+/*++
+/* NAME
+/* unix_connect 3
+/* SUMMARY
+/* connect to UNIX-domain listener
+/* SYNOPSIS
+/* #include <connect.h>
+/*
+/* int unix_connect(addr, block_mode, timeout)
+/* const char *addr;
+/* int block_mode;
+/* int timeout;
+/* DESCRIPTION
+/* unix_connect() connects to a listener in the UNIX domain at the
+/* specified address, and returns the resulting file descriptor.
+/*
+/* Arguments:
+/* .IP addr
+/* Null-terminated string with connection destination.
+/* .IP block_mode
+/* Either NON_BLOCKING for a non-blocking socket, or BLOCKING for
+/* blocking mode.
+/* .IP timeout
+/* Bounds the number of seconds that the operation may take. Specify
+/* a value <= 0 to disable the time limit.
+/* DIAGNOSTICS
+/* The result is -1 in case the connection could not be made.
+/* Fatal errors: other system call failures.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System interfaces. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "iostuff.h"
+#include "sane_connect.h"
+#include "connect.h"
+#include "timed_connect.h"
+
+/* unix_connect - connect to UNIX-domain listener */
+
+int unix_connect(const char *addr, int block_mode, int timeout)
+{
+#undef sun
+ struct sockaddr_un sun;
+ ssize_t len = strlen(addr);
+ int sock;
+
+ /*
+ * Translate address information to internal form.
+ */
+ if (len >= sizeof(sun.sun_path))
+ msg_fatal("unix-domain name too long: %s", addr);
+ memset((void *) &sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+#ifdef HAS_SUN_LEN
+ sun.sun_len = len + 1;
+#endif
+ memcpy(sun.sun_path, addr, len + 1);
+
+ /*
+ * Create a client socket.
+ */
+ if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
+ return (-1);
+
+ /*
+ * Timed connect.
+ */
+ if (timeout > 0) {
+ non_blocking(sock, NON_BLOCKING);
+ if (timed_connect(sock, (struct sockaddr *) &sun, sizeof(sun), timeout) < 0) {
+ close(sock);
+ return (-1);
+ }
+ if (block_mode != NON_BLOCKING)
+ non_blocking(sock, block_mode);
+ return (sock);
+ }
+
+ /*
+ * Maybe block until connected.
+ */
+ else {
+ non_blocking(sock, block_mode);
+ if (sane_connect(sock, (struct sockaddr *) &sun, sizeof(sun)) < 0
+ && errno != EINPROGRESS) {
+ close(sock);
+ return (-1);
+ }
+ return (sock);
+ }
+}
diff --git a/src/util/unix_dgram_connect.c b/src/util/unix_dgram_connect.c
new file mode 100644
index 0000000..3df8963
--- /dev/null
+++ b/src/util/unix_dgram_connect.c
@@ -0,0 +1,91 @@
+/*++
+/* NAME
+/* unix_dgram_connect 3
+/* SUMMARY
+/* connect to UNIX-domain datagram server
+/* SYNOPSIS
+/* #include <connect.h>
+/*
+/* int unix_dgram_connect(
+/* const char *path,
+/* int block_mode)
+/* DESCRIPTION
+/* unix_dgram_connect() connects to the specified UNIX-domain
+/* datagram server, and returns the resulting file descriptor.
+/*
+/* Arguments:
+/* .IP path
+/* Null-terminated string with connection destination.`
+/* .IP block_mode
+/* Either NON_BLOCKING for a non-blocking socket, or BLOCKING for
+/* blocking mode.
+/* DIAGNOSTICS
+/* Fatal errors: path too large, can't create socket.
+/*
+/* Other errors result in a -1 result value, with errno indicating
+/* why the service is unavailable.
+/* .sp
+/* ENOENT: the named socket does not exist.
+/* .sp
+/* ECONNREFUSED: the named socket is not open.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <string.h>
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <connect.h>
+#include <iostuff.h>
+
+/* unix_dgram_connect - connect to UNIX-domain datagram service */
+
+int unix_dgram_connect(const char *path, int block_mode)
+{
+ const char myname[] = "unix_dgram_connect";
+#undef sun
+ struct sockaddr_un sun;
+ ssize_t path_len;
+ int sock;
+
+ /*
+ * Translate address information to internal form.
+ */
+ if ((path_len = strlen(path)) >= sizeof(sun.sun_path))
+ msg_fatal("%s: unix-domain name too long: %s", myname, path);
+ memset((void *) &sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+#ifdef HAS_SUN_LEN
+ sun.sun_len = path_len + 1;
+#endif
+ memcpy(sun.sun_path, path, path_len + 1);
+
+ /*
+ * Create a client socket.
+ */
+ if ((sock = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0)
+ msg_fatal("%s: socket: %m", myname);
+ if (connect(sock, (struct sockaddr *) &sun, sizeof(sun)) < 0) {
+ close(sock);
+ return (-1);
+ }
+ non_blocking(sock, block_mode);
+ return (sock);
+}
diff --git a/src/util/unix_dgram_listen.c b/src/util/unix_dgram_listen.c
new file mode 100644
index 0000000..e73ad4e
--- /dev/null
+++ b/src/util/unix_dgram_listen.c
@@ -0,0 +1,93 @@
+/*++
+/* NAME
+/* unix_dgram_listen 3
+/* SUMMARY
+/* listen to UNIX-domain datagram server
+/* SYNOPSIS
+/* #include <listen.h>
+/*
+/* int unix_dgram_listen(
+/* const char *path,
+/* int block_mode)
+/* DESCRIPTION
+/* unix_dgram_listen() binds to the specified UNIX-domain
+/* datagram endpoint, and returns the resulting file descriptor.
+/*
+/* Arguments:
+/* .IP path
+/* Null-terminated string with connection destination.
+/* .IP backlog
+/* Either NON_BLOCKING for a non-blocking socket, or BLOCKING for
+/* blocking mode.
+/* DIAGNOSTICS
+/* Fatal errors: path too large, can't create socket.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+ /*
+ * Utility library.
+ */
+#include <iostuff.h>
+#include <listen.h>
+#include <msg.h>
+
+/* unix_dgram_listen - bind to UNIX-domain datagram endpoint */
+
+int unix_dgram_listen(const char *path, int block_mode)
+{
+ const char myname[] = "unix_dgram_listen";
+#undef sun
+ struct sockaddr_un sun;
+ ssize_t path_len;
+ int sock;
+
+ /*
+ * Translate address information to internal form.
+ */
+ if ((path_len = strlen(path)) >= sizeof(sun.sun_path))
+ msg_fatal("%s: unix-domain name too long: %s", myname, path);
+ memset((void *) &sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+#ifdef HAS_SUN_LEN
+ sun.sun_len = path_len + 1;
+#endif
+ memcpy(sun.sun_path, path, path_len + 1);
+
+ /*
+ * Create a 'server' socket.
+ */
+ if ((sock = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0)
+ msg_fatal("%s: socket: %m", myname);
+ if (unlink(path) < 0 && errno != ENOENT)
+ msg_fatal( "remove %s: %m", path);
+ if (bind(sock, (struct sockaddr *) & sun, sizeof(sun)) < 0)
+ msg_fatal( "bind: %s: %m", path);
+#ifdef FCHMOD_UNIX_SOCKETS
+ if (fchmod(sock, 0666) < 0)
+ msg_fatal("fchmod socket %s: %m", path);
+#else
+ if (chmod(path, 0666) < 0)
+ msg_fatal("chmod socket %s: %m", path);
+#endif
+ non_blocking(sock, block_mode);
+ return (sock);
+}
diff --git a/src/util/unix_listen.c b/src/util/unix_listen.c
new file mode 100644
index 0000000..6440406
--- /dev/null
+++ b/src/util/unix_listen.c
@@ -0,0 +1,113 @@
+/*++
+/* NAME
+/* unix_listen 3
+/* SUMMARY
+/* start UNIX-domain listener
+/* SYNOPSIS
+/* #include <listen.h>
+/*
+/* int unix_listen(addr, backlog, block_mode)
+/* const char *addr;
+/* int backlog;
+/* int block_mode;
+/*
+/* int unix_accept(fd)
+/* int fd;
+/* DESCRIPTION
+/* The \fBunix_listen\fR() routine starts a listener in the UNIX domain
+/* on the specified address, with the specified backlog, and returns
+/* the resulting file descriptor.
+/*
+/* unix_accept() accepts a connection and sanitizes error results.
+/*
+/* Arguments:
+/* .IP addr
+/* Null-terminated string with connection destination.
+/* .IP backlog
+/* This argument is passed on to the \fIlisten(2)\fR routine.
+/* .IP block_mode
+/* Either NON_BLOCKING for a non-blocking socket, or BLOCKING for
+/* blocking mode.
+/* .IP fd
+/* File descriptor returned by unix_listen().
+/* DIAGNOSTICS
+/* Fatal errors: unix_listen() aborts upon any system call failure.
+/* unix_accept() leaves all error handling up to the caller.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System interfaces. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "iostuff.h"
+#include "listen.h"
+#include "sane_accept.h"
+
+/* unix_listen - create UNIX-domain listener */
+
+int unix_listen(const char *addr, int backlog, int block_mode)
+{
+#undef sun
+ struct sockaddr_un sun;
+ ssize_t len = strlen(addr);
+ int sock;
+
+ /*
+ * Translate address information to internal form.
+ */
+ if (len >= sizeof(sun.sun_path))
+ msg_fatal("unix-domain name too long: %s", addr);
+ memset((void *) &sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+#ifdef HAS_SUN_LEN
+ sun.sun_len = len + 1;
+#endif
+ memcpy(sun.sun_path, addr, len + 1);
+
+ /*
+ * Create a listener socket. Do whatever we can so we don't run into
+ * trouble when this process is restarted after crash.
+ */
+ if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
+ msg_fatal("socket: %m");
+ if (unlink(addr) < 0 && errno != ENOENT)
+ msg_fatal("remove %s: %m", addr);
+ if (bind(sock, (struct sockaddr *) &sun, sizeof(sun)) < 0)
+ msg_fatal("bind: %s: %m", addr);
+#ifdef FCHMOD_UNIX_SOCKETS
+ if (fchmod(sock, 0666) < 0)
+ msg_fatal("fchmod socket %s: %m", addr);
+#else
+ if (chmod(addr, 0666) < 0)
+ msg_fatal("chmod socket %s: %m", addr);
+#endif
+ non_blocking(sock, block_mode);
+ if (listen(sock, backlog) < 0)
+ msg_fatal("listen: %m");
+ return (sock);
+}
+
+/* unix_accept - accept connection */
+
+int unix_accept(int fd)
+{
+ return (sane_accept(fd, (struct sockaddr *) 0, (SOCKADDR_SIZE *) 0));
+}
diff --git a/src/util/unix_pass_fd_fix.c b/src/util/unix_pass_fd_fix.c
new file mode 100644
index 0000000..9522e61
--- /dev/null
+++ b/src/util/unix_pass_fd_fix.c
@@ -0,0 +1,67 @@
+/*++
+/* NAME
+/* unix_pass_fd_fix 3
+/* SUMMARY
+/* file descriptor passing bug workarounds
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* void set_unix_pass_fd_fix(workarounds)
+/* const char *workarounds;
+/* DESCRIPTION
+/* This module supports programmatic control over workarounds
+/* for sending or receiving file descriptors over UNIX-domain
+/* sockets.
+/*
+/* set_unix_pass_fd_fix() takes a list of workarounds in external
+/* form, and stores their internal representation. The result
+/* is used by unix_send_fd() and unix_recv_fd().
+/*
+/* Arguments:
+/* .IP workarounds
+/* List of zero or more of the following, separated by comma
+/* or whitespace.
+/* .RS
+/* .IP cmsg_len
+/* Send the CMSG_LEN of the file descriptor, instead of
+/* the total message buffer length.
+/* .RE
+/* SEE ALSO
+/* unix_send_fd(3) send file descriptor
+/* unix_recv_fd(3) receive file descriptor
+/* DIAGNOSTICS
+/* Fatal errors: non-existent workaround.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <iostuff.h>
+#include <name_mask.h>
+
+int unix_pass_fd_fix = 0;
+
+/* set_unix_pass_fd_fix - set workaround programmatically */
+
+void set_unix_pass_fd_fix(const char *workarounds)
+{
+ const static NAME_MASK table[] = {
+ "cmsg_len", UNIX_PASS_FD_FIX_CMSG_LEN,
+ 0,
+ };
+
+ unix_pass_fd_fix = name_mask("descriptor passing workarounds",
+ table, workarounds);
+}
diff --git a/src/util/unix_recv_fd.c b/src/util/unix_recv_fd.c
new file mode 100644
index 0000000..3410cff
--- /dev/null
+++ b/src/util/unix_recv_fd.c
@@ -0,0 +1,178 @@
+/*++
+/* NAME
+/* unix_recv_fd 3
+/* SUMMARY
+/* receive file descriptor
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* int unix_recv_fd(fd)
+/* int fd;
+/* DESCRIPTION
+/* unix_recv_fd() receives a file descriptor via the specified
+/* UNIX-domain socket. The result value is the received descriptor.
+/*
+/* Arguments:
+/* .IP fd
+/* File descriptor that connects the sending and receiving processes.
+/* DIAGNOSTICS
+/* unix_recv_fd() returns -1 upon failure.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h> /* includes <sys/types.h> */
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+/* unix_recv_fd - receive file descriptor */
+
+int unix_recv_fd(int fd)
+{
+ const char *myname = "unix_recv_fd";
+
+ /*
+ * This code does not work with version <2.2 Linux kernels, and it does
+ * not compile with version <2 Linux libraries.
+ */
+#ifdef CANT_USE_SEND_RECV_MSG
+ msg_warn("%s: your system has no support for file descriptor passing",
+ myname);
+ return (-1);
+#else
+ struct msghdr msg;
+ int newfd;
+ struct iovec iov[1];
+ char buf[1];
+
+ /*
+ * Adapted from: W. Richard Stevens, UNIX Network Programming, Volume 1,
+ * Second edition. Except that we use CMSG_LEN instead of CMSG_SPACE, for
+ * portability to some LP64 environments. See also unix_send_fd.c.
+ */
+#if defined(CMSG_SPACE) && !defined(NO_MSGHDR_MSG_CONTROL)
+ union {
+ struct cmsghdr just_for_alignment;
+ char control[CMSG_SPACE(sizeof(newfd))];
+ } control_un;
+ struct cmsghdr *cmptr;
+
+ memset((void *) &msg, 0, sizeof(msg)); /* Fix 200512 */
+ msg.msg_control = control_un.control;
+ if (unix_pass_fd_fix & UNIX_PASS_FD_FIX_CMSG_LEN) {
+ msg.msg_controllen = CMSG_LEN(sizeof(newfd)); /* Fix 200506 */
+ } else {
+ msg.msg_controllen = sizeof(control_un.control); /* normal */
+ }
+#else
+ msg.msg_accrights = (char *) &newfd;
+ msg.msg_accrightslen = sizeof(newfd);
+#endif
+
+ msg.msg_name = 0;
+ msg.msg_namelen = 0;
+
+ /*
+ * XXX We don't want to pass any data, just a file descriptor. However,
+ * setting msg.msg_iov = 0 and msg.msg_iovlen = 0 causes trouble: we need
+ * to read_wait() before we can receive the descriptor, and the code
+ * fails after the first descriptor when we attempt to receive a sequence
+ * of descriptors.
+ */
+ iov->iov_base = buf;
+ iov->iov_len = sizeof(buf);
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+
+ if (recvmsg(fd, &msg, 0) < 0)
+ return (-1);
+
+#if defined(CMSG_SPACE) && !defined(NO_MSGHDR_MSG_CONTROL)
+ if ((cmptr = CMSG_FIRSTHDR(&msg)) != 0
+ && cmptr->cmsg_len == CMSG_LEN(sizeof(newfd))) {
+ if (cmptr->cmsg_level != SOL_SOCKET)
+ msg_fatal("%s: control level %d != SOL_SOCKET",
+ myname, cmptr->cmsg_level);
+ if (cmptr->cmsg_type != SCM_RIGHTS)
+ msg_fatal("%s: control type %d != SCM_RIGHTS",
+ myname, cmptr->cmsg_type);
+ return (*(int *) CMSG_DATA(cmptr));
+ } else
+ return (-1);
+#else
+ if (msg.msg_accrightslen == sizeof(newfd))
+ return (newfd);
+ else
+ return (-1);
+#endif
+#endif
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept program. Receive a descriptor (presumably from the
+ * unix_send_fd test program) and copy its content until EOF.
+ */
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <split_at.h>
+#include <listen.h>
+
+int main(int argc, char **argv)
+{
+ char *transport;
+ char *endpoint;
+ int listen_sock;
+ int client_sock;
+ int client_fd;
+ ssize_t read_count;
+ char buf[1024];
+
+ if (argc < 2 || argc > 3
+ || (endpoint = split_at(transport = argv[1], ':')) == 0
+ || *endpoint == 0 || *transport == 0)
+ msg_fatal("usage: %s transport:endpoint [workaround]", argv[0]);
+
+ if (strcmp(transport, "unix") == 0) {
+ listen_sock = unix_listen(endpoint, 10, BLOCKING);
+ } else {
+ msg_fatal("invalid transport name: %s", transport);
+ }
+ if (listen_sock < 0)
+ msg_fatal("listen %s:%s: %m", transport, endpoint);
+
+ client_sock = accept(listen_sock, (struct sockaddr *) 0, (SOCKADDR_SIZE) 0);
+ if (client_sock < 0)
+ msg_fatal("accept: %m");
+
+ set_unix_pass_fd_fix(argv[2] ? argv[2] : "");
+
+ while ((client_fd = unix_recv_fd(client_sock)) >= 0) {
+ msg_info("client_fd = %d, fix=%d", client_fd, unix_pass_fd_fix);
+ while ((read_count = read(client_fd, buf, sizeof(buf))) > 0)
+ write(1, buf, read_count);
+ if (read_count < 0)
+ msg_fatal("read: %m");
+ close(client_fd);
+ }
+ exit(0);
+}
+
+#endif
diff --git a/src/util/unix_send_fd.c b/src/util/unix_send_fd.c
new file mode 100644
index 0000000..15cc4df
--- /dev/null
+++ b/src/util/unix_send_fd.c
@@ -0,0 +1,199 @@
+/*++
+/* NAME
+/* unix_send_fd 3
+/* SUMMARY
+/* send file descriptor
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* int unix_send_fd(fd, sendfd)
+/* int fd;
+/* int sendfd;
+/* DESCRIPTION
+/* unix_send_fd() sends a file descriptor over the specified
+/* UNIX-domain socket.
+/*
+/* Arguments:
+/* .IP fd
+/* File descriptor that connects the sending and receiving processes.
+/* .IP sendfd
+/* The file descriptor to be sent.
+/* DIAGNOSTICS
+/* unix_send_fd() returns -1 upon failure.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h> /* includes <sys/types.h> */
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+/* unix_send_fd - send file descriptor */
+
+int unix_send_fd(int fd, int sendfd)
+{
+
+ /*
+ * This code does not work with version <2.2 Linux kernels, and it does
+ * not compile with version <2 Linux libraries.
+ */
+#ifdef CANT_USE_SEND_RECV_MSG
+ const char *myname = "unix_send_fd";
+
+ msg_warn("%s: your system has no support for file descriptor passing",
+ myname);
+ return (-1);
+#else
+ struct msghdr msg;
+ struct iovec iov[1];
+
+ /*
+ * Adapted from: W. Richard Stevens, UNIX Network Programming, Volume 1,
+ * Second edition. Except that we use CMSG_LEN instead of CMSG_SPACE, for
+ * portability to some LP64 environments. See also unix_recv_fd.c.
+ */
+#if defined(CMSG_SPACE) && !defined(NO_MSGHDR_MSG_CONTROL)
+ union {
+ struct cmsghdr just_for_alignment;
+ char control[CMSG_SPACE(sizeof(sendfd))];
+ } control_un;
+ struct cmsghdr *cmptr;
+
+ memset((void *) &msg, 0, sizeof(msg)); /* Fix 200512 */
+ memset((void *) &control_un, 0, sizeof(control_un)); /* Fix 202302 */
+ msg.msg_control = control_un.control;
+ if (unix_pass_fd_fix & UNIX_PASS_FD_FIX_CMSG_LEN) {
+ msg.msg_controllen = CMSG_LEN(sizeof(sendfd)); /* Fix 200506 */
+ } else {
+ msg.msg_controllen = sizeof(control_un.control); /* normal */
+ }
+ cmptr = CMSG_FIRSTHDR(&msg);
+ cmptr->cmsg_len = CMSG_LEN(sizeof(sendfd));
+ cmptr->cmsg_level = SOL_SOCKET;
+ cmptr->cmsg_type = SCM_RIGHTS;
+ *(int *) CMSG_DATA(cmptr) = sendfd;
+#else
+ msg.msg_accrights = (char *) &sendfd;
+ msg.msg_accrightslen = sizeof(sendfd);
+#endif
+
+ msg.msg_name = 0;
+ msg.msg_namelen = 0;
+
+ /*
+ * XXX We don't want to pass any data, just a file descriptor. However,
+ * setting msg.msg_iov = 0 and msg.msg_iovlen = 0 causes trouble. See the
+ * comments in the unix_recv_fd() routine.
+ */
+ iov->iov_base = "";
+ iov->iov_len = 1;
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+
+ /*
+ * The CMSG_LEN send/receive workaround was originally developed for
+ * OpenBSD 3.6 on SPARC64. After the workaround was verified to not break
+ * Solaris 8 on SPARC64, it was hard-coded with Postfix 2.3 for all
+ * platforms because of increasing pressure to work on other things. The
+ * workaround does nothing for 32-bit systems.
+ *
+ * The investigation was reopened with Postfix 2.7 because the workaround
+ * broke with NetBSD 5.0 on 64-bit architectures. This time it was found
+ * that OpenBSD <= 4.3 on AMD64 and SPARC64 needed the workaround for
+ * sending only. The following platforms worked with and without the
+ * workaround: OpenBSD 4.5 on AMD64 and SPARC64, FreeBSD 7.2 on AMD64,
+ * Solaris 8 on SPARC64, and Linux 2.6-11 on x86_64.
+ *
+ * As this appears to have been an OpenBSD-specific problem, we revert to
+ * the Postfix 2.2 behavior. Instead of hard-coding the workaround for
+ * all platforms, we now detect sendmsg() errors at run time and turn on
+ * the workaround dynamically.
+ *
+ * The workaround was made run-time configurable to investigate the problem
+ * on multiple platforms. Though set_unix_pass_fd_fix() is over-kill for
+ * this specific problem, it is left in place so that it can serve as an
+ * example of how to add run-time configurable workarounds to Postfix.
+ */
+ if (sendmsg(fd, &msg, 0) >= 0)
+ return (0);
+ if (unix_pass_fd_fix == 0) {
+ if (msg_verbose)
+ msg_info("sendmsg error (%m). Trying CMSG_LEN workaround.");
+ unix_pass_fd_fix = UNIX_PASS_FD_FIX_CMSG_LEN;
+ return (unix_send_fd(fd, sendfd));
+ } else {
+ return (-1);
+ }
+#endif
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept program. Open a file and send the descriptor, presumably
+ * to the unix_recv_fd test program.
+ */
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <split_at.h>
+#include <connect.h>
+
+int main(int argc, char **argv)
+{
+ char *transport;
+ char *endpoint;
+ char *path;
+ int server_sock;
+ int client_fd;
+
+ msg_verbose = 1;
+
+ if (argc < 3
+ || (endpoint = split_at(transport = argv[1], ':')) == 0
+ || *endpoint == 0 || *transport == 0)
+ msg_fatal("usage: %s transport:endpoint file...", argv[0]);
+
+ if (strcmp(transport, "unix") == 0) {
+ server_sock = unix_connect(endpoint, BLOCKING, 0);
+ } else {
+ msg_fatal("invalid transport name: %s", transport);
+ }
+ if (server_sock < 0)
+ msg_fatal("connect %s:%s: %m", transport, endpoint);
+
+ argv += 2;
+ while ((path = *argv++) != 0) {
+ if ((client_fd = open(path, O_RDONLY, 0)) < 0)
+ msg_fatal("open %s: %m", path);
+ msg_info("path=%s fd=%d", path, client_fd);
+ if (unix_send_fd(server_sock, client_fd) < 0)
+ msg_fatal("send file descriptor: %m");
+ if (close(client_fd) != 0)
+ msg_fatal("close(%d): %m", client_fd);
+ }
+ exit(0);
+}
+
+#endif
diff --git a/src/util/unix_trigger.c b/src/util/unix_trigger.c
new file mode 100644
index 0000000..59a18ff
--- /dev/null
+++ b/src/util/unix_trigger.c
@@ -0,0 +1,131 @@
+/*++
+/* NAME
+/* unix_trigger 3
+/* SUMMARY
+/* wakeup UNIX-domain server
+/* SYNOPSIS
+/* #include <trigger.h>
+/*
+/* int unix_trigger(service, buf, len, timeout)
+/* const char *service;
+/* const char *buf;
+/* ssize_t len;
+/* int timeout;
+/* DESCRIPTION
+/* unix_trigger() wakes up the named UNIX-domain server by making
+/* a brief connection to it and writing the named buffer.
+/*
+/* The connection is closed by a background thread. Some kernels
+/* cannot handle client-side disconnect before the server has
+/* received the message.
+/*
+/* Arguments:
+/* .IP service
+/* Name of the communication endpoint.
+/* .IP buf
+/* Address of data to be written.
+/* .IP len
+/* Amount of data to be written.
+/* .IP timeout
+/* Deadline in seconds. Specify a value <= 0 to disable
+/* the time limit.
+/* DIAGNOSTICS
+/* The result is zero in case of success, -1 in case of problems.
+/* SEE ALSO
+/* unix_connect(3), UNIX-domain client
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <connect.h>
+#include <iostuff.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <trigger.h>
+
+struct unix_trigger {
+ int fd;
+ char *service;
+};
+
+/* unix_trigger_event - disconnect from peer */
+
+static void unix_trigger_event(int event, void *context)
+{
+ struct unix_trigger *up = (struct unix_trigger *) context;
+ static const char *myname = "unix_trigger_event";
+
+ /*
+ * Disconnect.
+ */
+ if (event == EVENT_TIME)
+ msg_warn("%s: read timeout for service %s", myname, up->service);
+ event_disable_readwrite(up->fd);
+ event_cancel_timer(unix_trigger_event, context);
+ if (close(up->fd) < 0)
+ msg_warn("%s: close %s: %m", myname, up->service);
+ myfree(up->service);
+ myfree((void *) up);
+}
+
+/* unix_trigger - wakeup UNIX-domain server */
+
+int unix_trigger(const char *service, const char *buf, ssize_t len, int timeout)
+{
+ const char *myname = "unix_trigger";
+ struct unix_trigger *up;
+ int fd;
+
+ if (msg_verbose > 1)
+ msg_info("%s: service %s", myname, service);
+
+ /*
+ * Connect...
+ */
+ if ((fd = unix_connect(service, BLOCKING, timeout)) < 0) {
+ if (msg_verbose)
+ msg_warn("%s: connect to %s: %m", myname, service);
+ return (-1);
+ }
+ close_on_exec(fd, CLOSE_ON_EXEC);
+
+ /*
+ * Stash away context.
+ */
+ up = (struct unix_trigger *) mymalloc(sizeof(*up));
+ up->fd = fd;
+ up->service = mystrdup(service);
+
+ /*
+ * Write the request...
+ */
+ if (write_buf(fd, buf, len, timeout) < 0
+ || write_buf(fd, "", 1, timeout) < 0)
+ if (msg_verbose)
+ msg_warn("%s: write to %s: %m", myname, service);
+
+ /*
+ * Wakeup when the peer disconnects, or when we lose patience.
+ */
+ if (timeout > 0)
+ event_request_timer(unix_trigger_event, (void *) up, timeout + 100);
+ event_enable_read(fd, unix_trigger_event, (void *) up);
+ return (0);
+}
diff --git a/src/util/unsafe.c b/src/util/unsafe.c
new file mode 100644
index 0000000..5d307c9
--- /dev/null
+++ b/src/util/unsafe.c
@@ -0,0 +1,77 @@
+/*++
+/* NAME
+/* unsafe 3
+/* SUMMARY
+/* are we running at non-user privileges
+/* SYNOPSIS
+/* #include <safe.h>
+/*
+/* int unsafe()
+/* DESCRIPTION
+/* The \fBunsafe()\fR routine attempts to determine if the process
+/* (runs with privileges or has access to information) that the
+/* controlling user has no access to. The purpose is to prevent
+/* misuse of privileges, including access to protected information.
+/*
+/* The result is always false when both of the following conditions
+/* are true:
+/* .IP \(bu
+/* The real UID is zero.
+/* .IP \(bu
+/* The effective UID is zero.
+/* .PP
+/* Otherwise, the result is true if any of the following conditions
+/* is true:
+/* .IP \(bu
+/* The issetuid kernel flag is non-zero (on systems that support
+/* this concept).
+/* .IP \(bu
+/* The real and effective user id differ.
+/* .IP \(bu
+/* The real and effective group id differ.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <unistd.h>
+
+/* Utility library. */
+
+#include "safe.h"
+
+/* unsafe - can we trust user-provided environment, working directory, etc. */
+
+int unsafe(void)
+{
+
+ /*
+ * The super-user is trusted.
+ */
+ if (getuid() == 0 && geteuid() == 0)
+ return (0);
+
+ /*
+ * Danger: don't trust inherited process attributes, and don't leak
+ * privileged info that the parent has no access to.
+ */
+ return (geteuid() != getuid()
+#ifdef HAS_ISSETUGID
+ || issetugid()
+#endif
+ || getgid() != getegid());
+}
diff --git a/src/util/uppercase.c b/src/util/uppercase.c
new file mode 100644
index 0000000..9c61622
--- /dev/null
+++ b/src/util/uppercase.c
@@ -0,0 +1,43 @@
+/*++
+/* NAME
+/* uppercase 3
+/* SUMMARY
+/* map lowercase characters to uppercase
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* char *uppercase(buf)
+/* char *buf;
+/* DESCRIPTION
+/* uppercase() replaces lowercase characters in its null-terminated
+/* input by their uppercase equivalent.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <ctype.h>
+
+/* Utility library. */
+
+#include "stringops.h"
+
+char *uppercase(char *string)
+{
+ char *cp;
+ int ch;
+
+ for (cp = string; (ch = *cp) != 0; cp++)
+ if (ISLOWER(ch))
+ *cp = TOUPPER(ch);
+ return (string);
+}
diff --git a/src/util/username.c b/src/util/username.c
new file mode 100644
index 0000000..680161c
--- /dev/null
+++ b/src/util/username.c
@@ -0,0 +1,47 @@
+/*++
+/* NAME
+/* username 3
+/* SUMMARY
+/* lookup name of real user
+/* SYNOPSIS
+/* #include <username.h>
+/*
+/* const char *username()
+/* DESCRIPTION
+/* username() jumps whatever system-specific hoops it takes to
+/* get the name of the user who started the process. The result
+/* is volatile. Make a copy if it is to be used for an appreciable
+/* amount of time.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <unistd.h>
+#include <pwd.h>
+
+/* Utility library. */
+
+#include "username.h"
+
+/* username - get name of user */
+
+const char *username(void)
+{
+ uid_t uid;
+ struct passwd *pwd;
+
+ uid = getuid();
+ if ((pwd = getpwuid(uid)) == 0)
+ return (0);
+ return (pwd->pw_name);
+}
diff --git a/src/util/username.h b/src/util/username.h
new file mode 100644
index 0000000..648be45
--- /dev/null
+++ b/src/util/username.h
@@ -0,0 +1,29 @@
+#ifndef _USERNAME_H_INCLUDED_
+#define _USERNAME_H_INCLUDED_
+
+/*++
+/* NAME
+/* username 3h
+/* SUMMARY
+/* lookup name of real user
+/* SYNOPSIS
+/* #include <username.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface. */
+
+extern const char *username(void);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/valid_hostname.c b/src/util/valid_hostname.c
new file mode 100644
index 0000000..8b234c4
--- /dev/null
+++ b/src/util/valid_hostname.c
@@ -0,0 +1,412 @@
+/*++
+/* NAME
+/* valid_hostname 3
+/* SUMMARY
+/* network name validation
+/* SYNOPSIS
+/* #include <valid_hostname.h>
+/*
+/* int valid_hostname(name, gripe)
+/* const char *name;
+/* int gripe;
+/*
+/* int valid_hostaddr(addr, gripe)
+/* const char *addr;
+/* int gripe;
+/*
+/* int valid_ipv4_hostaddr(addr, gripe)
+/* const char *addr;
+/* int gripe;
+/*
+/* int valid_ipv6_hostaddr(addr, gripe)
+/* const char *addr;
+/* int gripe;
+/*
+/* int valid_hostport(port, gripe)
+/* const char *port;
+/* int gripe;
+/* DESCRIPTION
+/* valid_hostname() scrutinizes a hostname: the name should
+/* be no longer than VALID_HOSTNAME_LEN characters, should
+/* contain only letters, digits, dots and hyphens, no adjacent
+/* dots, no leading or trailing dots or hyphens, no labels
+/* longer than VALID_LABEL_LEN characters, and it should not
+/* be all numeric.
+/*
+/* valid_hostaddr() requires that the input is a valid string
+/* representation of an IPv4 or IPv6 network address as
+/* described next.
+/*
+/* valid_ipv4_hostaddr() and valid_ipv6_hostaddr() implement
+/* protocol-specific address syntax checks. A valid IPv4
+/* address is in dotted-quad decimal form. A valid IPv6 address
+/* has 16-bit hexadecimal fields separated by ":", and does not
+/* include the RFC 2821 style "IPv6:" prefix.
+/*
+/* These routines operate silently unless the gripe parameter
+/* specifies a non-zero value. The macros DO_GRIPE and DONT_GRIPE
+/* provide suitable constants.
+/*
+/* valid_hostport() requires that the input is a valid string
+/* representation of a TCP or UDP port number.
+/* BUGS
+/* valid_hostmumble() does not guarantee that string lengths
+/* fit the buffer sizes defined in myaddrinfo(3h).
+/* DIAGNOSTICS
+/* All functions return zero if they disagree with the input.
+/* SEE ALSO
+/* RFC 952, RFC 1123, RFC 1035, RFC 2373.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "mymalloc.h"
+#include "stringops.h"
+#include "valid_hostname.h"
+
+/* valid_hostname - screen out bad hostnames */
+
+int valid_hostname(const char *name, int flags)
+{
+ const char *myname = "valid_hostname";
+ const char *cp;
+ int label_length = 0;
+ int label_count = 0;
+ int non_numeric = 0;
+ int ch;
+ int gripe = flags & DO_GRIPE;
+
+ /*
+ * Trivial cases first.
+ */
+ if (*name == 0) {
+ if (gripe)
+ msg_warn("%s: empty hostname", myname);
+ return (0);
+ }
+
+ /*
+ * Find bad characters or label lengths. Find adjacent delimiters.
+ */
+ for (cp = name; (ch = *(unsigned char *) cp) != 0; cp++) {
+ if (ISALNUM(ch) || ch == '_') { /* grr.. */
+ if (label_length == 0)
+ label_count++;
+ label_length++;
+ if (label_length > VALID_LABEL_LEN) {
+ if (gripe)
+ msg_warn("%s: hostname label too long: %.100s", myname, name);
+ return (0);
+ }
+ if (!ISDIGIT(ch))
+ non_numeric = 1;
+ } else if ((flags & DO_WILDCARD) && ch == '*') {
+ if (label_length || label_count || (cp[1] && cp[1] != '.')) {
+ if (gripe)
+ msg_warn("%s: '*' can be the first label only: %.100s", myname, name);
+ return (0);
+ }
+ label_count++;
+ label_length++;
+ non_numeric = 1;
+ } else if (ch == '.') {
+ if (label_length == 0 || cp[1] == 0) {
+ if (gripe)
+ msg_warn("%s: misplaced delimiter: %.100s", myname, name);
+ return (0);
+ }
+ label_length = 0;
+ } else if (ch == '-') {
+ non_numeric = 1;
+ label_length++;
+ if (label_length == 1 || cp[1] == 0 || cp[1] == '.') {
+ if (gripe)
+ msg_warn("%s: misplaced hyphen: %.100s", myname, name);
+ return (0);
+ }
+ }
+#ifdef SLOPPY_VALID_HOSTNAME
+ else if (ch == ':' && valid_ipv6_hostaddr(name, DONT_GRIPE)) {
+ non_numeric = 0;
+ break;
+ }
+#endif
+ else {
+ if (gripe)
+ msg_warn("%s: invalid character %d(decimal): %.100s",
+ myname, ch, name);
+ return (0);
+ }
+ }
+
+ if (non_numeric == 0) {
+ if (gripe)
+ msg_warn("%s: numeric hostname: %.100s", myname, name);
+#ifndef SLOPPY_VALID_HOSTNAME
+ return (0);
+#endif
+ }
+ if (cp - name > VALID_HOSTNAME_LEN) {
+ if (gripe)
+ msg_warn("%s: bad length %d for %.100s...",
+ myname, (int) (cp - name), name);
+ return (0);
+ }
+ return (1);
+}
+
+/* valid_hostaddr - verify numerical address syntax */
+
+int valid_hostaddr(const char *addr, int gripe)
+{
+ const char *myname = "valid_hostaddr";
+
+ /*
+ * Trivial cases first.
+ */
+ if (*addr == 0) {
+ if (gripe)
+ msg_warn("%s: empty address", myname);
+ return (0);
+ }
+
+ /*
+ * Protocol-dependent processing next.
+ */
+ if (strchr(addr, ':') != 0)
+ return (valid_ipv6_hostaddr(addr, gripe));
+ else
+ return (valid_ipv4_hostaddr(addr, gripe));
+}
+
+/* valid_ipv4_hostaddr - test dotted quad string for correctness */
+
+int valid_ipv4_hostaddr(const char *addr, int gripe)
+{
+ const char *cp;
+ const char *myname = "valid_ipv4_hostaddr";
+ int in_byte = 0;
+ int byte_count = 0;
+ int byte_val = 0;
+ int ch;
+
+#define BYTES_NEEDED 4
+
+ /*
+ * Scary code to avoid sscanf() overflow nasties.
+ *
+ * This routine is called by valid_ipv6_hostaddr(). It must not call that
+ * routine, to avoid deadly recursion.
+ */
+ for (cp = addr; (ch = *(unsigned const char *) cp) != 0; cp++) {
+ if (ISDIGIT(ch)) {
+ if (in_byte == 0) {
+ in_byte = 1;
+ byte_val = 0;
+ byte_count++;
+ }
+ byte_val *= 10;
+ byte_val += ch - '0';
+ if (byte_val > 255) {
+ if (gripe)
+ msg_warn("%s: invalid octet value: %.100s", myname, addr);
+ return (0);
+ }
+ } else if (ch == '.') {
+ if (in_byte == 0 || cp[1] == 0) {
+ if (gripe)
+ msg_warn("%s: misplaced dot: %.100s", myname, addr);
+ return (0);
+ }
+ /* XXX Allow 0.0.0.0 but not 0.1.2.3 */
+ if (byte_count == 1 && byte_val == 0 && addr[strspn(addr, "0.")]) {
+ if (gripe)
+ msg_warn("%s: bad initial octet value: %.100s", myname, addr);
+ return (0);
+ }
+ in_byte = 0;
+ } else {
+ if (gripe)
+ msg_warn("%s: invalid character %d(decimal): %.100s",
+ myname, ch, addr);
+ return (0);
+ }
+ }
+
+ if (byte_count != BYTES_NEEDED) {
+ if (gripe)
+ msg_warn("%s: invalid octet count: %.100s", myname, addr);
+ return (0);
+ }
+ return (1);
+}
+
+/* valid_ipv6_hostaddr - validate IPv6 address syntax */
+
+int valid_ipv6_hostaddr(const char *addr, int gripe)
+{
+ const char *myname = "valid_ipv6_hostaddr";
+ int null_field = 0;
+ int field = 0;
+ unsigned char *cp = (unsigned char *) addr;
+ int len = 0;
+
+ /*
+ * FIX 200501 The IPv6 patch validated syntax with getaddrinfo(), but I
+ * am not confident that everyone's system library routines are robust
+ * enough, like buffer overflow free. Remember, the valid_hostmumble()
+ * routines are meant to protect Postfix against malformed information in
+ * data received from the network.
+ *
+ * We require eight-field hex addresses of the form 0:1:2:3:4:5:6:7,
+ * 0:1:2:3:4:5:6a.6b.7c.7d, or some :: compressed version of the same.
+ *
+ * Note: the character position is advanced inside the loop. I have added
+ * comments to show why we can't get stuck.
+ */
+ for (;;) {
+ switch (*cp) {
+ case 0:
+ /* Terminate the loop. */
+ if (field < 2) {
+ if (gripe)
+ msg_warn("%s: too few `:' in IPv6 address: %.100s",
+ myname, addr);
+ return (0);
+ } else if (len == 0 && null_field != field - 1) {
+ if (gripe)
+ msg_warn("%s: bad null last field in IPv6 address: %.100s",
+ myname, addr);
+ return (0);
+ } else
+ return (1);
+ case '.':
+ /* Terminate the loop. */
+ if (field < 2 || field > 6) {
+ if (gripe)
+ msg_warn("%s: malformed IPv4-in-IPv6 address: %.100s",
+ myname, addr);
+ return (0);
+ } else
+ /* NOT: valid_hostaddr(). Avoid recursion. */
+ return (valid_ipv4_hostaddr((char *) cp - len, gripe));
+ case ':':
+ /* Advance by exactly 1 character position or terminate. */
+ if (field == 0 && len == 0 && ISALNUM(cp[1])) {
+ if (gripe)
+ msg_warn("%s: bad null first field in IPv6 address: %.100s",
+ myname, addr);
+ return (0);
+ }
+ field++;
+ if (field > 7) {
+ if (gripe)
+ msg_warn("%s: too many `:' in IPv6 address: %.100s",
+ myname, addr);
+ return (0);
+ }
+ cp++;
+ len = 0;
+ if (*cp == ':') {
+ if (null_field > 0) {
+ if (gripe)
+ msg_warn("%s: too many `::' in IPv6 address: %.100s",
+ myname, addr);
+ return (0);
+ }
+ null_field = field;
+ }
+ break;
+ default:
+ /* Advance by at least 1 character position or terminate. */
+ len = strspn((char *) cp, "0123456789abcdefABCDEF");
+ if (len /* - strspn((char *) cp, "0") */ > 4) {
+ if (gripe)
+ msg_warn("%s: malformed IPv6 address: %.100s",
+ myname, addr);
+ return (0);
+ }
+ if (len <= 0) {
+ if (gripe)
+ msg_warn("%s: invalid character %d(decimal) in IPv6 address: %.100s",
+ myname, *cp, addr);
+ return (0);
+ }
+ cp += len;
+ break;
+ }
+ }
+}
+
+/* valid_hostport - validate numeric port */
+
+int valid_hostport(const char *str, int gripe)
+{
+ const char *myname = "valid_hostport";
+ int port;
+
+ if (str[0] == '0' && str[1] != 0) {
+ if (gripe)
+ msg_warn("%s: leading zero in port number: %.100s", myname, str);
+ return (0);
+ }
+ if (alldig(str) == 0) {
+ if (gripe)
+ msg_warn("%s: non-numeric port number: %.100s", myname, str);
+ return (0);
+ }
+ if (strlen(str) > strlen("65535")
+ || (port = atoi(str)) > 65535 || port < 0) {
+ if (gripe)
+ msg_warn("%s: out-of-range port number: %.100s", myname, str);
+ return (0);
+ }
+ return (1);
+}
+
+#ifdef TEST
+
+ /*
+ * Test program - reads hostnames from stdin, reports invalid hostnames to
+ * stderr.
+ */
+#include <stdlib.h>
+
+#include "vstring.h"
+#include "vstream.h"
+#include "vstring_vstream.h"
+#include "msg_vstream.h"
+
+int main(int unused_argc, char **argv)
+{
+ VSTRING *buffer = vstring_alloc(1);
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ msg_verbose = 1;
+
+ while (vstring_fgets_nonl(buffer, VSTREAM_IN)) {
+ msg_info("testing: \"%s\"", vstring_str(buffer));
+ valid_hostname(vstring_str(buffer), DO_GRIPE);
+ valid_hostaddr(vstring_str(buffer), DO_GRIPE);
+ }
+ exit(0);
+}
+
+#endif
diff --git a/src/util/valid_hostname.h b/src/util/valid_hostname.h
new file mode 100644
index 0000000..463bc6e
--- /dev/null
+++ b/src/util/valid_hostname.h
@@ -0,0 +1,41 @@
+#ifndef _VALID_HOSTNAME_H_INCLUDED_
+#define _VALID_HOSTNAME_H_INCLUDED_
+
+/*++
+/* NAME
+/* valid_hostname 3h
+/* SUMMARY
+/* validate hostname
+/* SYNOPSIS
+/* #include <valid_hostname.h>
+/* DESCRIPTION
+/* .nf
+
+ /* External interface */
+
+#define VALID_HOSTNAME_LEN 255 /* RFC 1035 */
+#define VALID_LABEL_LEN 63 /* RFC 1035 */
+
+#define DONT_GRIPE 0
+#define DO_GRIPE 1
+#define DONT_WILDCARD 0
+#define DO_WILDCARD (1<<1)
+
+extern int valid_hostname(const char *, int);
+extern int valid_hostaddr(const char *, int);
+extern int valid_ipv4_hostaddr(const char *, int);
+extern int valid_ipv6_hostaddr(const char *, int);
+extern int valid_hostport(const char *, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/valid_hostname.in b/src/util/valid_hostname.in
new file mode 100644
index 0000000..608c0d1
--- /dev/null
+++ b/src/util/valid_hostname.in
@@ -0,0 +1,55 @@
+123456789012345678901234567890123456789012345678901234567890123
+1234567890123456789012345678901234567890123456789012345678901234
+a.123456789012345678901234567890123456789012345678901234567890123.b
+a.1234567890123456789012345678901234567890123456789012345678901234.b
+1.2.3.4
+321.255.255.255
+0.0.0.0
+255.255.255.255
+0.255.255.255
+1.2.3.321
+1.2.3
+1.2.3.4.5
+1..2.3.4
+.1.2.3.4
+1.2.3.4.5.
+1
+.
+
+321
+f
+f.2.3.4
+1f.2.3.4
+f1.2.3.4
+1.2f.3.4
+1.f2.3.4
+1.2.3.4f
+1.2.3.f4
+1.2.3.f
+-.a.b
+a.-.b
+a.b.-
+-aa.b.b
+aa-.b.b
+a.-bb.b
+a.bb-.b
+a.b.-bb
+a.b.bb-
+a-a.b-b
+:
+::
+:::
+a::
+::a
+::1.2.3.4
+a:a:a:a:a:1.2.3.4
+a:a:a:a:a:a:1.2.3.4
+a:a:a:a:a:a:a:1.2.3.4
+a:a:a:a:a:a:1.2.3.
+a:a:a:a:a:a:1.2.3
+a:a:a:a:a:a:a:a
+a:a:a:a:a:a:a:a:a
+g:a:a:a:a:a:a:a
+a::b
+:a::b
+a::b:
diff --git a/src/util/valid_hostname.ref b/src/util/valid_hostname.ref
new file mode 100644
index 0000000..08b23b8
--- /dev/null
+++ b/src/util/valid_hostname.ref
@@ -0,0 +1,143 @@
+./valid_hostname: testing: "123456789012345678901234567890123456789012345678901234567890123"
+./valid_hostname: warning: valid_hostname: numeric hostname: 123456789012345678901234567890123456789012345678901234567890123
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet value: 123456789012345678901234567890123456789012345678901234567890123
+./valid_hostname: testing: "1234567890123456789012345678901234567890123456789012345678901234"
+./valid_hostname: warning: valid_hostname: hostname label too long: 1234567890123456789012345678901234567890123456789012345678901234
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet value: 1234567890123456789012345678901234567890123456789012345678901234
+./valid_hostname: testing: "a.123456789012345678901234567890123456789012345678901234567890123.b"
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.123456789012345678901234567890123456789012345678901234567890123.b
+./valid_hostname: testing: "a.1234567890123456789012345678901234567890123456789012345678901234.b"
+./valid_hostname: warning: valid_hostname: hostname label too long: a.1234567890123456789012345678901234567890123456789012345678901234.b
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.1234567890123456789012345678901234567890123456789012345678901234.b
+./valid_hostname: testing: "1.2.3.4"
+./valid_hostname: warning: valid_hostname: numeric hostname: 1.2.3.4
+./valid_hostname: testing: "321.255.255.255"
+./valid_hostname: warning: valid_hostname: numeric hostname: 321.255.255.255
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet value: 321.255.255.255
+./valid_hostname: testing: "0.0.0.0"
+./valid_hostname: warning: valid_hostname: numeric hostname: 0.0.0.0
+./valid_hostname: testing: "255.255.255.255"
+./valid_hostname: warning: valid_hostname: numeric hostname: 255.255.255.255
+./valid_hostname: testing: "0.255.255.255"
+./valid_hostname: warning: valid_hostname: numeric hostname: 0.255.255.255
+./valid_hostname: warning: valid_ipv4_hostaddr: bad initial octet value: 0.255.255.255
+./valid_hostname: testing: "1.2.3.321"
+./valid_hostname: warning: valid_hostname: numeric hostname: 1.2.3.321
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet value: 1.2.3.321
+./valid_hostname: testing: "1.2.3"
+./valid_hostname: warning: valid_hostname: numeric hostname: 1.2.3
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet count: 1.2.3
+./valid_hostname: testing: "1.2.3.4.5"
+./valid_hostname: warning: valid_hostname: numeric hostname: 1.2.3.4.5
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet count: 1.2.3.4.5
+./valid_hostname: testing: "1..2.3.4"
+./valid_hostname: warning: valid_hostname: misplaced delimiter: 1..2.3.4
+./valid_hostname: warning: valid_ipv4_hostaddr: misplaced dot: 1..2.3.4
+./valid_hostname: testing: ".1.2.3.4"
+./valid_hostname: warning: valid_hostname: misplaced delimiter: .1.2.3.4
+./valid_hostname: warning: valid_ipv4_hostaddr: misplaced dot: .1.2.3.4
+./valid_hostname: testing: "1.2.3.4.5."
+./valid_hostname: warning: valid_hostname: misplaced delimiter: 1.2.3.4.5.
+./valid_hostname: warning: valid_ipv4_hostaddr: misplaced dot: 1.2.3.4.5.
+./valid_hostname: testing: "1"
+./valid_hostname: warning: valid_hostname: numeric hostname: 1
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet count: 1
+./valid_hostname: testing: "."
+./valid_hostname: warning: valid_hostname: misplaced delimiter: .
+./valid_hostname: warning: valid_ipv4_hostaddr: misplaced dot: .
+./valid_hostname: testing: ""
+./valid_hostname: warning: valid_hostname: empty hostname
+./valid_hostname: warning: valid_hostaddr: empty address
+./valid_hostname: testing: "321"
+./valid_hostname: warning: valid_hostname: numeric hostname: 321
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet value: 321
+./valid_hostname: testing: "f"
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): f
+./valid_hostname: testing: "f.2.3.4"
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): f.2.3.4
+./valid_hostname: testing: "1f.2.3.4"
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): 1f.2.3.4
+./valid_hostname: testing: "f1.2.3.4"
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): f1.2.3.4
+./valid_hostname: testing: "1.2f.3.4"
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): 1.2f.3.4
+./valid_hostname: testing: "1.f2.3.4"
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): 1.f2.3.4
+./valid_hostname: testing: "1.2.3.4f"
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): 1.2.3.4f
+./valid_hostname: testing: "1.2.3.f4"
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): 1.2.3.f4
+./valid_hostname: testing: "1.2.3.f"
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): 1.2.3.f
+./valid_hostname: testing: "-.a.b"
+./valid_hostname: warning: valid_hostname: misplaced hyphen: -.a.b
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 45(decimal): -.a.b
+./valid_hostname: testing: "a.-.b"
+./valid_hostname: warning: valid_hostname: misplaced hyphen: a.-.b
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.-.b
+./valid_hostname: testing: "a.b.-"
+./valid_hostname: warning: valid_hostname: misplaced hyphen: a.b.-
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.b.-
+./valid_hostname: testing: "-aa.b.b"
+./valid_hostname: warning: valid_hostname: misplaced hyphen: -aa.b.b
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 45(decimal): -aa.b.b
+./valid_hostname: testing: "aa-.b.b"
+./valid_hostname: warning: valid_hostname: misplaced hyphen: aa-.b.b
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): aa-.b.b
+./valid_hostname: testing: "a.-bb.b"
+./valid_hostname: warning: valid_hostname: misplaced hyphen: a.-bb.b
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.-bb.b
+./valid_hostname: testing: "a.bb-.b"
+./valid_hostname: warning: valid_hostname: misplaced hyphen: a.bb-.b
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.bb-.b
+./valid_hostname: testing: "a.b.-bb"
+./valid_hostname: warning: valid_hostname: misplaced hyphen: a.b.-bb
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.b.-bb
+./valid_hostname: testing: "a.b.bb-"
+./valid_hostname: warning: valid_hostname: misplaced hyphen: a.b.bb-
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.b.bb-
+./valid_hostname: testing: "a-a.b-b"
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a-a.b-b
+./valid_hostname: testing: ":"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): :
+./valid_hostname: warning: valid_ipv6_hostaddr: too few `:' in IPv6 address: :
+./valid_hostname: testing: "::"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): ::
+./valid_hostname: testing: ":::"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): :::
+./valid_hostname: warning: valid_ipv6_hostaddr: too many `::' in IPv6 address: :::
+./valid_hostname: testing: "a::"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a::
+./valid_hostname: testing: "::a"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): ::a
+./valid_hostname: testing: "::1.2.3.4"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): ::1.2.3.4
+./valid_hostname: testing: "a:a:a:a:a:1.2.3.4"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:a:a:a:a:1.2.3.4
+./valid_hostname: testing: "a:a:a:a:a:a:1.2.3.4"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:a:a:a:a:a:1.2.3.4
+./valid_hostname: testing: "a:a:a:a:a:a:a:1.2.3.4"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:a:a:a:a:a:a:1.2.3.4
+./valid_hostname: warning: valid_ipv6_hostaddr: malformed IPv4-in-IPv6 address: a:a:a:a:a:a:a:1.2.3.4
+./valid_hostname: testing: "a:a:a:a:a:a:1.2.3."
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:a:a:a:a:a:1.2.3.
+./valid_hostname: warning: valid_ipv4_hostaddr: misplaced dot: 1.2.3.
+./valid_hostname: testing: "a:a:a:a:a:a:1.2.3"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:a:a:a:a:a:1.2.3
+./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet count: 1.2.3
+./valid_hostname: testing: "a:a:a:a:a:a:a:a"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:a:a:a:a:a:a:a
+./valid_hostname: testing: "a:a:a:a:a:a:a:a:a"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:a:a:a:a:a:a:a:a
+./valid_hostname: warning: valid_ipv6_hostaddr: too many `:' in IPv6 address: a:a:a:a:a:a:a:a:a
+./valid_hostname: testing: "g:a:a:a:a:a:a:a"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): g:a:a:a:a:a:a:a
+./valid_hostname: warning: valid_ipv6_hostaddr: invalid character 103(decimal) in IPv6 address: g:a:a:a:a:a:a:a
+./valid_hostname: testing: "a::b"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a::b
+./valid_hostname: testing: ":a::b"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): :a::b
+./valid_hostname: warning: valid_ipv6_hostaddr: bad null first field in IPv6 address: :a::b
+./valid_hostname: testing: "a::b:"
+./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a::b:
+./valid_hostname: warning: valid_ipv6_hostaddr: bad null last field in IPv6 address: a::b:
diff --git a/src/util/valid_utf8_hostname.c b/src/util/valid_utf8_hostname.c
new file mode 100644
index 0000000..3d6922a
--- /dev/null
+++ b/src/util/valid_utf8_hostname.c
@@ -0,0 +1,87 @@
+/*++
+/* NAME
+/* valid_utf8_hostname 3
+/* SUMMARY
+/* validate (maybe UTF-8) domain name
+/* SYNOPSIS
+/* #include <valid_utf8_hostname.h>
+/*
+/* int valid_utf8_hostname(
+/* int enable_utf8,
+/* const char *domain,
+/* int gripe)
+/* DESCRIPTION
+/* valid_utf8_hostname() is a wrapper around valid_hostname().
+/* If EAI support is compiled in, and enable_utf8 is true, the
+/* name is converted from UTF-8 to ASCII per IDNA rules, before
+/* invoking valid_hostname().
+/* SEE ALSO
+/* valid_hostname(3) STD3 hostname validation.
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation problem.
+/* Warnings: malformed domain name.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <valid_hostname.h>
+#include <midna_domain.h>
+#include <valid_utf8_hostname.h>
+
+/* valid_utf8_hostname - validate internationalized domain name */
+
+int valid_utf8_hostname(int enable_utf8, const char *name, int gripe)
+{
+ static const char myname[] = "valid_utf8_hostname";
+
+ /*
+ * Trivial cases first.
+ */
+ if (*name == 0) {
+ if (gripe)
+ msg_warn("%s: empty domain name", myname);
+ return (0);
+ }
+
+ /*
+ * Convert non-ASCII domain name to ASCII and validate the result per
+ * STD3. midna_domain_to_ascii() applies valid_hostname() to the result.
+ * Propagate the gripe parameter for better diagnostics (note that
+ * midna_domain_to_ascii() logs a problem only when the result is not
+ * cached).
+ */
+#ifndef NO_EAI
+ if (enable_utf8 && !allascii(name)) {
+ if (midna_domain_to_ascii(name) == 0) {
+ if (gripe)
+ msg_warn("%s: malformed UTF-8 domain name", myname);
+ return (0);
+ } else {
+ return (1);
+ }
+ }
+#endif
+
+ /*
+ * Validate ASCII name per STD3.
+ */
+ return (valid_hostname(name, gripe));
+}
diff --git a/src/util/valid_utf8_hostname.h b/src/util/valid_utf8_hostname.h
new file mode 100644
index 0000000..0c0b41f
--- /dev/null
+++ b/src/util/valid_utf8_hostname.h
@@ -0,0 +1,35 @@
+#ifndef _VALID_UTF8_HOSTNAME_H_INCLUDED_
+#define _VALID_UTF8_HOSTNAME_H_INCLUDED_
+
+/*++
+/* NAME
+/* valid_utf8_hostname 3h
+/* SUMMARY
+/* validate (maybe UTF-8) domain name
+/* SYNOPSIS
+/* #include <valid_utf8_hostname.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <valid_hostname.h>
+
+ /*
+ * External interface
+ */
+extern int valid_utf8_hostname(int, const char *, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/valid_utf8_string.c b/src/util/valid_utf8_string.c
new file mode 100644
index 0000000..96b5b4d
--- /dev/null
+++ b/src/util/valid_utf8_string.c
@@ -0,0 +1,139 @@
+/*++
+/* NAME
+/* valid_utf8_string 3
+/* SUMMARY
+/* predicate if string is valid UTF-8
+/* SYNOPSIS
+/* #include <stringops.h>
+/*
+/* int valid_utf8_string(str, len)
+/* const char *str;
+/* ssize_t len;
+/* DESCRIPTION
+/* valid_utf8_string() determines if a string satisfies the UTF-8
+/* definition in RFC 3629. That is, it contains proper encodings
+/* of code points U+0000..U+10FFFF, excluding over-long encodings
+/* and excluding U+D800..U+DFFF surrogates.
+/*
+/* A zero-length string is considered valid.
+/* DIAGNOSTICS
+/* The result value is zero when the caller specifies a negative
+/* length, or a string that violates RFC 3629, for example a
+/* string that is truncated in the middle of a multi-byte
+/* sequence.
+/* BUGS
+/* But wait, there is more. Code points in the range U+FDD0..U+FDEF
+/* and ending in FFFE or FFFF are non-characters in UNICODE. This
+/* function does not block these.
+/* SEE ALSO
+/* RFC 3629
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <stringops.h>
+
+/* valid_utf8_string - validate string according to RFC 3629 */
+
+int valid_utf8_string(const char *str, ssize_t len)
+{
+ const unsigned char *end = (const unsigned char *) str + len;
+ const unsigned char *cp;
+ unsigned char c0, ch;
+
+ if (len < 0)
+ return (0);
+ if (len <= 0)
+ return (1);
+
+ /*
+ * Optimized for correct input, time, space, and for CPUs that have a
+ * decent number of registers.
+ */
+ for (cp = (const unsigned char *) str; cp < end; cp++) {
+ /* Single-byte encodings. */
+ if (EXPECTED((c0 = *cp) <= 0x7f) /* we know that c0 >= 0x0 */ ) {
+ /* void */ ;
+ }
+ /* Two-byte encodings. */
+ else if (EXPECTED(c0 <= 0xdf) /* we know that c0 >= 0x80 */ ) {
+ /* Exclude over-long encodings. */
+ if (UNEXPECTED(c0 < 0xc2)
+ || UNEXPECTED(cp + 1 >= end)
+ /* Require UTF-8 tail byte. */
+ || UNEXPECTED(((ch = *++cp) & 0xc0) != 0x80))
+ return (0);
+ }
+ /* Three-byte encodings. */
+ else if (EXPECTED(c0 <= 0xef) /* we know that c0 >= 0xe0 */ ) {
+ if (UNEXPECTED(cp + 2 >= end)
+ /* Exclude over-long encodings. */
+ || UNEXPECTED((ch = *++cp) < (c0 == 0xe0 ? 0xa0 : 0x80))
+ /* Exclude U+D800..U+DFFF. */
+ || UNEXPECTED(ch > (c0 == 0xed ? 0x9f : 0xbf))
+ /* Require UTF-8 tail byte. */
+ || UNEXPECTED(((ch = *++cp) & 0xc0) != 0x80))
+ return (0);
+ }
+ /* Four-byte encodings. */
+ else if (EXPECTED(c0 <= 0xf4) /* we know that c0 >= 0xf0 */ ) {
+ if (UNEXPECTED(cp + 3 >= end)
+ /* Exclude over-long encodings. */
+ || UNEXPECTED((ch = *++cp) < (c0 == 0xf0 ? 0x90 : 0x80))
+ /* Exclude code points above U+10FFFF. */
+ || UNEXPECTED(ch > (c0 == 0xf4 ? 0x8f : 0xbf))
+ /* Require UTF-8 tail byte. */
+ || UNEXPECTED(((ch = *++cp) & 0xc0) != 0x80)
+ /* Require UTF-8 tail byte. */
+ || UNEXPECTED(((ch = *++cp) & 0xc0) != 0x80))
+ return (0);
+ }
+ /* Invalid: c0 >= 0xf5 */
+ else {
+ return (0);
+ }
+ }
+ return (1);
+}
+
+ /*
+ * Stand-alone test program. Each string is a line without line terminator.
+ */
+#ifdef TEST
+#include <stdlib.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <vstring_vstream.h>
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+int main(void)
+{
+ VSTRING *buf = vstring_alloc(1);
+
+ while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
+ vstream_printf("%c", (LEN(buf) && !valid_utf8_string(STR(buf), LEN(buf))) ?
+ '!' : ' ');
+ vstream_fwrite(VSTREAM_OUT, STR(buf), LEN(buf));
+ vstream_printf("\n");
+ }
+ vstream_fflush(VSTREAM_OUT);
+ vstring_free(buf);
+ exit(0);
+}
+
+#endif
diff --git a/src/util/vbuf.c b/src/util/vbuf.c
new file mode 100644
index 0000000..924e230
--- /dev/null
+++ b/src/util/vbuf.c
@@ -0,0 +1,238 @@
+/*++
+/* NAME
+/* vbuf 3
+/* SUMMARY
+/* generic buffer package
+/* SYNOPSIS
+/* #include <vbuf.h>
+/*
+/* int VBUF_GET(bp)
+/* VBUF *bp;
+/*
+/* int VBUF_PUT(bp, ch)
+/* VBUF *bp;
+/* int ch;
+/*
+/* int VBUF_SPACE(bp, len)
+/* VBUF *bp;
+/* ssize_t len;
+/*
+/* int vbuf_unget(bp, ch)
+/* VBUF *bp;
+/* int ch;
+/*
+/* ssize_t vbuf_read(bp, buf, len)
+/* VBUF *bp;
+/* void *buf;
+/* ssize_t len;
+/*
+/* ssize_t vbuf_write(bp, buf, len)
+/* VBUF *bp;
+/* const void *buf;
+/* ssize_t len;
+/*
+/* int vbuf_err(bp)
+/* VBUF *bp;
+/*
+/* int vbuf_eof(bp)
+/* VBUF *bp;
+/*
+/* int vbuf_timeout(bp)
+/* VBUF *bp;
+/*
+/* int vbuf_clearerr(bp)
+/* VBUF *bp;
+/*
+/* int vbuf_rd_err(bp)
+/* VBUF *bp;
+/*
+/* int vbuf_wr_err(bp)
+/* VBUF *bp;
+/*
+/* int vbuf_rd_timeout(bp)
+/* VBUF *bp;
+/*
+/* int vbuf_wr_timeout(bp)
+/* VBUF *bp;
+/* DESCRIPTION
+/* This module implements a buffer with read/write primitives that
+/* automatically handle buffer-empty or buffer-full conditions.
+/* The application is expected to provide callback routines that run
+/* when the read-write primitives detect a buffer-empty/full condition.
+/*
+/* VBUF buffers provide primitives to store and retrieve characters,
+/* and to look up buffer status information.
+/* By design, VBUF buffers provide no explicit primitives for buffer
+/* memory management. This is left to the application to avoid any bias
+/* toward specific management models. The application is free to use
+/* whatever strategy suits best: memory-resident buffer, memory mapped
+/* file, or stdio-like window to an open file.
+/*
+/* VBUF_GET() returns the next character from the specified buffer,
+/* or VBUF_EOF when none is available. VBUF_GET() is an unsafe macro
+/* that evaluates its argument more than once.
+/*
+/* VBUF_PUT() stores one character into the specified buffer. The result
+/* is the stored character, or VBUF_EOF in case of problems. VBUF_PUT()
+/* is an unsafe macro that evaluates its arguments more than once.
+/*
+/* VBUF_SPACE() requests that the requested amount of buffer space be
+/* made available, so that it can be accessed without using VBUF_PUT().
+/* The result value is 0 for success, VBUF_EOF for problems.
+/* VBUF_SPACE() is an unsafe macro that evaluates its arguments more
+/* than once. VBUF_SPACE() does not support read-only streams.
+/*
+/* vbuf_unget() provides at least one character of pushback, and returns
+/* the pushed back character, or VBUF_EOF in case of problems. It is
+/* an error to call vbuf_unget() on a buffer before reading any data
+/* from it. vbuf_unget() clears the buffer's end-of-file indicator upon
+/* success, and sets the buffer's error indicator when an attempt is
+/* made to push back a non-character value.
+/*
+/* vbuf_read() and vbuf_write() do bulk I/O. The result value is the
+/* number of bytes transferred. A short count is returned in case of
+/* an error.
+/*
+/* vbuf_timeout() is a macro that returns non-zero if a timeout error
+/* condition was detected while reading or writing the buffer. The
+/* error status can be reset by calling vbuf_clearerr().
+/*
+/* vbuf_err() is a macro that returns non-zero if a non-EOF error
+/* (including timeout) condition was detected while reading or writing
+/* the buffer. The error status can be reset by calling vbuf_clearerr().
+/*
+/* The vbuf_rd_mumble() and vbuf_wr_mumble() macros report on
+/* read and write error conditions, respectively.
+/*
+/* vbuf_eof() is a macro that returns non-zero if an end-of-file
+/* condition was detected while reading or writing the buffer. The error
+/* status can be reset by calling vbuf_clearerr().
+/* APPLICATION CALLBACK SYNOPSIS
+/* int get_ready(bp)
+/* VBUF *bp;
+/*
+/* int put_ready(bp)
+/* VBUF *bp;
+/*
+/* int space(bp, len)
+/* VBUF *bp;
+/* ssize_t len;
+/* APPLICATION CALLBACK DESCRIPTION
+/* .ad
+/* .fi
+/* get_ready() is called when VBUF_GET() detects a buffer-empty condition.
+/* The result is zero when more data could be read, VBUF_EOF otherwise.
+/*
+/* put_ready() is called when VBUF_PUT() detects a buffer-full condition.
+/* The result is zero when the buffer could be flushed, VBUF_EOF otherwise.
+/*
+/* space() performs whatever magic necessary to make at least \fIlen\fR
+/* bytes available for access without using VBUF_PUT(). The result is 0
+/* in case of success, VBUF_EOF otherwise.
+/* SEE ALSO
+/* vbuf(3h) layout of the VBUF data structure.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <string.h>
+
+/* Utility library. */
+
+#include "vbuf.h"
+
+/* vbuf_unget - implement at least one character pushback */
+
+int vbuf_unget(VBUF *bp, int ch)
+{
+ if ((ch & 0xff) != ch || -bp->cnt >= bp->len) {
+ bp->flags |= VBUF_FLAG_RD_ERR; /* This error affects reads! */
+ return (VBUF_EOF);
+ } else {
+ bp->cnt--;
+ bp->flags &= ~VBUF_FLAG_EOF;
+ return (*--bp->ptr = ch);
+ }
+}
+
+/* vbuf_get - handle read buffer empty condition */
+
+int vbuf_get(VBUF *bp)
+{
+ return (bp->get_ready(bp) ?
+ ((bp->flags |= VBUF_FLAG_EOF), VBUF_EOF) : VBUF_GET(bp));
+}
+
+/* vbuf_put - handle write buffer full condition */
+
+int vbuf_put(VBUF *bp, int ch)
+{
+ return (bp->put_ready(bp) ? VBUF_EOF : VBUF_PUT(bp, ch));
+}
+
+/* vbuf_read - bulk read from buffer */
+
+ssize_t vbuf_read(VBUF *bp, void *buf, ssize_t len)
+{
+ ssize_t count;
+ void *cp;
+ ssize_t n;
+
+#if 0
+ for (count = 0; count < len; count++)
+ if ((buf[count] = VBUF_GET(bp)) < 0)
+ break;
+ return (count);
+#else
+ for (cp = buf, count = len; count > 0; cp += n, count -= n) {
+ if (bp->cnt >= 0 && bp->get_ready(bp))
+ break;
+ n = (count < -bp->cnt ? count : -bp->cnt);
+ memcpy(cp, bp->ptr, n);
+ bp->ptr += n;
+ bp->cnt += n;
+ }
+ return (len - count);
+#endif
+}
+
+/* vbuf_write - bulk write to buffer */
+
+ssize_t vbuf_write(VBUF *bp, const void *buf, ssize_t len)
+{
+ ssize_t count;
+ const void *cp;
+ ssize_t n;
+
+#if 0
+ for (count = 0; count < len; count++)
+ if (VBUF_PUT(bp, buf[count]) < 0)
+ break;
+ return (count);
+#else
+ for (cp = buf, count = len; count > 0; cp += n, count -= n) {
+ if (bp->cnt <= 0 && bp->put_ready(bp) != 0)
+ break;
+ n = (count < bp->cnt ? count : bp->cnt);
+ memcpy(bp->ptr, cp, n);
+ bp->ptr += n;
+ bp->cnt -= n;
+ }
+ return (len - count);
+#endif
+}
diff --git a/src/util/vbuf.h b/src/util/vbuf.h
new file mode 100644
index 0000000..149fe0c
--- /dev/null
+++ b/src/util/vbuf.h
@@ -0,0 +1,110 @@
+#ifndef _VBUF_H_INCLUDED_
+#define _VBUF_H_INCLUDED_
+
+/*++
+/* NAME
+/* vbuf 3h
+/* SUMMARY
+/* generic buffer
+/* SYNOPSIS
+/* #include <vbuf.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * The VBUF buffer is defined by 1) its structure, by 2) the VBUF_GET() and
+ * 3) VBUF_PUT() operations that automatically handle buffer empty and
+ * buffer full conditions, and 4) by the VBUF_SPACE() operation that allows
+ * the user to reserve buffer space ahead of time, to allow for situations
+ * where calling VBUF_PUT() is not possible or desirable.
+ *
+ * The VBUF buffer does not specify primitives for memory allocation or
+ * deallocation. The purpose is to allow different applications to have
+ * different strategies: a memory-resident buffer; a memory-mapped file; or
+ * a stdio-like window to an open file. Each application provides its own
+ * get(), put() and space() methods that perform the necessary magic.
+ *
+ * This interface is pretty normal. With one exception: the number of bytes
+ * left to read is negated. This is done so that we can change direction
+ * between reading and writing on the fly. The alternative would be to use
+ * separate read and write counters per buffer.
+ */
+typedef struct VBUF VBUF;
+typedef int (*VBUF_GET_READY_FN) (VBUF *);
+typedef int (*VBUF_PUT_READY_FN) (VBUF *);
+typedef int (*VBUF_SPACE_FN) (VBUF *, ssize_t);
+
+struct VBUF {
+ int flags; /* status, see below */
+ unsigned char *data; /* variable-length buffer */
+ ssize_t len; /* buffer length */
+ ssize_t cnt; /* bytes left to read/write */
+ unsigned char *ptr; /* read/write position */
+ VBUF_GET_READY_FN get_ready; /* read buffer empty action */
+ VBUF_PUT_READY_FN put_ready; /* write buffer full action */
+ VBUF_SPACE_FN space; /* request for buffer space */
+};
+
+ /*
+ * Typically, an application will embed a VBUF structure into a larger
+ * structure that also contains application-specific members. This approach
+ * gives us the best of both worlds. The application can still use the
+ * generic VBUF primitives for reading and writing VBUFs. The macro below
+ * transforms a pointer from VBUF structure to the structure that contains
+ * it.
+ */
+#define VBUF_TO_APPL(vbuf_ptr,app_type,vbuf_member) \
+ ((app_type *) (((char *) (vbuf_ptr)) - offsetof(app_type,vbuf_member)))
+
+ /*
+ * Buffer status management.
+ */
+#define VBUF_FLAG_RD_ERR (1<<0) /* read error */
+#define VBUF_FLAG_WR_ERR (1<<1) /* write error */
+#define VBUF_FLAG_ERR (VBUF_FLAG_RD_ERR | VBUF_FLAG_WR_ERR)
+#define VBUF_FLAG_EOF (1<<2) /* end of data */
+#define VBUF_FLAG_RD_TIMEOUT (1<<3) /* read timeout */
+#define VBUF_FLAG_WR_TIMEOUT (1<<4) /* write timeout */
+#define VBUF_FLAG_TIMEOUT (VBUF_FLAG_RD_TIMEOUT | VBUF_FLAG_WR_TIMEOUT)
+#define VBUF_FLAG_BAD (VBUF_FLAG_ERR | VBUF_FLAG_EOF | VBUF_FLAG_TIMEOUT)
+#define VBUF_FLAG_FIXED (1<<5) /* fixed-size buffer */
+
+#define vbuf_rd_error(v) ((v)->flags & (VBUF_FLAG_RD_ERR | VBUF_FLAG_RD_TIMEOUT))
+#define vbuf_wr_error(v) ((v)->flags & (VBUF_FLAG_WR_ERR | VBUF_FLAG_WR_TIMEOUT))
+#define vbuf_rd_timeout(v) ((v)->flags & VBUF_FLAG_RD_TIMEOUT)
+#define vbuf_wr_timeout(v) ((v)->flags & VBUF_FLAG_WR_TIMEOUT)
+
+#define vbuf_error(v) ((v)->flags & (VBUF_FLAG_ERR | VBUF_FLAG_TIMEOUT))
+#define vbuf_eof(v) ((v)->flags & VBUF_FLAG_EOF)
+#define vbuf_timeout(v) ((v)->flags & VBUF_FLAG_TIMEOUT)
+#define vbuf_clearerr(v) ((v)->flags &= ~VBUF_FLAG_BAD)
+
+ /*
+ * Buffer I/O-like operations and results.
+ */
+#define VBUF_GET(v) ((v)->cnt < 0 ? ++(v)->cnt, \
+ (int) *(v)->ptr++ : vbuf_get(v))
+#define VBUF_PUT(v,c) ((v)->cnt > 0 ? --(v)->cnt, \
+ (int) (*(v)->ptr++ = (c)) : vbuf_put((v),(c)))
+#define VBUF_SPACE(v,n) ((v)->space((v),(n)))
+
+#define VBUF_EOF (-1) /* no more space or data */
+
+extern int vbuf_get(VBUF *);
+extern int vbuf_put(VBUF *, int);
+extern int vbuf_unget(VBUF *, int);
+extern ssize_t vbuf_read(VBUF *, void *, ssize_t);
+extern ssize_t vbuf_write(VBUF *, const void *, ssize_t);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/vbuf_print.c b/src/util/vbuf_print.c
new file mode 100644
index 0000000..d7a323f
--- /dev/null
+++ b/src/util/vbuf_print.c
@@ -0,0 +1,385 @@
+/*++
+/* NAME
+/* vbuf_print 3
+/* SUMMARY
+/* formatted print to generic buffer
+/* SYNOPSIS
+/* #include <stdarg.h>
+/* #include <vbuf_print.h>
+/*
+/* VBUF *vbuf_print(bp, format, ap)
+/* VBUF *bp;
+/* const char *format;
+/* va_list ap;
+/* DESCRIPTION
+/* vbuf_print() appends data to the named buffer according to its
+/* \fIformat\fR argument. It understands the s, c, d, u, o, x, X, p, e,
+/* f and g format types, the l modifier, field width and precision,
+/* sign, and padding with zeros or spaces.
+/*
+/* In addition, vbuf_print() recognizes the %m format specifier
+/* and expands it to the error message corresponding to the current
+/* value of the global \fIerrno\fR variable.
+/* REENTRANCY
+/* .ad
+/* .fi
+/* vbuf_print() allocates a static buffer. After completion
+/* of the first vbuf_print() call, this buffer is safe for
+/* reentrant vbuf_print() calls by (asynchronous) terminating
+/* signal handlers or by (synchronous) terminating error
+/* handlers. vbuf_print() initialization typically happens
+/* upon the first formatted output to a VSTRING or VSTREAM.
+/*
+/* However, it is up to the caller to ensure that the destination
+/* VSTREAM or VSTRING buffer is protected against reentrant usage.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h> /* 44bsd stdarg.h uses abort() */
+#include <stdio.h> /* sprintf() prototype */
+#include <float.h> /* range of doubles */
+#include <errno.h>
+#include <limits.h> /* CHAR_BIT, INT_MAX */
+
+/* Application-specific. */
+
+#include "msg.h"
+#include "mymalloc.h"
+#include "vbuf.h"
+#include "vstring.h"
+#include "vbuf_print.h"
+
+ /*
+ * What we need here is a *sprintf() routine that can ask for more room (as
+ * in 4.4 BSD). However, that functionality is not widely available, and I
+ * have no plans to maintain a complete 4.4 BSD *sprintf() alternative.
+ *
+ * Postfix vbuf_print() was implemented when many mainstream systems had no
+ * usable snprintf() implementation (usable means: return the length,
+ * excluding terminator, that the output would have if the buffer were large
+ * enough). For example, GLIBC before 2.1 (1999) snprintf() did not
+ * distinguish between formatting error and buffer size error, while SUN had
+ * no snprintf() implementation before Solaris 2.6 (1997).
+ *
+ * For the above reasons, vbuf_print() was implemented with sprintf() and a
+ * generously-sized output buffer. Current vbuf_print() implementations use
+ * snprintf(), and report an error if the output does not fit (in that case,
+ * the old sprintf()-based implementation would have had a buffer overflow
+ * vulnerability). The old implementation is still available for building
+ * Postfix on ancient systems.
+ *
+ * Guessing the output size of a string (%s) conversion is not hard. The
+ * problem is with numerical results. Instead of making an accurate guess we
+ * take a wide margin when reserving space. The INT_SPACE margin should be
+ * large enough to hold the result from any (octal, hex, decimal) integer
+ * conversion that has no explicit width or precision specifiers. With
+ * floating-point numbers, use a similar estimate, and add DBL_MAX_10_EXP
+ * just to be sure.
+ */
+#define INT_SPACE ((CHAR_BIT * sizeof(long)) / 2)
+#define DBL_SPACE ((CHAR_BIT * sizeof(double)) / 2 + DBL_MAX_10_EXP)
+#define PTR_SPACE ((CHAR_BIT * sizeof(char *)) / 2)
+
+ /*
+ * Helper macros... Note that there is no need to check the result from
+ * VSTRING_SPACE() because that always succeeds or never returns.
+ */
+#ifndef NO_SNPRINTF
+#define VBUF_SNPRINTF(bp, sz, fmt, arg) do { \
+ ssize_t _ret; \
+ if (VBUF_SPACE((bp), (sz)) != 0) \
+ return (bp); \
+ _ret = snprintf((char *) (bp)->ptr, (bp)->cnt, (fmt), (arg)); \
+ if (_ret < 0) \
+ msg_panic("%s: output error for '%s'", myname, mystrdup(fmt)); \
+ if (_ret >= (bp)->cnt) \
+ msg_panic("%s: output for '%s' exceeds space %ld", \
+ myname, mystrdup(fmt), (long) (bp)->cnt); \
+ VBUF_SKIP(bp); \
+ } while (0)
+#else
+#define VBUF_SNPRINTF(bp, sz, fmt, arg) do { \
+ if (VBUF_SPACE((bp), (sz)) != 0) \
+ return (bp); \
+ sprintf((char *) (bp)->ptr, (fmt), (arg)); \
+ VBUF_SKIP(bp); \
+ } while (0)
+#endif
+
+#define VBUF_SKIP(bp) do { \
+ while ((bp)->cnt > 0 && *(bp)->ptr) \
+ (bp)->ptr++, (bp)->cnt--; \
+ } while (0)
+
+#define VSTRING_ADDNUM(vp, n) do { \
+ VBUF_SNPRINTF(&(vp)->vbuf, INT_SPACE, "%d", n); \
+ } while (0)
+
+#define VBUF_STRCAT(bp, s) do { \
+ unsigned char *_cp = (unsigned char *) (s); \
+ int _ch; \
+ while ((_ch = *_cp++) != 0) \
+ VBUF_PUT((bp), _ch); \
+ } while (0)
+
+/* vbuf_print - format string, vsprintf-like interface */
+
+VBUF *vbuf_print(VBUF *bp, const char *format, va_list ap)
+{
+ const char *myname = "vbuf_print";
+ static VSTRING *fmt; /* format specifier */
+ unsigned char *cp;
+ int width; /* width and numerical precision */
+ int prec; /* are signed for overflow defense */
+ unsigned long_flag; /* long or plain integer */
+ int ch;
+ char *s;
+ int saved_errno = errno; /* VBUF_SPACE() may clobber it */
+
+ /*
+ * Assume that format strings are short.
+ */
+ if (fmt == 0)
+ fmt = vstring_alloc(INT_SPACE);
+
+ /*
+ * Iterate over characters in the format string, picking up arguments
+ * when format specifiers are found.
+ */
+ for (cp = (unsigned char *) format; *cp; cp++) {
+ if (*cp != '%') {
+ VBUF_PUT(bp, *cp); /* ordinary character */
+ } else if (cp[1] == '%') {
+ VBUF_PUT(bp, *cp++); /* %% becomes % */
+ } else {
+
+ /*
+ * Handle format specifiers one at a time, since we can only deal
+ * with arguments one at a time. Try to determine the end of the
+ * format specifier. We do not attempt to fully parse format
+ * strings, since we are ging to let sprintf() do the hard work.
+ * In regular expression notation, we recognize:
+ *
+ * %-?+?0?([0-9]+|\*)?(\.([0-9]+|\*))?l?[a-zA-Z]
+ *
+ * which includes some combinations that do not make sense. Garbage
+ * in, garbage out.
+ */
+ VSTRING_RESET(fmt); /* clear format string */
+ VSTRING_ADDCH(fmt, *cp++);
+ if (*cp == '-') /* left-adjusted field? */
+ VSTRING_ADDCH(fmt, *cp++);
+ if (*cp == '+') /* signed field? */
+ VSTRING_ADDCH(fmt, *cp++);
+ if (*cp == '0') /* zero-padded field? */
+ VSTRING_ADDCH(fmt, *cp++);
+ if (*cp == '*') { /* dynamic field width */
+ width = va_arg(ap, int);
+ if (width < 0) {
+ msg_warn("%s: bad width %d in %.50s",
+ myname, width, format);
+ width = 0;
+ } else
+ VSTRING_ADDNUM(fmt, width);
+ cp++;
+ } else { /* hard-coded field width */
+ for (width = 0; ch = *cp, ISDIGIT(ch); cp++) {
+ int digit = ch - '0';
+
+ if (width > INT_MAX / 10
+ || (width *= 10) > INT_MAX - digit)
+ msg_panic("%s: bad width %d... in %.50s",
+ myname, width, format);
+ width += digit;
+ VSTRING_ADDCH(fmt, ch);
+ }
+ }
+ if (*cp == '.') { /* width/precision separator */
+ VSTRING_ADDCH(fmt, *cp++);
+ if (*cp == '*') { /* dynamic precision */
+ prec = va_arg(ap, int);
+ if (prec < 0) {
+ msg_warn("%s: bad precision %d in %.50s",
+ myname, prec, format);
+ prec = -1;
+ } else
+ VSTRING_ADDNUM(fmt, prec);
+ cp++;
+ } else { /* hard-coded precision */
+ for (prec = 0; ch = *cp, ISDIGIT(ch); cp++) {
+ int digit = ch - '0';
+
+ if (prec > INT_MAX / 10
+ || (prec *= 10) > INT_MAX - digit)
+ msg_panic("%s: bad precision %d... in %.50s",
+ myname, prec, format);
+ prec += digit;
+ VSTRING_ADDCH(fmt, ch);
+ }
+ }
+ } else {
+ prec = -1;
+ }
+ if ((long_flag = (*cp == 'l')) != 0)/* long whatever */
+ VSTRING_ADDCH(fmt, *cp++);
+ if (*cp == 0) /* premature end, punt */
+ break;
+ VSTRING_ADDCH(fmt, *cp); /* type (checked below) */
+ VSTRING_TERMINATE(fmt); /* null terminate */
+
+ /*
+ * Execute the format string - let sprintf() do the hard work for
+ * non-trivial cases only. For simple string conversions and for
+ * long string conversions, do a direct copy to the output
+ * buffer.
+ */
+ switch (*cp) {
+ case 's': /* string-valued argument */
+ if (long_flag)
+ msg_panic("%s: %%l%c is not supported", myname, *cp);
+ s = va_arg(ap, char *);
+ if (prec >= 0 || (width > 0 && width > strlen(s))) {
+ VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE,
+ vstring_str(fmt), s);
+ } else {
+ VBUF_STRCAT(bp, s);
+ }
+ break;
+ case 'c': /* integral-valued argument */
+ if (long_flag)
+ msg_panic("%s: %%l%c is not supported", myname, *cp);
+ /* FALLTHROUGH */
+ case 'd':
+ case 'u':
+ case 'o':
+ case 'x':
+ case 'X':
+ if (long_flag)
+ VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE,
+ vstring_str(fmt), va_arg(ap, long));
+ else
+ VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE,
+ vstring_str(fmt), va_arg(ap, int));
+ break;
+ case 'e': /* float-valued argument */
+ case 'f':
+ case 'g':
+ /* C99 *printf ignore the 'l' modifier. */
+ VBUF_SNPRINTF(bp, (width > prec ? width : prec) + DBL_SPACE,
+ vstring_str(fmt), va_arg(ap, double));
+ break;
+ case 'm':
+ /* Ignore the 'l' modifier, width and precision. */
+ VBUF_STRCAT(bp, saved_errno ?
+ strerror(saved_errno) : "Application error");
+ break;
+ case 'p':
+ if (long_flag)
+ msg_panic("%s: %%l%c is not supported", myname, *cp);
+ VBUF_SNPRINTF(bp, (width > prec ? width : prec) + PTR_SPACE,
+ vstring_str(fmt), va_arg(ap, char *));
+ break;
+ default: /* anything else is bad */
+ msg_panic("vbuf_print: unknown format type: %c", *cp);
+ /* NOTREACHED */
+ break;
+ }
+ }
+ }
+ return (bp);
+}
+
+#ifdef TEST
+#include <argv.h>
+#include <msg_vstream.h>
+#include <vstring.h>
+#include <vstring_vstream.h>
+
+int main(int argc, char **argv)
+{
+ VSTRING *ibuf = vstring_alloc(100);
+
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ while (vstring_fgets_nonl(ibuf, VSTREAM_IN)) {
+ ARGV *args = argv_split(vstring_str(ibuf), CHARS_SPACE);
+ char *cp;
+
+ if (args->argc == 0 || *(cp = args->argv[0]) == '#') {
+ /* void */ ;
+ } else if (args->argc != 2 || *cp != '%') {
+ msg_warn("usage: format number");
+ } else {
+ char *fmt = cp++;
+ int lflag;
+
+ /* Determine the vstring_sprintf() argument type. */
+ cp += strspn(cp, "+-*0123456789.");
+ if ((lflag = (*cp == 'l')) != 0)
+ cp++;
+ if (cp[1] != 0) {
+ msg_warn("bad format: \"%s\"", fmt);
+ } else {
+ VSTRING *obuf = vstring_alloc(1);
+ char *val = args->argv[1];
+
+ /* Test the worst-case memory allocation. */
+#ifdef CA_VSTRING_CTL_EXACT
+ vstring_ctl(obuf, CA_VSTRING_CTL_EXACT, CA_VSTRING_CTL_END);
+#endif
+ switch (*cp) {
+ case 'c':
+ case 'd':
+ case 'o':
+ case 'u':
+ case 'x':
+ case 'X':
+ if (lflag)
+ vstring_sprintf(obuf, fmt, atol(val));
+ else
+ vstring_sprintf(obuf, fmt, atoi(val));
+ msg_info("\"%s\"", vstring_str(obuf));
+ break;
+ case 's':
+ vstring_sprintf(obuf, fmt, val);
+ msg_info("\"%s\"", vstring_str(obuf));
+ break;
+ case 'f':
+ case 'g':
+ vstring_sprintf(obuf, fmt, atof(val));
+ msg_info("\"%s\"", vstring_str(obuf));
+ break;
+ default:
+ msg_warn("bad format: \"%s\"", fmt);
+ break;
+ }
+ vstring_free(obuf);
+ }
+ }
+ argv_free(args);
+ }
+ vstring_free(ibuf);
+ return (0);
+}
+
+#endif
diff --git a/src/util/vbuf_print.h b/src/util/vbuf_print.h
new file mode 100644
index 0000000..32549c1
--- /dev/null
+++ b/src/util/vbuf_print.h
@@ -0,0 +1,40 @@
+#ifndef _VBUF_PRINT_H_INCLUDED_
+#define _VBUF_PRINT_H_INCLUDED_
+
+/*++
+/* NAME
+/* vbuf_print 3h
+/* SUMMARY
+/* formatted print to generic buffer
+/* SYNOPSIS
+/* #include <vbuf_print.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <stdarg.h>
+
+ /*
+ * Utility library.
+ */
+#include <vbuf.h>
+
+ /*
+ * External interface.
+ */
+extern VBUF *vbuf_print(VBUF *, const char *, va_list);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/vbuf_print_test.in b/src/util/vbuf_print_test.in
new file mode 100644
index 0000000..5ed13dc
--- /dev/null
+++ b/src/util/vbuf_print_test.in
@@ -0,0 +1,33 @@
+# Check width and precision.
+%-30.20s 123456789012345678901234567890
+%30.20s 123456789012345678901234567890
+
+%d 123456789
+%+10d 123456789
+%-10d 123456789
+%10d 123456789
+%10.10d 123456789
+
+%+ld 123456789
+%-ld 123456789
+%ld 123456789
+%10ld 123456789
+%10.10ld 123456789
+
+%+lo 123456789
+%-lo 123456789
+%lo 123456789
+%10lo 123456789
+%10.10lo 123456789
+
+%f 1e308
+%.100f 1e308
+%g 1e308
+%.309g 1e308
+
+%s foo
+%0s foo
+%.0s foo
+%10s foo
+%+10s foo
+%-10s foo
diff --git a/src/util/vbuf_print_test.ref b/src/util/vbuf_print_test.ref
new file mode 100644
index 0000000..346c919
--- /dev/null
+++ b/src/util/vbuf_print_test.ref
@@ -0,0 +1,27 @@
+./vbuf_print: "12345678901234567890 "
+./vbuf_print: " 12345678901234567890"
+./vbuf_print: "123456789"
+./vbuf_print: "+123456789"
+./vbuf_print: "123456789 "
+./vbuf_print: " 123456789"
+./vbuf_print: "0123456789"
+./vbuf_print: "+123456789"
+./vbuf_print: "123456789"
+./vbuf_print: "123456789"
+./vbuf_print: " 123456789"
+./vbuf_print: "0123456789"
+./vbuf_print: "726746425"
+./vbuf_print: "726746425"
+./vbuf_print: "726746425"
+./vbuf_print: " 726746425"
+./vbuf_print: "0726746425"
+./vbuf_print: "100000000000000001097906362944045541740492309677311846336810682903157585404911491537163328978494688899061249669721172515611590283743140088328307009198146046031271664502933027185697489699588559043338384466165001178426897626212945177628091195786707458122783970171784415105291802893207873272974885715430223118336.000000"
+./vbuf_print: "100000000000000001097906362944045541740492309677311846336810682903157585404911491537163328978494688899061249669721172515611590283743140088328307009198146046031271664502933027185697489699588559043338384466165001178426897626212945177628091195786707458122783970171784415105291802893207873272974885715430223118336.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+./vbuf_print: "1e+308"
+./vbuf_print: "100000000000000001097906362944045541740492309677311846336810682903157585404911491537163328978494688899061249669721172515611590283743140088328307009198146046031271664502933027185697489699588559043338384466165001178426897626212945177628091195786707458122783970171784415105291802893207873272974885715430223118336"
+./vbuf_print: "foo"
+./vbuf_print: "foo"
+./vbuf_print: ""
+./vbuf_print: " foo"
+./vbuf_print: " foo"
+./vbuf_print: "foo "
diff --git a/src/util/vstream.c b/src/util/vstream.c
new file mode 100644
index 0000000..b4f9fbb
--- /dev/null
+++ b/src/util/vstream.c
@@ -0,0 +1,2046 @@
+/*++
+/* NAME
+/* vstream 3
+/* SUMMARY
+/* light-weight buffered I/O package
+/* SYNOPSIS
+/* #include <vstream.h>
+/*
+/* VSTREAM *vstream_fopen(path, flags, mode)
+/* const char *path;
+/* int flags;
+/* mode_t mode;
+/*
+/* VSTREAM *vstream_fdopen(fd, flags)
+/* int fd;
+/* int flags;
+/*
+/* VSTREAM *vstream_memopen(string, flags)
+/* VSTRING *string;
+/* int flags;
+/*
+/* VSTREAM *vstream_memreopen(stream, string, flags)
+/* VSTREAM *stream;
+/* VSTRING *string;
+/* int flags;
+/*
+/* int vstream_fclose(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_fdclose(stream)
+/* VSTREAM *stream;
+/*
+/* VSTREAM *vstream_printf(format, ...)
+/* const char *format;
+/*
+/* VSTREAM *vstream_fprintf(stream, format, ...)
+/* VSTREAM *stream;
+/* const char *format;
+/*
+/* int VSTREAM_GETC(stream)
+/* VSTREAM *stream;
+/*
+/* int VSTREAM_PUTC(ch, stream)
+/* int ch;
+/*
+/* int VSTREAM_GETCHAR(void)
+/*
+/* int VSTREAM_PUTCHAR(ch)
+/* int ch;
+/*
+/* int vstream_ungetc(stream, ch)
+/* VSTREAM *stream;
+/* int ch;
+/*
+/* int vstream_fputs(str, stream)
+/* const char *str;
+/* VSTREAM *stream;
+/*
+/* off_t vstream_ftell(stream)
+/* VSTREAM *stream;
+/*
+/* off_t vstream_fseek(stream, offset, whence)
+/* VSTREAM *stream;
+/* off_t offset;
+/* int whence;
+/*
+/* int vstream_fflush(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_fpurge(stream, direction)
+/* VSTREAM *stream;
+/* int direction;
+/*
+/* ssize_t vstream_fread(stream, buf, len)
+/* VSTREAM *stream;
+/* void *buf;
+/* ssize_t len;
+/*
+/* ssize_t vstream_fwrite(stream, buf, len)
+/* VSTREAM *stream;
+/* void *buf;
+/* ssize_t len;
+/*
+/* ssize_t vstream_fread_app(stream, buf, len)
+/* VSTREAM *stream;
+/* VSTRING *buf;
+/* ssize_t len;
+/*
+/* ssize_t vstream_fread_buf(stream, buf, len)
+/* VSTREAM *stream;
+/* VSTRING *buf;
+/* ssize_t len;
+/*
+/* void vstream_control(stream, name, ...)
+/* VSTREAM *stream;
+/* int name;
+/*
+/* int vstream_fileno(stream)
+/* VSTREAM *stream;
+/*
+/* const ssize_t vstream_req_bufsize(stream)
+/* VSTREAM *stream;
+/*
+/* void *vstream_context(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_ferror(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_ftimeout(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_feof(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_clearerr(stream)
+/* VSTREAM *stream;
+/*
+/* const char *VSTREAM_PATH(stream)
+/* VSTREAM *stream;
+/*
+/* char *vstream_vprintf(format, ap)
+/* const char *format;
+/* va_list *ap;
+/*
+/* char *vstream_vfprintf(stream, format, ap)
+/* VSTREAM *stream;
+/* const char *format;
+/* va_list *ap;
+/*
+/* ssize_t vstream_bufstat(stream, command)
+/* VSTREAM *stream;
+/* int command;
+/*
+/* ssize_t vstream_peek(stream)
+/* VSTREAM *stream;
+/*
+/* const char *vstream_peek_data(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_setjmp(stream)
+/* VSTREAM *stream;
+/*
+/* void vstream_longjmp(stream, val)
+/* VSTREAM *stream;
+/* int val;
+/*
+/* time_t vstream_ftime(stream)
+/* VSTREAM *stream;
+/*
+/* struct timeval vstream_ftimeval(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_rd_error(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_wr_error(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_rd_timeout(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_wr_timeout(stream)
+/* VSTREAM *stream;
+/*
+/* int vstream_fstat(stream, flags)
+/* VSTREAM *stream;
+/* int flags;
+/* DESCRIPTION
+/* The \fIvstream\fR module implements light-weight buffered I/O
+/* similar to the standard I/O routines.
+/*
+/* The interface is implemented in terms of VSTREAM structure
+/* pointers, also called streams. For convenience, three streams
+/* are predefined: VSTREAM_IN, VSTREAM_OUT, and VSTREAM_ERR. These
+/* streams are connected to the standard input, output and error
+/* file descriptors, respectively.
+/*
+/* Although the interface is patterned after the standard I/O
+/* library, there are some major differences:
+/* .IP \(bu
+/* File descriptors are not limited to the range 0..255. This
+/* was reason #1 to write these routines in the first place.
+/* .IP \(bu
+/* The application can switch between reading and writing on
+/* the same stream without having to perform a flush or seek
+/* operation, and can change write position without having to
+/* flush. This was reason #2. Upon position or direction change,
+/* unread input is discarded, and unwritten output is flushed
+/* automatically. Exception: with double-buffered streams, unread
+/* input is not discarded upon change of I/O direction, and
+/* output flushing is delayed until the read buffer must be refilled.
+/* .IP \(bu
+/* A bidirectional stream can read and write with the same buffer
+/* and file descriptor, or it can have separate read/write
+/* buffers and/or file descriptors.
+/* .IP \(bu
+/* No automatic flushing of VSTREAM_OUT upon program exit, or of
+/* VSTREAM_ERR at any time. No unbuffered or line buffered modes.
+/* This functionality may be added when it is really needed.
+/* .PP
+/* vstream_fopen() opens the named file and associates a buffered
+/* stream with it. The \fIpath\fR, \fIflags\fR and \fImode\fR
+/* arguments are passed on to the open(2) routine. The result is
+/* a null pointer in case of problems. The \fIpath\fR argument is
+/* copied and can be looked up with VSTREAM_PATH().
+/*
+/* vstream_fdopen() takes an open file and associates a buffered
+/* stream with it. The \fIflags\fR argument specifies how the file
+/* was opened. vstream_fdopen() either succeeds or never returns.
+/*
+/* vstream_memopen() opens a VSTRING as a stream. The \fIflags\fR
+/* argument must specify one of O_RDONLY, O_WRONLY, or O_APPEND.
+/* vstream_memopen() either succeeds or never returns. Streams
+/* opened with vstream_memopen() have limitations: they can't
+/* be opened in read/write mode, they can't seek beyond the
+/* end of the VSTRING, and they don't support vstream_control()
+/* methods that manipulate buffers, file descriptors, or I/O
+/* functions. After a VSTRING is opened for writing, its content
+/* will be in an indeterminate state while the stream is open,
+/* and will be null-terminated when the stream is closed.
+/*
+/* vstream_memreopen() reopens a memory stream. When the
+/* \fIstream\fR argument is a null pointer, the behavior is that
+/* of vstream_memopen().
+/*
+/* vstream_fclose() closes the named buffered stream. The result
+/* is 0 in case of success, VSTREAM_EOF in case of problems.
+/* vstream_fclose() reports the same errors as vstream_ferror().
+/*
+/* vstream_fdclose() leaves the file(s) open but is otherwise
+/* identical to vstream_fclose().
+/*
+/* vstream_fprintf() formats its arguments according to the
+/* \fIformat\fR argument and writes the result to the named stream.
+/* The result is the stream argument. It understands the s, c, d, u,
+/* o, x, X, e, f and g format types, the l modifier, field width and
+/* precision, sign, and padding with zeros or spaces. In addition,
+/* vstream_fprintf() recognizes the %m format specifier and expands
+/* it to the error message corresponding to the current value of the
+/* global \fIerrno\fR variable.
+/*
+/* vstream_printf() performs formatted output to the standard output
+/* stream.
+/*
+/* VSTREAM_GETC() reads the next character from the named stream.
+/* The result is VSTREAM_EOF when end-of-file is reached or if a read
+/* error was detected. VSTREAM_GETC() is an unsafe macro that
+/* evaluates some arguments more than once.
+/*
+/* VSTREAM_GETCHAR() is an alias for VSTREAM_GETC(VSTREAM_IN).
+/*
+/* VSTREAM_PUTC() appends the specified character to the specified
+/* stream. The result is the stored character, or VSTREAM_EOF in
+/* case of problems. VSTREAM_PUTC() is an unsafe macro that
+/* evaluates some arguments more than once.
+/*
+/* VSTREAM_PUTCHAR(c) is an alias for VSTREAM_PUTC(c, VSTREAM_OUT).
+/*
+/* vstream_ungetc() pushes back a character onto the specified stream
+/* and returns the character, or VSTREAM_EOF in case of problems.
+/* It is an error to push back before reading (or immediately after
+/* changing the stream offset via vstream_fseek()). Upon successful
+/* return, vstream_ungetc() clears the end-of-file stream flag.
+/*
+/* vstream_fputs() appends the given null-terminated string to the
+/* specified buffered stream. The result is 0 in case of success,
+/* VSTREAM_EOF in case of problems.
+/*
+/* vstream_ftell() returns the file offset for the specified stream,
+/* -1 if the stream is connected to a non-seekable file.
+/*
+/* vstream_fseek() changes the file position for the next read or write
+/* operation. Unwritten output is flushed. With unidirectional streams,
+/* unread input is discarded. The \fIoffset\fR argument specifies the file
+/* position from the beginning of the file (\fIwhence\fR is SEEK_SET),
+/* from the current file position (\fIwhence\fR is SEEK_CUR), or from
+/* the file end (SEEK_END). The result value is the file offset
+/* from the beginning of the file, -1 in case of problems.
+/*
+/* vstream_fflush() flushes unwritten data to a file that was
+/* opened in read-write or write-only mode.
+/* vstream_fflush() returns 0 in case of success, VSTREAM_EOF in
+/* case of problems. It is an error to flush a read-only stream.
+/* vstream_fflush() reports the same errors as vstream_ferror().
+/*
+/* vstream_fpurge() discards the contents of the stream buffer.
+/* If direction is VSTREAM_PURGE_READ, it discards unread data,
+/* else if direction is VSTREAM_PURGE_WRITE, it discards unwritten
+/* data. In the case of a double-buffered stream, if direction is
+/* VSTREAM_PURGE_BOTH, it discards the content of both the read
+/* and write buffers. vstream_fpurge() returns 0 in case of success,
+/* VSTREAM_EOF in case of problems.
+/*
+/* vstream_fread() and vstream_fwrite() perform unformatted I/O
+/* on the named stream. The result value is the number of bytes
+/* transferred. A short count is returned in case of end-of-file
+/* or error conditions.
+/*
+/* vstream_fread_buf() resets the buffer write position,
+/* allocates space for the specified number of bytes in the
+/* buffer, reads the bytes from the specified VSTREAM, and
+/* adjusts the buffer write position. The buffer is NOT
+/* null-terminated. The result value is as with vstream_fread().
+/* NOTE: do not skip calling vstream_fread_buf() when len == 0.
+/* This function has side effects including resetting the buffer
+/* write position, and skipping the call would invalidate the
+/* buffer state.
+/*
+/* vstream_fread_app() is like vstream_fread_buf() but appends
+/* to existing buffer content, instead of writing over it.
+/*
+/* vstream_control() allows the user to fine tune the behavior of
+/* the specified stream. The arguments are a list of macros with
+/* zero or more arguments, terminated with CA_VSTREAM_CTL_END
+/* which has none. The following lists the names and the types
+/* of the corresponding value arguments.
+/* .IP "CA_VSTREAM_CTL_READ_FN(ssize_t (*)(int, void *, size_t, int, void *))"
+/* The argument specifies an alternative for the timed_read(3) function,
+/* for example, a read function that performs decryption.
+/* This function receives as arguments a file descriptor, buffer pointer,
+/* buffer length, timeout value, and the VSTREAM's context value.
+/* A timeout value <= 0 disables the time limit.
+/* This function should return the positive number of bytes transferred,
+/* 0 upon EOF, and -1 upon error with errno set appropriately.
+/* .IP "CA_VSTREAM_CTL_WRITE_FN(ssize_t (*)(int, void *, size_t, int, void *))"
+/* The argument specifies an alternative for the timed_write(3) function,
+/* for example, a write function that performs encryption.
+/* This function receives as arguments a file descriptor, buffer pointer,
+/* buffer length, timeout value, and the VSTREAM's context value.
+/* A timeout value <= 0 disables the time limit.
+/* This function should return the positive number of bytes transferred,
+/* and -1 upon error with errno set appropriately. Instead of -1 it may
+/* also return 0, e.g., upon remote party-initiated protocol shutdown.
+/* .IP "CA_VSTREAM_CTL_CONTEXT(void *)"
+/* The argument specifies application context that is passed on to
+/* the application-specified read/write routines. No copy is made.
+/* .IP "CA_VSTREAM_CTL_PATH(const char *)"
+/* Updates the stored pathname of the specified stream. The pathname
+/* is copied.
+/* .IP "CA_VSTREAM_CTL_DOUBLE (no arguments)"
+/* Use separate buffers for reading and for writing. This prevents
+/* unread input from being discarded upon change of I/O direction.
+/* .IP "CA_VSTREAM_CTL_READ_FD(int)"
+/* The argument specifies the file descriptor to be used for reading.
+/* This feature is limited to double-buffered streams, and makes the
+/* stream non-seekable.
+/* .IP "CA_VSTREAM_CTL_WRITE_FD(int)"
+/* The argument specifies the file descriptor to be used for writing.
+/* This feature is limited to double-buffered streams, and makes the
+/* stream non-seekable.
+/* .IP "CA_VSTREAM_CTL_SWAP_FD(VSTREAM *)"
+/* The argument specifies a VSTREAM pointer; the request swaps the
+/* file descriptor members of the two streams. This feature is limited
+/* to streams that are both double-buffered or both single-buffered.
+/* .IP "CA_VSTREAM_CTL_DUPFD(int)"
+/* The argument specifies a minimum file descriptor value. If
+/* the actual stream's file descriptors are below the minimum,
+/* reallocate the descriptors to the first free value greater
+/* than or equal to the minimum. The VSTREAM_CTL_DUPFD macro
+/* is defined only on systems with fcntl() F_DUPFD support.
+/* .IP "CA_VSTREAM_CTL_WAITPID_FN(int (*)(pid_t, WAIT_STATUS_T *, int))"
+/* A pointer to function that behaves like waitpid(). This information
+/* is used by the vstream_pclose() routine.
+/* .IP "CA_VSTREAM_CTL_TIMEOUT(int)"
+/* The deadline for a descriptor to become readable in case of a read
+/* request, or writable in case of a write request. Specify a value
+/* of 0 to disable deadlines.
+/* .IP "CA_VSTREAM_CTL_EXCEPT (no arguments)"
+/* Enable exception handling with vstream_setjmp() and vstream_longjmp().
+/* This involves allocation of additional memory that normally isn't
+/* used.
+/* .IP "CA_VSTREAM_CTL_BUFSIZE(ssize_t)"
+/* Specify a non-default buffer size for the next read(2) or
+/* write(2) operation, or zero to implement a no-op. Requests
+/* to reduce the buffer size are silently ignored (i.e. any
+/* positive value <= vstream_req_bufsize()). To get a buffer
+/* size smaller than VSTREAM_BUFSIZE, make the VSTREAM_CTL_BUFSIZE
+/* request before the first stream read or write operation
+/* (i.e., vstream_req_bufsize() returns zero). Requests to
+/* change a fixed-size buffer (i.e., VSTREAM_ERR) are not
+/* allowed.
+/*
+/* NOTE: the vstream_*printf() routines may silently expand a
+/* buffer, so that the result of some %letter specifiers can
+/* be written to contiguous memory.
+/* .IP CA_VSTREAM_CTL_START_DEADLINE (no arguments)
+/* Change the VSTREAM_CTL_TIMEOUT behavior, to a deadline for
+/* the total amount of time for all subsequent file descriptor
+/* read or write operations, and recharge the deadline timer.
+/* .IP CA_VSTREAM_CTL_STOP_DEADLINE (no arguments)
+/* Revert VSTREAM_CTL_TIMEOUT behavior to the default, i.e.
+/* a time limit for individual file descriptor read or write
+/* operations.
+/* .IP CA_VSTREAM_CTL_MIN_DATA_RATE (int)
+/* When the DEADLINE is enabled, the amount of data that must
+/* be transferred to add 1 second to the deadline. However,
+/* the deadline will never exceed the timeout specified with
+/* VSTREAM_CTL_TIMEOUT. A zero value requests no update to the
+/* deadline as data is transferred; that is appropriate for
+/* request/reply interactions.
+/* .IP CA_VSTREAM_CTL_OWN_VSTRING (no arguments)
+/* Transfer ownership of the VSTRING that was opened with
+/* vstream_memopen() etc. to the stream, so that the VSTRING
+/* is automatically destroyed when the stream is closed.
+/* .PP
+/* vstream_fileno() gives access to the file handle associated with
+/* a buffered stream. With streams that have separate read/write
+/* file descriptors, the result is the current descriptor.
+/*
+/* vstream_req_bufsize() returns the buffer size that will be
+/* used for the next read(2) or write(2) operation on the named
+/* stream. A zero result means that the next read(2) or write(2)
+/* operation will use the default buffer size (VSTREAM_BUFSIZE).
+/*
+/* vstream_context() returns the application context that is passed on to
+/* the application-specified read/write routines.
+/*
+/* VSTREAM_PATH() is an unsafe macro that returns the name stored
+/* with vstream_fopen() or with vstream_control(). The macro is
+/* unsafe because it evaluates some arguments more than once.
+/*
+/* vstream_feof() returns non-zero when a previous operation on the
+/* specified stream caused an end-of-file condition.
+/* Although further read requests after EOF may complete
+/* successfully, vstream_feof() will keep returning non-zero
+/* until vstream_clearerr() is called for that stream.
+/*
+/* vstream_ferror() returns non-zero when a previous operation on the
+/* specified stream caused a non-EOF error condition, including timeout.
+/* After a non-EOF error on a stream, no I/O request will
+/* complete until after vstream_clearerr() is called for that stream.
+/*
+/* vstream_ftimeout() returns non-zero when a previous operation on the
+/* specified stream caused a timeout error condition. See
+/* vstream_ferror() for error persistence details.
+/*
+/* vstream_clearerr() resets the timeout, error and end-of-file indication
+/* of the specified stream, and returns no useful result.
+/*
+/* vstream_vfprintf() provides an alternate interface
+/* for formatting an argument list according to a format string.
+/*
+/* vstream_vprintf() provides a similar alternative interface.
+/*
+/* vstream_bufstat() provides input and output buffer status
+/* information. The command is one of the following:
+/* .IP VSTREAM_BST_IN_PEND
+/* Return the number of characters that can be read without
+/* refilling the read buffer.
+/* .IP VSTREAM_BST_OUT_PEND
+/* Return the number of characters that are waiting in the
+/* write buffer.
+/* .PP
+/* vstream_peek() returns the number of characters that can be
+/* read from the named stream without refilling the read buffer.
+/* This is an alias for vstream_bufstat(stream, VSTREAM_BST_IN_PEND).
+/*
+/* vstream_peek_data() returns a pointer to the unread bytes
+/* that exist according to vstream_peek(), or null if no unread
+/* bytes are available.
+/*
+/* vstream_setjmp() saves processing context and makes that context
+/* available for use with vstream_longjmp(). Normally, vstream_setjmp()
+/* returns zero. A non-zero result means that vstream_setjmp() returned
+/* through a vstream_longjmp() call; the result is the \fIval\fR argument
+/* given to vstream_longjmp().
+/*
+/* NB: non-local jumps such as vstream_longjmp() are not safe
+/* for jumping out of any routine that manipulates VSTREAM data.
+/* longjmp() like calls are best avoided in signal handlers.
+/*
+/* vstream_ftime() returns the time of initialization, the last buffer
+/* fill operation, or the last buffer flush operation for the specified
+/* stream. This information is maintained only when stream timeouts are
+/* enabled.
+/*
+/* vstream_ftimeval() is like vstream_ftime() but returns more
+/* detail.
+/*
+/* vstream_rd_mumble() and vstream_wr_mumble() report on
+/* read and write error conditions, respectively.
+/*
+/* vstream_fstat() queries stream status information about
+/* user-requested features. The \fIflags\fR argument is the
+/* bitwise OR of one or more of the following, and the result
+/* value is the bitwise OR of the features that are activated.
+/* .IP VSTREAM_FLAG_DEADLINE
+/* The deadline feature is activated.
+/* .IP VSTREAM_FLAG_DOUBLE
+/* The double-buffering feature is activated.
+/* .IP VSTREAM_FLAG_MEMORY
+/* The stream is connected to a VSTRING buffer.
+/* .IP VSTREAM_FLAG_OWN_VSTRING
+/* The stream 'owns' the VSTRING buffer, and is responsible
+/* for cleaning up when the stream is closed.
+/* DIAGNOSTICS
+/* Panics: interface violations. Fatal errors: out of memory.
+/* SEE ALSO
+/* timed_read(3) default read routine
+/* timed_write(3) default write routine
+/* vbuf_print(3) formatting engine
+/* setjmp(3) non-local jumps
+/* BUGS
+/* Should use mmap() on reasonable systems.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+#include <errno.h>
+#include <string.h>
+
+/* Utility library. */
+
+#define VSTRING_INTERNAL
+
+#include "mymalloc.h"
+#include "msg.h"
+#include "vbuf_print.h"
+#include "iostuff.h"
+#include "vstring.h"
+#include "vstream.h"
+
+/* Application-specific. */
+
+ /*
+ * Forward declarations.
+ */
+static int vstream_buf_get_ready(VBUF *);
+static int vstream_buf_put_ready(VBUF *);
+static int vstream_buf_space(VBUF *, ssize_t);
+
+ /*
+ * Initialization of the three pre-defined streams. Pre-allocate a static
+ * I/O buffer for the standard error stream, so that the error handler can
+ * produce a diagnostic even when memory allocation fails.
+ */
+static unsigned char vstream_fstd_buf[VSTREAM_BUFSIZE];
+
+VSTREAM vstream_fstd[] = {
+ {{
+ 0, /* flags */
+ 0, 0, 0, 0, /* buffer */
+ vstream_buf_get_ready, vstream_buf_put_ready, vstream_buf_space,
+ }, STDIN_FILENO, (VSTREAM_RW_FN) timed_read, (VSTREAM_RW_FN) timed_write,
+ 0,},
+ {{
+ 0, /* flags */
+ 0, 0, 0, 0, /* buffer */
+ vstream_buf_get_ready, vstream_buf_put_ready, vstream_buf_space,
+ }, STDOUT_FILENO, (VSTREAM_RW_FN) timed_read, (VSTREAM_RW_FN) timed_write,
+ 0,},
+ {{
+ VBUF_FLAG_FIXED | VSTREAM_FLAG_WRITE,
+ vstream_fstd_buf, VSTREAM_BUFSIZE, VSTREAM_BUFSIZE, vstream_fstd_buf,
+ vstream_buf_get_ready, vstream_buf_put_ready, vstream_buf_space,
+ }, STDERR_FILENO, (VSTREAM_RW_FN) timed_read, (VSTREAM_RW_FN) timed_write,
+ VSTREAM_BUFSIZE,},
+};
+
+#define VSTREAM_STATIC(v) ((v) >= VSTREAM_IN && (v) <= VSTREAM_ERR)
+
+ /*
+ * A bunch of macros to make some expressions more readable. XXX We're
+ * assuming that O_RDONLY == 0, O_WRONLY == 1, O_RDWR == 2.
+ */
+#define VSTREAM_ACC_MASK(f) ((f) & (O_APPEND | O_WRONLY | O_RDWR))
+
+#define VSTREAM_CAN_READ(f) (VSTREAM_ACC_MASK(f) == O_RDONLY \
+ || VSTREAM_ACC_MASK(f) == O_RDWR)
+#define VSTREAM_CAN_WRITE(f) (VSTREAM_ACC_MASK(f) & O_WRONLY \
+ || VSTREAM_ACC_MASK(f) & O_RDWR \
+ || VSTREAM_ACC_MASK(f) & O_APPEND)
+
+#define VSTREAM_BUF_COUNT(bp, n) \
+ ((bp)->flags & VSTREAM_FLAG_READ ? -(n) : (n))
+
+#define VSTREAM_BUF_AT_START(bp) { \
+ (bp)->cnt = VSTREAM_BUF_COUNT((bp), (bp)->len); \
+ (bp)->ptr = (bp)->data; \
+ }
+
+#define VSTREAM_BUF_AT_OFFSET(bp, offset) { \
+ (bp)->ptr = (bp)->data + (offset); \
+ (bp)->cnt = VSTREAM_BUF_COUNT(bp, (bp)->len - (offset)); \
+ }
+
+#define VSTREAM_BUF_AT_END(bp) { \
+ (bp)->cnt = 0; \
+ (bp)->ptr = (bp)->data + (bp)->len; \
+ }
+
+#define VSTREAM_BUF_ZERO(bp) { \
+ (bp)->flags = 0; \
+ (bp)->data = (bp)->ptr = 0; \
+ (bp)->len = (bp)->cnt = 0; \
+ }
+
+#define VSTREAM_BUF_ACTIONS(bp, get_action, put_action, space_action) { \
+ (bp)->get_ready = (get_action); \
+ (bp)->put_ready = (put_action); \
+ (bp)->space = (space_action); \
+ }
+
+#define VSTREAM_SAVE_STATE(stream, buffer, filedes) { \
+ stream->buffer = stream->buf; \
+ stream->filedes = stream->fd; \
+ }
+
+#define VSTREAM_RESTORE_STATE(stream, buffer, filedes) do { \
+ stream->buffer.flags = stream->buf.flags; \
+ stream->buf = stream->buffer; \
+ stream->fd = stream->filedes; \
+ } while(0)
+
+#define VSTREAM_FORK_STATE(stream, buffer, filedes) { \
+ stream->buffer = stream->buf; \
+ stream->filedes = stream->fd; \
+ stream->buffer.data = stream->buffer.ptr = 0; \
+ stream->buffer.len = stream->buffer.cnt = 0; \
+ stream->buffer.flags &= ~VSTREAM_FLAG_FIXED; \
+ };
+
+#define VSTREAM_FLAG_READ_DOUBLE (VSTREAM_FLAG_READ | VSTREAM_FLAG_DOUBLE)
+#define VSTREAM_FLAG_WRITE_DOUBLE (VSTREAM_FLAG_WRITE | VSTREAM_FLAG_DOUBLE)
+
+#define VSTREAM_FFLUSH_SOME(stream) \
+ vstream_fflush_some((stream), (stream)->buf.len - (stream)->buf.cnt)
+
+/* Note: this does not change a negative result into a zero result. */
+#define VSTREAM_SUB_TIME(x, y, z) \
+ do { \
+ (x).tv_sec = (y).tv_sec - (z).tv_sec; \
+ (x).tv_usec = (y).tv_usec - (z).tv_usec; \
+ while ((x).tv_usec < 0) { \
+ (x).tv_usec += 1000000; \
+ (x).tv_sec -= 1; \
+ } \
+ while ((x).tv_usec >= 1000000) { \
+ (x).tv_usec -= 1000000; \
+ (x).tv_sec += 1; \
+ } \
+ } while (0)
+
+#define VSTREAM_ADD_TIME(x, y, z) \
+ do { \
+ (x).tv_sec = (y).tv_sec + (z).tv_sec; \
+ (x).tv_usec = (y).tv_usec + (z).tv_usec; \
+ while ((x).tv_usec >= 1000000) { \
+ (x).tv_usec -= 1000000; \
+ (x).tv_sec += 1; \
+ } \
+ } while (0)
+
+/* vstream_buf_init - initialize buffer */
+
+static void vstream_buf_init(VBUF *bp, int flags)
+{
+
+ /*
+ * Initialize the buffer such that the first data access triggers a
+ * buffer boundary action.
+ */
+ VSTREAM_BUF_ZERO(bp);
+ VSTREAM_BUF_ACTIONS(bp,
+ VSTREAM_CAN_READ(flags) ? vstream_buf_get_ready : 0,
+ VSTREAM_CAN_WRITE(flags) ? vstream_buf_put_ready : 0,
+ vstream_buf_space);
+}
+
+/* vstream_buf_alloc - allocate buffer memory */
+
+static void vstream_buf_alloc(VBUF *bp, ssize_t len)
+{
+ VSTREAM *stream = VBUF_TO_APPL(bp, VSTREAM, buf);
+ ssize_t used = bp->ptr - bp->data;
+ const char *myname = "vstream_buf_alloc";
+
+ if (len < bp->len)
+ msg_panic("%s: attempt to shrink buffer", myname);
+ if (bp->flags & VSTREAM_FLAG_FIXED)
+ msg_panic("%s: unable to extend fixed-size buffer", myname);
+
+ /*
+ * Late buffer allocation allows the user to override the default policy.
+ * If a buffer already exists, allow for the presence of (output) data.
+ */
+ bp->data = (unsigned char *)
+ (bp->data ? myrealloc((void *) bp->data, len) : mymalloc(len));
+ if (bp->flags & VSTREAM_FLAG_MEMORY)
+ memset(bp->data + bp->len, 0, len - bp->len);
+ bp->len = len;
+ if (bp->flags & VSTREAM_FLAG_READ) {
+ bp->ptr = bp->data + used;
+ if (bp->flags & VSTREAM_FLAG_DOUBLE)
+ VSTREAM_SAVE_STATE(stream, read_buf, read_fd);
+ } else {
+ VSTREAM_BUF_AT_OFFSET(bp, used);
+ if (bp->flags & VSTREAM_FLAG_DOUBLE)
+ VSTREAM_SAVE_STATE(stream, write_buf, write_fd);
+ }
+}
+
+/* vstream_buf_wipe - reset buffer to initial state */
+
+static void vstream_buf_wipe(VBUF *bp)
+{
+ if ((bp->flags & VBUF_FLAG_FIXED) == 0 && bp->data)
+ myfree((void *) bp->data);
+ VSTREAM_BUF_ZERO(bp);
+ VSTREAM_BUF_ACTIONS(bp, 0, 0, 0);
+}
+
+/* vstream_fflush_some - flush some buffered data */
+
+static int vstream_fflush_some(VSTREAM *stream, ssize_t to_flush)
+{
+ const char *myname = "vstream_fflush_some";
+ VBUF *bp = &stream->buf;
+ ssize_t used;
+ ssize_t left_over;
+ void *data;
+ ssize_t len;
+ ssize_t n;
+ int timeout;
+ struct timeval before;
+ struct timeval elapsed;
+ struct timeval bonus;
+
+ /*
+ * Sanity checks. It is illegal to flush a read-only stream. Otherwise,
+ * if there is buffered input, discard the input. If there is buffered
+ * output, require that the amount to flush is larger than the amount to
+ * keep, so that we can memcpy() the residue.
+ */
+ if (bp->put_ready == 0)
+ msg_panic("%s: read-only stream", myname);
+ switch (bp->flags & (VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ)) {
+ case VSTREAM_FLAG_READ: /* discard input */
+ VSTREAM_BUF_AT_END(bp);
+ /* FALLTHROUGH */
+ case 0: /* flush after seek? */
+ return ((bp->flags & VSTREAM_FLAG_ERR) ? VSTREAM_EOF : 0);
+ case VSTREAM_FLAG_WRITE: /* output buffered */
+ break;
+ case VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ:
+ msg_panic("%s: read/write stream", myname);
+ }
+ used = bp->len - bp->cnt;
+ left_over = used - to_flush;
+
+ if (msg_verbose > 2 && stream != VSTREAM_ERR)
+ msg_info("%s: fd %d flush %ld", myname, stream->fd, (long) to_flush);
+ if (to_flush < 0 || left_over < 0)
+ msg_panic("%s: bad to_flush %ld", myname, (long) to_flush);
+ if (to_flush < left_over)
+ msg_panic("%s: to_flush < left_over", myname);
+ if (to_flush == 0)
+ return ((bp->flags & VSTREAM_FLAG_ERR) ? VSTREAM_EOF : 0);
+ if (bp->flags & VSTREAM_FLAG_ERR)
+ return (VSTREAM_EOF);
+
+ /*
+ * When flushing a buffer, allow for partial writes. These can happen
+ * while talking to a network. Update the cached file seek position, if
+ * any.
+ *
+ * When deadlines are enabled, we count the elapsed time for each write
+ * operation instead of simply comparing the time-of-day clock with a
+ * per-stream deadline. The latter could result in anomalies when an
+ * application does lengthy processing between write operations. Keep in
+ * mind that a receiver may not be able to keep up when a sender suddenly
+ * floods it with a lot of data as it tries to catch up with a deadline.
+ */
+ for (data = (void *) bp->data, len = to_flush; len > 0; len -= n, data += n) {
+ if (bp->flags & VSTREAM_FLAG_DEADLINE) {
+ timeout = stream->time_limit.tv_sec + (stream->time_limit.tv_usec > 0);
+ if (timeout <= 0) {
+ bp->flags |= (VSTREAM_FLAG_WR_ERR | VSTREAM_FLAG_WR_TIMEOUT);
+ errno = ETIMEDOUT;
+ return (VSTREAM_EOF);
+ }
+ if (len == to_flush)
+ GETTIMEOFDAY(&before);
+ else
+ before = stream->iotime;
+ } else
+ timeout = stream->timeout;
+ if ((n = stream->write_fn(stream->fd, data, len, timeout, stream->context)) <= 0) {
+ bp->flags |= VSTREAM_FLAG_WR_ERR;
+ if (errno == ETIMEDOUT) {
+ bp->flags |= VSTREAM_FLAG_WR_TIMEOUT;
+ stream->time_limit.tv_sec = stream->time_limit.tv_usec = 0;
+ }
+ return (VSTREAM_EOF);
+ }
+ if (timeout) {
+ GETTIMEOFDAY(&stream->iotime);
+ if (bp->flags & VSTREAM_FLAG_DEADLINE) {
+ VSTREAM_SUB_TIME(elapsed, stream->iotime, before);
+ VSTREAM_SUB_TIME(stream->time_limit, stream->time_limit, elapsed);
+ if (stream->min_data_rate > 0) {
+ bonus.tv_sec = n / stream->min_data_rate;
+ bonus.tv_usec = (n % stream->min_data_rate) * 1000000;
+ bonus.tv_usec /= stream->min_data_rate;
+ VSTREAM_ADD_TIME(stream->time_limit, stream->time_limit,
+ bonus);
+ if (stream->time_limit.tv_sec >= stream->timeout) {
+ stream->time_limit.tv_sec = stream->timeout;
+ stream->time_limit.tv_usec = 0;
+ }
+ }
+ }
+ }
+ if (msg_verbose > 2 && stream != VSTREAM_ERR && n != to_flush)
+ msg_info("%s: %d flushed %ld/%ld", myname, stream->fd,
+ (long) n, (long) to_flush);
+ }
+ if (bp->flags & VSTREAM_FLAG_SEEK)
+ stream->offset += to_flush;
+
+ /*
+ * Allow for partial buffer flush requests. We use memcpy() for reasons
+ * of portability to pre-ANSI environments (SunOS 4.x or Ultrix 4.x :-).
+ * This is OK because we have already verified that the to_flush count is
+ * larger than the left_over count.
+ */
+ if (left_over > 0)
+ memcpy(bp->data, bp->data + to_flush, left_over);
+ bp->cnt += to_flush;
+ bp->ptr -= to_flush;
+ return ((bp->flags & VSTREAM_FLAG_ERR) ? VSTREAM_EOF : 0);
+}
+
+/* vstream_fflush_delayed - delayed stream flush for double-buffered stream */
+
+static int vstream_fflush_delayed(VSTREAM *stream)
+{
+ int status;
+
+ /*
+ * Sanity check.
+ */
+ if ((stream->buf.flags & VSTREAM_FLAG_READ_DOUBLE) != VSTREAM_FLAG_READ_DOUBLE)
+ msg_panic("vstream_fflush_delayed: bad flags");
+
+ /*
+ * Temporarily swap buffers and flush unwritten data. This may seem like
+ * a lot of work, but it's peanuts compared to the write(2) call that we
+ * already have avoided. For example, delayed flush is never used on a
+ * non-pipelined SMTP connection.
+ */
+ stream->buf.flags &= ~VSTREAM_FLAG_READ;
+ VSTREAM_SAVE_STATE(stream, read_buf, read_fd);
+ stream->buf.flags |= VSTREAM_FLAG_WRITE;
+ VSTREAM_RESTORE_STATE(stream, write_buf, write_fd);
+
+ status = VSTREAM_FFLUSH_SOME(stream);
+
+ stream->buf.flags &= ~VSTREAM_FLAG_WRITE;
+ VSTREAM_SAVE_STATE(stream, write_buf, write_fd);
+ stream->buf.flags |= VSTREAM_FLAG_READ;
+ VSTREAM_RESTORE_STATE(stream, read_buf, read_fd);
+
+ return (status);
+}
+
+/* vstream_buf_get_ready - vbuf callback to make buffer ready for reading */
+
+static int vstream_buf_get_ready(VBUF *bp)
+{
+ VSTREAM *stream = VBUF_TO_APPL(bp, VSTREAM, buf);
+ const char *myname = "vstream_buf_get_ready";
+ ssize_t n;
+ struct timeval before;
+ struct timeval elapsed;
+ struct timeval bonus;
+ int timeout;
+
+ /*
+ * Detect a change of I/O direction or position. If so, flush any
+ * unwritten output immediately when the stream is single-buffered, or
+ * when the stream is double-buffered and the read buffer is empty.
+ */
+ switch (bp->flags & (VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ)) {
+ case VSTREAM_FLAG_WRITE: /* change direction */
+ if (bp->ptr > bp->data)
+ if ((bp->flags & VSTREAM_FLAG_DOUBLE) == 0
+ || stream->read_buf.cnt >= 0)
+ if (VSTREAM_FFLUSH_SOME(stream))
+ return (VSTREAM_EOF);
+ bp->flags &= ~VSTREAM_FLAG_WRITE;
+ if (bp->flags & VSTREAM_FLAG_DOUBLE)
+ VSTREAM_SAVE_STATE(stream, write_buf, write_fd);
+ /* FALLTHROUGH */
+ case 0: /* change position */
+ bp->flags |= VSTREAM_FLAG_READ;
+ if (bp->flags & VSTREAM_FLAG_DOUBLE) {
+ VSTREAM_RESTORE_STATE(stream, read_buf, read_fd);
+ if (bp->cnt < 0)
+ return (0);
+ }
+ /* FALLTHROUGH */
+ case VSTREAM_FLAG_READ: /* no change */
+ break;
+ case VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ:
+ msg_panic("%s: read/write stream", myname);
+ }
+
+ /*
+ * If this is the first GET operation, allocate a buffer. Late buffer
+ * allocation gives the application a chance to override the default
+ * buffering policy.
+ *
+ * XXX Subtle code to set the preferred buffer size as late as possible.
+ */
+ if (stream->req_bufsize == 0)
+ stream->req_bufsize = VSTREAM_BUFSIZE;
+ if (bp->len < stream->req_bufsize)
+ vstream_buf_alloc(bp, stream->req_bufsize);
+
+ /*
+ * If the stream is double-buffered and the write buffer is not empty,
+ * this is the time to flush the write buffer. Delayed flushes reduce
+ * system call overhead, and on TCP sockets, avoid triggering Nagle's
+ * algorithm.
+ */
+ if ((bp->flags & VSTREAM_FLAG_DOUBLE)
+ && stream->write_buf.len > stream->write_buf.cnt)
+ if (vstream_fflush_delayed(stream))
+ return (VSTREAM_EOF);
+
+ /*
+ * Did we receive an EOF indication?
+ */
+ if (bp->flags & VSTREAM_FLAG_EOF)
+ return (VSTREAM_EOF);
+
+ /*
+ * Fill the buffer with as much data as we can handle, or with as much
+ * data as is available right now, whichever is less. Update the cached
+ * file seek position, if any.
+ *
+ * When deadlines are enabled, we count the elapsed time for each read
+ * operation instead of simply comparing the time-of-day clock with a
+ * per-stream deadline. The latter could result in anomalies when an
+ * application does lengthy processing between read operations. Keep in
+ * mind that a sender may get blocked, and may not be able to keep up
+ * when a receiver suddenly wants to read a lot of data as it tries to
+ * catch up with a deadline.
+ */
+ if (bp->flags & VSTREAM_FLAG_DEADLINE) {
+ timeout = stream->time_limit.tv_sec + (stream->time_limit.tv_usec > 0);
+ if (timeout <= 0) {
+ bp->flags |= (VSTREAM_FLAG_RD_ERR | VSTREAM_FLAG_RD_TIMEOUT);
+ errno = ETIMEDOUT;
+ return (VSTREAM_EOF);
+ }
+ GETTIMEOFDAY(&before);
+ } else
+ timeout = stream->timeout;
+ switch (n = stream->read_fn(stream->fd, bp->data, bp->len, timeout, stream->context)) {
+ case -1:
+ bp->flags |= VSTREAM_FLAG_RD_ERR;
+ if (errno == ETIMEDOUT) {
+ bp->flags |= VSTREAM_FLAG_RD_TIMEOUT;
+ stream->time_limit.tv_sec = stream->time_limit.tv_usec = 0;
+ }
+ return (VSTREAM_EOF);
+ case 0:
+ bp->flags |= VSTREAM_FLAG_EOF;
+ return (VSTREAM_EOF);
+ default:
+ if (timeout) {
+ GETTIMEOFDAY(&stream->iotime);
+ if (bp->flags & VSTREAM_FLAG_DEADLINE) {
+ VSTREAM_SUB_TIME(elapsed, stream->iotime, before);
+ VSTREAM_SUB_TIME(stream->time_limit, stream->time_limit, elapsed);
+ if (stream->min_data_rate > 0) {
+ bonus.tv_sec = n / stream->min_data_rate;
+ bonus.tv_usec = (n % stream->min_data_rate) * 1000000;
+ bonus.tv_usec /= stream->min_data_rate;
+ VSTREAM_ADD_TIME(stream->time_limit, stream->time_limit,
+ bonus);
+ if (stream->time_limit.tv_sec >= stream->timeout) {
+ stream->time_limit.tv_sec = stream->timeout;
+ stream->time_limit.tv_usec = 0;
+ }
+ }
+ }
+ }
+ if (msg_verbose > 2)
+ msg_info("%s: fd %d got %ld", myname, stream->fd, (long) n);
+ bp->cnt = -n;
+ bp->ptr = bp->data;
+ if (bp->flags & VSTREAM_FLAG_SEEK)
+ stream->offset += n;
+ return (0);
+ }
+}
+
+/* vstream_buf_put_ready - vbuf callback to make buffer ready for writing */
+
+static int vstream_buf_put_ready(VBUF *bp)
+{
+ VSTREAM *stream = VBUF_TO_APPL(bp, VSTREAM, buf);
+ const char *myname = "vstream_buf_put_ready";
+
+ /*
+ * Sanity checks. Detect a change of I/O direction or position. If so,
+ * discard unread input, and reset the buffer to the beginning.
+ */
+ switch (bp->flags & (VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ)) {
+ case VSTREAM_FLAG_READ: /* change direction */
+ bp->flags &= ~VSTREAM_FLAG_READ;
+ if (bp->flags & VSTREAM_FLAG_DOUBLE)
+ VSTREAM_SAVE_STATE(stream, read_buf, read_fd);
+ /* FALLTHROUGH */
+ case 0: /* change position */
+ bp->flags |= VSTREAM_FLAG_WRITE;
+ if (bp->flags & VSTREAM_FLAG_DOUBLE)
+ VSTREAM_RESTORE_STATE(stream, write_buf, write_fd);
+ else
+ VSTREAM_BUF_AT_START(bp);
+ /* FALLTHROUGH */
+ case VSTREAM_FLAG_WRITE: /* no change */
+ break;
+ case VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ:
+ msg_panic("%s: read/write stream", myname);
+ }
+
+ /*
+ * Remember the direction. If this is the first PUT operation for this
+ * stream or if the buffer is smaller than the requested size, allocate a
+ * new buffer; obviously there is no data to be flushed yet. Otherwise,
+ * flush the buffer.
+ *
+ * XXX Subtle code to set the preferred buffer size as late as possible.
+ */
+ if (stream->req_bufsize == 0)
+ stream->req_bufsize = VSTREAM_BUFSIZE;
+ if (bp->len < stream->req_bufsize) {
+ vstream_buf_alloc(bp, stream->req_bufsize);
+ } else if (bp->cnt <= 0) {
+ if (VSTREAM_FFLUSH_SOME(stream))
+ return (VSTREAM_EOF);
+ }
+ return (0);
+}
+
+/* vstream_buf_space - reserve space ahead of time */
+
+static int vstream_buf_space(VBUF *bp, ssize_t want)
+{
+ VSTREAM *stream = VBUF_TO_APPL(bp, VSTREAM, buf);
+ ssize_t used;
+ ssize_t incr;
+ ssize_t shortage;
+ const char *myname = "vstream_buf_space";
+
+ /*
+ * Sanity checks. Reserving space implies writing. It is illegal to write
+ * to a read-only stream. Detect a change of I/O direction or position.
+ * If so, reset the buffer to the beginning.
+ */
+ if (bp->put_ready == 0)
+ msg_panic("%s: read-only stream", myname);
+ if (want < 0)
+ msg_panic("%s: bad length %ld", myname, (long) want);
+ switch (bp->flags & (VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE)) {
+ case VSTREAM_FLAG_READ: /* change direction */
+ bp->flags &= ~VSTREAM_FLAG_READ;
+ if (bp->flags & VSTREAM_FLAG_DOUBLE)
+ VSTREAM_SAVE_STATE(stream, read_buf, read_fd);
+ /* FALLTHROUGH */
+ case 0: /* change position */
+ bp->flags |= VSTREAM_FLAG_WRITE;
+ if (bp->flags & VSTREAM_FLAG_DOUBLE)
+ VSTREAM_RESTORE_STATE(stream, write_buf, write_fd);
+ else
+ VSTREAM_BUF_AT_START(bp);
+ /* FALLTHROUGH */
+ case VSTREAM_FLAG_WRITE: /* no change */
+ break;
+ case VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE:
+ msg_panic("%s: read/write stream", myname);
+ }
+
+ /*
+ * See if enough space is available. If not, flush a multiple of
+ * VSTREAM_BUFSIZE bytes and resize the buffer to a multiple of
+ * VSTREAM_BUFSIZE. We flush multiples of VSTREAM_BUFSIZE in an attempt
+ * to keep file updates block-aligned for better performance.
+ *
+ * XXX Subtle code to set the preferred buffer size as late as possible.
+ */
+#define VSTREAM_TRUNCATE(count, base) (((count) / (base)) * (base))
+#define VSTREAM_ROUNDUP(count, base) VSTREAM_TRUNCATE(count + base - 1, base)
+
+ if (stream->req_bufsize == 0)
+ stream->req_bufsize = VSTREAM_BUFSIZE;
+ if (want > bp->cnt) {
+ if ((used = bp->len - bp->cnt) > stream->req_bufsize)
+ if (vstream_fflush_some(stream, VSTREAM_TRUNCATE(used, stream->req_bufsize)))
+ return (VSTREAM_EOF);
+ if ((shortage = (want - bp->cnt)) > 0) {
+ if ((bp->flags & VSTREAM_FLAG_FIXED)
+ || shortage > __MAXINT__(ssize_t) -bp->len - stream->req_bufsize) {
+ bp->flags |= VSTREAM_FLAG_WR_ERR;
+ } else {
+ incr = VSTREAM_ROUNDUP(shortage, stream->req_bufsize);
+ vstream_buf_alloc(bp, bp->len + incr);
+ }
+ }
+ }
+ return (vstream_ferror(stream) ? VSTREAM_EOF : 0); /* mmap() may fail */
+}
+
+/* vstream_fpurge - discard unread or unwritten content */
+
+int vstream_fpurge(VSTREAM *stream, int direction)
+{
+ const char *myname = "vstream_fpurge";
+ VBUF *bp = &stream->buf;
+
+#define VSTREAM_MAYBE_PURGE_WRITE(d, b) if ((d) & VSTREAM_PURGE_WRITE) \
+ VSTREAM_BUF_AT_START((b))
+#define VSTREAM_MAYBE_PURGE_READ(d, b) if ((d) & VSTREAM_PURGE_READ) \
+ VSTREAM_BUF_AT_END((b))
+
+ /*
+ * To discard all unread contents, position the read buffer at its end,
+ * so that we skip over any unread data, and so that the next read
+ * operation will refill the buffer.
+ *
+ * To discard all unwritten content, position the write buffer at its
+ * beginning, so that the next write operation clobbers any unwritten
+ * data.
+ */
+ switch (bp->flags & (VSTREAM_FLAG_READ_DOUBLE | VSTREAM_FLAG_WRITE)) {
+ case VSTREAM_FLAG_READ_DOUBLE:
+ VSTREAM_MAYBE_PURGE_WRITE(direction, &stream->write_buf);
+ /* FALLTHROUGH */
+ case VSTREAM_FLAG_READ:
+ VSTREAM_MAYBE_PURGE_READ(direction, bp);
+ break;
+ case VSTREAM_FLAG_DOUBLE:
+ VSTREAM_MAYBE_PURGE_WRITE(direction, &stream->write_buf);
+ VSTREAM_MAYBE_PURGE_READ(direction, &stream->read_buf);
+ break;
+ case VSTREAM_FLAG_WRITE_DOUBLE:
+ VSTREAM_MAYBE_PURGE_READ(direction, &stream->read_buf);
+ /* FALLTHROUGH */
+ case VSTREAM_FLAG_WRITE:
+ VSTREAM_MAYBE_PURGE_WRITE(direction, bp);
+ break;
+ case VSTREAM_FLAG_READ_DOUBLE | VSTREAM_FLAG_WRITE:
+ case VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE:
+ msg_panic("%s: read/write stream", myname);
+ }
+
+ /*
+ * Invalidate the cached file seek position.
+ */
+ bp->flags &= ~VSTREAM_FLAG_SEEK;
+ stream->offset = 0;
+
+ return (0);
+}
+
+/* vstream_fseek - change I/O position */
+
+off_t vstream_fseek(VSTREAM *stream, off_t offset, int whence)
+{
+ const char *myname = "vstream_fseek";
+ VBUF *bp = &stream->buf;
+
+ /*
+ * TODO: support data length (data length != buffer length). Without data
+ * length information, Without explicit data length information,
+ * vstream_memopen(O_RDONLY) has to set the VSTREAM buffer length to the
+ * vstring payload length to avoid accessing unwritten data after
+ * vstream_fseek(), because for lseek() compatibility, vstream_fseek()
+ * must allow seeking past the end of a file.
+ */
+ if (stream->buf.flags & VSTREAM_FLAG_MEMORY) {
+ if (whence == SEEK_CUR)
+ offset += (bp->ptr - bp->data);
+ else if (whence == SEEK_END)
+ offset += bp->len;
+ if (offset < 0) {
+ errno = EINVAL;
+ return (-1);
+ }
+ if (offset > bp->len && (bp->flags & VSTREAM_FLAG_WRITE))
+ vstream_buf_space(bp, offset - bp->len);
+ VSTREAM_BUF_AT_OFFSET(bp, offset);
+ return (offset);
+ }
+
+ /*
+ * Flush any unwritten output. Discard any unread input. Position the
+ * buffer at the end, so that the next GET or PUT operation triggers a
+ * buffer boundary action.
+ */
+ switch (bp->flags & (VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE)) {
+ case VSTREAM_FLAG_WRITE:
+ if (bp->ptr > bp->data) {
+ if (whence == SEEK_CUR)
+ offset += (bp->ptr - bp->data); /* add unwritten data */
+ else if (whence == SEEK_END)
+ bp->flags &= ~VSTREAM_FLAG_SEEK;
+ if (VSTREAM_FFLUSH_SOME(stream))
+ return (-1);
+ }
+ VSTREAM_BUF_AT_END(bp);
+ break;
+ case VSTREAM_FLAG_READ:
+ if (whence == SEEK_CUR)
+ offset += bp->cnt; /* subtract unread data */
+ else if (whence == SEEK_END)
+ bp->flags &= ~VSTREAM_FLAG_SEEK;
+ /* FALLTHROUGH */
+ case 0:
+ VSTREAM_BUF_AT_END(bp);
+ break;
+ case VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE:
+ msg_panic("%s: read/write stream", myname);
+ }
+
+ /*
+ * Clear the read/write flags to inform the buffer boundary action
+ * routines that we may have changed I/O position.
+ */
+ bp->flags &= ~(VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE);
+
+ /*
+ * Shave an unnecessary system call.
+ */
+ if (bp->flags & VSTREAM_FLAG_NSEEK) {
+ errno = ESPIPE;
+ return (-1);
+ }
+
+ /*
+ * Update the cached file seek position.
+ */
+ if ((stream->offset = lseek(stream->fd, offset, whence)) < 0) {
+ if (errno == ESPIPE)
+ bp->flags |= VSTREAM_FLAG_NSEEK;
+ } else {
+ bp->flags |= VSTREAM_FLAG_SEEK;
+ }
+ bp->flags &= ~VSTREAM_FLAG_EOF;
+ return (stream->offset);
+}
+
+/* vstream_ftell - return file offset */
+
+off_t vstream_ftell(VSTREAM *stream)
+{
+ VBUF *bp = &stream->buf;
+
+ /*
+ * Special case for memory buffer.
+ */
+ if (stream->buf.flags & VSTREAM_FLAG_MEMORY)
+ return (bp->ptr - bp->data);
+
+ /*
+ * Shave an unnecessary syscall.
+ */
+ if (bp->flags & VSTREAM_FLAG_NSEEK) {
+ errno = ESPIPE;
+ return (-1);
+ }
+
+ /*
+ * Use the cached file offset when available. This is the offset after
+ * the last read, write or seek operation.
+ */
+ if ((bp->flags & VSTREAM_FLAG_SEEK) == 0) {
+ if ((stream->offset = lseek(stream->fd, (off_t) 0, SEEK_CUR)) < 0) {
+ bp->flags |= VSTREAM_FLAG_NSEEK;
+ return (-1);
+ }
+ bp->flags |= VSTREAM_FLAG_SEEK;
+ }
+
+ /*
+ * If this is a read buffer, subtract the number of unread bytes from the
+ * cached offset. Remember that read counts are negative.
+ */
+ if (bp->flags & VSTREAM_FLAG_READ)
+ return (stream->offset + bp->cnt);
+
+ /*
+ * If this is a write buffer, add the number of unwritten bytes to the
+ * cached offset.
+ */
+ if (bp->flags & VSTREAM_FLAG_WRITE)
+ return (stream->offset + (bp->ptr - bp->data));
+
+ /*
+ * Apparently, this is a new buffer, or a buffer after seek, so there is
+ * no need to account for unread or unwritten data.
+ */
+ return (stream->offset);
+}
+
+/* vstream_subopen - initialize everything except buffers and I/O handlers */
+
+static VSTREAM *vstream_subopen(void)
+{
+ VSTREAM *stream;
+
+ /* Note: memset() is not a portable way to initialize non-integer types. */
+ stream = (VSTREAM *) mymalloc(sizeof(*stream));
+ stream->offset = 0;
+ stream->path = 0;
+ stream->pid = 0;
+ stream->waitpid_fn = 0;
+ stream->timeout = 0;
+ stream->context = 0;
+ stream->jbuf = 0;
+ stream->iotime.tv_sec = stream->iotime.tv_usec = 0;
+ stream->time_limit.tv_sec = stream->time_limit.tv_usec = 0;
+ stream->req_bufsize = 0;
+ stream->vstring = 0;
+ stream->min_data_rate = 0;
+ return (stream);
+}
+
+/* vstream_fdopen - add buffering to pre-opened stream */
+
+VSTREAM *vstream_fdopen(int fd, int flags)
+{
+ VSTREAM *stream;
+
+ /*
+ * Sanity check.
+ */
+ if (fd < 0)
+ msg_panic("vstream_fdopen: bad file %d", fd);
+
+ /*
+ * Initialize buffers etc. but do as little as possible. Late buffer
+ * allocation etc. gives the application a chance to override default
+ * policies. Either this, or the vstream*open() routines would have to
+ * have a really ugly interface with lots of mostly-unused arguments (can
+ * you say VMS?).
+ */
+ stream = vstream_subopen();
+ stream->fd = fd;
+ stream->read_fn = VSTREAM_CAN_READ(flags) ? (VSTREAM_RW_FN) timed_read : 0;
+ stream->write_fn = VSTREAM_CAN_WRITE(flags) ? (VSTREAM_RW_FN) timed_write : 0;
+ vstream_buf_init(&stream->buf, flags);
+ return (stream);
+}
+
+/* vstream_fopen - open buffered file stream */
+
+VSTREAM *vstream_fopen(const char *path, int flags, mode_t mode)
+{
+ VSTREAM *stream;
+ int fd;
+
+ if ((fd = open(path, flags, mode)) < 0) {
+ return (0);
+ } else {
+ stream = vstream_fdopen(fd, flags);
+ stream->path = mystrdup(path);
+ return (stream);
+ }
+}
+
+/* vstream_fflush - flush write buffer */
+
+int vstream_fflush(VSTREAM *stream)
+{
+
+ /*
+ * With VSTRING, the write pointer must be positioned behind the end of
+ * data. But vstream_fseek() changes the write position, and causes the
+ * data length to be forgotten. Before flushing to vstream, remember the
+ * current write position, move the write pointer and do what needs to be
+ * done, then move the write pointer back to the saved location.
+ */
+ if (stream->buf.flags & VSTREAM_FLAG_MEMORY) {
+ if (stream->buf.flags & VSTREAM_FLAG_WRITE) {
+ VSTRING *string = stream->vstring;
+
+#ifdef PENDING_VSTREAM_FSEEK_FOR_MEMORY
+ VSTREAM_BUF_AT_OFFSET(&stream->buf, stream->buf.data_len);
+#endif
+ memcpy(&string->vbuf, &stream->buf, sizeof(stream->buf));
+ string->vbuf.flags &= VSTRING_FLAG_MASK;
+ VSTRING_TERMINATE(string);
+ }
+ return (0);
+ }
+ if ((stream->buf.flags & VSTREAM_FLAG_READ_DOUBLE)
+ == VSTREAM_FLAG_READ_DOUBLE
+ && stream->write_buf.len > stream->write_buf.cnt)
+ vstream_fflush_delayed(stream);
+ return (VSTREAM_FFLUSH_SOME(stream));
+}
+
+/* vstream_fclose - close buffered stream */
+
+int vstream_fclose(VSTREAM *stream)
+{
+ int err;
+
+ /*
+ * NOTE: Negative file descriptors are not part of the external
+ * interface. They are for internal use only, in order to support
+ * vstream_fdclose() without a lot of code duplication. Applications that
+ * rely on negative VSTREAM file descriptors will break without warning.
+ */
+ if (stream->pid != 0)
+ msg_panic("vstream_fclose: stream has process");
+ if ((stream->buf.flags & VSTREAM_FLAG_MEMORY)
+ || ((stream->buf.flags & VSTREAM_FLAG_WRITE_DOUBLE) != 0
+ && stream->fd >= 0))
+ vstream_fflush(stream);
+ /* Do not remove: vstream_fdclose() depends on this error test. */
+ err = vstream_ferror(stream);
+ if (stream->buf.flags & VSTREAM_FLAG_DOUBLE) {
+ if (stream->read_fd >= 0)
+ err |= close(stream->read_fd);
+ if (stream->write_fd != stream->read_fd)
+ if (stream->write_fd >= 0)
+ err |= close(stream->write_fd);
+ vstream_buf_wipe(&stream->read_buf);
+ vstream_buf_wipe(&stream->write_buf);
+ stream->buf = stream->read_buf;
+ } else {
+ if (stream->fd >= 0)
+ err |= close(stream->fd);
+ if ((stream->buf.flags & VSTREAM_FLAG_MEMORY) == 0)
+ vstream_buf_wipe(&stream->buf);
+ }
+ if (stream->path)
+ myfree(stream->path);
+ if (stream->jbuf)
+ myfree((void *) stream->jbuf);
+ if (stream->vstring && (stream->buf.flags & VSTREAM_FLAG_OWN_VSTRING))
+ vstring_free(stream->vstring);
+ if (!VSTREAM_STATIC(stream))
+ myfree((void *) stream);
+ return (err ? VSTREAM_EOF : 0);
+}
+
+/* vstream_fdclose - close stream, leave file(s) open */
+
+int vstream_fdclose(VSTREAM *stream)
+{
+
+ /*
+ * Flush unwritten output, just like vstream_fclose(). Errors are
+ * reported by vstream_fclose().
+ */
+ if ((stream->buf.flags & VSTREAM_FLAG_WRITE_DOUBLE) != 0)
+ (void) vstream_fflush(stream);
+
+ /*
+ * NOTE: Negative file descriptors are not part of the external
+ * interface. They are for internal use only, in order to support
+ * vstream_fdclose() without a lot of code duplication. Applications that
+ * rely on negative VSTREAM file descriptors will break without warning.
+ */
+ if (stream->buf.flags & VSTREAM_FLAG_DOUBLE) {
+ stream->fd = stream->read_fd = stream->write_fd = -1;
+ } else {
+ stream->fd = -1;
+ }
+ return (vstream_fclose(stream));
+}
+
+/* vstream_printf - formatted print to stdout */
+
+VSTREAM *vstream_printf(const char *fmt,...)
+{
+ VSTREAM *stream = VSTREAM_OUT;
+ va_list ap;
+
+ va_start(ap, fmt);
+ vbuf_print(&stream->buf, fmt, ap);
+ va_end(ap);
+ return (stream);
+}
+
+/* vstream_fprintf - formatted print to buffered stream */
+
+VSTREAM *vstream_fprintf(VSTREAM *stream, const char *fmt,...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vbuf_print(&stream->buf, fmt, ap);
+ va_end(ap);
+ return (stream);
+}
+
+/* vstream_fputs - write string to stream */
+
+int vstream_fputs(const char *str, VSTREAM *stream)
+{
+ int ch;
+
+ while ((ch = *str++) != 0)
+ if (VSTREAM_PUTC(ch, stream) == VSTREAM_EOF)
+ return (VSTREAM_EOF);
+ return (0);
+}
+
+/* vstream_fread_buf - unformatted read to VSTRING */
+
+ssize_t vstream_fread_buf(VSTREAM *fp, VSTRING *vp, ssize_t len)
+{
+ ssize_t ret;
+
+ VSTRING_RESET(vp);
+ VSTRING_SPACE(vp, len);
+ ret = vstream_fread(fp, vstring_str(vp), len);
+ if (ret > 0)
+ VSTRING_AT_OFFSET(vp, ret);
+ return (ret);
+}
+
+/* vstream_fread_app - unformatted read to VSTRING */
+
+ssize_t vstream_fread_app(VSTREAM *fp, VSTRING *vp, ssize_t len)
+{
+ ssize_t ret;
+
+ VSTRING_SPACE(vp, len);
+ ret = vstream_fread(fp, vstring_end(vp), len);
+ if (ret > 0)
+ VSTRING_AT_OFFSET(vp, VSTRING_LEN(vp) + ret);
+ return (ret);
+}
+
+/* vstream_control - fine control */
+
+void vstream_control(VSTREAM *stream, int name,...)
+{
+ const char *myname = "vstream_control";
+ va_list ap;
+ int floor;
+ int old_fd;
+ ssize_t req_bufsize = 0;
+ VSTREAM *stream2;
+ int min_data_rate;
+
+#define SWAP(type,a,b) do { type temp = (a); (a) = (b); (b) = (temp); } while (0)
+
+ /*
+ * A crude 'allow' filter for memory streams.
+ */
+ int memory_ops =
+ ((1 << VSTREAM_CTL_END) | (1 << VSTREAM_CTL_CONTEXT)
+ | (1 << VSTREAM_CTL_PATH) | (1 << VSTREAM_CTL_EXCEPT)
+ | (1 << VSTREAM_CTL_OWN_VSTRING));
+
+ for (va_start(ap, name); name != VSTREAM_CTL_END; name = va_arg(ap, int)) {
+ if ((stream->buf.flags & VSTREAM_FLAG_MEMORY)
+ && (memory_ops & (1 << name)) == 0)
+ msg_panic("%s: memory stream does not support VSTREAM_CTL_%d",
+ VSTREAM_PATH(stream), name);
+ switch (name) {
+ case VSTREAM_CTL_READ_FN:
+ stream->read_fn = va_arg(ap, VSTREAM_RW_FN);
+ break;
+ case VSTREAM_CTL_WRITE_FN:
+ stream->write_fn = va_arg(ap, VSTREAM_RW_FN);
+ break;
+ case VSTREAM_CTL_CONTEXT:
+ stream->context = va_arg(ap, void *);
+ break;
+ case VSTREAM_CTL_PATH:
+ if (stream->path)
+ myfree(stream->path);
+ stream->path = mystrdup(va_arg(ap, char *));
+ break;
+ case VSTREAM_CTL_DOUBLE:
+ if ((stream->buf.flags & VSTREAM_FLAG_DOUBLE) == 0) {
+ stream->buf.flags |= VSTREAM_FLAG_DOUBLE;
+ if (stream->buf.flags & VSTREAM_FLAG_READ) {
+ VSTREAM_SAVE_STATE(stream, read_buf, read_fd);
+ VSTREAM_FORK_STATE(stream, write_buf, write_fd);
+ } else {
+ VSTREAM_SAVE_STATE(stream, write_buf, write_fd);
+ VSTREAM_FORK_STATE(stream, read_buf, read_fd);
+ }
+ }
+ break;
+ case VSTREAM_CTL_READ_FD:
+ if ((stream->buf.flags & VSTREAM_FLAG_DOUBLE) == 0)
+ msg_panic("VSTREAM_CTL_READ_FD requires double buffering");
+ stream->read_fd = va_arg(ap, int);
+ stream->buf.flags |= VSTREAM_FLAG_NSEEK;
+ break;
+ case VSTREAM_CTL_WRITE_FD:
+ if ((stream->buf.flags & VSTREAM_FLAG_DOUBLE) == 0)
+ msg_panic("VSTREAM_CTL_WRITE_FD requires double buffering");
+ stream->write_fd = va_arg(ap, int);
+ stream->buf.flags |= VSTREAM_FLAG_NSEEK;
+ break;
+ case VSTREAM_CTL_SWAP_FD:
+ stream2 = va_arg(ap, VSTREAM *);
+ if ((stream->buf.flags & VSTREAM_FLAG_DOUBLE)
+ != (stream2->buf.flags & VSTREAM_FLAG_DOUBLE))
+ msg_panic("VSTREAM_CTL_SWAP_FD can't swap descriptors between "
+ "single-buffered and double-buffered streams");
+ if (stream->buf.flags & VSTREAM_FLAG_DOUBLE) {
+ SWAP(int, stream->read_fd, stream2->read_fd);
+ SWAP(int, stream->write_fd, stream2->write_fd);
+ stream->fd = ((stream->buf.flags & VSTREAM_FLAG_WRITE) ?
+ stream->write_fd : stream->read_fd);
+ } else {
+ SWAP(int, stream->fd, stream2->fd);
+ }
+ break;
+ case VSTREAM_CTL_TIMEOUT:
+ if (stream->timeout == 0)
+ GETTIMEOFDAY(&stream->iotime);
+ stream->timeout = va_arg(ap, int);
+ if (stream->timeout < 0)
+ msg_panic("%s: bad timeout %d", myname, stream->timeout);
+ break;
+ case VSTREAM_CTL_EXCEPT:
+ if (stream->jbuf == 0)
+ stream->jbuf =
+ (VSTREAM_JMP_BUF *) mymalloc(sizeof(VSTREAM_JMP_BUF));
+ break;
+
+#ifdef VSTREAM_CTL_DUPFD
+
+#define VSTREAM_TRY_DUPFD(backup, fd, floor) do { \
+ if (((backup) = (fd)) < floor) { \
+ if (((fd) = fcntl((backup), F_DUPFD, (floor))) < 0) \
+ msg_fatal("fcntl F_DUPFD %d: %m", (floor)); \
+ (void) close(backup); \
+ } \
+ } while (0)
+
+ case VSTREAM_CTL_DUPFD:
+ floor = va_arg(ap, int);
+ if (stream->buf.flags & VSTREAM_FLAG_DOUBLE) {
+ VSTREAM_TRY_DUPFD(old_fd, stream->read_fd, floor);
+ if (stream->write_fd == old_fd)
+ stream->write_fd = stream->read_fd;
+ else
+ VSTREAM_TRY_DUPFD(old_fd, stream->write_fd, floor);
+ stream->fd = (stream->buf.flags & VSTREAM_FLAG_READ) ?
+ stream->read_fd : stream->write_fd;
+ } else {
+ VSTREAM_TRY_DUPFD(old_fd, stream->fd, floor);
+ }
+ break;
+#endif
+
+ /*
+ * Postpone memory (re)allocation until the space is needed.
+ */
+ case VSTREAM_CTL_BUFSIZE:
+ req_bufsize = va_arg(ap, ssize_t);
+ /* Heuristic to detect missing (ssize_t) type cast on LP64 hosts. */
+ if (req_bufsize < 0 || req_bufsize > INT_MAX)
+ msg_panic("unreasonable VSTREAM_CTL_BUFSIZE request: %ld",
+ (long) req_bufsize);
+ if ((stream->buf.flags & VSTREAM_FLAG_FIXED) == 0
+ && req_bufsize > stream->req_bufsize) {
+ if (msg_verbose)
+ msg_info("fd=%d: stream buffer size old=%ld new=%ld",
+ vstream_fileno(stream),
+ (long) stream->req_bufsize,
+ (long) req_bufsize);
+ stream->req_bufsize = req_bufsize;
+ }
+ break;
+
+ /*
+ * Make no gettimeofday() etc. system call until we really know
+ * that we need to do I/O. This avoids a performance hit when
+ * sending or receiving body content one line at a time.
+ */
+ case VSTREAM_CTL_STOP_DEADLINE:
+ stream->buf.flags &= ~VSTREAM_FLAG_DEADLINE;
+ break;
+ case VSTREAM_CTL_START_DEADLINE:
+ if (stream->timeout <= 0)
+ msg_panic("%s: bad timeout %d", myname, stream->timeout);
+ stream->buf.flags |= VSTREAM_FLAG_DEADLINE;
+ stream->time_limit.tv_sec = stream->timeout;
+ stream->time_limit.tv_usec = 0;
+ break;
+ case VSTREAM_CTL_MIN_DATA_RATE:
+ min_data_rate = va_arg(ap, int);
+ if (min_data_rate < 0)
+ msg_panic("%s: bad min_data_rate %d", myname, min_data_rate);
+ stream->min_data_rate = min_data_rate;
+ break;
+ case VSTREAM_CTL_OWN_VSTRING:
+ if ((stream->buf.flags |= VSTREAM_FLAG_MEMORY) == 0)
+ msg_panic("%s: operation on non-VSTRING stream", myname);
+ stream->buf.flags |= VSTREAM_FLAG_OWN_VSTRING;
+ break;
+ default:
+ msg_panic("%s: bad name %d", myname, name);
+ }
+ }
+ va_end(ap);
+}
+
+/* vstream_vprintf - formatted print to stdout */
+
+VSTREAM *vstream_vprintf(const char *format, va_list ap)
+{
+ VSTREAM *vp = VSTREAM_OUT;
+
+ vbuf_print(&vp->buf, format, ap);
+ return (vp);
+}
+
+/* vstream_vfprintf - formatted print engine */
+
+VSTREAM *vstream_vfprintf(VSTREAM *vp, const char *format, va_list ap)
+{
+ vbuf_print(&vp->buf, format, ap);
+ return (vp);
+}
+
+/* vstream_bufstat - get stream buffer status */
+
+ssize_t vstream_bufstat(VSTREAM *vp, int command)
+{
+ VBUF *bp;
+
+ switch (command & VSTREAM_BST_MASK_DIR) {
+ case VSTREAM_BST_FLAG_IN:
+ if (vp->buf.flags & VSTREAM_FLAG_READ) {
+ bp = &vp->buf;
+ } else if (vp->buf.flags & VSTREAM_FLAG_DOUBLE) {
+ bp = &vp->read_buf;
+ } else {
+ bp = 0;
+ }
+ switch (command & ~VSTREAM_BST_MASK_DIR) {
+ case VSTREAM_BST_FLAG_PEND:
+ return (bp ? -bp->cnt : 0);
+ /* Add other requests below. */
+ }
+ break;
+ case VSTREAM_BST_FLAG_OUT:
+ if (vp->buf.flags & VSTREAM_FLAG_WRITE) {
+ bp = &vp->buf;
+ } else if (vp->buf.flags & VSTREAM_FLAG_DOUBLE) {
+ bp = &vp->write_buf;
+ } else {
+ bp = 0;
+ }
+ switch (command & ~VSTREAM_BST_MASK_DIR) {
+ case VSTREAM_BST_FLAG_PEND:
+ return (bp ? bp->len - bp->cnt : 0);
+ /* Add other requests below. */
+ }
+ break;
+ }
+ msg_panic("vstream_bufstat: unknown command: %d", command);
+}
+
+#undef vstream_peek /* API binary compatibility. */
+
+/* vstream_peek - peek at a stream */
+
+ssize_t vstream_peek(VSTREAM *vp)
+{
+ if (vp->buf.flags & VSTREAM_FLAG_READ) {
+ return (-vp->buf.cnt);
+ } else if (vp->buf.flags & VSTREAM_FLAG_DOUBLE) {
+ return (-vp->read_buf.cnt);
+ } else {
+ return (0);
+ }
+}
+
+/* vstream_peek_data - peek at unread data */
+
+const char *vstream_peek_data(VSTREAM *vp)
+{
+ if (vp->buf.flags & VSTREAM_FLAG_READ) {
+ return ((const char *) vp->buf.ptr);
+ } else if (vp->buf.flags & VSTREAM_FLAG_DOUBLE) {
+ return ((const char *) vp->read_buf.ptr);
+ } else {
+ return (0);
+ }
+}
+
+/* vstream_memopen - open a VSTRING */
+
+VSTREAM *vstream_memreopen(VSTREAM *stream, VSTRING *string, int flags)
+{
+ if (stream == 0)
+ stream = vstream_subopen();
+ else if ((stream->buf.flags & VSTREAM_FLAG_MEMORY) == 0)
+ msg_panic("vstream_memreopen: cannot reopen non-memory stream");
+ stream->fd = -1;
+ stream->read_fn = 0;
+ stream->write_fn = 0;
+ stream->vstring = string;
+ memcpy(&stream->buf, &stream->vstring->vbuf, sizeof(stream->buf));
+ stream->buf.flags |= VSTREAM_FLAG_MEMORY;
+ switch (VSTREAM_ACC_MASK(flags)) {
+ case O_RDONLY:
+ stream->buf.flags |= VSTREAM_FLAG_READ;
+ /* Prevent reading unwritten data after vstream_fseek(). */
+ stream->buf.len = stream->buf.ptr - stream->buf.data;
+ VSTREAM_BUF_AT_OFFSET(&stream->buf, 0);
+ break;
+ case O_WRONLY:
+ stream->buf.flags |= VSTREAM_FLAG_WRITE;
+ VSTREAM_BUF_AT_OFFSET(&stream->buf, 0);
+ break;
+ case O_APPEND:
+ stream->buf.flags |= VSTREAM_FLAG_WRITE;
+ VSTREAM_BUF_AT_OFFSET(&stream->buf,
+ stream->buf.ptr - stream->buf.data);
+ break;
+ default:
+ msg_panic("vstream_memopen: flags must be one of "
+ "O_RDONLY, O_WRONLY, or O_APPEND");
+ }
+ return (stream);
+}
+
+#ifdef TEST
+
+static void copy_line(ssize_t bufsize)
+{
+ int c;
+
+ /*
+ * Demonstrates that VSTREAM_CTL_BUFSIZE increases the buffer size, but
+ * does not decrease it. Uses VSTREAM_ERR for non-test output to avoid
+ * interfering with the test.
+ */
+ vstream_fprintf(VSTREAM_ERR, "buffer size test: copy text with %ld buffer size, ignore requests to shrink\n",
+ (long) bufsize);
+ vstream_fflush(VSTREAM_ERR);
+ vstream_control(VSTREAM_IN, CA_VSTREAM_CTL_BUFSIZE(bufsize), VSTREAM_CTL_END);
+ vstream_control(VSTREAM_OUT, CA_VSTREAM_CTL_BUFSIZE(bufsize), VSTREAM_CTL_END);
+ while ((c = VSTREAM_GETC(VSTREAM_IN)) != VSTREAM_EOF) {
+ VSTREAM_PUTC(c, VSTREAM_OUT);
+ if (c == '\n')
+ break;
+ }
+ vstream_fflush(VSTREAM_OUT);
+ vstream_fprintf(VSTREAM_ERR, "actual read/write buffer sizes: %ld/%ld\n\n",
+ (long) VSTREAM_IN->buf.len, (long) VSTREAM_OUT->buf.len);
+ vstream_fflush(VSTREAM_ERR);
+}
+
+static void printf_number(void)
+{
+
+ /*
+ * Demonstrates that vstream_printf() use vbuf_print().
+ */
+ vstream_printf("formatting test: print a number\n");
+ vstream_printf("%d\n\n", 1234567890);
+ vstream_fflush(VSTREAM_OUT);
+}
+
+static void do_memory_stream(void)
+{
+ VSTRING *buf = vstring_alloc(1);
+ VSTREAM *fp;
+ off_t offset;
+ int ch;
+
+ /*
+ * Preload the string.
+ */
+ vstream_printf("memory stream test prep: prefill the VSTRING\n");
+ vstring_strcpy(buf, "01234567");
+ vstream_printf("VSTRING content length: %ld/%ld, content: %s\n",
+ (long) VSTRING_LEN(buf), (long) buf->vbuf.len,
+ vstring_str(buf));
+ VSTREAM_PUTCHAR('\n');
+ vstream_fflush(VSTREAM_OUT);
+
+ /*
+ * Test: open the memory VSTREAM in write-only mode, and clobber it.
+ */
+ vstream_printf("memory stream test: open the VSTRING for writing, overwrite, close\n");
+ fp = vstream_memopen(buf, O_WRONLY);
+ vstream_printf("initial memory VSTREAM write offset: %ld/%ld\n",
+ (long) vstream_ftell(fp), (long) fp->buf.len);
+ vstream_fprintf(fp, "hallo");
+ vstream_printf("final memory VSTREAM write offset: %ld/%ld\n",
+ (long) vstream_ftell(fp), (long) fp->buf.len);
+ vstream_fclose(fp);
+ vstream_printf("VSTRING content length: %ld/%ld, content: %s\n",
+ (long) VSTRING_LEN(buf), (long) buf->vbuf.len,
+ vstring_str(buf));
+ VSTREAM_PUTCHAR('\n');
+ vstream_fflush(VSTREAM_OUT);
+
+ /*
+ * Test: open the memory VSTREAM for append. vstream_memopen() sets the
+ * buffer length to the VSTRING buffer length, and positions the write
+ * pointer at the VSTRING write position. Write some content, then
+ * overwrite one character.
+ */
+ vstream_printf("memory stream test: open the VSTRING for append, write multiple, then overwrite 1\n");
+ fp = vstream_memopen(buf, O_APPEND);
+ vstream_printf("initial memory VSTREAM write offset: %ld/%ld\n",
+ (long) vstream_ftell(fp), (long) fp->buf.len);
+ vstream_fprintf(fp, " world");
+ vstream_printf("final memory VSTREAM write offset: %ld/%ld\n",
+ (long) vstream_ftell(fp), (long) fp->buf.len);
+ if (vstream_fflush(fp))
+ msg_fatal("vstream_fflush: %m");
+ vstream_printf("VSTRING content length: %ld/%ld, content: %s\n",
+ (long) VSTRING_LEN(buf), (long) buf->vbuf.len,
+ vstring_str(buf));
+ VSTREAM_PUTCHAR('\n');
+
+ /*
+ * While the stream is still open, replace the second character.
+ */
+ vstream_printf("replace second character and close\n");
+ if ((offset = vstream_fseek(fp, 1, SEEK_SET)) != 1)
+ msg_panic("unexpected vstream_fseek return: %ld, expected: %ld",
+ (long) offset, (long) 1);
+ VSTREAM_PUTC('e', fp);
+
+ /*
+ * Skip to the end of the content, so that vstream_fflush() will update
+ * the VSTRING with the right content length.
+ */
+ if ((offset = vstream_fseek(fp, VSTRING_LEN(buf), SEEK_SET)) != VSTRING_LEN(buf))
+ msg_panic("unexpected vstream_fseek return: %ld, expected: %ld",
+ (long) offset, (long) VSTRING_LEN(buf));
+ vstream_fclose(fp);
+
+ vstream_printf("VSTRING content length: %ld/%ld, content: %s\n",
+ (long) VSTRING_LEN(buf), (long) buf->vbuf.len,
+ vstring_str(buf));
+ VSTREAM_PUTCHAR('\n');
+ vstream_fflush(VSTREAM_OUT);
+
+ /*
+ * TODO: test that in write/append mode, seek past the end of data will
+ * result in zero-filled space.
+ */
+
+ /*
+ * Test: Open the VSTRING for reading. This time, vstream_memopen() will
+ * set the VSTREAM buffer length to the content length of the VSTRING, so
+ * that it won't attempt to read past the end of the content.
+ */
+ vstream_printf("memory stream test: open VSTRING for reading, then read\n");
+ fp = vstream_memopen(buf, O_RDONLY);
+ vstream_printf("initial memory VSTREAM read offset: %ld/%ld\n",
+ (long) vstream_ftell(fp), (long) fp->buf.len);
+ vstream_printf("reading memory VSTREAM: ");
+ while ((ch = VSTREAM_GETC(fp)) != VSTREAM_EOF)
+ VSTREAM_PUTCHAR(ch);
+ VSTREAM_PUTCHAR('\n');
+ vstream_printf("final memory VSTREAM read offset: %ld/%ld\n",
+ (long) vstream_ftell(fp), (long) fp->buf.len);
+ vstream_printf("seeking to offset %ld should work: ",
+ (long) fp->buf.len + 1);
+ vstream_fflush(VSTREAM_OUT);
+ if ((offset = vstream_fseek(fp, fp->buf.len + 1, SEEK_SET)) != fp->buf.len + 1)
+ msg_panic("unexpected vstream_fseek return: %ld, expected: %ld",
+ (long) offset, (long) fp->buf.len + 1);
+ vstream_printf("PASS\n");
+ vstream_fflush(VSTREAM_OUT);
+ vstream_printf("VSTREAM_GETC should return VSTREAM_EOF\n");
+ ch = VSTREAM_GETC(fp);
+ if (ch != VSTREAM_EOF)
+ msg_panic("unexpected vstream_fseek VSTREAM_GETC return: %d, expected: %d",
+ ch, VSTREAM_EOF);
+ vstream_printf("PASS\n");
+ vstream_printf("final memory VSTREAM read offset: %ld/%ld\n",
+ (long) vstream_ftell(fp), (long) fp->buf.len);
+ vstream_printf("VSTRING content length: %ld/%ld, content: %s\n",
+ (long) VSTRING_LEN(buf), (long) buf->vbuf.len,
+ vstring_str(buf));
+ VSTREAM_PUTCHAR('\n');
+ vstream_fflush(VSTREAM_OUT);
+ vstream_fclose(fp);
+ vstring_free(buf);
+}
+
+ /*
+ * Exercise some of the features.
+ */
+
+#include <msg_vstream.h>
+
+int main(int argc, char **argv)
+{
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+
+ /*
+ * Test buffer expansion and shrinking. Formatted print may silently
+ * expand the write buffer and cause multiple bytes to be written.
+ */
+ copy_line(1); /* one-byte read/write */
+ copy_line(2); /* two-byte read/write */
+ copy_line(1); /* two-byte read/write */
+ printf_number(); /* multi-byte write */
+ do_memory_stream();
+
+ exit(0);
+}
+
+#endif
diff --git a/src/util/vstream.h b/src/util/vstream.h
new file mode 100644
index 0000000..23688c7
--- /dev/null
+++ b/src/util/vstream.h
@@ -0,0 +1,293 @@
+#ifndef _VSTREAM_H_INCLUDED_
+#define _VSTREAM_H_INCLUDED_
+
+/*++
+/* NAME
+/* vstream 3h
+/* SUMMARY
+/* simple buffered I/O package
+/* SYNOPSIS
+/* #include <vstream.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <sys/time.h>
+#include <time.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <setjmp.h>
+#include <unistd.h>
+
+ /*
+ * Utility library.
+ */
+#include <vbuf.h>
+#include <check_arg.h>
+
+ /*
+ * Simple buffered stream. The members of this structure are not part of the
+ * official interface and can change without prior notice.
+ */
+typedef ssize_t (*VSTREAM_RW_FN) (int, void *, size_t, int, void *);
+typedef pid_t(*VSTREAM_WAITPID_FN) (pid_t, WAIT_STATUS_T *, int);
+
+#ifdef NO_SIGSETJMP
+#define VSTREAM_JMP_BUF jmp_buf
+#else
+#define VSTREAM_JMP_BUF sigjmp_buf
+#endif
+
+typedef struct VSTREAM {
+ VBUF buf; /* generic intelligent buffer */
+ int fd; /* file handle, no 256 limit */
+ VSTREAM_RW_FN read_fn; /* buffer fill action */
+ VSTREAM_RW_FN write_fn; /* buffer flush action */
+ ssize_t req_bufsize; /* requested read/write buffer size */
+ void *context; /* application context */
+ off_t offset; /* cached seek info */
+ char *path; /* give it at least try */
+ int read_fd; /* read channel (double-buffered) */
+ int write_fd; /* write channel (double-buffered) */
+ VBUF read_buf; /* read buffer (double-buffered) */
+ VBUF write_buf; /* write buffer (double-buffered) */
+ pid_t pid; /* vstream_popen/close() */
+ VSTREAM_WAITPID_FN waitpid_fn; /* vstream_popen/close() */
+ int timeout; /* read/write timeout */
+ VSTREAM_JMP_BUF *jbuf; /* exception handling */
+ struct timeval iotime; /* time of last fill/flush */
+ struct timeval time_limit; /* read/write time limit */
+ int min_data_rate; /* min data rate for time limit */
+ struct VSTRING *vstring; /* memory-backed stream */
+} VSTREAM;
+
+extern VSTREAM vstream_fstd[]; /* pre-defined streams */
+
+#define VSTREAM_IN (&vstream_fstd[0])
+#define VSTREAM_OUT (&vstream_fstd[1])
+#define VSTREAM_ERR (&vstream_fstd[2])
+
+#define VSTREAM_FLAG_RD_ERR VBUF_FLAG_RD_ERR /* read error */
+#define VSTREAM_FLAG_WR_ERR VBUF_FLAG_WR_ERR /* write error */
+#define VSTREAM_FLAG_RD_TIMEOUT VBUF_FLAG_RD_TIMEOUT /* read timeout */
+#define VSTREAM_FLAG_WR_TIMEOUT VBUF_FLAG_WR_TIMEOUT /* write timeout */
+
+#define VSTREAM_FLAG_ERR VBUF_FLAG_ERR /* some I/O error */
+#define VSTREAM_FLAG_EOF VBUF_FLAG_EOF /* end of file */
+#define VSTREAM_FLAG_TIMEOUT VBUF_FLAG_TIMEOUT /* timeout error */
+#define VSTREAM_FLAG_FIXED VBUF_FLAG_FIXED /* fixed-size buffer */
+#define VSTREAM_FLAG_BAD VBUF_FLAG_BAD
+
+/* Flags 1<<24 and above are reserved for VSTRING. */
+#define VSTREAM_FLAG_READ (1<<8) /* read buffer */
+#define VSTREAM_FLAG_WRITE (1<<9) /* write buffer */
+#define VSTREAM_FLAG_SEEK (1<<10) /* seek info valid */
+#define VSTREAM_FLAG_NSEEK (1<<11) /* can't seek this file */
+#define VSTREAM_FLAG_DOUBLE (1<<12) /* double buffer */
+#define VSTREAM_FLAG_DEADLINE (1<<13) /* deadline active */
+#define VSTREAM_FLAG_MEMORY (1<<14) /* internal stream */
+#define VSTREAM_FLAG_OWN_VSTRING (1<<15)/* owns VSTRING resource */
+
+#define VSTREAM_PURGE_READ (1<<0) /* flush unread data */
+#define VSTREAM_PURGE_WRITE (1<<1) /* flush unwritten data */
+#define VSTREAM_PURGE_BOTH (VSTREAM_PURGE_READ|VSTREAM_PURGE_WRITE)
+
+#define VSTREAM_BUFSIZE 4096
+
+extern VSTREAM *vstream_fopen(const char *, int, mode_t);
+extern int vstream_fclose(VSTREAM *);
+extern off_t WARN_UNUSED_RESULT vstream_fseek(VSTREAM *, off_t, int);
+extern off_t vstream_ftell(VSTREAM *);
+extern int vstream_fpurge(VSTREAM *, int);
+extern int vstream_fflush(VSTREAM *);
+extern int vstream_fputs(const char *, VSTREAM *);
+extern VSTREAM *vstream_fdopen(int, int);
+extern int vstream_fdclose(VSTREAM *);
+
+#define vstream_fread(v, b, n) vbuf_read(&(v)->buf, (b), (n))
+#define vstream_fwrite(v, b, n) vbuf_write(&(v)->buf, (b), (n))
+
+#define VSTREAM_PUTC(ch, vp) VBUF_PUT(&(vp)->buf, (ch))
+#define VSTREAM_GETC(vp) VBUF_GET(&(vp)->buf)
+#define vstream_ungetc(vp, ch) vbuf_unget(&(vp)->buf, (ch))
+#define VSTREAM_EOF VBUF_EOF
+
+#define VSTREAM_PUTCHAR(ch) VSTREAM_PUTC((ch), VSTREAM_OUT)
+#define VSTREAM_GETCHAR() VSTREAM_GETC(VSTREAM_IN)
+
+#define vstream_fileno(vp) ((vp)->fd)
+#define vstream_req_bufsize(vp) ((const ssize_t) ((vp)->req_bufsize))
+#define vstream_context(vp) ((vp)->context)
+#define vstream_rd_error(vp) vbuf_rd_error(&(vp)->buf)
+#define vstream_wr_error(vp) vbuf_wr_error(&(vp)->buf)
+#define vstream_ferror(vp) vbuf_error(&(vp)->buf)
+#define vstream_feof(vp) vbuf_eof(&(vp)->buf)
+#define vstream_rd_timeout(vp) vbuf_rd_timeout(&(vp)->buf)
+#define vstream_wr_timeout(vp) vbuf_wr_timeout(&(vp)->buf)
+#define vstream_ftimeout(vp) vbuf_timeout(&(vp)->buf)
+#define vstream_clearerr(vp) vbuf_clearerr(&(vp)->buf)
+#define VSTREAM_PATH(vp) ((vp)->path ? (const char *) (vp)->path : "unknown_stream")
+#define vstream_ftime(vp) ((time_t) ((vp)->iotime.tv_sec))
+#define vstream_ftimeval(vp) ((vp)->iotime)
+
+#define vstream_fstat(vp, fl) ((vp)->buf.flags & (fl))
+
+extern ssize_t vstream_fread_buf(VSTREAM *, struct VSTRING *, ssize_t);
+extern ssize_t vstream_fread_app(VSTREAM *, struct VSTRING *, ssize_t);
+extern void vstream_control(VSTREAM *, int,...);
+
+/* Legacy API: type-unchecked arguments, internal use. */
+#define VSTREAM_CTL_END 0
+#define VSTREAM_CTL_READ_FN 1
+#define VSTREAM_CTL_WRITE_FN 2
+#define VSTREAM_CTL_PATH 3
+#define VSTREAM_CTL_DOUBLE 4
+#define VSTREAM_CTL_READ_FD 5
+#define VSTREAM_CTL_WRITE_FD 6
+#define VSTREAM_CTL_WAITPID_FN 7
+#define VSTREAM_CTL_TIMEOUT 8
+#define VSTREAM_CTL_EXCEPT 9
+#define VSTREAM_CTL_CONTEXT 10
+#ifdef F_DUPFD
+#define VSTREAM_CTL_DUPFD 11
+#endif
+#define VSTREAM_CTL_BUFSIZE 12
+#define VSTREAM_CTL_SWAP_FD 13
+#define VSTREAM_CTL_START_DEADLINE 14
+#define VSTREAM_CTL_STOP_DEADLINE 15
+#define VSTREAM_CTL_OWN_VSTRING 16
+#define VSTREAM_CTL_MIN_DATA_RATE 17
+
+/* Safer API: type-checked arguments, external use. */
+#define CA_VSTREAM_CTL_END VSTREAM_CTL_END
+#define CA_VSTREAM_CTL_READ_FN(v) VSTREAM_CTL_READ_FN, CHECK_VAL(VSTREAM_CTL, VSTREAM_RW_FN, (v))
+#define CA_VSTREAM_CTL_WRITE_FN(v) VSTREAM_CTL_WRITE_FN, CHECK_VAL(VSTREAM_CTL, VSTREAM_RW_FN, (v))
+#define CA_VSTREAM_CTL_PATH(v) VSTREAM_CTL_PATH, CHECK_CPTR(VSTREAM_CTL, char, (v))
+#define CA_VSTREAM_CTL_DOUBLE VSTREAM_CTL_DOUBLE
+#define CA_VSTREAM_CTL_READ_FD(v) VSTREAM_CTL_READ_FD, CHECK_VAL(VSTREAM_CTL, int, (v))
+#define CA_VSTREAM_CTL_WRITE_FD(v) VSTREAM_CTL_WRITE_FD, CHECK_VAL(VSTREAM_CTL, int, (v))
+#define CA_VSTREAM_CTL_WAITPID_FN(v) VSTREAM_CTL_WAITPID_FN, CHECK_VAL(VSTREAM_CTL, VSTREAM_WAITPID_FN, (v))
+#define CA_VSTREAM_CTL_TIMEOUT(v) VSTREAM_CTL_TIMEOUT, CHECK_VAL(VSTREAM_CTL, int, (v))
+#define CA_VSTREAM_CTL_EXCEPT VSTREAM_CTL_EXCEPT
+#define CA_VSTREAM_CTL_CONTEXT(v) VSTREAM_CTL_CONTEXT, CHECK_PTR(VSTREAM_CTL, void, (v))
+#ifdef F_DUPFD
+#define CA_VSTREAM_CTL_DUPFD(v) VSTREAM_CTL_DUPFD, CHECK_VAL(VSTREAM_CTL, int, (v))
+#endif
+#define CA_VSTREAM_CTL_BUFSIZE(v) VSTREAM_CTL_BUFSIZE, CHECK_VAL(VSTREAM_CTL, ssize_t, (v))
+#define CA_VSTREAM_CTL_SWAP_FD(v) VSTREAM_CTL_SWAP_FD, CHECK_PTR(VSTREAM_CTL, VSTREAM, (v))
+#define CA_VSTREAM_CTL_START_DEADLINE VSTREAM_CTL_START_DEADLINE
+#define CA_VSTREAM_CTL_STOP_DEADLINE VSTREAM_CTL_STOP_DEADLINE
+#define CA_VSTREAM_CTL_MIN_DATA_RATE(v) VSTREAM_CTL_MIN_DATA_RATE, CHECK_VAL(VSTREAM_CTL, int, (v))
+
+CHECK_VAL_HELPER_DCL(VSTREAM_CTL, ssize_t);
+CHECK_VAL_HELPER_DCL(VSTREAM_CTL, int);
+CHECK_VAL_HELPER_DCL(VSTREAM_CTL, VSTREAM_WAITPID_FN);
+CHECK_VAL_HELPER_DCL(VSTREAM_CTL, VSTREAM_RW_FN);
+CHECK_PTR_HELPER_DCL(VSTREAM_CTL, void);
+CHECK_PTR_HELPER_DCL(VSTREAM_CTL, VSTREAM);
+CHECK_CPTR_HELPER_DCL(VSTREAM_CTL, char);
+
+extern VSTREAM *PRINTFLIKE(1, 2) vstream_printf(const char *,...);
+extern VSTREAM *PRINTFLIKE(2, 3) vstream_fprintf(VSTREAM *, const char *,...);
+
+extern VSTREAM *vstream_popen(int,...);
+extern int vstream_pclose(VSTREAM *);
+
+#define vstream_ispipe(vp) ((vp)->pid != 0)
+
+/* Legacy API: type-unchecked arguments, internal use. */
+#define VSTREAM_POPEN_END 0 /* terminator */
+#define VSTREAM_POPEN_COMMAND 1 /* command is string */
+#define VSTREAM_POPEN_ARGV 2 /* command is array */
+#define VSTREAM_POPEN_UID 3 /* privileges */
+#define VSTREAM_POPEN_GID 4 /* privileges */
+#define VSTREAM_POPEN_ENV 5 /* extra environment */
+#define VSTREAM_POPEN_SHELL 6 /* alternative shell */
+#define VSTREAM_POPEN_WAITPID_FN 7 /* child catcher, waitpid() compat. */
+#define VSTREAM_POPEN_EXPORT 8 /* exportable environment */
+
+/* Safer API: type-checked arguments, external use. */
+#define CA_VSTREAM_POPEN_END VSTREAM_POPEN_END
+#define CA_VSTREAM_POPEN_COMMAND(v) VSTREAM_POPEN_COMMAND, CHECK_CPTR(VSTREAM_PPN, char, (v))
+#define CA_VSTREAM_POPEN_ARGV(v) VSTREAM_POPEN_ARGV, CHECK_PPTR(VSTREAM_PPN, char, (v))
+#define CA_VSTREAM_POPEN_UID(v) VSTREAM_POPEN_UID, CHECK_VAL(VSTREAM_PPN, uid_t, (v))
+#define CA_VSTREAM_POPEN_GID(v) VSTREAM_POPEN_GID, CHECK_VAL(VSTREAM_PPN, gid_t, (v))
+#define CA_VSTREAM_POPEN_ENV(v) VSTREAM_POPEN_ENV, CHECK_PPTR(VSTREAM_PPN, char, (v))
+#define CA_VSTREAM_POPEN_SHELL(v) VSTREAM_POPEN_SHELL, CHECK_CPTR(VSTREAM_PPN, char, (v))
+#define CA_VSTREAM_POPEN_WAITPID_FN(v) VSTREAM_POPEN_WAITPID_FN, CHECK_VAL(VSTREAM_PPN, VSTREAM_WAITPID_FN, (v))
+#define CA_VSTREAM_POPEN_EXPORT(v) VSTREAM_POPEN_EXPORT, CHECK_PPTR(VSTREAM_PPN, char, (v))
+
+CHECK_VAL_HELPER_DCL(VSTREAM_PPN, uid_t);
+CHECK_VAL_HELPER_DCL(VSTREAM_PPN, gid_t);
+CHECK_VAL_HELPER_DCL(VSTREAM_PPN, VSTREAM_WAITPID_FN);
+CHECK_PPTR_HELPER_DCL(VSTREAM_PPN, char);
+CHECK_CPTR_HELPER_DCL(VSTREAM_PPN, char);
+
+extern VSTREAM *vstream_vprintf(const char *, va_list);
+extern VSTREAM *vstream_vfprintf(VSTREAM *, const char *, va_list);
+
+extern ssize_t vstream_peek(VSTREAM *);
+extern ssize_t vstream_bufstat(VSTREAM *, int);
+
+#define VSTREAM_BST_FLAG_IN (1<<0)
+#define VSTREAM_BST_FLAG_OUT (1<<1)
+#define VSTREAM_BST_FLAG_PEND (1<<2)
+
+#define VSTREAM_BST_MASK_DIR (VSTREAM_BST_FLAG_IN | VSTREAM_BST_FLAG_OUT)
+#define VSTREAM_BST_IN_PEND (VSTREAM_BST_FLAG_IN | VSTREAM_BST_FLAG_PEND)
+#define VSTREAM_BST_OUT_PEND (VSTREAM_BST_FLAG_OUT | VSTREAM_BST_FLAG_PEND)
+
+#define vstream_peek(vp) vstream_bufstat((vp), VSTREAM_BST_IN_PEND)
+
+extern const char *vstream_peek_data(VSTREAM *);
+
+ /*
+ * Exception handling. We use pointer to jmp_buf to avoid a lot of unused
+ * baggage for streams that don't need this functionality.
+ *
+ * XXX sigsetjmp()/siglongjmp() save and restore the signal mask which can
+ * avoid surprises in code that manipulates signals, but unfortunately some
+ * systems have bugs in their implementation.
+ */
+#ifdef NO_SIGSETJMP
+#define vstream_setjmp(stream) setjmp((stream)->jbuf[0])
+#define vstream_longjmp(stream, val) longjmp((stream)->jbuf[0], (val))
+#else
+#define vstream_setjmp(stream) sigsetjmp((stream)->jbuf[0], 1)
+#define vstream_longjmp(stream, val) siglongjmp((stream)->jbuf[0], (val))
+#endif
+
+ /*
+ * Tweaks and workarounds.
+ */
+extern int vstream_tweak_sock(VSTREAM *);
+extern int vstream_tweak_tcp(VSTREAM *);
+
+#define vstream_flags(stream) ((const int) (stream)->buf.flags)
+
+ /*
+ * Read/write VSTRING memory.
+ */
+#define vstream_memopen(string, flags) \
+ vstream_memreopen((VSTREAM *) 0, (string), (flags))
+VSTREAM *vstream_memreopen(VSTREAM *, struct VSTRING *, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/vstream_popen.c b/src/util/vstream_popen.c
new file mode 100644
index 0000000..d00d49e
--- /dev/null
+++ b/src/util/vstream_popen.c
@@ -0,0 +1,363 @@
+/*++
+/* NAME
+/* vstream_popen 3
+/* SUMMARY
+/* open stream to child process
+/* SYNOPSIS
+/* #include <vstream.h>
+/*
+/* VSTREAM *vstream_popen(flags, key, value, ...)
+/* int flags;
+/* int key;
+/*
+/* int vstream_pclose(stream)
+/* VSTREAM *stream;
+/* DESCRIPTION
+/* vstream_popen() opens a one-way or two-way stream to a user-specified
+/* command, which is executed by a child process. The \fIflags\fR
+/* argument is as with vstream_fopen(). The child's standard input and
+/* standard output are redirected to the stream, which is based on a
+/* socketpair or other suitable local IPC. vstream_popen() takes a list
+/* of macros with zero or more arguments, terminated by
+/* CA_VSTREAM_POPEN_END. The following is a listing of macros
+/* with the expected argument type.
+/* .RS
+/* .IP "CA_VSTREAM_POPEN_COMMAND(const char *)"
+/* Specifies the command to execute as a string. The string is
+/* passed to the shell when it contains shell meta characters
+/* or when it appears to be a shell built-in command, otherwise
+/* the command is executed without invoking a shell.
+/* One of CA_VSTREAM_POPEN_COMMAND or VSTREAM_POPEN_ARGV must be specified.
+/* .IP "CA_VSTREAM_POPEN_ARGV(char **)"
+/* The command is specified as an argument vector. This vector is
+/* passed without further inspection to the \fIexecvp\fR() routine.
+/* One of CA_VSTREAM_POPEN_COMMAND or VSTREAM_POPEN_ARGV must be specified.
+/* See also the CA_VSTREAM_POPEN_SHELL attribute below.
+/* .IP "CA_VSTREAM_POPEN_ENV(char **)"
+/* Additional environment information, in the form of a null-terminated
+/* list of name, value, name, value, ... elements. By default only the
+/* command search path is initialized to _PATH_DEFPATH.
+/* .IP "CA_VSTREAM_POPEN_EXPORT(char **)"
+/* This argument is passed to clean_env().
+/* Null-terminated array of names of environment parameters
+/* that can be exported. By default, everything is exported.
+/* .IP "CA_VSTREAM_POPEN_UID(uid_t)"
+/* The user ID to execute the command as. The user ID must be non-zero.
+/* .IP "CA_VSTREAM_POPEN_GID(gid_t)"
+/* The group ID to execute the command as. The group ID must be non-zero.
+/* .IP "CA_VSTREAM_POPEN_SHELL(const char *)"
+/* The shell to use when executing the command specified with
+/* CA_VSTREAM_POPEN_COMMAND. This shell is invoked regardless of the
+/* command content.
+/* .IP "CA_VSTREAM_POPEN_WAITPID_FN(pid_t (*)(pid_t, WAIT_STATUS_T *, int))"
+/* waitpid()-like function to reap the child exit status when
+/* vstream_pclose() is called.
+/* .RE
+/* .PP
+/* vstream_pclose() closes the named stream and returns the child
+/* exit status. It is an error to specify a stream that was not
+/* returned by vstream_popen() or that is no longer open.
+/* DIAGNOSTICS
+/* Panics: interface violations. Fatal errors: out of memory.
+/*
+/* vstream_popen() returns a null pointer in case of trouble.
+/* The nature of the problem is specified via the \fIerrno\fR
+/* global variable.
+/*
+/* vstream_pclose() returns -1 in case of trouble.
+/* The nature of the problem is specified via the \fIerrno\fR
+/* global variable.
+/* SEE ALSO
+/* vstream(3) light-weight buffered I/O
+/* BUGS
+/* The interface, stolen from popen()/pclose(), ignores errors
+/* returned when the stream is closed, and does not distinguish
+/* between exit status codes and kill signals.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#ifdef USE_PATHS_H
+#include <paths.h>
+#endif
+#include <syslog.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <exec_command.h>
+#include <vstream.h>
+#include <argv.h>
+#include <set_ugid.h>
+#include <clean_env.h>
+#include <iostuff.h>
+
+/* Application-specific. */
+
+typedef struct VSTREAM_POPEN_ARGS {
+ char **argv;
+ char *command;
+ uid_t uid;
+ gid_t gid;
+ int privileged;
+ char **env;
+ char **export;
+ char *shell;
+ VSTREAM_WAITPID_FN waitpid_fn;
+} VSTREAM_POPEN_ARGS;
+
+/* vstream_parse_args - get arguments from variadic list */
+
+static void vstream_parse_args(VSTREAM_POPEN_ARGS *args, va_list ap)
+{
+ const char *myname = "vstream_parse_args";
+ int key;
+
+ /*
+ * First, set the default values (on all non-zero entries)
+ */
+ args->argv = 0;
+ args->command = 0;
+ args->uid = 0;
+ args->gid = 0;
+ args->privileged = 0;
+ args->env = 0;
+ args->export = 0;
+ args->shell = 0;
+ args->waitpid_fn = 0;
+
+ /*
+ * Then, override the defaults with user-supplied inputs.
+ */
+ while ((key = va_arg(ap, int)) != VSTREAM_POPEN_END) {
+ switch (key) {
+ case VSTREAM_POPEN_ARGV:
+ if (args->command != 0)
+ msg_panic("%s: got VSTREAM_POPEN_ARGV and VSTREAM_POPEN_COMMAND", myname);
+ args->argv = va_arg(ap, char **);
+ break;
+ case VSTREAM_POPEN_COMMAND:
+ if (args->argv != 0)
+ msg_panic("%s: got VSTREAM_POPEN_ARGV and VSTREAM_POPEN_COMMAND", myname);
+ args->command = va_arg(ap, char *);
+ break;
+ case VSTREAM_POPEN_UID:
+ args->privileged = 1;
+ args->uid = va_arg(ap, uid_t);
+ break;
+ case VSTREAM_POPEN_GID:
+ args->privileged = 1;
+ args->gid = va_arg(ap, gid_t);
+ break;
+ case VSTREAM_POPEN_ENV:
+ args->env = va_arg(ap, char **);
+ break;
+ case VSTREAM_POPEN_EXPORT:
+ args->export = va_arg(ap, char **);
+ break;
+ case VSTREAM_POPEN_SHELL:
+ args->shell = va_arg(ap, char *);
+ break;
+ case VSTREAM_POPEN_WAITPID_FN:
+ args->waitpid_fn = va_arg(ap, VSTREAM_WAITPID_FN);
+ break;
+ default:
+ msg_panic("%s: unknown key: %d", myname, key);
+ }
+ }
+
+ if (args->command == 0 && args->argv == 0)
+ msg_panic("%s: missing VSTREAM_POPEN_ARGV or VSTREAM_POPEN_COMMAND", myname);
+ if (args->privileged != 0 && args->uid == 0)
+ msg_panic("%s: privileged uid", myname);
+ if (args->privileged != 0 && args->gid == 0)
+ msg_panic("%s: privileged gid", myname);
+}
+
+/* vstream_popen - open stream to child process */
+
+VSTREAM *vstream_popen(int flags,...)
+{
+ const char *myname = "vstream_popen";
+ VSTREAM_POPEN_ARGS args;
+ va_list ap;
+ VSTREAM *stream;
+ int sockfd[2];
+ int pid;
+ int fd;
+ ARGV *argv;
+ char **cpp;
+
+ va_start(ap, flags);
+ vstream_parse_args(&args, ap);
+ va_end(ap);
+
+ if (args.command == 0)
+ args.command = args.argv[0];
+
+ if (duplex_pipe(sockfd) < 0)
+ return (0);
+
+ switch (pid = fork()) {
+ case -1: /* error */
+ (void) close(sockfd[0]);
+ (void) close(sockfd[1]);
+ return (0);
+ case 0: /* child */
+ (void) msg_cleanup((MSG_CLEANUP_FN) 0);
+ if (close(sockfd[1]))
+ msg_warn("close: %m");
+ for (fd = 0; fd < 2; fd++)
+ if (sockfd[0] != fd)
+ if (DUP2(sockfd[0], fd) < 0)
+ msg_fatal("dup2: %m");
+ if (sockfd[0] >= 2 && close(sockfd[0]))
+ msg_warn("close: %m");
+
+ /*
+ * Don't try to become someone else unless the user specified it.
+ */
+ if (args.privileged)
+ set_ugid(args.uid, args.gid);
+
+ /*
+ * Environment plumbing. Always reset the command search path. XXX
+ * That should probably be done by clean_env().
+ */
+ if (args.export)
+ clean_env(args.export);
+ if (setenv("PATH", _PATH_DEFPATH, 1))
+ msg_fatal("%s: setenv: %m", myname);
+ if (args.env)
+ for (cpp = args.env; *cpp; cpp += 2)
+ if (setenv(cpp[0], cpp[1], 1))
+ msg_fatal("setenv: %m");
+
+ /*
+ * Process plumbing. If possible, avoid running a shell.
+ */
+ closelog();
+ if (args.argv) {
+ execvp(args.argv[0], args.argv);
+ msg_fatal("%s: execvp %s: %m", myname, args.argv[0]);
+ } else if (args.shell && *args.shell) {
+ argv = argv_split(args.shell, CHARS_SPACE);
+ argv_add(argv, args.command, (char *) 0);
+ argv_terminate(argv);
+ execvp(argv->argv[0], argv->argv);
+ msg_fatal("%s: execvp %s: %m", myname, argv->argv[0]);
+ } else {
+ exec_command(args.command);
+ }
+ /* NOTREACHED */
+ default: /* parent */
+ if (close(sockfd[0]))
+ msg_warn("close: %m");
+ stream = vstream_fdopen(sockfd[1], flags);
+ stream->waitpid_fn = args.waitpid_fn;
+ stream->pid = pid;
+ return (stream);
+ }
+}
+
+/* vstream_pclose - close stream to child process */
+
+int vstream_pclose(VSTREAM *stream)
+{
+ pid_t saved_pid = stream->pid;
+ VSTREAM_WAITPID_FN saved_waitpid_fn = stream->waitpid_fn;
+ pid_t pid;
+ WAIT_STATUS_T wait_status;
+
+ /*
+ * Close the pipe. Don't trigger an alarm in vstream_fclose().
+ */
+ if (saved_pid == 0)
+ msg_panic("vstream_pclose: stream has no process");
+ stream->pid = 0;
+ vstream_fclose(stream);
+
+ /*
+ * Reap the child exit status.
+ */
+ do {
+ if (saved_waitpid_fn != 0)
+ pid = saved_waitpid_fn(saved_pid, &wait_status, 0);
+ else
+ pid = waitpid(saved_pid, &wait_status, 0);
+ } while (pid == -1 && errno == EINTR);
+ return (pid == -1 ? -1 :
+ WIFSIGNALED(wait_status) ? WTERMSIG(wait_status) :
+ WEXITSTATUS(wait_status));
+}
+
+#ifdef TEST
+
+#include <fcntl.h>
+#include <vstring.h>
+#include <vstring_vstream.h>
+
+ /*
+ * Test program. Run a command and copy lines one by one.
+ */
+int main(int argc, char **argv)
+{
+ VSTRING *buf = vstring_alloc(100);
+ VSTREAM *stream;
+ int status;
+
+ /*
+ * Sanity check.
+ */
+ if (argc < 2)
+ msg_fatal("usage: %s 'command'", argv[0]);
+
+ /*
+ * Open stream to child process.
+ */
+ if ((stream = vstream_popen(O_RDWR,
+ VSTREAM_POPEN_ARGV, argv + 1,
+ VSTREAM_POPEN_END)) == 0)
+ msg_fatal("vstream_popen: %m");
+
+ /*
+ * Copy loop, one line at a time.
+ */
+ while (vstring_fgets(buf, stream) != 0) {
+ if (vstream_fwrite(VSTREAM_OUT, vstring_str(buf), VSTRING_LEN(buf))
+ != VSTRING_LEN(buf))
+ msg_fatal("vstream_fwrite: %m");
+ if (vstream_fflush(VSTREAM_OUT) != 0)
+ msg_fatal("vstream_fflush: %m");
+ if (vstring_fgets(buf, VSTREAM_IN) == 0)
+ break;
+ if (vstream_fwrite(stream, vstring_str(buf), VSTRING_LEN(buf))
+ != VSTRING_LEN(buf))
+ msg_fatal("vstream_fwrite: %m");
+ }
+
+ /*
+ * Cleanup.
+ */
+ vstring_free(buf);
+ if ((status = vstream_pclose(stream)) != 0)
+ msg_warn("exit status: %d", status);
+
+ exit(status);
+}
+
+#endif
diff --git a/src/util/vstream_test.in b/src/util/vstream_test.in
new file mode 100644
index 0000000..b6687c8
--- /dev/null
+++ b/src/util/vstream_test.in
@@ -0,0 +1,3 @@
+abcdef
+ghijkl
+mnopqr
diff --git a/src/util/vstream_test.ref b/src/util/vstream_test.ref
new file mode 100644
index 0000000..eaa9952
--- /dev/null
+++ b/src/util/vstream_test.ref
@@ -0,0 +1,41 @@
+buffer size test: copy text with 1 buffer size, ignore requests to shrink
+abcdef
+actual read/write buffer sizes: 1/1
+
+buffer size test: copy text with 2 buffer size, ignore requests to shrink
+ghijkl
+actual read/write buffer sizes: 2/2
+
+buffer size test: copy text with 1 buffer size, ignore requests to shrink
+mnopqr
+actual read/write buffer sizes: 2/2
+
+formatting test: print a number
+1234567890
+
+memory stream test prep: prefill the VSTRING
+VSTRING content length: 8/8, content: 01234567
+
+memory stream test: open the VSTRING for writing, overwrite, close
+initial memory VSTREAM write offset: 0/8
+final memory VSTREAM write offset: 5/8
+VSTRING content length: 5/8, content: hallo
+
+memory stream test: open the VSTRING for append, write multiple, then overwrite 1
+initial memory VSTREAM write offset: 5/8
+final memory VSTREAM write offset: 11/16
+VSTRING content length: 11/16, content: hallo world
+
+replace second character and close
+VSTRING content length: 11/16, content: hello world
+
+memory stream test: open VSTRING for reading, then read
+initial memory VSTREAM read offset: 0/11
+reading memory VSTREAM: hello world
+final memory VSTREAM read offset: 11/11
+seeking to offset 12 should work: PASS
+VSTREAM_GETC should return VSTREAM_EOF
+PASS
+final memory VSTREAM read offset: 12/11
+VSTRING content length: 11/16, content: hello world
+
diff --git a/src/util/vstream_tweak.c b/src/util/vstream_tweak.c
new file mode 100644
index 0000000..7100bc6
--- /dev/null
+++ b/src/util/vstream_tweak.c
@@ -0,0 +1,168 @@
+/*++
+/* NAME
+/* vstream_tweak 3
+/* SUMMARY
+/* performance tweaks
+/* SYNOPSIS
+/* #include <vstream.h>
+/*
+/* VSTREAM *vstream_tweak_sock(stream)
+/* VSTREAM *stream;
+/*
+/* VSTREAM *vstream_tweak_tcp(stream)
+/* VSTREAM *stream;
+/* DESCRIPTION
+/* vstream_tweak_sock() does a best effort to boost your
+/* network performance on the specified generic stream.
+/*
+/* vstream_tweak_tcp() does a best effort to boost your
+/* Internet performance on the specified TCP stream.
+/*
+/* Arguments:
+/* .IP stream
+/* The stream being boosted.
+/* DIAGNOSTICS
+/* Panics: interface violations.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+
+/* Application-specific. */
+
+#ifdef HAS_IPV6
+#define SOCKADDR_STORAGE struct sockaddr_storage
+#else
+#define SOCKADDR_STORAGE struct sockaddr
+#endif
+
+/* vstream_tweak_sock - boost your generic network performance */
+
+int vstream_tweak_sock(VSTREAM *fp)
+{
+ SOCKADDR_STORAGE ss;
+ struct sockaddr *sa = (struct sockaddr *) &ss;
+ SOCKADDR_SIZE sa_length = sizeof(ss);
+ int ret;
+
+ /*
+ * If the caller doesn't know if this socket is AF_LOCAL, AF_INET, etc.,
+ * figure it out for them.
+ */
+ if ((ret = getsockname(vstream_fileno(fp), sa, &sa_length)) >= 0) {
+ switch (sa->sa_family) {
+#ifdef AF_INET6
+ case AF_INET6:
+#endif
+ case AF_INET:
+ ret = vstream_tweak_tcp(fp);
+ break;
+ }
+ }
+ return (ret);
+}
+
+/* vstream_tweak_tcp - boost your TCP performance */
+
+int vstream_tweak_tcp(VSTREAM *fp)
+{
+ const char *myname = "vstream_tweak_tcp";
+ int mss = 0;
+ SOCKOPT_SIZE mss_len = sizeof(mss);
+ int err;
+
+ /*
+ * Avoid Nagle delays when VSTREAM buffers are smaller than the MSS.
+ *
+ * Forcing TCP_NODELAY to be "always on" would hurt performance in the
+ * common case where VSTREAM buffers are larger than the MSS.
+ *
+ * Instead we ask the kernel what the current MSS is, and take appropriate
+ * action. Linux <= 2.2 getsockopt(TCP_MAXSEG) always returns zero (or
+ * whatever value was stored last with setsockopt()).
+ *
+ * Some ancient FreeBSD kernels don't report 'host unreachable' errors with
+ * getsockopt(SO_ERROR), and then treat getsockopt(TCP_MAXSEG) as a NOOP,
+ * leaving the mss parameter value unchanged. To work around these two
+ * getsockopt() bugs we set mss = 0, which is a harmless value.
+ */
+ if ((err = getsockopt(vstream_fileno(fp), IPPROTO_TCP, TCP_MAXSEG,
+ (void *) &mss, &mss_len)) < 0
+ && errno != ECONNRESET) {
+ msg_warn("%s: getsockopt TCP_MAXSEG: %m", myname);
+ return (err);
+ }
+ if (msg_verbose)
+ msg_info("%s: TCP_MAXSEG %d", myname, mss);
+
+ /*
+ * Fix for recent Postfix versions: increase the VSTREAM buffer size if
+ * it is smaller than the MSS. Note: the MSS may change when the route
+ * changes and IP path MTU discovery is turned on, so we choose a
+ * somewhat larger buffer.
+ *
+ * Note: as of 20120527, the CA_VSTREAM_CTL_BUFSIZE request can reduce the
+ * stream buffer size to less than VSTREAM_BUFSIZE, when the request is
+ * made before the first stream read or write operation. We don't want to
+ * reduce the buffer size.
+ *
+ * As of 20190820 we increase the mss size multiplier from 2x to 4x, because
+ * some LINUX loopback TCP stacks report an MSS of 21845 which is 3x
+ * smaller than the MTU of 65536. Even with a VSTREAM buffer 2x the
+ * reported MSS size, performance would suck due to Nagle or delayed ACK
+ * delays.
+ */
+#define EFF_BUFFER_SIZE(fp) (vstream_req_bufsize(fp) ? \
+ vstream_req_bufsize(fp) : VSTREAM_BUFSIZE)
+
+#ifdef CA_VSTREAM_CTL_BUFSIZE
+ if (mss > EFF_BUFFER_SIZE(fp) / 4) {
+ if (mss < INT_MAX / 2)
+ mss *= 2;
+ if (mss < INT_MAX / 2)
+ mss *= 2;
+ vstream_control(fp,
+ CA_VSTREAM_CTL_BUFSIZE(mss),
+ CA_VSTREAM_CTL_END);
+ }
+
+ /*
+ * Workaround for older Postfix versions: turn on TCP_NODELAY if the
+ * VSTREAM buffer size is smaller than the MSS.
+ */
+#else
+ if (mss > VSTREAM_BUFSIZE) {
+ int nodelay = 1;
+
+ if ((err = setsockopt(vstream_fileno(fp), IPPROTO_TCP, TCP_NODELAY,
+ (void *) &nodelay, sizeof(nodelay))) < 0
+ && errno != ECONNRESET)
+ msg_warn("%s: setsockopt TCP_NODELAY: %m", myname);
+ }
+#endif
+ return (err);
+}
diff --git a/src/util/vstring.c b/src/util/vstring.c
new file mode 100644
index 0000000..43897eb
--- /dev/null
+++ b/src/util/vstring.c
@@ -0,0 +1,719 @@
+/*++
+/* NAME
+/* vstring 3
+/* SUMMARY
+/* arbitrary-length string manager
+/* SYNOPSIS
+/* #include <vstring.h>
+/*
+/* VSTRING *vstring_alloc(len)
+/* ssize_t len;
+/*
+/* vstring_ctl(vp, type, value, ..., VSTRING_CTL_END)
+/* VSTRING *vp;
+/* int type;
+/*
+/* VSTRING *vstring_free(vp)
+/* VSTRING *vp;
+/*
+/* char *vstring_str(vp)
+/* VSTRING *vp;
+/*
+/* ssize_t VSTRING_LEN(vp)
+/* VSTRING *vp;
+/*
+/* char *vstring_end(vp)
+/* VSTRING *vp;
+/*
+/* void VSTRING_ADDCH(vp, ch)
+/* VSTRING *vp;
+/* int ch;
+/*
+/* int VSTRING_SPACE(vp, len)
+/* VSTRING *vp;
+/* ssize_t len;
+/*
+/* ssize_t vstring_avail(vp)
+/* VSTRING *vp;
+/*
+/* VSTRING *vstring_truncate(vp, len)
+/* VSTRING *vp;
+/* ssize_t len;
+/*
+/* VSTRING *vstring_set_payload_size(vp, len)
+/* VSTRING *vp;
+/* ssize_t len;
+/*
+/* void VSTRING_RESET(vp)
+/* VSTRING *vp;
+/*
+/* void VSTRING_TERMINATE(vp)
+/* VSTRING *vp;
+/*
+/* void VSTRING_SKIP(vp)
+/* VSTRING *vp;
+/*
+/* VSTRING *vstring_strcpy(vp, src)
+/* VSTRING *vp;
+/* const char *src;
+/*
+/* VSTRING *vstring_strncpy(vp, src, len)
+/* VSTRING *vp;
+/* const char *src;
+/* ssize_t len;
+/*
+/* VSTRING *vstring_strcat(vp, src)
+/* VSTRING *vp;
+/* const char *src;
+/*
+/* VSTRING *vstring_strncat(vp, src, len)
+/* VSTRING *vp;
+/* const char *src;
+/* ssize_t len;
+/*
+/* VSTRING *vstring_memcpy(vp, src, len)
+/* VSTRING *vp;
+/* const char *src;
+/* ssize_t len;
+/*
+/* VSTRING *vstring_memcat(vp, src, len)
+/* VSTRING *vp;
+/* const char *src;
+/* ssize_t len;
+/*
+/* char *vstring_memchr(vp, ch)
+/* VSTRING *vp;
+/* int ch;
+/*
+/* VSTRING *vstring_insert(vp, start, src, len)
+/* VSTRING *vp;
+/* ssize_t start;
+/* const char *src;
+/* ssize_t len;
+/*
+/* VSTRING *vstring_prepend(vp, src, len)
+/* VSTRING *vp;
+/* const char *src;
+/* ssize_t len;
+/*
+/* VSTRING *vstring_sprintf(vp, format, ...)
+/* VSTRING *vp;
+/* const char *format;
+/*
+/* VSTRING *vstring_sprintf_append(vp, format, ...)
+/* VSTRING *vp;
+/* const char *format;
+/*
+/* VSTRING *vstring_sprintf_prepend(vp, format, ...)
+/* VSTRING *vp;
+/* const char *format;
+/*
+/* VSTRING *vstring_vsprintf(vp, format, ap)
+/* VSTRING *vp;
+/* const char *format;
+/* va_list ap;
+/*
+/* VSTRING *vstring_vsprintf_append(vp, format, ap)
+/* VSTRING *vp;
+/* const char *format;
+/* va_list ap;
+/* AUXILIARY FUNCTIONS
+/* char *vstring_export(vp)
+/* VSTRING *vp;
+/*
+/* VSTRING *vstring_import(str)
+/* char *str;
+/* DESCRIPTION
+/* The functions and macros in this module implement arbitrary-length
+/* strings and common operations on those strings. The strings do not
+/* need to be null terminated and may contain arbitrary binary data.
+/* The strings manage their own memory and grow automatically when full.
+/* The optional string null terminator does not add to the string length.
+/*
+/* vstring_alloc() allocates storage for a variable-length string
+/* of at least "len" bytes. The minimal length is 1. The result
+/* is a null-terminated string of length zero.
+/*
+/* vstring_ctl() gives additional control over VSTRING behavior.
+/* The function takes a VSTRING pointer and a list of zero or
+/* more macros with zer or more arguments, terminated with
+/* CA_VSTRING_CTL_END which has none.
+/* .IP "CA_VSTRING_CTL_MAXLEN(ssize_t len)"
+/* Specifies a hard upper limit on a string's length. When the
+/* length would be exceeded, the program simulates a memory
+/* allocation problem (i.e. it terminates through msg_fatal()).
+/* This functionality is currently unimplemented.
+/* .IP "CA_VSTRING_CTL_EXACT (no argument)"
+/* Allocate the requested amounts, instead of rounding up.
+/* This should be used for tests only.
+/* .IP "CA_VSTRING_CTL_END (no argument)"
+/* Specifies the end of the argument list. Forgetting to terminate
+/* the argument list may cause the program to crash.
+/* .PP
+/* VSTRING_SPACE() ensures that the named string has room for
+/* "len" more characters. VSTRING_SPACE() is an unsafe macro
+/* that either returns zero or never returns.
+/*
+/* vstring_avail() returns the number of bytes that can be placed
+/* into the buffer before the buffer would need to grow.
+/*
+/* vstring_free() reclaims storage for a variable-length string.
+/* It conveniently returns a null pointer.
+/*
+/* vstring_str() is a macro that returns the string value
+/* of a variable-length string. It is a safe macro that
+/* evaluates its argument only once.
+/*
+/* VSTRING_LEN() is a macro that returns the current length of
+/* its argument (i.e. the distance from the start of the string
+/* to the current write position). VSTRING_LEN() is an unsafe macro
+/* that evaluates its argument more than once.
+/*
+/* vstring_end() is a macro that returns the current write position of
+/* its argument. It is a safe macro that evaluates its argument only once.
+/*
+/* VSTRING_ADDCH() adds a character to a variable-length string
+/* and extends the string if it fills up. \fIvs\fP is a pointer
+/* to a VSTRING structure; \fIch\fP the character value to be written.
+/* The result is the written character.
+/* Note that VSTRING_ADDCH() is an unsafe macro that evaluates some
+/* arguments more than once. The result is NOT null-terminated.
+/*
+/* vstring_truncate() truncates the named string to the specified
+/* length. If length is negative, the trailing portion is kept.
+/* The operation has no effect when the string is shorter.
+/* The string is not null-terminated.
+/*
+/* vstring_set_payload_size() sets the number of 'used' bytes
+/* in the named buffer's metadata. This determines the buffer
+/* write position and the VSTRING_LEN() result. The payload
+/* size must be within the closed range [0, number of allocated
+/* bytes]. The typical usage is to request buffer space with
+/* VSTRING_SPACE(), to use some non-VSTRING operations to write
+/* to the buffer, and to call vstring_set_payload_size() to
+/* update buffer metadata, perhaps followed by VSTRING_TERMINATE().
+/*
+/* VSTRING_RESET() is a macro that resets the write position of its
+/* string argument to the very beginning. Note that VSTRING_RESET()
+/* is an unsafe macro that evaluates some arguments more than once.
+/* The result is NOT null-terminated.
+/*
+/* VSTRING_TERMINATE() null-terminates its string argument.
+/* VSTRING_TERMINATE() is an unsafe macro that evaluates some
+/* arguments more than once.
+/* VSTRING_TERMINATE() does not return an interesting result.
+/*
+/* VSTRING_SKIP() is a macro that moves the write position to the first
+/* null byte after the current write position. VSTRING_SKIP() is an unsafe
+/* macro that evaluates some arguments more than once.
+/*
+/* vstring_strcpy() copies a null-terminated string to a variable-length
+/* string. \fIsrc\fP provides the data to be copied; \fIvp\fP is the
+/* target and result value. The result is null-terminated.
+/*
+/* vstring_strncpy() copies at most \fIlen\fR characters. Otherwise it is
+/* identical to vstring_strcpy().
+/*
+/* vstring_strcat() appends a null-terminated string to a variable-length
+/* string. \fIsrc\fP provides the data to be copied; \fIvp\fP is the
+/* target and result value. The result is null-terminated.
+/*
+/* vstring_strncat() copies at most \fIlen\fR characters. Otherwise it is
+/* identical to vstring_strcat().
+/*
+/* vstring_memcpy() copies \fIlen\fR bytes to a variable-length string.
+/* \fIsrc\fP provides the data to be copied; \fIvp\fP is the
+/* target and result value. The result is not null-terminated.
+/*
+/* vstring_memcat() appends \fIlen\fR bytes to a variable-length string.
+/* \fIsrc\fP provides the data to be copied; \fIvp\fP is the
+/* target and result value. The result is not null-terminated.
+/*
+/* vstring_memchr() locates a byte in a variable-length string.
+/*
+/* vstring_insert() inserts a buffer content into a variable-length
+/* string at the specified start position. The result is
+/* null-terminated.
+/*
+/* vstring_prepend() prepends a buffer content to a variable-length
+/* string. The result is null-terminated.
+/*
+/* vstring_sprintf() produces a formatted string according to its
+/* \fIformat\fR argument. See vstring_vsprintf() for details.
+/*
+/* vstring_sprintf_append() is like vstring_sprintf(), but appends
+/* to the end of the result buffer.
+/*
+/* vstring_sprintf_append() is like vstring_sprintf(), but prepends
+/* to the beginning of the result buffer.
+/*
+/* vstring_vsprintf() returns a null-terminated string according to
+/* the \fIformat\fR argument. It understands the s, c, d, u,
+/* o, x, X, p, e, f and g format types, the l modifier, field width
+/* and precision, sign, and null or space padding. This module
+/* can format strings as large as available memory permits.
+/*
+/* vstring_vsprintf_append() is like vstring_vsprintf(), but appends
+/* to the end of the result buffer.
+/*
+/* In addition to stdio-like format specifiers, vstring_vsprintf()
+/* recognizes %m and expands it to the corresponding errno text.
+/*
+/* vstring_export() extracts the string value from a VSTRING.
+/* The VSTRING is destroyed. The result should be passed to myfree().
+/*
+/* vstring_import() takes a `bare' string and converts it to
+/* a VSTRING. The string argument must be obtained from mymalloc().
+/* The string argument is not copied.
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation failure.
+/* BUGS
+/* Auto-resizing may change the address of the string data in
+/* a vstring structure. Beware of dangling pointers.
+/* HISTORY
+/* .ad
+/* .fi
+/* A vstring module appears in the UNPROTO software by Wietse Venema.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System libraries. */
+
+#include <sys_defs.h>
+#include <stddef.h>
+#include <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+#include <string.h>
+
+/* Utility library. */
+
+#define VSTRING_INTERNAL
+
+#include "mymalloc.h"
+#include "msg.h"
+#include "vbuf_print.h"
+#include "vstring.h"
+
+/* vstring_extend - variable-length string buffer extension policy */
+
+static void vstring_extend(VBUF *bp, ssize_t incr)
+{
+ size_t used = bp->ptr - bp->data;
+ ssize_t new_len;
+
+ /*
+ * Note: vp->vbuf.len is the current buffer size (both on entry and on
+ * exit of this routine). We round up the increment size to the buffer
+ * size to avoid silly little buffer increments. With really large
+ * strings we might want to abandon the length doubling strategy, and go
+ * to fixed increments.
+ *
+ * The length overflow tests here and in vstring_alloc() should protect us
+ * against all length overflow problems within vstring library routines.
+ *
+ * Safety net: add a gratuitous null terminator so that C-style string
+ * operations won't scribble past the end.
+ */
+ if ((bp->flags & VSTRING_FLAG_EXACT) == 0 && bp->len > incr)
+ incr = bp->len;
+ if (bp->len > SSIZE_T_MAX - incr - 1)
+ msg_fatal("vstring_extend: length overflow");
+ new_len = bp->len + incr;
+ bp->data = (unsigned char *) myrealloc((void *) bp->data, new_len + 1);
+ bp->data[new_len] = 0;
+ bp->len = new_len;
+ bp->ptr = bp->data + used;
+ bp->cnt = bp->len - used;
+}
+
+/* vstring_buf_get_ready - vbuf callback for read buffer empty condition */
+
+static int vstring_buf_get_ready(VBUF *unused_buf)
+{
+ return (VBUF_EOF); /* be VSTREAM-friendly */
+}
+
+/* vstring_buf_put_ready - vbuf callback for write buffer full condition */
+
+static int vstring_buf_put_ready(VBUF *bp)
+{
+ vstring_extend(bp, 1);
+ return (0);
+}
+
+/* vstring_buf_space - vbuf callback to reserve space */
+
+static int vstring_buf_space(VBUF *bp, ssize_t len)
+{
+ ssize_t need;
+
+ if (len < 0)
+ msg_panic("vstring_buf_space: bad length %ld", (long) len);
+ if ((need = len - bp->cnt) > 0)
+ vstring_extend(bp, need);
+ return (0);
+}
+
+/* vstring_alloc - create variable-length string */
+
+VSTRING *vstring_alloc(ssize_t len)
+{
+ VSTRING *vp;
+
+ /*
+ * Safety net: add a gratuitous null terminator so that C-style string
+ * operations won't scribble past the end.
+ */
+ if (len < 1 || len > SSIZE_T_MAX - 1)
+ msg_panic("vstring_alloc: bad length %ld", (long) len);
+ vp = (VSTRING *) mymalloc(sizeof(*vp));
+ vp->vbuf.flags = 0;
+ vp->vbuf.len = 0;
+ vp->vbuf.data = (unsigned char *) mymalloc(len + 1);
+ vp->vbuf.data[len] = 0;
+ vp->vbuf.len = len;
+ VSTRING_RESET(vp);
+ vp->vbuf.data[0] = 0;
+ vp->vbuf.get_ready = vstring_buf_get_ready;
+ vp->vbuf.put_ready = vstring_buf_put_ready;
+ vp->vbuf.space = vstring_buf_space;
+ return (vp);
+}
+
+/* vstring_free - destroy variable-length string */
+
+VSTRING *vstring_free(VSTRING *vp)
+{
+ if (vp->vbuf.data)
+ myfree((void *) vp->vbuf.data);
+ myfree((void *) vp);
+ return (0);
+}
+
+/* vstring_ctl - modify memory management policy */
+
+void vstring_ctl(VSTRING *vp,...)
+{
+ va_list ap;
+ int code;
+
+ va_start(ap, vp);
+ while ((code = va_arg(ap, int)) != VSTRING_CTL_END) {
+ switch (code) {
+ default:
+ msg_panic("vstring_ctl: unknown code: %d", code);
+ case VSTRING_CTL_EXACT:
+ vp->vbuf.flags |= VSTRING_FLAG_EXACT;
+ break;
+ }
+ }
+ va_end(ap);
+}
+
+/* vstring_truncate - truncate string */
+
+VSTRING *vstring_truncate(VSTRING *vp, ssize_t len)
+{
+ ssize_t move;
+
+ if (len < 0) {
+ len = (-len);
+ if ((move = VSTRING_LEN(vp) - len) > 0)
+ memmove(vstring_str(vp), vstring_str(vp) + move, len);
+ }
+ if (len < VSTRING_LEN(vp))
+ VSTRING_AT_OFFSET(vp, len);
+ return (vp);
+}
+
+/* vstring_set_payload_size - public version of VSTRING_AT_OFFSET */
+
+VSTRING *vstring_set_payload_size(VSTRING *vp, ssize_t len)
+{
+ if (len < 0 || len > vp->vbuf.len)
+ msg_panic("vstring_set_payload_size: invalid offset: %ld", (long) len);
+ if (vp->vbuf.data[vp->vbuf.len] != 0)
+ msg_panic("vstring_set_payload_size: no safety null byte");
+ VSTRING_AT_OFFSET(vp, len);
+ return (vp);
+}
+
+/* vstring_strcpy - copy string */
+
+VSTRING *vstring_strcpy(VSTRING *vp, const char *src)
+{
+ VSTRING_RESET(vp);
+
+ while (*src) {
+ VSTRING_ADDCH(vp, *src);
+ src++;
+ }
+ VSTRING_TERMINATE(vp);
+ return (vp);
+}
+
+/* vstring_strncpy - copy string of limited length */
+
+VSTRING *vstring_strncpy(VSTRING *vp, const char *src, ssize_t len)
+{
+ VSTRING_RESET(vp);
+
+ while (len-- > 0 && *src) {
+ VSTRING_ADDCH(vp, *src);
+ src++;
+ }
+ VSTRING_TERMINATE(vp);
+ return (vp);
+}
+
+/* vstring_strcat - append string */
+
+VSTRING *vstring_strcat(VSTRING *vp, const char *src)
+{
+ while (*src) {
+ VSTRING_ADDCH(vp, *src);
+ src++;
+ }
+ VSTRING_TERMINATE(vp);
+ return (vp);
+}
+
+/* vstring_strncat - append string of limited length */
+
+VSTRING *vstring_strncat(VSTRING *vp, const char *src, ssize_t len)
+{
+ while (len-- > 0 && *src) {
+ VSTRING_ADDCH(vp, *src);
+ src++;
+ }
+ VSTRING_TERMINATE(vp);
+ return (vp);
+}
+
+/* vstring_memcpy - copy buffer of limited length */
+
+VSTRING *vstring_memcpy(VSTRING *vp, const char *src, ssize_t len)
+{
+ VSTRING_RESET(vp);
+
+ VSTRING_SPACE(vp, len);
+ memcpy(vstring_str(vp), src, len);
+ VSTRING_AT_OFFSET(vp, len);
+ return (vp);
+}
+
+/* vstring_memcat - append buffer of limited length */
+
+VSTRING *vstring_memcat(VSTRING *vp, const char *src, ssize_t len)
+{
+ VSTRING_SPACE(vp, len);
+ memcpy(vstring_end(vp), src, len);
+ len += VSTRING_LEN(vp);
+ VSTRING_AT_OFFSET(vp, len);
+ return (vp);
+}
+
+/* vstring_memchr - locate byte in buffer */
+
+char *vstring_memchr(VSTRING *vp, int ch)
+{
+ unsigned char *cp;
+
+ for (cp = (unsigned char *) vstring_str(vp); cp < (unsigned char *) vstring_end(vp); cp++)
+ if (*cp == ch)
+ return ((char *) cp);
+ return (0);
+}
+
+/* vstring_insert - insert text into string */
+
+VSTRING *vstring_insert(VSTRING *vp, ssize_t start, const char *buf, ssize_t len)
+{
+ ssize_t new_len;
+
+ /*
+ * Sanity check.
+ */
+ if (start < 0 || start >= VSTRING_LEN(vp))
+ msg_panic("vstring_insert: bad start %ld", (long) start);
+ if (len < 0)
+ msg_panic("vstring_insert: bad length %ld", (long) len);
+
+ /*
+ * Move the existing content and copy the new content.
+ */
+ new_len = VSTRING_LEN(vp) + len;
+ VSTRING_SPACE(vp, len);
+ memmove(vstring_str(vp) + start + len, vstring_str(vp) + start,
+ VSTRING_LEN(vp) - start);
+ memcpy(vstring_str(vp) + start, buf, len);
+ VSTRING_AT_OFFSET(vp, new_len);
+ VSTRING_TERMINATE(vp);
+ return (vp);
+}
+
+/* vstring_prepend - prepend text to string */
+
+VSTRING *vstring_prepend(VSTRING *vp, const char *buf, ssize_t len)
+{
+ ssize_t new_len;
+
+ /*
+ * Sanity check.
+ */
+ if (len < 0)
+ msg_panic("vstring_prepend: bad length %ld", (long) len);
+
+ /*
+ * Move the existing content and copy the new content.
+ */
+ new_len = VSTRING_LEN(vp) + len;
+ VSTRING_SPACE(vp, len);
+ memmove(vstring_str(vp) + len, vstring_str(vp), VSTRING_LEN(vp));
+ memcpy(vstring_str(vp), buf, len);
+ VSTRING_AT_OFFSET(vp, new_len);
+ VSTRING_TERMINATE(vp);
+ return (vp);
+}
+
+/* vstring_export - VSTRING to bare string */
+
+char *vstring_export(VSTRING *vp)
+{
+ char *cp;
+
+ cp = (char *) vp->vbuf.data;
+ vp->vbuf.data = 0;
+ myfree((void *) vp);
+ return (cp);
+}
+
+/* vstring_import - bare string to vstring */
+
+VSTRING *vstring_import(char *str)
+{
+ VSTRING *vp;
+ ssize_t len;
+
+ vp = (VSTRING *) mymalloc(sizeof(*vp));
+ len = strlen(str);
+ vp->vbuf.flags = 0;
+ vp->vbuf.len = 0;
+ vp->vbuf.data = (unsigned char *) str;
+ vp->vbuf.len = len + 1;
+ VSTRING_AT_OFFSET(vp, len);
+ vp->vbuf.get_ready = vstring_buf_get_ready;
+ vp->vbuf.put_ready = vstring_buf_put_ready;
+ vp->vbuf.space = vstring_buf_space;
+ return (vp);
+}
+
+/* vstring_sprintf - formatted string */
+
+VSTRING *vstring_sprintf(VSTRING *vp, const char *format,...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ vp = vstring_vsprintf(vp, format, ap);
+ va_end(ap);
+ return (vp);
+}
+
+/* vstring_vsprintf - format string, vsprintf-like interface */
+
+VSTRING *vstring_vsprintf(VSTRING *vp, const char *format, va_list ap)
+{
+ VSTRING_RESET(vp);
+ vbuf_print(&vp->vbuf, format, ap);
+ VSTRING_TERMINATE(vp);
+ return (vp);
+}
+
+/* vstring_sprintf_append - append formatted string */
+
+VSTRING *vstring_sprintf_append(VSTRING *vp, const char *format,...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ vp = vstring_vsprintf_append(vp, format, ap);
+ va_end(ap);
+ return (vp);
+}
+
+/* vstring_vsprintf_append - format + append string, vsprintf-like interface */
+
+VSTRING *vstring_vsprintf_append(VSTRING *vp, const char *format, va_list ap)
+{
+ vbuf_print(&vp->vbuf, format, ap);
+ VSTRING_TERMINATE(vp);
+ return (vp);
+}
+
+/* vstring_sprintf_prepend - format + prepend string, vsprintf-like interface */
+
+VSTRING *vstring_sprintf_prepend(VSTRING *vp, const char *format,...)
+{
+ va_list ap;
+ ssize_t old_len = VSTRING_LEN(vp);
+ ssize_t result_len;
+
+ /* Construct: old|new|free */
+ va_start(ap, format);
+ vp = vstring_vsprintf_append(vp, format, ap);
+ va_end(ap);
+ result_len = VSTRING_LEN(vp);
+
+ /* Construct: old|new|old|free */
+ VSTRING_SPACE(vp, old_len);
+ vstring_memcat(vp, vstring_str(vp), old_len);
+
+ /* Construct: new|old|free */
+ memmove(vstring_str(vp), vstring_str(vp) + old_len, result_len);
+ VSTRING_AT_OFFSET(vp, result_len);
+ VSTRING_TERMINATE(vp);
+ return (vp);
+}
+
+#ifdef TEST
+
+ /*
+ * Test program - concatenate all command-line arguments into one string.
+ */
+#include <stdio.h>
+
+int main(int argc, char **argv)
+{
+ VSTRING *vp = vstring_alloc(1);
+ int n;
+
+ /*
+ * Report the location of the gratuitous null terminator.
+ */
+ for (n = 1; n <= 5; n++) {
+ VSTRING_ADDCH(vp, 'x');
+ printf("payload/buffer size %d/%ld, strlen() %ld\n",
+ n, (long) (vp)->vbuf.len, (long) strlen(vstring_str(vp)));
+ }
+
+ VSTRING_RESET(vp);
+ while (argc-- > 0) {
+ vstring_strcat(vp, *argv++);
+ vstring_strcat(vp, ".");
+ }
+ printf("argv concatenated: %s\n", vstring_str(vp));
+ vstring_free(vp);
+ return (0);
+}
+
+#endif
diff --git a/src/util/vstring.h b/src/util/vstring.h
new file mode 100644
index 0000000..49fd960
--- /dev/null
+++ b/src/util/vstring.h
@@ -0,0 +1,125 @@
+#ifndef _VSTRING_H_INCLUDED_
+#define _VSTRING_H_INCLUDED_
+
+/*++
+/* NAME
+/* vstring 3h
+/* SUMMARY
+/* arbitrary-length string manager
+/* SYNOPSIS
+/* #include "vstring.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <stdarg.h>
+
+ /*
+ * Utility library.
+ */
+#include <vbuf.h>
+#include <check_arg.h>
+
+ /*
+ * We can't allow bare VBUFs in the interface, because VSTRINGs have a
+ * specific initialization and destruction sequence.
+ */
+typedef struct VSTRING {
+ VBUF vbuf;
+} VSTRING;
+
+extern VSTRING *vstring_alloc(ssize_t);
+extern void vstring_ctl(VSTRING *,...);
+extern VSTRING *vstring_truncate(VSTRING *, ssize_t);
+extern VSTRING *vstring_set_payload_size(VSTRING *, ssize_t);
+extern VSTRING *vstring_free(VSTRING *);
+extern VSTRING *vstring_strcpy(VSTRING *, const char *);
+extern VSTRING *vstring_strncpy(VSTRING *, const char *, ssize_t);
+extern VSTRING *vstring_strcat(VSTRING *, const char *);
+extern VSTRING *vstring_strncat(VSTRING *, const char *, ssize_t);
+extern VSTRING *vstring_memcpy(VSTRING *, const char *, ssize_t);
+extern VSTRING *vstring_memcat(VSTRING *, const char *, ssize_t);
+extern char *vstring_memchr(VSTRING *, int);
+extern VSTRING *vstring_insert(VSTRING *, ssize_t, const char *, ssize_t);
+extern VSTRING *vstring_prepend(VSTRING *, const char *, ssize_t);
+extern VSTRING *PRINTFLIKE(2, 3) vstring_sprintf(VSTRING *, const char *,...);
+extern VSTRING *PRINTFLIKE(2, 3) vstring_sprintf_append(VSTRING *, const char *,...);
+extern VSTRING *PRINTFLIKE(2, 3) vstring_sprintf_prepend(VSTRING *, const char *,...);
+extern char *vstring_export(VSTRING *);
+extern VSTRING *vstring_import(char *);
+
+/* Legacy API: constant plus type-unchecked argument. */
+#define VSTRING_CTL_EXACT 2
+#define VSTRING_CTL_END 0
+
+/* Safer API: type-checked arguments. */
+#define CA_VSTRING_CTL_END VSTRING_CTL_END
+#define CA_VSTRING_CTL_EXACT VSTRING_CTL_EXACT
+
+CHECK_VAL_HELPER_DCL(VSTRING_CTL, ssize_t);
+
+/* Flags 24..31 are reserved for VSTRING. */
+#define VSTRING_FLAG_EXACT (1<<24) /* exact allocation for tests */
+#define VSTRING_FLAG_MASK (255 << 24)
+
+ /*
+ * Macros. Unsafe macros have UPPERCASE names.
+ */
+#define VSTRING_SPACE(vp, len) ((vp)->vbuf.space(&(vp)->vbuf, (len)))
+#define vstring_str(vp) ((char *) (vp)->vbuf.data)
+#define VSTRING_LEN(vp) ((ssize_t) ((vp)->vbuf.ptr - (vp)->vbuf.data))
+#define vstring_end(vp) ((char *) (vp)->vbuf.ptr)
+#define VSTRING_TERMINATE(vp) do { \
+ *(vp)->vbuf.ptr = 0; \
+ } while (0)
+#define VSTRING_RESET(vp) do { \
+ (vp)->vbuf.ptr = (vp)->vbuf.data; \
+ (vp)->vbuf.cnt = (vp)->vbuf.len; \
+ } while (0)
+#define VSTRING_ADDCH(vp, ch) VBUF_PUT(&(vp)->vbuf, ch)
+#define VSTRING_SKIP(vp) do { \
+ while ((vp)->vbuf.cnt > 0 && *(vp)->vbuf.ptr) \
+ (vp)->vbuf.ptr++, (vp)->vbuf.cnt--; \
+ } while (0)
+#define vstring_avail(vp) ((vp)->vbuf.cnt)
+
+ /*
+ * The following macro is not part of the public interface, because it can
+ * really screw up a buffer by positioning past allocated memory.
+ */
+#ifdef VSTRING_INTERNAL
+#define VSTRING_AT_OFFSET(vp, offset) do { \
+ (vp)->vbuf.ptr = (vp)->vbuf.data + (offset); \
+ (vp)->vbuf.cnt = (vp)->vbuf.len - (offset); \
+ } while (0)
+#endif
+
+extern VSTRING *vstring_vsprintf(VSTRING *, const char *, va_list);
+extern VSTRING *vstring_vsprintf_append(VSTRING *, const char *, va_list);
+
+/* BUGS
+/* Auto-resizing may change the address of the string data in
+/* a vstring structure. Beware of dangling pointers.
+/* HISTORY
+/* .ad
+/* .fi
+/* A vstring module appears in the UNPROTO software by Wietse Venema.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/vstring_test.ref b/src/util/vstring_test.ref
new file mode 100644
index 0000000..54c54cf
--- /dev/null
+++ b/src/util/vstring_test.ref
@@ -0,0 +1,6 @@
+payload/buffer size 1/1, strlen() 1
+payload/buffer size 2/2, strlen() 2
+payload/buffer size 3/4, strlen() 4
+payload/buffer size 4/4, strlen() 4
+payload/buffer size 5/8, strlen() 8
+argv concatenated: ./vstring.one.two.three.
diff --git a/src/util/vstring_vstream.c b/src/util/vstring_vstream.c
new file mode 100644
index 0000000..451cc50
--- /dev/null
+++ b/src/util/vstring_vstream.c
@@ -0,0 +1,267 @@
+/*++
+/* NAME
+/* vstring_vstream 3
+/* SUMMARY
+/* auto-resizing string library, standard I/O interface
+/* SYNOPSIS
+/* #include <vstring_vstream.h>
+/*
+/* int vstring_get_flags(vp, fp, flags)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/* int flags
+/*
+/* int vstring_get_flags_nonl(vp, fp, flags)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/* int flags
+/*
+/* int vstring_get_flags_null(vp, fp, flags)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/* int flags
+/*
+/* int vstring_get_flags_bound(vp, fp, flags, bound)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/* ssize_t bound;
+/* int flags
+/*
+/* int vstring_get_flags_nonl_bound(vp, fp, flags, bound)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/* ssize_t bound;
+/* int flags
+/*
+/* int vstring_get_flags_null_bound(vp, fp, flags, bound)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/* ssize_t bound;
+/* int flags
+/* CONVENIENCE API
+/* int vstring_get(vp, fp)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/*
+/* int vstring_get_nonl(vp, fp)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/*
+/* int vstring_get_null(vp, fp)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/*
+/* int vstring_get_bound(vp, fp, bound)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/* ssize_t bound;
+/*
+/* int vstring_get_nonl_bound(vp, fp, bound)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/* ssize_t bound;
+/*
+/* int vstring_get_null_bound(vp, fp, bound)
+/* VSTRING *vp;
+/* VSTREAM *fp;
+/* ssize_t bound;
+/* DESCRIPTION
+/* The routines in this module each read one newline or null-terminated
+/* string from an input stream. In all cases the result is either the
+/* last character read, typically the record terminator, or VSTREAM_EOF.
+/* The flags argument is VSTRING_GET_FLAG_NONE (default) or
+/* VSTRING_GET_FLAG_APPEND (append instead of overwrite).
+/*
+/* vstring_get_flags() reads one line from the named stream, including the
+/* terminating newline character if present.
+/*
+/* vstring_get_flags_nonl() reads a line from the named stream and strips
+/* the trailing newline character.
+/*
+/* vstring_get_flags_null() reads a null-terminated string from the named
+/* stream.
+/*
+/* the vstring_get_flags<whatever>_bound() routines read no more
+/* than \fIbound\fR characters. Otherwise they behave like the
+/* unbounded versions documented above.
+/*
+/* The functions without _flags in their name accept the same
+/* arguments except flags. These functions use the default
+/* flags value.
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation failure.
+/* Panic: improper string bound.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <stdio.h>
+#include <string.h>
+
+/* Application-specific. */
+
+#include "msg.h"
+#include "vstring.h"
+#include "vstream.h"
+#include "vstring_vstream.h"
+
+ /*
+ * Macro to return the last character added to a VSTRING, for consistency.
+ */
+#define VSTRING_GET_RESULT(vp, baselen) \
+ (VSTRING_LEN(vp) > (base_len) ? vstring_end(vp)[-1] : VSTREAM_EOF)
+
+/* vstring_get_flags - read line from file, keep newline */
+
+int vstring_get_flags(VSTRING *vp, VSTREAM *fp, int flags)
+{
+ int c;
+ ssize_t base_len;
+
+ if ((flags & VSTRING_GET_FLAG_APPEND) == 0)
+ VSTRING_RESET(vp);
+ base_len = VSTRING_LEN(vp);
+ while ((c = VSTREAM_GETC(fp)) != VSTREAM_EOF) {
+ VSTRING_ADDCH(vp, c);
+ if (c == '\n')
+ break;
+ }
+ VSTRING_TERMINATE(vp);
+ return (VSTRING_GET_RESULT(vp, baselen));
+}
+
+/* vstring_get_flags_nonl - read line from file, strip newline */
+
+int vstring_get_flags_nonl(VSTRING *vp, VSTREAM *fp, int flags)
+{
+ int c;
+ ssize_t base_len;
+
+ if ((flags & VSTRING_GET_FLAG_APPEND) == 0)
+ VSTRING_RESET(vp);
+ base_len = VSTRING_LEN(vp);
+ while ((c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != '\n')
+ VSTRING_ADDCH(vp, c);
+ VSTRING_TERMINATE(vp);
+ return (c == '\n' ? c : VSTRING_GET_RESULT(vp, baselen));
+}
+
+/* vstring_get_flags_null - read null-terminated string from file */
+
+int vstring_get_flags_null(VSTRING *vp, VSTREAM *fp, int flags)
+{
+ int c;
+ ssize_t base_len;
+
+ if ((flags & VSTRING_GET_FLAG_APPEND) == 0)
+ VSTRING_RESET(vp);
+ base_len = VSTRING_LEN(vp);
+ while ((c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != 0)
+ VSTRING_ADDCH(vp, c);
+ VSTRING_TERMINATE(vp);
+ return (c == 0 ? c : VSTRING_GET_RESULT(vp, baselen));
+}
+
+/* vstring_get_flags_bound - read line from file, keep newline, up to bound */
+
+int vstring_get_flags_bound(VSTRING *vp, VSTREAM *fp, int flags,
+ ssize_t bound)
+{
+ int c;
+ ssize_t base_len;
+
+ if (bound <= 0)
+ msg_panic("vstring_get_bound: invalid bound %ld", (long) bound);
+
+ if ((flags & VSTRING_GET_FLAG_APPEND) == 0)
+ VSTRING_RESET(vp);
+ base_len = VSTRING_LEN(vp);
+ while (bound-- > 0 && (c = VSTREAM_GETC(fp)) != VSTREAM_EOF) {
+ VSTRING_ADDCH(vp, c);
+ if (c == '\n')
+ break;
+ }
+ VSTRING_TERMINATE(vp);
+ return (VSTRING_GET_RESULT(vp, baselen));
+}
+
+/* vstring_get_flags_nonl_bound - read line from file, strip newline, up to bound */
+
+int vstring_get_flags_nonl_bound(VSTRING *vp, VSTREAM *fp, int flags,
+ ssize_t bound)
+{
+ int c;
+ ssize_t base_len;
+
+ if (bound <= 0)
+ msg_panic("vstring_get_nonl_bound: invalid bound %ld", (long) bound);
+
+ if ((flags & VSTRING_GET_FLAG_APPEND) == 0)
+ VSTRING_RESET(vp);
+ base_len = VSTRING_LEN(vp);
+ while (bound-- > 0 && (c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != '\n')
+ VSTRING_ADDCH(vp, c);
+ VSTRING_TERMINATE(vp);
+ return (c == '\n' ? c : VSTRING_GET_RESULT(vp, baselen));
+}
+
+/* vstring_get_flags_null_bound - read null-terminated string from file */
+
+int vstring_get_flags_null_bound(VSTRING *vp, VSTREAM *fp, int flags,
+ ssize_t bound)
+{
+ int c;
+ ssize_t base_len;
+
+ if (bound <= 0)
+ msg_panic("vstring_get_null_bound: invalid bound %ld", (long) bound);
+
+ if ((flags & VSTRING_GET_FLAG_APPEND) == 0)
+ VSTRING_RESET(vp);
+ base_len = VSTRING_LEN(vp);
+ while (bound-- > 0 && (c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != 0)
+ VSTRING_ADDCH(vp, c);
+ VSTRING_TERMINATE(vp);
+ return (c == 0 ? c : VSTRING_GET_RESULT(vp, baselen));
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program: copy the source to this module to stdout.
+ */
+#include <fcntl.h>
+
+#define TEXT_VSTREAM "vstring_vstream.c"
+
+int main(void)
+{
+ VSTRING *vp = vstring_alloc(1);
+ VSTREAM *fp;
+
+ if ((fp = vstream_fopen(TEXT_VSTREAM, O_RDONLY, 0)) == 0)
+ msg_fatal("open %s: %m", TEXT_VSTREAM);
+ while (vstring_fgets(vp, fp))
+ vstream_fprintf(VSTREAM_OUT, "%s", vstring_str(vp));
+ vstream_fclose(fp);
+ vstream_fflush(VSTREAM_OUT);
+ vstring_free(vp);
+ return (0);
+}
+
+#endif
diff --git a/src/util/vstring_vstream.h b/src/util/vstring_vstream.h
new file mode 100644
index 0000000..e0dc801
--- /dev/null
+++ b/src/util/vstring_vstream.h
@@ -0,0 +1,82 @@
+#ifndef _VSTRING_VSTREAM_H_INCLUDED_
+#define _VSTRING_VSTREAM_H_INCLUDED_
+
+/*++
+/* NAME
+/* vstring_vstream 3h
+/* SUMMARY
+/* auto-resizing string library
+/* SYNOPSIS
+/* #include <vstring_vstream.h>
+/* DESCRIPTION
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+#define VSTRING_GET_FLAG_NONE (0)
+#define VSTRING_GET_FLAG_APPEND (1<<1) /* append instead of overwrite */
+
+extern int WARN_UNUSED_RESULT vstring_get_flags(VSTRING *, VSTREAM *, int);
+extern int WARN_UNUSED_RESULT vstring_get_flags_nonl(VSTRING *, VSTREAM *, int);
+extern int WARN_UNUSED_RESULT vstring_get_flags_null(VSTRING *, VSTREAM *, int);
+extern int WARN_UNUSED_RESULT vstring_get_flags_bound(VSTRING *, VSTREAM *, int, ssize_t);
+extern int WARN_UNUSED_RESULT vstring_get_flags_nonl_bound(VSTRING *, VSTREAM *, int, ssize_t);
+extern int WARN_UNUSED_RESULT vstring_get_flags_null_bound(VSTRING *, VSTREAM *, int, ssize_t);
+
+ /*
+ * Convenience aliases for most use cases.
+ */
+#define vstring_get(string, stream) \
+ vstring_get_flags((string), (stream), VSTRING_GET_FLAG_NONE)
+#define vstring_get_nonl(string, stream) \
+ vstring_get_flags_nonl((string), (stream), VSTRING_GET_FLAG_NONE)
+#define vstring_get_null(string, stream) \
+ vstring_get_flags_null((string), (stream), VSTRING_GET_FLAG_NONE)
+
+#define vstring_get_bound(string, stream, size) \
+ vstring_get_flags_bound((string), (stream), VSTRING_GET_FLAG_NONE, size)
+#define vstring_get_nonl_bound(string, stream, size) \
+ vstring_get_flags_nonl_bound((string), (stream), \
+ VSTRING_GET_FLAG_NONE, size)
+#define vstring_get_null_bound(string, stream, size) \
+ vstring_get_flags_null_bound((string), (stream), \
+ VSTRING_GET_FLAG_NONE, size)
+
+ /*
+ * Backwards compatibility for code that still uses the vstring_fgets()
+ * interface. Unfortunately we can't change the macro name to upper case.
+ */
+#define vstring_fgets(s, p) \
+ (vstring_get((s), (p)) == VSTREAM_EOF ? 0 : (s))
+#define vstring_fgets_nonl(s, p) \
+ (vstring_get_nonl((s), (p)) == VSTREAM_EOF ? 0 : (s))
+#define vstring_fgets_null(s, p) \
+ (vstring_get_null((s), (p)) == VSTREAM_EOF ? 0 : (s))
+#define vstring_fgets_bound(s, p, l) \
+ (vstring_get_bound((s), (p), (l)) == VSTREAM_EOF ? 0 : (s))
+#define vstring_fgets_nonl_bound(s, p, l) \
+ (vstring_get_nonl_bound((s), (p), (l)) == VSTREAM_EOF ? 0 : (s))
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
diff --git a/src/util/warn_stat.c b/src/util/warn_stat.c
new file mode 100644
index 0000000..fd885d9
--- /dev/null
+++ b/src/util/warn_stat.c
@@ -0,0 +1,101 @@
+/*++
+/* NAME
+/* warn_stat 3
+/* SUMMARY
+/* baby-sit stat() error returns
+/* SYNOPSIS
+/* #include <warn_stat.h>
+/*
+/* int warn_stat(path, st)
+/* const char *path;
+/* struct stat *st;
+/*
+/* int warn_lstat(path, st)
+/* const char *path;
+/* struct stat *st;
+/*
+/* int warn_fstat(fd, st)
+/* int fd;
+/* struct stat *st;
+/* DESCRIPTION
+/* warn_stat(), warn_fstat() and warn_lstat() wrap the stat(),
+/* fstat() and lstat() system calls with code that logs a
+/* diagnosis for common error cases.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#define WARN_STAT_INTERNAL
+#include <warn_stat.h>
+
+/* diagnose_stat - log stat warning */
+
+static void diagnose_stat(void)
+{
+ struct stat st;
+
+ /*
+ * When *stat() fails with EOVERFLOW, and the interface uses 32-bit data
+ * types, suggest that the program be recompiled with larger data types.
+ */
+#ifdef EOVERFLOW
+ if (errno == EOVERFLOW && sizeof(st.st_size) == 4) {
+ msg_warn("this program was built for 32-bit file handles, "
+ "but some number does not fit in 32 bits");
+ msg_warn("possible solution: recompile in 64-bit mode, or "
+ "recompile in 32-bit mode with 'large file' support");
+ }
+#endif
+}
+
+/* warn_stat - stat with warning */
+
+int warn_stat(const char *path, struct stat * st)
+{
+ int ret;
+
+ ret = stat(path, st);
+ if (ret < 0)
+ diagnose_stat();
+ return (ret);
+}
+
+/* warn_lstat - lstat with warning */
+
+int warn_lstat(const char *path, struct stat * st)
+{
+ int ret;
+
+ ret = lstat(path, st);
+ if (ret < 0)
+ diagnose_stat();
+ return (ret);
+}
+
+/* warn_fstat - fstat with warning */
+
+int warn_fstat(int fd, struct stat * st)
+{
+ int ret;
+
+ ret = fstat(fd, st);
+ if (ret < 0)
+ diagnose_stat();
+ return (ret);
+}
diff --git a/src/util/warn_stat.h b/src/util/warn_stat.h
new file mode 100644
index 0000000..92ddce0
--- /dev/null
+++ b/src/util/warn_stat.h
@@ -0,0 +1,38 @@
+#ifndef _WARN_STAT_H_
+#define _WARN_STAT_H_
+
+/*++
+/* NAME
+/* warn_stat 3h
+/* SUMMARY
+/* baby-sit stat() error returns
+/* SYNOPSIS
+/* #include <warn_stat.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * External interface.
+ */
+#ifndef WARN_STAT_INTERNAL
+#define stat(p, s) warn_stat((p), (s))
+#define lstat(p, s) warn_lstat((p), (s))
+#define fstat(f, s) warn_fstat((f), (s))
+#endif
+
+extern int warn_stat(const char *path, struct stat *);
+extern int warn_lstat(const char *path, struct stat *);
+extern int warn_fstat(int, struct stat *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/watchdog.c b/src/util/watchdog.c
new file mode 100644
index 0000000..3ec1fbc
--- /dev/null
+++ b/src/util/watchdog.c
@@ -0,0 +1,314 @@
+/*++
+/* NAME
+/* watchdog 3
+/* SUMMARY
+/* watchdog timer
+/* SYNOPSIS
+/* #include <watchdog.h>
+/*
+/* WATCHDOG *watchdog_create(timeout, action, context)
+/* unsigned timeout;
+/* void (*action)(WATCHDOG *watchdog, char *context);
+/* char *context;
+/*
+/* void watchdog_start(watchdog)
+/* WATCHDOG *watchdog;
+/*
+/* void watchdog_stop(watchdog)
+/* WATCHDOG *watchdog;
+/*
+/* void watchdog_destroy(watchdog)
+/* WATCHDOG *watchdog;
+/*
+/* void watchdog_pat()
+/* DESCRIPTION
+/* This module implements watchdog timers that are based on ugly
+/* UNIX alarm timers. The module is designed to survive systems
+/* with clocks that jump occasionally.
+/*
+/* Watchdog timers can be stacked. Only one watchdog timer can be
+/* active at a time. Only the last created watchdog timer can be
+/* manipulated. Watchdog timers must be destroyed in reverse order
+/* of creation.
+/*
+/* watchdog_create() suspends the current watchdog timer, if any,
+/* and instantiates a new watchdog timer.
+/*
+/* watchdog_start() starts or restarts the watchdog timer.
+/*
+/* watchdog_stop() stops the watchdog timer.
+/*
+/* watchdog_destroy() stops the watchdog timer, and resumes the
+/* watchdog timer instance that was suspended by watchdog_create().
+/*
+/* watchdog_pat() pats the watchdog, so it stays quiet.
+/*
+/* Arguments:
+/* .IP timeout
+/* The watchdog time limit. When the watchdog timer runs, the
+/* process must invoke watchdog_start(), watchdog_stop() or
+/* watchdog_destroy() before the time limit is reached.
+/* .IP action
+/* A null pointer, or pointer to function that is called when the
+/* watchdog alarm goes off. The default action is to terminate
+/* the process with a fatal error.
+/* .IP context
+/* Application context that is passed to the action routine.
+/* .IP watchdog
+/* Must be a pointer to the most recently created watchdog instance.
+/* This argument is checked upon each call.
+/* BUGS
+/* UNIX alarm timers are not stackable, so there can be at most one
+/* watchdog instance active at any given time.
+/* SEE ALSO
+/* msg(3) diagnostics interface
+/* DIAGNOSTICS
+/* Fatal errors: memory allocation problem, system call failure.
+/* Panics: interface violations.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <unistd.h>
+#include <signal.h>
+#include <posix_signals.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <killme_after.h>
+#include <watchdog.h>
+
+/* Application-specific. */
+
+ /*
+ * Rather than having one timer that goes off when it is too late, we break
+ * up the time limit into smaller intervals so that we can deal with clocks
+ * that jump occasionally.
+ */
+#define WATCHDOG_STEPS 3
+
+ /*
+ * UNIX alarms are not stackable, but we can save and restore state, so that
+ * watchdogs can at least be nested, sort of.
+ */
+struct WATCHDOG {
+ unsigned timeout; /* our time resolution */
+ WATCHDOG_FN action; /* application routine */
+ char *context; /* application context */
+ int trip_run; /* number of successive timeouts */
+ WATCHDOG *saved_watchdog; /* saved state */
+ struct sigaction saved_action; /* saved state */
+ unsigned saved_time; /* saved state */
+};
+
+ /*
+ * However, only one watchdog instance can be current, and the caller has to
+ * restore state before a prior watchdog instance can be manipulated.
+ */
+static WATCHDOG *watchdog_curr;
+
+ /*
+ * Workaround for systems where the alarm signal does not wakeup the event
+ * machinery, and therefore does not restart the watchdog timer in the
+ * single_server etc. skeletons. The symptom is that programs abort when the
+ * watchdog timeout is less than the max_idle time.
+ */
+#ifdef USE_WATCHDOG_PIPE
+#include <errno.h>
+#include <iostuff.h>
+#include <events.h>
+
+static int watchdog_pipe[2];
+
+/* watchdog_read - read event pipe */
+
+static void watchdog_read(int unused_event, void *unused_context)
+{
+ char ch;
+
+ while (read(watchdog_pipe[0], &ch, 1) > 0)
+ /* void */ ;
+}
+
+#endif /* USE_WATCHDOG_PIPE */
+
+/* watchdog_event - handle timeout event */
+
+static void watchdog_event(int unused_sig)
+{
+ const char *myname = "watchdog_event";
+ WATCHDOG *wp;
+
+ /*
+ * This routine runs as a signal handler. We should not do anything that
+ * could involve memory allocation/deallocation, but exiting without
+ * proper explanation would be unacceptable. For this reason, msg(3) was
+ * made safe for usage by signal handlers that terminate the process.
+ */
+ if ((wp = watchdog_curr) == 0)
+ msg_panic("%s: no instance", myname);
+ if (msg_verbose > 1)
+ msg_info("%s: %p %d", myname, (void *) wp, wp->trip_run);
+ if (++(wp->trip_run) < WATCHDOG_STEPS) {
+#ifdef USE_WATCHDOG_PIPE
+ int saved_errno = errno;
+
+ /* Wake up the events(3) engine. */
+ if (write(watchdog_pipe[1], "", 1) != 1)
+ msg_warn("%s: write watchdog_pipe: %m", myname);
+ errno = saved_errno;
+#endif
+ alarm(wp->timeout);
+ } else {
+ if (wp->action)
+ wp->action(wp, wp->context);
+ else {
+ killme_after(5);
+#ifdef TEST
+ pause();
+#endif
+ msg_fatal("watchdog timeout");
+ }
+ }
+}
+
+/* watchdog_create - create watchdog instance */
+
+WATCHDOG *watchdog_create(unsigned timeout, WATCHDOG_FN action, char *context)
+{
+ const char *myname = "watchdog_create";
+ struct sigaction sig_action;
+ WATCHDOG *wp;
+
+ wp = (WATCHDOG *) mymalloc(sizeof(*wp));
+ if ((wp->timeout = timeout / WATCHDOG_STEPS) == 0)
+ msg_panic("%s: timeout %d is too small", myname, timeout);
+ wp->action = action;
+ wp->context = context;
+ wp->saved_watchdog = watchdog_curr;
+ wp->saved_time = alarm(0);
+ sigemptyset(&sig_action.sa_mask);
+#ifdef SA_RESTART
+ sig_action.sa_flags = SA_RESTART;
+#else
+ sig_action.sa_flags = 0;
+#endif
+ sig_action.sa_handler = watchdog_event;
+ if (sigaction(SIGALRM, &sig_action, &wp->saved_action) < 0)
+ msg_fatal("%s: sigaction(SIGALRM): %m", myname);
+ if (msg_verbose > 1)
+ msg_info("%s: %p %d", myname, (void *) wp, timeout);
+#ifdef USE_WATCHDOG_PIPE
+ if (watchdog_curr == 0) {
+ if (pipe(watchdog_pipe) < 0)
+ msg_fatal("%s: pipe: %m", myname);
+ non_blocking(watchdog_pipe[0], NON_BLOCKING);
+ non_blocking(watchdog_pipe[1], NON_BLOCKING);
+ close_on_exec(watchdog_pipe[0], CLOSE_ON_EXEC); /* Fix 20190126 */
+ close_on_exec(watchdog_pipe[1], CLOSE_ON_EXEC); /* Fix 20190126 */
+ event_enable_read(watchdog_pipe[0], watchdog_read, (void *) 0);
+ }
+#endif
+ return (watchdog_curr = wp);
+}
+
+/* watchdog_destroy - destroy watchdog instance, restore state */
+
+void watchdog_destroy(WATCHDOG *wp)
+{
+ const char *myname = "watchdog_destroy";
+
+ watchdog_stop(wp);
+ watchdog_curr = wp->saved_watchdog;
+ if (sigaction(SIGALRM, &wp->saved_action, (struct sigaction *) 0) < 0)
+ msg_fatal("%s: sigaction(SIGALRM): %m", myname);
+ if (wp->saved_time)
+ alarm(wp->saved_time);
+ myfree((void *) wp);
+#ifdef USE_WATCHDOG_PIPE
+ if (watchdog_curr == 0) {
+ event_disable_readwrite(watchdog_pipe[0]);
+ (void) close(watchdog_pipe[0]);
+ (void) close(watchdog_pipe[1]);
+ }
+#endif
+ if (msg_verbose > 1)
+ msg_info("%s: %p", myname, (void *) wp);
+}
+
+/* watchdog_start - enable watchdog timer */
+
+void watchdog_start(WATCHDOG *wp)
+{
+ const char *myname = "watchdog_start";
+
+ if (wp != watchdog_curr)
+ msg_panic("%s: wrong watchdog instance", myname);
+ wp->trip_run = 0;
+ alarm(wp->timeout);
+ if (msg_verbose > 1)
+ msg_info("%s: %p", myname, (void *) wp);
+}
+
+/* watchdog_stop - disable watchdog timer */
+
+void watchdog_stop(WATCHDOG *wp)
+{
+ const char *myname = "watchdog_stop";
+
+ if (wp != watchdog_curr)
+ msg_panic("%s: wrong watchdog instance", myname);
+ alarm(0);
+ if (msg_verbose > 1)
+ msg_info("%s: %p", myname, (void *) wp);
+}
+
+/* watchdog_pat - pat the dog so it stays quiet */
+
+void watchdog_pat(void)
+{
+ const char *myname = "watchdog_pat";
+
+ if (watchdog_curr)
+ watchdog_curr->trip_run = 0;
+ if (msg_verbose > 1)
+ msg_info("%s: %p", myname, (void *) watchdog_curr);
+}
+
+#ifdef TEST
+
+#include <vstream.h>
+
+int main(int unused_argc, char **unused_argv)
+{
+ WATCHDOG *wp;
+
+ msg_verbose = 2;
+
+ wp = watchdog_create(10, (WATCHDOG_FN) 0, (void *) 0);
+ watchdog_start(wp);
+ do {
+ watchdog_pat();
+ } while (VSTREAM_GETCHAR() != VSTREAM_EOF);
+ watchdog_destroy(wp);
+ return (0);
+}
+
+#endif
diff --git a/src/util/watchdog.h b/src/util/watchdog.h
new file mode 100644
index 0000000..ee01faf
--- /dev/null
+++ b/src/util/watchdog.h
@@ -0,0 +1,36 @@
+#ifndef _WATCHDOG_H_INCLUDED_
+#define _WATCHDOG_H_INCLUDED_
+
+/*++
+/* NAME
+/* watchdog 3h
+/* SUMMARY
+/* watchdog timer
+/* SYNOPSIS
+/* #include "watchdog.h"
+ DESCRIPTION
+ .nf
+
+ /*
+ * External interface.
+ */
+typedef struct WATCHDOG WATCHDOG;
+typedef void (*WATCHDOG_FN) (WATCHDOG *, char *);
+extern WATCHDOG *watchdog_create(unsigned, WATCHDOG_FN, char *);
+extern void watchdog_start(WATCHDOG *);
+extern void watchdog_stop(WATCHDOG *);
+extern void watchdog_destroy(WATCHDOG *);
+extern void watchdog_pat(void);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
diff --git a/src/util/write_buf.c b/src/util/write_buf.c
new file mode 100644
index 0000000..968a468
--- /dev/null
+++ b/src/util/write_buf.c
@@ -0,0 +1,89 @@
+/*++
+/* NAME
+/* write_buf 3
+/* SUMMARY
+/* write buffer or bust
+/* SYNOPSIS
+/* #include <iostuff.h>
+/*
+/* ssize_t write_buf(fd, buf, len, timeout)
+/* int fd;
+/* const char *buf;
+/* ssize_t len;
+/* int timeout;
+/* DESCRIPTION
+/* write_buf() writes a buffer to the named stream in as many
+/* fragments as needed, and returns the number of bytes written,
+/* which is always the number requested or an error indication.
+/*
+/* Arguments:
+/* .IP fd
+/* File descriptor in the range 0..FD_SETSIZE.
+/* .IP buf
+/* Address of data to be written.
+/* .IP len
+/* Amount of data to be written.
+/* .IP timeout
+/* Bounds the time in seconds to wait until \fIfd\fD becomes writable.
+/* A value <= 0 means do not wait; this is useful only when \fIfd\fR
+/* uses blocking I/O.
+/* DIAGNOSTICS
+/* write_buf() returns -1 in case of trouble. The global \fIerrno\fR
+/* variable reflects the nature of the problem.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <unistd.h>
+#include <errno.h>
+#include <time.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+
+/* write_buf - write buffer or bust */
+
+ssize_t write_buf(int fd, const char *buf, ssize_t len, int timeout)
+{
+ const char *start = buf;
+ ssize_t count;
+ time_t expire;
+ int time_left = timeout;
+
+ if (time_left > 0)
+ expire = time((time_t *) 0) + time_left;
+
+ while (len > 0) {
+ if (time_left > 0 && write_wait(fd, time_left) < 0)
+ return (-1);
+ if ((count = write(fd, buf, len)) < 0) {
+ if ((errno == EAGAIN && time_left > 0) || errno == EINTR)
+ /* void */ ;
+ else
+ return (-1);
+ } else {
+ buf += count;
+ len -= count;
+ }
+ if (len > 0 && time_left > 0) {
+ time_left = expire - time((time_t *) 0);
+ if (time_left <= 0) {
+ errno = ETIMEDOUT;
+ return (-1);
+ }
+ }
+ }
+ return (buf - start);
+}