diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:18:56 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:18:56 +0000 |
commit | b7c15c31519dc44c1f691e0466badd556ffe9423 (patch) | |
tree | f944572f288bab482a615e09af627d9a2b6727d8 /src/util | |
parent | Initial commit. (diff) | |
download | postfix-b7c15c31519dc44c1f691e0466badd556ffe9423.tar.xz postfix-b7c15c31519dc44c1f691e0466badd556ffe9423.zip |
Adding upstream version 3.7.10.upstream/3.7.10
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
456 files changed, 63539 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..c59cdf9 --- /dev/null +++ b/src/util/Makefile.in @@ -0,0 +1,2756 @@ +SHELL = /bin/sh +SRCS = alldig.c allprint.c argv.c argv_split.c attr_clnt.c attr_print0.c \ + attr_print64.c attr_print_plain.c attr_scan0.c attr_scan64.c \ + attr_scan_plain.c auto_clnt.c base64_code.c basename.c binhash.c \ + chroot_uid.c cidr_match.c clean_env.c close_on_exec.c concatenate.c \ + ctable.c dict.c dict_alloc.c dict_cdb.c dict_cidr.c dict_db.c \ + dict_dbm.c dict_debug.c dict_env.c dict_ht.c dict_lmdb.c dict_ni.c dict_nis.c \ + dict_nisplus.c dict_open.c dict_pcre.c dict_regexp.c dict_sdbm.c \ + dict_static.c dict_tcp.c dict_unix.c dir_forest.c doze.c dummy_read.c \ + dummy_write.c duplex_pipe.c environ.c events.c exec_command.c \ + fifo_listen.c fifo_trigger.c file_limit.c find_inet.c fsspace.c \ + fullname.c get_domainname.c get_hostname.c hex_code.c hex_quote.c \ + host_port.c htable.c inet_addr_host.c inet_addr_list.c \ + inet_addr_local.c inet_connect.c inet_listen.c inet_proto.c \ + inet_trigger.c line_wrap.c lowercase.c lstat_as.c mac_expand.c \ + mac_parse.c make_dirs.c mask_addr.c match_list.c match_ops.c msg.c \ + msg_output.c msg_syslog.c msg_vstream.c mvect.c myaddrinfo.c myflock.c \ + mymalloc.c myrand.c mystrtok.c name_code.c name_mask.c netstring.c \ + neuter.c non_blocking.c nvtable.c open_as.c open_limit.c open_lock.c \ + peekfd.c posix_signals.c printable.c rand_sleep.c \ + readlline.c ring.c safe_getenv.c safe_open.c \ + sane_accept.c sane_connect.c sane_link.c sane_rename.c \ + sane_socketpair.c sane_time.c scan_dir.c set_eugid.c set_ugid.c \ + load_lib.c \ + sigdelay.c skipblanks.c sock_addr.c spawn_command.c split_at.c \ + split_nameval.c stat_as.c strcasecmp.c stream_connect.c \ + stream_listen.c stream_recv_fd.c stream_send_fd.c stream_trigger.c \ + sys_compat.c timed_connect.c timed_read.c timed_wait.c timed_write.c \ + translit.c trimblanks.c unescape.c unix_connect.c unix_listen.c \ + unix_recv_fd.c unix_send_fd.c unix_trigger.c unsafe.c uppercase.c \ + username.c valid_hostname.c vbuf.c vbuf_print.c vstream.c \ + vstream_popen.c vstring.c vstring_vstream.c watchdog.c \ + write_buf.c sane_basename.c format_tv.c allspace.c \ + allascii.c load_file.c killme_after.c vstream_tweak.c \ + pass_trigger.c edit_file.c inet_windowsize.c \ + unix_pass_fd_fix.c dict_cache.c valid_utf8_string.c dict_thash.c \ + ip_match.c nbbio.c base32_code.c dict_test.c \ + dict_fail.c msg_rate_delay.c dict_surrogate.c warn_stat.c \ + dict_sockmap.c line_number.c recv_pass_attr.c pass_accept.c \ + poll_fd.c timecmp.c slmdb.c dict_pipe.c dict_random.c \ + valid_utf8_hostname.c midna_domain.c argv_splitq.c balpar.c dict_union.c \ + extpar.c dict_inline.c casefold.c dict_utf8.c strcasecmp_utf8.c \ + split_qnameval.c argv_attr_print.c argv_attr_scan.c dict_file.c \ + msg_logger.c logwriter.c unix_dgram_connect.c unix_dgram_listen.c \ + byte_mask.c known_tcp_ports.c argv_split_at.c dict_stream.c \ + sane_strtol.c hash_fnv.c ldseed.c +OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \ + attr_print64.o attr_print_plain.o attr_scan0.o attr_scan64.o \ + attr_scan_plain.o auto_clnt.o base64_code.o basename.o binhash.o \ + chroot_uid.o cidr_match.o clean_env.o close_on_exec.o concatenate.o \ + ctable.o dict.o dict_alloc.o dict_cidr.o dict_db.o \ + dict_dbm.o dict_debug.o dict_env.o dict_ht.o dict_ni.o dict_nis.o \ + dict_nisplus.o dict_open.o dict_regexp.o \ + dict_static.o dict_tcp.o dict_unix.o dir_forest.o doze.o dummy_read.o \ + dummy_write.o duplex_pipe.o environ.o events.o exec_command.o \ + fifo_listen.o fifo_trigger.o file_limit.o find_inet.o fsspace.o \ + fullname.o get_domainname.o get_hostname.o hex_code.o hex_quote.o \ + host_port.o htable.o inet_addr_host.o inet_addr_list.o \ + inet_addr_local.o inet_connect.o inet_listen.o inet_proto.o \ + inet_trigger.o line_wrap.o lowercase.o lstat_as.o mac_expand.o \ + load_lib.o \ + mac_parse.o make_dirs.o mask_addr.o match_list.o match_ops.o msg.o \ + msg_output.o msg_syslog.o msg_vstream.o mvect.o myaddrinfo.o myflock.o \ + mymalloc.o myrand.o mystrtok.o name_code.o name_mask.o netstring.o \ + neuter.o non_blocking.o nvtable.o open_as.o open_limit.o open_lock.o \ + peekfd.o posix_signals.o printable.o rand_sleep.o \ + readlline.o ring.o safe_getenv.o safe_open.o \ + sane_accept.o sane_connect.o sane_link.o sane_rename.o \ + sane_socketpair.o sane_time.o scan_dir.o set_eugid.o set_ugid.o \ + sigdelay.o skipblanks.o sock_addr.o spawn_command.o split_at.o \ + split_nameval.o stat_as.o $(STRCASE) stream_connect.o \ + stream_listen.o stream_recv_fd.o stream_send_fd.o stream_trigger.o \ + sys_compat.o timed_connect.o timed_read.o timed_wait.o timed_write.o \ + translit.o trimblanks.o unescape.o unix_connect.o unix_listen.o \ + unix_recv_fd.o unix_send_fd.o unix_trigger.o unsafe.o uppercase.o \ + username.o valid_hostname.o vbuf.o vbuf_print.o vstream.o \ + vstream_popen.o vstring.o vstring_vstream.o watchdog.o \ + write_buf.o sane_basename.o format_tv.o allspace.o \ + allascii.o load_file.o killme_after.o vstream_tweak.o \ + pass_trigger.o edit_file.o inet_windowsize.o \ + unix_pass_fd_fix.o dict_cache.o valid_utf8_string.o dict_thash.o \ + ip_match.o nbbio.o base32_code.o dict_test.o \ + dict_fail.o msg_rate_delay.o dict_surrogate.o warn_stat.o \ + dict_sockmap.o line_number.o recv_pass_attr.o pass_accept.o \ + poll_fd.o timecmp.o $(NON_PLUGIN_MAP_OBJ) dict_pipe.o dict_random.o \ + valid_utf8_hostname.o midna_domain.o argv_splitq.o balpar.o dict_union.o \ + extpar.o dict_inline.o casefold.o dict_utf8.o strcasecmp_utf8.o \ + split_qnameval.o argv_attr_print.o argv_attr_scan.o dict_file.o \ + msg_logger.o logwriter.o unix_dgram_connect.o unix_dgram_listen.o \ + byte_mask.o known_tcp_ports.o argv_split_at.o dict_stream.o \ + sane_strtol.o hash_fnv.o ldseed.o +# MAP_OBJ is for maps that may be dynamically loaded with dynamicmaps.cf. +# When hard-linking these, makedefs sets NON_PLUGIN_MAP_OBJ=$(MAP_OBJ), +# otherwise it sets the PLUGIN_* macros. +MAP_OBJ = dict_pcre.o $(LIB_MAP_OBJ) +LIB_MAP_OBJ = dict_cdb.o dict_lmdb.o dict_sdbm.o slmdb.o +HDRS = argv.h attr.h attr_clnt.h auto_clnt.h base64_code.h binhash.h \ + chroot_uid.h cidr_match.h clean_env.h connect.h ctable.h dict.h \ + dict_cdb.h dict_cidr.h dict_db.h dict_dbm.h dict_env.h dict_ht.h \ + dict_lmdb.h dict_ni.h dict_nis.h dict_nisplus.h dict_pcre.h dict_regexp.h \ + dict_sdbm.h dict_static.h dict_tcp.h dict_unix.h dir_forest.h \ + events.h exec_command.h find_inet.h fsspace.h fullname.h \ + get_domainname.h get_hostname.h hex_code.h hex_quote.h host_port.h \ + htable.h inet_addr_host.h inet_addr_list.h inet_addr_local.h \ + inet_proto.h iostuff.h line_wrap.h listen.h lstat_as.h mac_expand.h \ + mac_parse.h make_dirs.h mask_addr.h match_list.h msg.h \ + msg_output.h msg_syslog.h msg_vstream.h mvect.h myaddrinfo.h myflock.h \ + mymalloc.h myrand.h name_code.h name_mask.h netstring.h nvtable.h \ + open_as.h open_lock.h posix_signals.h readlline.h ring.h \ + safe.h safe_open.h sane_accept.h sane_connect.h sane_fsops.h \ + load_lib.h \ + sane_socketpair.h sane_time.h scan_dir.h set_eugid.h set_ugid.h \ + sigdelay.h sock_addr.h spawn_command.h split_at.h stat_as.h \ + stringops.h sys_defs.h timed_connect.h timed_wait.h trigger.h \ + username.h valid_hostname.h vbuf.h vbuf_print.h vstream.h vstring.h \ + vstring_vstream.h watchdog.h format_tv.h load_file.h killme_after.h \ + edit_file.h dict_cache.h dict_thash.h ip_match.h nbbio.h base32_code.h \ + dict_fail.h warn_stat.h dict_sockmap.h line_number.h timecmp.h \ + slmdb.h compat_va_copy.h dict_pipe.h dict_random.h \ + valid_utf8_hostname.h midna_domain.h dict_union.h dict_inline.h \ + check_arg.h argv_attr.h msg_logger.h logwriter.h byte_mask.h \ + known_tcp_ports.h sane_strtol.h hash_fnv.h ldseed.h +TESTSRC = fifo_open.c fifo_rdwr_bug.c fifo_rdonly_bug.c select_bug.c \ + stream_test.c dup2_pass_on_exec.c +DEFS = -I. -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +FILES = Makefile $(SRCS) $(HDRS) +INCL = +LIB = lib$(LIB_PREFIX)util$(LIB_SUFFIX) +TESTPROG= dict_open dup2_pass_on_exec events exec_command fifo_open \ + fifo_rdonly_bug fifo_rdwr_bug fifo_trigger fsspace fullname \ + inet_addr_host inet_addr_local mac_parse make_dirs msg_syslog \ + mystrtok sigdelay translit valid_hostname vstream_popen \ + vstring vstring_vstream doze select_bug stream_test mac_expand \ + watchdog unescape hex_quote name_mask rand_sleep sane_time ctable \ + inet_addr_list attr_print64 attr_scan64 base64_code attr_print0 \ + attr_scan0 host_port attr_scan_plain attr_print_plain htable \ + unix_recv_fd unix_send_fd stream_recv_fd stream_send_fd hex_code \ + myaddrinfo myaddrinfo4 inet_proto sane_basename format_tv \ + valid_utf8_string ip_match base32_code msg_rate_delay netstring \ + vstream timecmp dict_cache midna_domain casefold strcasecmp_utf8 \ + vbuf_print split_qnameval vstream msg_logger byte_mask \ + known_tcp_ports dict_stream find_inet binhash +PLUGIN_MAP_SO = $(LIB_PREFIX)pcre$(LIB_SUFFIX) +HTABLE_FIX = NORANDOMIZE=1 +LIB_DIR = ../../lib +INC_DIR = ../../include + +.c.o:; $(CC) $(SHLIB_CFLAGS) $(CFLAGS) -c $*.c + +all: $(LIB) $(PLUGIN_MAP_SO_MAKE) $(PLUGIN_MAP_OBJ) + +$(OBJS) $(PLUGIN_MAP_OBJ): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +$(LIB): $(OBJS) + $(AR) $(ARFL) $(LIB) $? + $(RANLIB) $(LIB) + $(SHLIB_LD) $(SHLIB_RPATH) -o $(LIB) $(OBJS) $(SHLIB_SYSLIBS) + +$(LIB_DIR)/$(LIB): $(LIB) + cp $(LIB) $(LIB_DIR) + $(RANLIB) $(LIB_DIR)/$(LIB) + +plugin_map_so_make: $(PLUGIN_MAP_SO) + +$(LIB_PREFIX)pcre$(LIB_SUFFIX): dict_pcre.o + $(PLUGIN_LD) $(SHLIB_RPATH) -o $@ dict_pcre.o $(AUXLIBS_PCRE) + +update: $(LIB_DIR)/$(LIB) $(HDRS) $(PLUGIN_MAP_SO_UPDATE) \ + $(PLUGIN_MAP_OBJ_UPDATE) + -for i in $(HDRS); \ + do \ + cmp -s $$i $(INC_DIR)/$$i 2>/dev/null || cp $$i $(INC_DIR); \ + done + cd $(INC_DIR); chmod 644 $(HDRS) + +plugin_map_so_update: $(PLUGIN_MAP_SO) + -for i in $(PLUGIN_MAP_SO); \ + do \ + for type in $(DEFINED_MAP_TYPES); do \ + case $$i in $(LIB_PREFIX)$$type$(LIB_SUFFIX)) \ + cmp -s $$i $(LIB_DIR)/$$i 2>/dev/null || cp $$i $(LIB_DIR); \ + continue 2;; \ + esac; \ + done; \ + rm -f $(LIB_DIR)/$$i; \ + done + +plugin_map_obj_update: $(LIB_MAP_OBJ) + -for i in $(LIB_MAP_OBJ); \ + do \ + cmp -s $$i $(LIB_DIR)/$$i 2>/dev/null || cp $$i $(LIB_DIR); \ + done + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +shar: + @shar $(FILES) + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o $(LIB) *core $(TESTPROG) junk $(MAKES) *.tmp + rm -rf printfck + +tidy: clean + +dup2_pass_on_exec: dup2_pass_on_exec.c + $(CC) $(CFLAGS) -o $@ $@.c $(SYSLIBS) + +vstring: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +msg_logger: msg_logger.c $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +msg_syslog: msg_syslog.c $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +vstring_vstream: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +valid_hostname: valid_hostname.c $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +events: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +dict_open: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +fullname: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +inet_addr_local: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +inet_addr_host: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +fifo_open: fifo_open.c + $(CC) $(CFLAGS) -o $@ $@.c $(SYSLIBS) + +sigdelay: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +mystrtok: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +fifo_rdwr_bug: fifo_rdwr_bug.c $(LIB) + $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(SYSLIBS) + +fifo_rdonly_bug: fifo_rdonly_bug.c $(LIB) + $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(SYSLIBS) + +select_bug: select_bug.c $(LIB) + $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(SYSLIBS) + +translit: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +fsspace: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +exec_command: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +make_dirs: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +mac_parse: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +vstream_popen: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +fifo_trigger: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +doze: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +mac_expand: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +watchdog: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +unescape: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +hex_quote: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +name_mask: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +byte_mask: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +rand_sleep: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +sane_time: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +ctable: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +inet_addr_list: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +attr_print64: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +attr_scan64: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +base64_code: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +attr_print0: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +attr_scan0: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +host_port: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +attr_scan_plain: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +attr_print_plain: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +htable: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +binhash: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +unix_recv_fd: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +unix_send_fd: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +stream_recv_fd: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +stream_send_fd: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +hex_code: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +myaddrinfo: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +myaddrinfo4: $(LIB) + mv myaddrinfo.o junk + $(CC) $(CFLAGS) -DTEST -DEMULATE_IPV4_ADDRINFO -o $@ myaddrinfo.c $(LIB) $(SYSLIBS) + mv junk myaddrinfo.o + +inet_proto: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +sane_basename: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +find_inet: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +stream_test: stream_test.c $(LIB) + $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(SYSLIBS) + +gcctest: gccw.c gccw.ref + rm -f gccw.o + make gccw.o 2>&1 | sed "s/\`/'/g; s/return-/return /" | sort >gccw.tmp + diff gccw.ref gccw.tmp + rm -f gccw.o gccw.tmp + +format_tv: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +valid_utf8_string: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +ip_match: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +base32_code: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +msg_rate_delay: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +netstring: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +vstream: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +timecmp: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +dict_cache: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +midna_domain: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +casefold: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +strcasecmp_utf8: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +vbuf_print: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +split_qnameval: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +known_tcp_ports: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +dict_stream: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +tests: all valid_hostname_test mac_expand_test dict_test unescape_test \ + hex_quote_test ctable_test inet_addr_list_test base64_code_test \ + attr_scan64_test attr_scan0_test dict_pcre_tests host_port_test \ + dict_cidr_test attr_scan_plain_test htable_test hex_code_test \ + myaddrinfo_test format_tv_test ip_match_test name_mask_tests \ + base32_code_test dict_thash_test surrogate_test timecmp_test \ + dict_static_test dict_inline_test midna_domain_test casefold_test \ + dict_utf8_test strcasecmp_utf8_test vbuf_print_test dict_regexp_test \ + dict_union_test dict_pipe_test miss_endif_cidr_test \ + miss_endif_regexp_test split_qnameval_test vstring_test \ + vstream_test dict_regexp_file_test dict_cidr_file_test \ + dict_static_file_test dict_random_test dict_random_file_test \ + dict_inline_file_test byte_mask_tests mystrtok_test \ + known_tcp_ports_test dict_stream_test dict_inline_regexp_test \ + dict_inline_cidr_test binhash_test + +dict_pcre_tests: dict_pcre_test miss_endif_pcre_test dict_pcre_file_test \ + dict_inline_pcre_test + +root_tests: + +valid_hostname_test: valid_hostname valid_hostname.in valid_hostname.ref + $(SHLIB_ENV) ${VALGRIND} ./valid_hostname <valid_hostname.in 2>valid_hostname.tmp + diff valid_hostname.ref valid_hostname.tmp + rm -f valid_hostname.tmp + +mac_expand_test: mac_expand mac_expand.in mac_expand.ref + $(SHLIB_ENV) ${VALGRIND} ./mac_expand <mac_expand.in >mac_expand.tmp 2>&1 + diff mac_expand.ref mac_expand.tmp + rm -f mac_expand.tmp + +unescape_test: unescape unescape.in unescape.ref + $(SHLIB_ENV) ${VALGRIND} ./unescape <unescape.in | od -cb >unescape.tmp + diff -b unescape.ref unescape.tmp +# $(SHLIB_ENV) ${VALGRIND} ./unescape <unescape.in | $(SHLIB_ENV) ./unescape -e >unescape.tmp +# diff unescape.in unescape.tmp + rm -f unescape.tmp + +hex_quote_test: hex_quote + $(SHLIB_ENV) ${VALGRIND} ./hex_quote <hex_quote.c | od -cb >hex_quote.tmp + od -cb <hex_quote.c >hex_quote.ref + cmp hex_quote.ref hex_quote.tmp + rm -f hex_quote.ref hex_quote.tmp + +ctable_test: ctable + $(SHLIB_ENV) ${VALGRIND} ./ctable <ctable.in >ctable.tmp 2>&1 + diff ctable.ref ctable.tmp + rm -f ctable.tmp + +# On Linux, following test may require "modprobe ipv6" to enable IPv6. + +inet_addr_list_test: inet_addr_list + $(SHLIB_ENV) ${VALGRIND} ./inet_addr_list `cat inet_addr_list.in` >inet_addr_list.tmp 2>&1 + diff inet_addr_list.ref inet_addr_list.tmp + rm -f inet_addr_list.tmp + +sane_basename_test: sane_basename + $(SHLIB_ENV) ${VALGRIND} ./sane_basename <sane_basename.in >sane_basename.tmp 2>&1 + diff sane_basename.ref sane_basename.tmp + rm -f sane_basename.tmp + +base64_code_test: base64_code + $(SHLIB_ENV) ${VALGRIND} ./base64_code + +attr_scan64_test: attr_print64 attr_scan64 attr_scan64.ref + ($(HTABLE_FIX) $(SHLIB_ENV) ${VALGRIND} ./attr_print64 2>&3 | (sleep 1; $(HTABLE_FIX) $(SHLIB_ENV) ./attr_scan64)) >attr_scan64.tmp 2>&1 3>&1 + diff attr_scan64.ref attr_scan64.tmp + rm -f attr_scan64.tmp + +attr_scan0_test: attr_print0 attr_scan0 attr_scan0.ref + ($(HTABLE_FIX) $(SHLIB_ENV) ${VALGRIND} ./attr_print0 2>&3 | (sleep 1; $(HTABLE_FIX) $(SHLIB_ENV) ./attr_scan0)) >attr_scan0.tmp 2>&1 3>&1 + diff attr_scan0.ref attr_scan0.tmp + rm -f attr_scan0.tmp + +dict_test: dict_open testdb dict_test.in dict_test.ref + rm -f testdb.db testdb.dir testdb.pag + $(SHLIB_ENV) ../postmap/postmap -N hash:testdb + $(SHLIB_ENV) ${VALGRIND} ./dict_open hash:testdb write < dict_test.in 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_test.tmp + diff dict_test.ref dict_test.tmp + $(SHLIB_ENV) ../postmap/postmap -n hash:testdb + $(SHLIB_ENV) ${VALGRIND} ./dict_open hash:testdb write < dict_test.in 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_test.tmp + diff dict_test.ref dict_test.tmp + rm -f testdb.db testdb.dir testdb.pag dict_test.tmp + +dict_pcre_test: dict_open dict_pcre.in dict_pcre.map dict_pcre.ref + $(SHLIB_ENV) ${VALGRIND} ./dict_open pcre:dict_pcre.map read \ + <dict_pcre.in 2>&1 | sed -e 's/uid=[0-9][0-9][0-9]*/uid=USER/' \ + -e 's/missing )/missing closing parenthesis/' >dict_pcre.tmp + diff dict_pcre.ref dict_pcre.tmp + rm -f dict_pcre.tmp + +dict_pcre_file_test: dict_open dict_pcre_file.in dict_pcre_file.map dict_pcre_file.ref + echo this-is-file1 > dict_pcre_file1 + echo this-is-file2 > dict_pcre_file2 + $(SHLIB_ENV) ${VALGRIND} ./dict_open pcre:dict_pcre_file.map \ + read src_rhs_is_file <dict_pcre_file.in 2>&1 | \ + sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_pcre_file.tmp + diff dict_pcre_file.ref dict_pcre_file.tmp + rm -f dict_pcre_file.tmp dict_pcre_file1 dict_pcre_file2 + +dict_regexp_test: dict_open dict_regexp.in dict_regexp.map dict_regexp.ref + $(SHLIB_ENV) ${VALGRIND} ./dict_open regexp:dict_regexp.map read <dict_regexp.in 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_regexp.tmp + diff dict_regexp.ref dict_regexp.tmp + rm -f dict_regexp.tmp + +dict_regexp_file_test: dict_open dict_regexp_file.in dict_regexp_file.map dict_regexp_file.ref + echo this-is-file1 > dict_regexp_file1 + echo this-is-file2 > dict_regexp_file2 + $(SHLIB_ENV) ${VALGRIND} ./dict_open regexp:dict_regexp_file.map \ + read src_rhs_is_file <dict_regexp_file.in 2>&1 | \ + sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_regexp_file.tmp + diff dict_regexp_file.ref dict_regexp_file.tmp + rm -f dict_regexp_file.tmp dict_regexp_file1 dict_regexp_file2 + +dict_cidr_test: dict_open dict_cidr.in dict_cidr.map dict_cidr.ref + $(SHLIB_ENV) ${VALGRIND} ./dict_open cidr:dict_cidr.map read <dict_cidr.in 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_cidr.tmp + diff dict_cidr.ref dict_cidr.tmp + rm -f dict_cidr.tmp + +dict_cidr_file_test: dict_open dict_cidr_file.in dict_cidr_file.map dict_cidr_file.ref + echo this-is-file1 > dict_cidr_file1 + echo this-is-file2 > dict_cidr_file2 + $(SHLIB_ENV) ${VALGRIND} ./dict_open cidr:dict_cidr_file.map \ + read src_rhs_is_file <dict_cidr_file.in 2>&1 | \ + sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_cidr_file.tmp + diff dict_cidr_file.ref dict_cidr_file.tmp + rm -f dict_cidr_file.tmp dict_cidr_file1 dict_cidr_file2 + +miss_endif_cidr_test: dict_open miss_endif_cidr.map miss_endif_cidr.ref + echo get 1.2.3.5 | $(SHLIB_ENV) ${VALGRIND} ./dict_open cidr:miss_endif_cidr.map read 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_cidr.tmp + diff miss_endif_cidr.ref dict_cidr.tmp + rm -f dict_cidr.tmp + +miss_endif_pcre_test: dict_open miss_endif_re.map miss_endif_pcre.ref + echo get 1.2.3.5 | $(SHLIB_ENV) ${VALGRIND} ./dict_open pcre:miss_endif_re.map read 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_pcre.tmp + diff miss_endif_pcre.ref dict_pcre.tmp + rm -f dict_pcre.tmp + +miss_endif_regexp_test: dict_open miss_endif_re.map miss_endif_regexp.ref + echo get 1.2.3.5 | $(SHLIB_ENV) ${VALGRIND} ./dict_open regexp:miss_endif_re.map read 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_regexp.tmp + diff miss_endif_regexp.ref dict_regexp.tmp + rm -f dict_regexp.tmp + +split_qnameval_test: split_qnameval update + $(SHLIB_ENV) ${VALGRIND} ./split_qnameval + +dict_seq_test: dict_open testdb dict_seq.in dict_seq.ref + rm -f testdb.db testdb.dir testdb.pag + $(SHLIB_ENV) ${VALGRIND} ./dict_open hash:testdb create sync < dict_seq.in 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' > dict_seq.tmp + diff dict_seq.ref dict_seq.tmp + rm -f testdb.db testdb.dir testdb.pag dict_seq.tmp + +host_port_test: host_port host_port.in host_port.ref + $(SHLIB_ENV) ${VALGRIND} ./host_port <host_port.in >host_port.tmp 2>&1 + diff host_port.ref host_port.tmp + rm -f host_port.tmp + +attr_scan_plain_test: attr_print_plain attr_scan_plain attr_scan_plain.ref + ($(HTABLE_FIX) $(SHLIB_ENV) ${VALGRIND} ./attr_print_plain 2>&3 | (sleep 1; $(HTABLE_FIX) $(SHLIB_ENV) ./attr_scan_plain)) >attr_scan_plain.tmp 2>&1 3>&1 + diff attr_scan_plain.ref attr_scan_plain.tmp + rm -f attr_scan_plain.tmp + +htable_test: htable /usr/share/dict/words + $(SHLIB_ENV) ${VALGRIND} ./htable < /usr/share/dict/words + +binhash_test: binhash /usr/share/dict/words + $(SHLIB_ENV) ${VALGRIND} ./binhash < /usr/share/dict/words + +hex_code_test: hex_code + $(SHLIB_ENV) ${VALGRIND} ./hex_code + +timecmp_test: timecmp + $(SHLIB_ENV) ${VALGRIND} ./timecmp + +myaddrinfo_test: myaddrinfo myaddrinfo.ref myaddrinfo.ref2 + $(SHLIB_ENV) ${VALGRIND} ./myaddrinfo all belly.porcupine.org 168.100.3.2 >myaddrinfo.tmp 2>&1 + diff myaddrinfo.ref myaddrinfo.tmp + rm -f myaddrinfo.tmp + $(SHLIB_ENV) ${VALGRIND} ./myaddrinfo all null.porcupine.org 10.0.0.0 >myaddrinfo.tmp 2>&1 + diff myaddrinfo.ref2 myaddrinfo.tmp + rm -f myaddrinfo.tmp + +myaddrinfo4_test: myaddrinfo4 myaddrinfo4.ref myaddrinfo4.ref2 + $(SHLIB_ENV) ${VALGRIND} ./myaddrinfo4 all belly.porcupine.org 168.100.3.2 >myaddrinfo4.tmp 2>&1 + diff myaddrinfo4.ref myaddrinfo4.tmp + $(SHLIB_ENV) ${VALGRIND} ./myaddrinfo4 all null.porcupine.org 10.0.0.0 >myaddrinfo4.tmp 2>&1 + diff myaddrinfo4.ref2 myaddrinfo4.tmp + rm -f myaddrinfo4.tmp + +format_tv_test: format_tv format_tv.in format_tv.ref + $(SHLIB_ENV) ${VALGRIND} ./format_tv <format_tv.in >format_tv.tmp + diff format_tv.ref format_tv.tmp + rm -f format_tv.tmp + +ip_match_test: ip_match ip_match.in ip_match.ref + $(SHLIB_ENV) ${VALGRIND} ./ip_match <ip_match.in >ip_match.tmp + diff ip_match.ref ip_match.tmp + rm -f ip_match.tmp + +name_mask_tests: name_mask_test0 name_mask_test1 name_mask_test2 \ + name_mask_test3 name_mask_test4 name_mask_test5 name_mask_test6 \ + name_mask_test7 name_mask_test8 name_mask_test9 + +name_mask_test0: name_mask name_mask.in name_mask.ref0 + $(SHLIB_ENV) ${VALGRIND} ./name_mask IGNORE IGNORE < name_mask.in > name_mask.tmp 2>&1 + diff name_mask.ref0 name_mask.tmp + rm -f name_mask.tmp + +name_mask_test1: name_mask name_mask.in name_mask.ref1 + $(SHLIB_ENV) ${VALGRIND} ./name_mask NUMBER,WARN NUMBER < name_mask.in > name_mask.tmp 2>&1 + diff name_mask.ref1 name_mask.tmp + rm -f name_mask.tmp + +name_mask_test2: name_mask name_mask.in name_mask.ref2 + $(SHLIB_ENV) ${VALGRIND} ./name_mask NUMBER,RETURN NUMBER < name_mask.in > name_mask.tmp 2>&1 + diff name_mask.ref2 name_mask.tmp + rm -f name_mask.tmp + +name_mask_test3: name_mask name_mask.in name_mask.ref3 + $(SHLIB_ENV) ${VALGRIND} ./name_mask WARN NUMBER < name_mask.in > name_mask.tmp 2>&1 + diff name_mask.ref3 name_mask.tmp + rm -f name_mask.tmp + +name_mask_test4: name_mask name_mask.in name_mask.ref4 + $(SHLIB_ENV) ${VALGRIND} ./name_mask RETURN NUMBER < name_mask.in > name_mask.tmp 2>&1 + diff name_mask.ref4 name_mask.tmp + rm -f name_mask.tmp + +name_mask_test5: name_mask name_mask.in name_mask.ref5 + $(SHLIB_ENV) ${VALGRIND} ./name_mask NUMBER,WARN RETURN < name_mask.in > name_mask.tmp 2>&1 + diff name_mask.ref5 name_mask.tmp + rm -f name_mask.tmp + +name_mask_test6: name_mask name_mask.in name_mask.ref6 + $(SHLIB_ENV) ${VALGRIND} ./name_mask NUMBER,WARN WARN < name_mask.in > name_mask.tmp 2>&1 + diff name_mask.ref6 name_mask.tmp + rm -f name_mask.tmp + +name_mask_test7: name_mask name_mask.in name_mask.ref7 + $(SHLIB_ENV) ${VALGRIND} ./name_mask NUMBER,WARN IGNORE < name_mask.in > name_mask.tmp 2>&1 + diff name_mask.ref7 name_mask.tmp + rm -f name_mask.tmp + +name_mask_test8: name_mask name_mask.in name_mask.ref8 + $(SHLIB_ENV) ${VALGRIND} ./name_mask NUMBER,WARN NUMBER,COMMA < name_mask.in > name_mask.tmp 2>&1 + diff name_mask.ref8 name_mask.tmp + rm -f name_mask.tmp + +name_mask_test9: name_mask name_mask.in name_mask.ref9 + $(SHLIB_ENV) ${VALGRIND} ./name_mask NUMBER,WARN NUMBER,PIPE < name_mask.in > name_mask.tmp 2>&1 + diff name_mask.ref9 name_mask.tmp + rm -f name_mask.tmp + +byte_mask_tests: byte_mask_test0 byte_mask_test1 byte_mask_test2 + +byte_mask_test0: byte_mask byte_mask.in byte_mask.ref0 + while read line; do \ + echo "$$line" | $(SHLIB_ENV) ${VALGRIND} ./byte_mask IGNORE IGNORE; \ + done < byte_mask.in > byte_mask.tmp 2>&1 + diff byte_mask.ref0 byte_mask.tmp + rm -f byte_mask.tmp + +byte_mask_test1: byte_mask byte_mask.in byte_mask.ref1 + while read line; do \ + echo "$$line" | $(SHLIB_ENV) ${VALGRIND} ./byte_mask WARN WARN; \ + done < byte_mask.in > byte_mask.tmp 2>&1 + diff byte_mask.ref1 byte_mask.tmp + rm -f byte_mask.tmp + +byte_mask_test2: byte_mask byte_mask.in byte_mask.ref2 + -while read line; do \ + echo "$$line" | $(SHLIB_ENV) ${VALGRIND} ./byte_mask FATAL FATAL; \ + done < byte_mask.in > byte_mask.tmp 2>&1 + diff byte_mask.ref2 byte_mask.tmp + rm -f byte_mask.tmp + +base32_code_test: base32_code + $(SHLIB_ENV) ${VALGRIND} ./base32_code + +dict_thash_test: ../postmap/postmap dict_thash.map dict_thash.in dict_thash.ref + $(SHLIB_ENV) ${VALGRIND} ../postmap/postmap -fs texthash:dict_thash.map 2>&1 | \ + LANG=C sort | diff dict_thash.map - + NORANDOMIZE=1 $(SHLIB_ENV) ${VALGRIND} ../postmap/postmap -fs texthash:dict_thash.in >dict_thash.tmp 2>&1 + diff dict_thash.ref dict_thash.tmp + rm -f dict_thash.tmp + +surrogate_test: dict_open surrogate.ref + cp /dev/null surrogate.tmp + echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open cidr:/xx write >>surrogate.tmp 2>&1 + echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open cidr:/xx read >>surrogate.tmp 2>&1 + echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open pcre:/xx write >>surrogate.tmp 2>&1 + echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open pcre:/xx read >>surrogate.tmp 2>&1 + echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open regexp:/xx write >>surrogate.tmp 2>&1 + echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open regexp:/xx read >>surrogate.tmp 2>&1 + echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open unix:xx write >>surrogate.tmp 2>&1 + echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open unix:xx read >>surrogate.tmp 2>&1 + echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open texthash:/xx write >>surrogate.tmp 2>&1 + echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open texthash:/xx read >>surrogate.tmp 2>&1 + echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open hash:/xx read >>surrogate.tmp 2>&1 + diff surrogate.ref surrogate.tmp + rm -f surrogate.tmp + +dict_static_test: dict_open dict_static.ref + (set -e; \ + (echo get foo; echo get bar) | $(SHLIB_ENV) \ + ${VALGRIND} ./dict_open static:fooxx read; \ + $(SHLIB_ENV) ${VALGRIND} ./dict_open static:'{ foo xx ' read </dev/null; \ + $(SHLIB_ENV) ${VALGRIND} ./dict_open static:'{ foo xx }x' read </dev/null; \ + (echo get foo; echo get bar) | $(SHLIB_ENV) \ + ${VALGRIND} ./dict_open static:'{ foo xx }' read; \ + ) >dict_static.tmp 2>&1 + diff dict_static.ref dict_static.tmp + rm -f dict_static.tmp + +dict_static_file_test: dict_open dict_static_file.ref + echo this-is-file1 > dict_static_file1 + (set -e; \ + echo get foo | $(SHLIB_ENV) \ + ${VALGRIND} ./dict_open static:fooxx read src_rhs_is_file; \ + (echo get file1; echo get file2) | $(SHLIB_ENV) \ + ${VALGRIND} ./dict_open static:'{ dict_static_file1 }' read src_rhs_is_file; \ + ) >dict_static_file.tmp 2>&1 + diff dict_static_file.ref dict_static_file.tmp + rm -f dict_static_file.tmp dict_static_file1 + +dict_random_test: dict_open dict_random.ref + (set -e; \ + (echo get foo; echo get bar) | $(SHLIB_ENV) \ + ${VALGRIND} ./dict_open randmap:'{123 123}' read; \ + echo get foo | $(SHLIB_ENV) ${VALGRIND} ./dict_open randmap:123 read; \ + echo get foo | $(SHLIB_ENV) ${VALGRIND} ./dict_open randmap:'{ 123 ' read; \ + echo get foo | $(SHLIB_ENV) ${VALGRIND} ./dict_open randmap:'{ 123 }x' read; \ + ) >dict_random.tmp 2>&1 + diff dict_random.ref dict_random.tmp + rm -f dict_random.tmp + +dict_random_file_test: dict_open dict_random_file.ref + echo this-is-file1 > dict_random_file1 + (set -e; \ + echo get foo | $(SHLIB_ENV) \ + ${VALGRIND} ./dict_open randmap:'{fooxx}' read src_rhs_is_file; \ + (echo get foo; echo get bar) | $(SHLIB_ENV) \ + ${VALGRIND} ./dict_open randmap:'{ dict_random_file1 }' read src_rhs_is_file; \ + ) >dict_random_file.tmp 2>&1 + diff dict_random_file.ref dict_random_file.tmp + rm -f dict_random_file.tmp dict_random_file1 + +dict_inline_test: dict_open dict_inline.ref + (set -e; \ + $(SHLIB_ENV) ${VALGRIND} ./dict_open inline:'{ }' read </dev/null; \ + $(SHLIB_ENV) ${VALGRIND} ./dict_open inline:'{ foo = xx }' read </dev/null; \ + $(SHLIB_ENV) ${VALGRIND} ./dict_open inline:'{ foo=xx }x' read </dev/null; \ + $(SHLIB_ENV) ${VALGRIND} ./dict_open inline:'{ foo=xx x' read </dev/null; \ + $(SHLIB_ENV) ${VALGRIND} ./dict_open inline:'{ foo=xx {x=y}x}' read </dev/null; \ + (echo get foo; echo get bar; echo get baz) | $(SHLIB_ENV) \ + ${VALGRIND} ./dict_open inline:'{ foo=XX, { bAr = lotsa stuff }}' read fold_fix; \ + (echo get foo; echo get bar; echo get baz) | $(SHLIB_ENV) \ + ${VALGRIND} ./dict_open inline:'{ foo=XX, { bAr = lotsa stuff }}' read 'fold_fix,utf8_request'; \ + ) >dict_inline.tmp 2>&1 + diff dict_inline.ref dict_inline.tmp + rm -f dict_inline.tmp + +dict_inline_file_test: dict_open dict_inline_file.ref + (set -e; \ + echo get foo | $(SHLIB_ENV) ${VALGRIND} \ + ./dict_open inline:'{ foo=xx, bar=yy }' read src_rhs_is_file; \ + echo this-is-file1 > dict_inline_file1; \ + echo this-is-file2 > dict_inline_file2; \ + (echo get file1; echo get file2; echo get file3) | $(SHLIB_ENV) \ + ${VALGRIND} ./dict_open inline:'{ file1=dict_inline_file1, file2=dict_inline_file2}' read src_rhs_is_file; \ + ) >dict_inline_file.tmp 2>&1 + diff dict_inline_file.ref dict_inline_file.tmp + rm -f dict_inline_file.tmp dict_inline_file1 dict_inline_file2 + +midna_domain_test: midna_domain midna_domain_test.in midna_domain_test.ref + $(SHLIB_ENV) ${VALGRIND} ./midna_domain <midna_domain_test.in >midna_domain_test.tmp 2>&1 + diff midna_domain_test.ref midna_domain_test.tmp + rm -f midna_domain_test.tmp + +casefold_test: casefold casefold_test.in casefold_test.ref + $(SHLIB_ENV) ${VALGRIND} ./casefold <casefold_test.in >casefold_test.tmp 2>&1 + diff casefold_test.ref casefold_test.tmp + rm -f casefold_test.tmp + +dict_utf8_test: dict_open dict_utf8_test.in dict_utf8_test.ref + $(SHLIB_ENV) sh dict_utf8_test.in >dict_utf8_test.tmp 2>&1 + diff dict_utf8_test.ref dict_utf8_test.tmp + rm -f dict_utf8_test.tmp + +strcasecmp_utf8_test: strcasecmp_utf8 strcasecmp_utf8_test.in \ + strcasecmp_utf8_test.ref + $(SHLIB_ENV) ${VALGRIND} ./strcasecmp_utf8 <strcasecmp_utf8_test.in >strcasecmp_utf8_test.tmp 2>&1 + diff strcasecmp_utf8_test.ref strcasecmp_utf8_test.tmp + rm -f strcasecmp_utf8_test.tmp + +vbuf_print_test: vbuf_print vbuf_print_test.in vbuf_print_test.ref + $(SHLIB_ENV) ${VALGRIND} ./vbuf_print <vbuf_print_test.in >vbuf_print_test.tmp 2>&1 + diff vbuf_print_test.ref vbuf_print_test.tmp + rm -f vbuf_print_test.tmp + +dict_union_test: dict_open dict_union_test.in dict_union_test.ref + $(SHLIB_ENV) ${VALGRIND} sh -x dict_union_test.in >dict_union_test.tmp 2>&1 + diff dict_union_test.ref dict_union_test.tmp + rm -f dict_union_test.tmp + +dict_pipe_test: dict_open dict_pipe_test.in dict_pipe_test.ref + $(SHLIB_ENV) ${VALGRIND} sh -x dict_pipe_test.in >dict_pipe_test.tmp 2>&1 + diff dict_pipe_test.ref dict_pipe_test.tmp + rm -f dict_pipe_test.tmp + +vstring_test: dict_open vstring vstring_test.ref + $(SHLIB_ENV) ${VALGRIND} ./vstring one two three >vstring_test.tmp 2>&1 + diff vstring_test.ref vstring_test.tmp + rm -f vstring_test.tmp + +vstream_test: vstream vstream_test.in vstream_test.ref + $(SHLIB_ENV) ${VALGRIND} ./vstream <vstream_test.in >vstream_test.tmp 2>&1 + diff vstream_test.ref vstream_test.tmp + rm -f vstream_test.tmp + +mystrtok_test: mystrtok mystrtok.ref + $(SHLIB_ENV) ${VALGRIND} ./mystrtok >mystrtok.tmp 2>&1 + diff mystrtok.ref mystrtok.tmp + rm -f mystrtok.tmp + +known_tcp_ports_test: known_tcp_ports known_tcp_ports.ref + $(SHLIB_ENV) ${VALGRIND} ./known_tcp_ports >known_tcp_ports.tmp 2>&1 + diff known_tcp_ports.ref known_tcp_ports.tmp + rm -f known_tcp_ports.tmp + +dict_stream_test: dict_stream dict_stream.ref + $(SHLIB_ENV) ${VALGRIND} ./dict_stream >dict_stream.tmp 2>&1 + diff dict_stream.ref dict_stream.tmp + rm -f dict_stream.tmp + +dict_inline_pcre_test: dict_open dict_inline_pcre.ref + (echo get foo; echo get bar) | \ + $(SHLIB_ENV) ${VALGRIND} ./dict_open 'pcre:{{/foo/ got foo}}' \ + read 2>&1 | grep -v uid= >dict_inline_pcre.tmp + diff dict_inline_pcre.ref dict_inline_pcre.tmp + rm -f dict_inline_pcre.tmp + +dict_inline_regexp_test: dict_open dict_inline_regexp.ref + (echo get foo; echo get bar) | \ + $(SHLIB_ENV) ${VALGRIND} ./dict_open 'regexp:{{/foo/ got foo}}' \ + read 2>&1 | grep -v uid= >dict_inline_regexp.tmp + diff dict_inline_regexp.ref dict_inline_regexp.tmp + rm -f dict_inline_regexp.tmp + +dict_inline_cidr_test: dict_open dict_inline_cidr.ref + (echo get 1.2.3.4; echo get 4.3.2.1) | \ + $(SHLIB_ENV) ${VALGRIND} ./dict_open \ + 'cidr:{{1.2.3.4 got 1.2.3.4}}' \ + read 2>&1 | grep -v uid= >dict_inline_cidr.tmp + diff dict_inline_cidr.ref dict_inline_cidr.tmp + rm -f dict_inline_cidr.tmp + +find_inet_test: find_inet find_inet.ref + $(SHLIB_ENV) ${VALGRIND} ./find_inet >find_inet.tmp 2>&1 + diff find_inet.ref find_inet.tmp + rm -f find_inet.tmp + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +allascii.o: allascii.c +allascii.o: check_arg.h +allascii.o: stringops.h +allascii.o: sys_defs.h +allascii.o: vbuf.h +allascii.o: vstring.h +alldig.o: alldig.c +alldig.o: check_arg.h +alldig.o: stringops.h +alldig.o: sys_defs.h +alldig.o: vbuf.h +alldig.o: vstring.h +allprint.o: allprint.c +allprint.o: check_arg.h +allprint.o: stringops.h +allprint.o: sys_defs.h +allprint.o: vbuf.h +allprint.o: vstring.h +allspace.o: allspace.c +allspace.o: check_arg.h +allspace.o: stringops.h +allspace.o: sys_defs.h +allspace.o: vbuf.h +allspace.o: vstring.h +argv.o: argv.c +argv.o: argv.h +argv.o: msg.h +argv.o: mymalloc.h +argv.o: sys_defs.h +argv_attr_print.o: argv.h +argv_attr_print.o: argv_attr.h +argv_attr_print.o: argv_attr_print.c +argv_attr_print.o: attr.h +argv_attr_print.o: check_arg.h +argv_attr_print.o: htable.h +argv_attr_print.o: msg.h +argv_attr_print.o: mymalloc.h +argv_attr_print.o: nvtable.h +argv_attr_print.o: sys_defs.h +argv_attr_print.o: vbuf.h +argv_attr_print.o: vstream.h +argv_attr_print.o: vstring.h +argv_attr_scan.o: argv.h +argv_attr_scan.o: argv_attr.h +argv_attr_scan.o: argv_attr_scan.c +argv_attr_scan.o: attr.h +argv_attr_scan.o: check_arg.h +argv_attr_scan.o: htable.h +argv_attr_scan.o: msg.h +argv_attr_scan.o: mymalloc.h +argv_attr_scan.o: nvtable.h +argv_attr_scan.o: sys_defs.h +argv_attr_scan.o: vbuf.h +argv_attr_scan.o: vstream.h +argv_attr_scan.o: vstring.h +argv_split.o: argv.h +argv_split.o: argv_split.c +argv_split.o: check_arg.h +argv_split.o: msg.h +argv_split.o: mymalloc.h +argv_split.o: stringops.h +argv_split.o: sys_defs.h +argv_split.o: vbuf.h +argv_split.o: vstring.h +argv_split_at.o: argv.h +argv_split_at.o: argv_split_at.c +argv_split_at.o: check_arg.h +argv_split_at.o: msg.h +argv_split_at.o: mymalloc.h +argv_split_at.o: split_at.h +argv_split_at.o: stringops.h +argv_split_at.o: sys_defs.h +argv_split_at.o: vbuf.h +argv_split_at.o: vstring.h +argv_splitq.o: argv.h +argv_splitq.o: argv_splitq.c +argv_splitq.o: check_arg.h +argv_splitq.o: msg.h +argv_splitq.o: mymalloc.h +argv_splitq.o: stringops.h +argv_splitq.o: sys_defs.h +argv_splitq.o: vbuf.h +argv_splitq.o: vstring.h +attr_clnt.o: attr.h +attr_clnt.o: attr_clnt.c +attr_clnt.o: attr_clnt.h +attr_clnt.o: auto_clnt.h +attr_clnt.o: check_arg.h +attr_clnt.o: compat_va_copy.h +attr_clnt.o: htable.h +attr_clnt.o: iostuff.h +attr_clnt.o: msg.h +attr_clnt.o: mymalloc.h +attr_clnt.o: nvtable.h +attr_clnt.o: sys_defs.h +attr_clnt.o: vbuf.h +attr_clnt.o: vstream.h +attr_clnt.o: vstring.h +attr_print0.o: attr.h +attr_print0.o: attr_print0.c +attr_print0.o: base64_code.h +attr_print0.o: check_arg.h +attr_print0.o: htable.h +attr_print0.o: msg.h +attr_print0.o: mymalloc.h +attr_print0.o: nvtable.h +attr_print0.o: sys_defs.h +attr_print0.o: vbuf.h +attr_print0.o: vstream.h +attr_print0.o: vstring.h +attr_print64.o: attr.h +attr_print64.o: attr_print64.c +attr_print64.o: base64_code.h +attr_print64.o: check_arg.h +attr_print64.o: htable.h +attr_print64.o: msg.h +attr_print64.o: mymalloc.h +attr_print64.o: nvtable.h +attr_print64.o: sys_defs.h +attr_print64.o: vbuf.h +attr_print64.o: vstream.h +attr_print64.o: vstring.h +attr_print_plain.o: attr.h +attr_print_plain.o: attr_print_plain.c +attr_print_plain.o: base64_code.h +attr_print_plain.o: check_arg.h +attr_print_plain.o: htable.h +attr_print_plain.o: msg.h +attr_print_plain.o: mymalloc.h +attr_print_plain.o: nvtable.h +attr_print_plain.o: sys_defs.h +attr_print_plain.o: vbuf.h +attr_print_plain.o: vstream.h +attr_print_plain.o: vstring.h +attr_scan0.o: attr.h +attr_scan0.o: attr_scan0.c +attr_scan0.o: base64_code.h +attr_scan0.o: check_arg.h +attr_scan0.o: htable.h +attr_scan0.o: msg.h +attr_scan0.o: mymalloc.h +attr_scan0.o: nvtable.h +attr_scan0.o: stringops.h +attr_scan0.o: sys_defs.h +attr_scan0.o: vbuf.h +attr_scan0.o: vstream.h +attr_scan0.o: vstring.h +attr_scan0.o: vstring_vstream.h +attr_scan64.o: attr.h +attr_scan64.o: attr_scan64.c +attr_scan64.o: base64_code.h +attr_scan64.o: check_arg.h +attr_scan64.o: htable.h +attr_scan64.o: msg.h +attr_scan64.o: mymalloc.h +attr_scan64.o: nvtable.h +attr_scan64.o: stringops.h +attr_scan64.o: sys_defs.h +attr_scan64.o: vbuf.h +attr_scan64.o: vstream.h +attr_scan64.o: vstring.h +attr_scan_plain.o: attr.h +attr_scan_plain.o: attr_scan_plain.c +attr_scan_plain.o: base64_code.h +attr_scan_plain.o: check_arg.h +attr_scan_plain.o: htable.h +attr_scan_plain.o: msg.h +attr_scan_plain.o: mymalloc.h +attr_scan_plain.o: nvtable.h +attr_scan_plain.o: stringops.h +attr_scan_plain.o: sys_defs.h +attr_scan_plain.o: vbuf.h +attr_scan_plain.o: vstream.h +attr_scan_plain.o: vstring.h +auto_clnt.o: auto_clnt.c +auto_clnt.o: auto_clnt.h +auto_clnt.o: check_arg.h +auto_clnt.o: connect.h +auto_clnt.o: events.h +auto_clnt.o: iostuff.h +auto_clnt.o: msg.h +auto_clnt.o: mymalloc.h +auto_clnt.o: split_at.h +auto_clnt.o: sys_defs.h +auto_clnt.o: vbuf.h +auto_clnt.o: vstream.h +balpar.o: balpar.c +balpar.o: check_arg.h +balpar.o: stringops.h +balpar.o: sys_defs.h +balpar.o: vbuf.h +balpar.o: vstring.h +base32_code.o: base32_code.c +base32_code.o: base32_code.h +base32_code.o: check_arg.h +base32_code.o: msg.h +base32_code.o: mymalloc.h +base32_code.o: sys_defs.h +base32_code.o: vbuf.h +base32_code.o: vstring.h +base64_code.o: base64_code.c +base64_code.o: base64_code.h +base64_code.o: check_arg.h +base64_code.o: msg.h +base64_code.o: mymalloc.h +base64_code.o: sys_defs.h +base64_code.o: vbuf.h +base64_code.o: vstring.h +basename.o: basename.c +basename.o: check_arg.h +basename.o: stringops.h +basename.o: sys_defs.h +basename.o: vbuf.h +basename.o: vstring.h +binhash.o: binhash.c +binhash.o: binhash.h +binhash.o: hash_fnv.h +binhash.o: msg.h +binhash.o: mymalloc.h +binhash.o: sys_defs.h +byte_mask.o: byte_mask.c +byte_mask.o: byte_mask.h +byte_mask.o: check_arg.h +byte_mask.o: msg.h +byte_mask.o: mymalloc.h +byte_mask.o: stringops.h +byte_mask.o: sys_defs.h +byte_mask.o: vbuf.h +byte_mask.o: vstring.h +casefold.o: casefold.c +casefold.o: check_arg.h +casefold.o: msg.h +casefold.o: stringops.h +casefold.o: sys_defs.h +casefold.o: vbuf.h +casefold.o: vstring.h +chroot_uid.o: chroot_uid.c +chroot_uid.o: chroot_uid.h +chroot_uid.o: msg.h +chroot_uid.o: sys_defs.h +cidr_match.o: check_arg.h +cidr_match.o: cidr_match.c +cidr_match.o: cidr_match.h +cidr_match.o: mask_addr.h +cidr_match.o: msg.h +cidr_match.o: myaddrinfo.h +cidr_match.o: split_at.h +cidr_match.o: stringops.h +cidr_match.o: sys_defs.h +cidr_match.o: vbuf.h +cidr_match.o: vstring.h +clean_env.o: argv.h +clean_env.o: clean_env.c +clean_env.o: clean_env.h +clean_env.o: msg.h +clean_env.o: safe.h +clean_env.o: sys_defs.h +close_on_exec.o: close_on_exec.c +close_on_exec.o: iostuff.h +close_on_exec.o: msg.h +close_on_exec.o: sys_defs.h +concatenate.o: check_arg.h +concatenate.o: compat_va_copy.h +concatenate.o: concatenate.c +concatenate.o: mymalloc.h +concatenate.o: stringops.h +concatenate.o: sys_defs.h +concatenate.o: vbuf.h +concatenate.o: vstring.h +ctable.o: ctable.c +ctable.o: ctable.h +ctable.o: htable.h +ctable.o: msg.h +ctable.o: mymalloc.h +ctable.o: ring.h +ctable.o: sys_defs.h +dict.o: argv.h +dict.o: check_arg.h +dict.o: dict.c +dict.o: dict.h +dict.o: dict_ht.h +dict.o: htable.h +dict.o: iostuff.h +dict.o: line_number.h +dict.o: mac_expand.h +dict.o: mac_parse.h +dict.o: msg.h +dict.o: myflock.h +dict.o: mymalloc.h +dict.o: name_mask.h +dict.o: readlline.h +dict.o: stringops.h +dict.o: sys_defs.h +dict.o: vbuf.h +dict.o: vstream.h +dict.o: vstring.h +dict.o: warn_stat.h +dict_alloc.o: argv.h +dict_alloc.o: check_arg.h +dict_alloc.o: dict.h +dict_alloc.o: dict_alloc.c +dict_alloc.o: msg.h +dict_alloc.o: myflock.h +dict_alloc.o: mymalloc.h +dict_alloc.o: sys_defs.h +dict_alloc.o: vbuf.h +dict_alloc.o: vstream.h +dict_alloc.o: vstring.h +dict_cache.o: argv.h +dict_cache.o: check_arg.h +dict_cache.o: dict.h +dict_cache.o: dict_cache.c +dict_cache.o: dict_cache.h +dict_cache.o: events.h +dict_cache.o: msg.h +dict_cache.o: myflock.h +dict_cache.o: mymalloc.h +dict_cache.o: sys_defs.h +dict_cache.o: vbuf.h +dict_cache.o: vstream.h +dict_cache.o: vstring.h +dict_cdb.o: argv.h +dict_cdb.o: check_arg.h +dict_cdb.o: dict.h +dict_cdb.o: dict_cdb.c +dict_cdb.o: dict_cdb.h +dict_cdb.o: iostuff.h +dict_cdb.o: msg.h +dict_cdb.o: myflock.h +dict_cdb.o: mymalloc.h +dict_cdb.o: stringops.h +dict_cdb.o: sys_defs.h +dict_cdb.o: vbuf.h +dict_cdb.o: vstream.h +dict_cdb.o: vstring.h +dict_cdb.o: warn_stat.h +dict_cidr.o: argv.h +dict_cidr.o: check_arg.h +dict_cidr.o: cidr_match.h +dict_cidr.o: dict.h +dict_cidr.o: dict_cidr.c +dict_cidr.o: dict_cidr.h +dict_cidr.o: msg.h +dict_cidr.o: mvect.h +dict_cidr.o: myaddrinfo.h +dict_cidr.o: myflock.h +dict_cidr.o: mymalloc.h +dict_cidr.o: readlline.h +dict_cidr.o: stringops.h +dict_cidr.o: sys_defs.h +dict_cidr.o: vbuf.h +dict_cidr.o: vstream.h +dict_cidr.o: vstring.h +dict_cidr.o: warn_stat.h +dict_db.o: argv.h +dict_db.o: check_arg.h +dict_db.o: dict.h +dict_db.o: dict_db.c +dict_db.o: dict_db.h +dict_db.o: iostuff.h +dict_db.o: msg.h +dict_db.o: myflock.h +dict_db.o: mymalloc.h +dict_db.o: stringops.h +dict_db.o: sys_defs.h +dict_db.o: vbuf.h +dict_db.o: vstream.h +dict_db.o: vstring.h +dict_db.o: warn_stat.h +dict_dbm.o: dict_dbm.c +dict_dbm.o: sys_defs.h +dict_debug.o: argv.h +dict_debug.o: check_arg.h +dict_debug.o: dict.h +dict_debug.o: dict_debug.c +dict_debug.o: msg.h +dict_debug.o: myflock.h +dict_debug.o: mymalloc.h +dict_debug.o: sys_defs.h +dict_debug.o: vbuf.h +dict_debug.o: vstream.h +dict_debug.o: vstring.h +dict_env.o: argv.h +dict_env.o: check_arg.h +dict_env.o: dict.h +dict_env.o: dict_env.c +dict_env.o: dict_env.h +dict_env.o: msg.h +dict_env.o: myflock.h +dict_env.o: mymalloc.h +dict_env.o: safe.h +dict_env.o: stringops.h +dict_env.o: sys_defs.h +dict_env.o: vbuf.h +dict_env.o: vstream.h +dict_env.o: vstring.h +dict_fail.o: argv.h +dict_fail.o: check_arg.h +dict_fail.o: dict.h +dict_fail.o: dict_fail.c +dict_fail.o: dict_fail.h +dict_fail.o: msg.h +dict_fail.o: myflock.h +dict_fail.o: mymalloc.h +dict_fail.o: sys_defs.h +dict_fail.o: vbuf.h +dict_fail.o: vstream.h +dict_fail.o: vstring.h +dict_file.o: argv.h +dict_file.o: base64_code.h +dict_file.o: check_arg.h +dict_file.o: dict.h +dict_file.o: dict_file.c +dict_file.o: msg.h +dict_file.o: myflock.h +dict_file.o: mymalloc.h +dict_file.o: sys_defs.h +dict_file.o: vbuf.h +dict_file.o: vstream.h +dict_file.o: vstring.h +dict_ht.o: argv.h +dict_ht.o: check_arg.h +dict_ht.o: dict.h +dict_ht.o: dict_ht.c +dict_ht.o: dict_ht.h +dict_ht.o: htable.h +dict_ht.o: myflock.h +dict_ht.o: mymalloc.h +dict_ht.o: stringops.h +dict_ht.o: sys_defs.h +dict_ht.o: vbuf.h +dict_ht.o: vstream.h +dict_ht.o: vstring.h +dict_inline.o: argv.h +dict_inline.o: check_arg.h +dict_inline.o: dict.h +dict_inline.o: dict_ht.h +dict_inline.o: dict_inline.c +dict_inline.o: dict_inline.h +dict_inline.o: htable.h +dict_inline.o: msg.h +dict_inline.o: myflock.h +dict_inline.o: mymalloc.h +dict_inline.o: stringops.h +dict_inline.o: sys_defs.h +dict_inline.o: vbuf.h +dict_inline.o: vstream.h +dict_inline.o: vstring.h +dict_lmdb.o: argv.h +dict_lmdb.o: check_arg.h +dict_lmdb.o: dict.h +dict_lmdb.o: dict_lmdb.c +dict_lmdb.o: dict_lmdb.h +dict_lmdb.o: htable.h +dict_lmdb.o: iostuff.h +dict_lmdb.o: msg.h +dict_lmdb.o: myflock.h +dict_lmdb.o: mymalloc.h +dict_lmdb.o: slmdb.h +dict_lmdb.o: stringops.h +dict_lmdb.o: sys_defs.h +dict_lmdb.o: vbuf.h +dict_lmdb.o: vstream.h +dict_lmdb.o: vstring.h +dict_lmdb.o: warn_stat.h +dict_ni.o: dict_ni.c +dict_ni.o: sys_defs.h +dict_nis.o: argv.h +dict_nis.o: check_arg.h +dict_nis.o: dict.h +dict_nis.o: dict_nis.c +dict_nis.o: dict_nis.h +dict_nis.o: msg.h +dict_nis.o: myflock.h +dict_nis.o: mymalloc.h +dict_nis.o: stringops.h +dict_nis.o: sys_defs.h +dict_nis.o: vbuf.h +dict_nis.o: vstream.h +dict_nis.o: vstring.h +dict_nisplus.o: argv.h +dict_nisplus.o: check_arg.h +dict_nisplus.o: dict.h +dict_nisplus.o: dict_nisplus.c +dict_nisplus.o: dict_nisplus.h +dict_nisplus.o: msg.h +dict_nisplus.o: myflock.h +dict_nisplus.o: mymalloc.h +dict_nisplus.o: stringops.h +dict_nisplus.o: sys_defs.h +dict_nisplus.o: vbuf.h +dict_nisplus.o: vstream.h +dict_nisplus.o: vstring.h +dict_open.o: argv.h +dict_open.o: check_arg.h +dict_open.o: dict.h +dict_open.o: dict_cdb.h +dict_open.o: dict_cidr.h +dict_open.o: dict_db.h +dict_open.o: dict_dbm.h +dict_open.o: dict_env.h +dict_open.o: dict_fail.h +dict_open.o: dict_ht.h +dict_open.o: dict_inline.h +dict_open.o: dict_lmdb.h +dict_open.o: dict_ni.h +dict_open.o: dict_nis.h +dict_open.o: dict_nisplus.h +dict_open.o: dict_open.c +dict_open.o: dict_pcre.h +dict_open.o: dict_pipe.h +dict_open.o: dict_random.h +dict_open.o: dict_regexp.h +dict_open.o: dict_sdbm.h +dict_open.o: dict_sockmap.h +dict_open.o: dict_static.h +dict_open.o: dict_tcp.h +dict_open.o: dict_thash.h +dict_open.o: dict_union.h +dict_open.o: dict_unix.h +dict_open.o: htable.h +dict_open.o: msg.h +dict_open.o: myflock.h +dict_open.o: mymalloc.h +dict_open.o: split_at.h +dict_open.o: stringops.h +dict_open.o: sys_defs.h +dict_open.o: vbuf.h +dict_open.o: vstream.h +dict_open.o: vstring.h +dict_pcre.o: argv.h +dict_pcre.o: check_arg.h +dict_pcre.o: dict.h +dict_pcre.o: dict_pcre.c +dict_pcre.o: dict_pcre.h +dict_pcre.o: mac_parse.h +dict_pcre.o: msg.h +dict_pcre.o: mvect.h +dict_pcre.o: myflock.h +dict_pcre.o: mymalloc.h +dict_pcre.o: readlline.h +dict_pcre.o: safe.h +dict_pcre.o: stringops.h +dict_pcre.o: sys_defs.h +dict_pcre.o: vbuf.h +dict_pcre.o: vstream.h +dict_pcre.o: vstring.h +dict_pcre.o: warn_stat.h +dict_pipe.o: argv.h +dict_pipe.o: check_arg.h +dict_pipe.o: dict.h +dict_pipe.o: dict_pipe.c +dict_pipe.o: dict_pipe.h +dict_pipe.o: htable.h +dict_pipe.o: msg.h +dict_pipe.o: myflock.h +dict_pipe.o: mymalloc.h +dict_pipe.o: stringops.h +dict_pipe.o: sys_defs.h +dict_pipe.o: vbuf.h +dict_pipe.o: vstream.h +dict_pipe.o: vstring.h +dict_random.o: argv.h +dict_random.o: check_arg.h +dict_random.o: dict.h +dict_random.o: dict_random.c +dict_random.o: dict_random.h +dict_random.o: msg.h +dict_random.o: myflock.h +dict_random.o: mymalloc.h +dict_random.o: myrand.h +dict_random.o: stringops.h +dict_random.o: sys_defs.h +dict_random.o: vbuf.h +dict_random.o: vstream.h +dict_random.o: vstring.h +dict_regexp.o: argv.h +dict_regexp.o: check_arg.h +dict_regexp.o: dict.h +dict_regexp.o: dict_regexp.c +dict_regexp.o: dict_regexp.h +dict_regexp.o: mac_parse.h +dict_regexp.o: msg.h +dict_regexp.o: mvect.h +dict_regexp.o: myflock.h +dict_regexp.o: mymalloc.h +dict_regexp.o: readlline.h +dict_regexp.o: safe.h +dict_regexp.o: stringops.h +dict_regexp.o: sys_defs.h +dict_regexp.o: vbuf.h +dict_regexp.o: vstream.h +dict_regexp.o: vstring.h +dict_regexp.o: warn_stat.h +dict_sdbm.o: argv.h +dict_sdbm.o: check_arg.h +dict_sdbm.o: dict.h +dict_sdbm.o: dict_sdbm.c +dict_sdbm.o: dict_sdbm.h +dict_sdbm.o: htable.h +dict_sdbm.o: iostuff.h +dict_sdbm.o: msg.h +dict_sdbm.o: myflock.h +dict_sdbm.o: mymalloc.h +dict_sdbm.o: stringops.h +dict_sdbm.o: sys_defs.h +dict_sdbm.o: vbuf.h +dict_sdbm.o: vstream.h +dict_sdbm.o: vstring.h +dict_sdbm.o: warn_stat.h +dict_sockmap.o: argv.h +dict_sockmap.o: auto_clnt.h +dict_sockmap.o: check_arg.h +dict_sockmap.o: dict.h +dict_sockmap.o: dict_sockmap.c +dict_sockmap.o: dict_sockmap.h +dict_sockmap.o: htable.h +dict_sockmap.o: msg.h +dict_sockmap.o: myflock.h +dict_sockmap.o: mymalloc.h +dict_sockmap.o: netstring.h +dict_sockmap.o: split_at.h +dict_sockmap.o: stringops.h +dict_sockmap.o: sys_defs.h +dict_sockmap.o: vbuf.h +dict_sockmap.o: vstream.h +dict_sockmap.o: vstring.h +dict_static.o: argv.h +dict_static.o: check_arg.h +dict_static.o: dict.h +dict_static.o: dict_static.c +dict_static.o: dict_static.h +dict_static.o: msg.h +dict_static.o: myflock.h +dict_static.o: mymalloc.h +dict_static.o: stringops.h +dict_static.o: sys_defs.h +dict_static.o: vbuf.h +dict_static.o: vstream.h +dict_static.o: vstring.h +dict_stream.o: argv.h +dict_stream.o: check_arg.h +dict_stream.o: dict.h +dict_stream.o: dict_stream.c +dict_stream.o: msg.h +dict_stream.o: myflock.h +dict_stream.o: mymalloc.h +dict_stream.o: stringops.h +dict_stream.o: sys_defs.h +dict_stream.o: vbuf.h +dict_stream.o: vstream.h +dict_stream.o: vstring.h +dict_surrogate.o: argv.h +dict_surrogate.o: check_arg.h +dict_surrogate.o: compat_va_copy.h +dict_surrogate.o: dict.h +dict_surrogate.o: dict_surrogate.c +dict_surrogate.o: msg.h +dict_surrogate.o: myflock.h +dict_surrogate.o: mymalloc.h +dict_surrogate.o: sys_defs.h +dict_surrogate.o: vbuf.h +dict_surrogate.o: vstream.h +dict_surrogate.o: vstring.h +dict_tcp.o: argv.h +dict_tcp.o: check_arg.h +dict_tcp.o: connect.h +dict_tcp.o: dict.h +dict_tcp.o: dict_tcp.c +dict_tcp.o: dict_tcp.h +dict_tcp.o: hex_quote.h +dict_tcp.o: iostuff.h +dict_tcp.o: msg.h +dict_tcp.o: myflock.h +dict_tcp.o: mymalloc.h +dict_tcp.o: stringops.h +dict_tcp.o: sys_defs.h +dict_tcp.o: vbuf.h +dict_tcp.o: vstream.h +dict_tcp.o: vstring.h +dict_tcp.o: vstring_vstream.h +dict_test.o: argv.h +dict_test.o: check_arg.h +dict_test.o: dict.h +dict_test.o: dict_db.h +dict_test.o: dict_lmdb.h +dict_test.o: dict_test.c +dict_test.o: msg.h +dict_test.o: msg_vstream.h +dict_test.o: myflock.h +dict_test.o: stringops.h +dict_test.o: sys_defs.h +dict_test.o: vbuf.h +dict_test.o: vstream.h +dict_test.o: vstring.h +dict_test.o: vstring_vstream.h +dict_thash.o: argv.h +dict_thash.o: check_arg.h +dict_thash.o: dict.h +dict_thash.o: dict_ht.h +dict_thash.o: dict_thash.c +dict_thash.o: dict_thash.h +dict_thash.o: htable.h +dict_thash.o: iostuff.h +dict_thash.o: msg.h +dict_thash.o: myflock.h +dict_thash.o: mymalloc.h +dict_thash.o: readlline.h +dict_thash.o: stringops.h +dict_thash.o: sys_defs.h +dict_thash.o: vbuf.h +dict_thash.o: vstream.h +dict_thash.o: vstring.h +dict_union.o: argv.h +dict_union.o: check_arg.h +dict_union.o: dict.h +dict_union.o: dict_union.c +dict_union.o: dict_union.h +dict_union.o: htable.h +dict_union.o: msg.h +dict_union.o: myflock.h +dict_union.o: mymalloc.h +dict_union.o: stringops.h +dict_union.o: sys_defs.h +dict_union.o: vbuf.h +dict_union.o: vstream.h +dict_union.o: vstring.h +dict_unix.o: argv.h +dict_unix.o: check_arg.h +dict_unix.o: dict.h +dict_unix.o: dict_unix.c +dict_unix.o: dict_unix.h +dict_unix.o: msg.h +dict_unix.o: myflock.h +dict_unix.o: mymalloc.h +dict_unix.o: stringops.h +dict_unix.o: sys_defs.h +dict_unix.o: vbuf.h +dict_unix.o: vstream.h +dict_unix.o: vstring.h +dict_utf8.o: argv.h +dict_utf8.o: check_arg.h +dict_utf8.o: dict.h +dict_utf8.o: dict_utf8.c +dict_utf8.o: msg.h +dict_utf8.o: myflock.h +dict_utf8.o: mymalloc.h +dict_utf8.o: stringops.h +dict_utf8.o: sys_defs.h +dict_utf8.o: vbuf.h +dict_utf8.o: vstream.h +dict_utf8.o: vstring.h +dir_forest.o: check_arg.h +dir_forest.o: dir_forest.c +dir_forest.o: dir_forest.h +dir_forest.o: msg.h +dir_forest.o: sys_defs.h +dir_forest.o: vbuf.h +dir_forest.o: vstring.h +doze.o: doze.c +doze.o: iostuff.h +doze.o: msg.h +doze.o: sys_defs.h +dummy_read.o: dummy_read.c +dummy_read.o: iostuff.h +dummy_read.o: msg.h +dummy_read.o: sys_defs.h +dummy_write.o: dummy_write.c +dummy_write.o: iostuff.h +dummy_write.o: msg.h +dummy_write.o: sys_defs.h +dup2_pass_on_exec.o: dup2_pass_on_exec.c +duplex_pipe.o: duplex_pipe.c +duplex_pipe.o: iostuff.h +duplex_pipe.o: sane_socketpair.h +duplex_pipe.o: sys_defs.h +edit_file.o: check_arg.h +edit_file.o: edit_file.c +edit_file.o: edit_file.h +edit_file.o: msg.h +edit_file.o: myflock.h +edit_file.o: mymalloc.h +edit_file.o: stringops.h +edit_file.o: sys_defs.h +edit_file.o: vbuf.h +edit_file.o: vstream.h +edit_file.o: vstring.h +edit_file.o: warn_stat.h +environ.o: environ.c +environ.o: sys_defs.h +events.o: events.c +events.o: events.h +events.o: iostuff.h +events.o: msg.h +events.o: mymalloc.h +events.o: ring.h +events.o: sys_defs.h +exec_command.o: argv.h +exec_command.o: exec_command.c +exec_command.o: exec_command.h +exec_command.o: msg.h +exec_command.o: sys_defs.h +extpar.o: check_arg.h +extpar.o: extpar.c +extpar.o: stringops.h +extpar.o: sys_defs.h +extpar.o: vbuf.h +extpar.o: vstring.h +fifo_listen.o: fifo_listen.c +fifo_listen.o: htable.h +fifo_listen.o: iostuff.h +fifo_listen.o: listen.h +fifo_listen.o: msg.h +fifo_listen.o: sys_defs.h +fifo_listen.o: warn_stat.h +fifo_open.o: fifo_open.c +fifo_rdonly_bug.o: fifo_rdonly_bug.c +fifo_rdonly_bug.o: sys_defs.h +fifo_rdwr_bug.o: fifo_rdwr_bug.c +fifo_rdwr_bug.o: sys_defs.h +fifo_trigger.o: check_arg.h +fifo_trigger.o: fifo_trigger.c +fifo_trigger.o: iostuff.h +fifo_trigger.o: msg.h +fifo_trigger.o: safe_open.h +fifo_trigger.o: sys_defs.h +fifo_trigger.o: trigger.h +fifo_trigger.o: vbuf.h +fifo_trigger.o: vstream.h +fifo_trigger.o: vstring.h +file_limit.o: file_limit.c +file_limit.o: iostuff.h +file_limit.o: msg.h +file_limit.o: sys_defs.h +find_inet.o: check_arg.h +find_inet.o: find_inet.c +find_inet.o: find_inet.h +find_inet.o: known_tcp_ports.h +find_inet.o: msg.h +find_inet.o: stringops.h +find_inet.o: sys_defs.h +find_inet.o: vbuf.h +find_inet.o: vstring.h +format_tv.o: check_arg.h +format_tv.o: format_tv.c +format_tv.o: format_tv.h +format_tv.o: msg.h +format_tv.o: sys_defs.h +format_tv.o: vbuf.h +format_tv.o: vstring.h +fsspace.o: fsspace.c +fsspace.o: fsspace.h +fsspace.o: msg.h +fsspace.o: sys_defs.h +fullname.o: check_arg.h +fullname.o: fullname.c +fullname.o: fullname.h +fullname.o: safe.h +fullname.o: sys_defs.h +fullname.o: vbuf.h +fullname.o: vstring.h +gccw.o: gccw.c +get_domainname.o: get_domainname.c +get_domainname.o: get_domainname.h +get_domainname.o: get_hostname.h +get_domainname.o: mymalloc.h +get_domainname.o: sys_defs.h +get_hostname.o: get_hostname.c +get_hostname.o: get_hostname.h +get_hostname.o: msg.h +get_hostname.o: mymalloc.h +get_hostname.o: sys_defs.h +get_hostname.o: valid_hostname.h +hash_fnv.o: hash_fnv.c +hash_fnv.o: hash_fnv.h +hash_fnv.o: ldseed.h +hash_fnv.o: msg.h +hash_fnv.o: sys_defs.h +hex_code.o: check_arg.h +hex_code.o: hex_code.c +hex_code.o: hex_code.h +hex_code.o: msg.h +hex_code.o: mymalloc.h +hex_code.o: sys_defs.h +hex_code.o: vbuf.h +hex_code.o: vstring.h +hex_quote.o: check_arg.h +hex_quote.o: hex_quote.c +hex_quote.o: hex_quote.h +hex_quote.o: msg.h +hex_quote.o: sys_defs.h +hex_quote.o: vbuf.h +hex_quote.o: vstring.h +host_port.o: check_arg.h +host_port.o: host_port.c +host_port.o: host_port.h +host_port.o: msg.h +host_port.o: split_at.h +host_port.o: stringops.h +host_port.o: sys_defs.h +host_port.o: valid_hostname.h +host_port.o: valid_utf8_hostname.h +host_port.o: vbuf.h +host_port.o: vstring.h +htable.o: hash_fnv.h +htable.o: htable.c +htable.o: htable.h +htable.o: msg.h +htable.o: mymalloc.h +htable.o: sys_defs.h +inet_addr_host.o: inet_addr_host.c +inet_addr_host.o: inet_addr_host.h +inet_addr_host.o: inet_addr_list.h +inet_addr_host.o: inet_proto.h +inet_addr_host.o: msg.h +inet_addr_host.o: myaddrinfo.h +inet_addr_host.o: mymalloc.h +inet_addr_host.o: sock_addr.h +inet_addr_host.o: sys_defs.h +inet_addr_list.o: inet_addr_list.c +inet_addr_list.o: inet_addr_list.h +inet_addr_list.o: msg.h +inet_addr_list.o: myaddrinfo.h +inet_addr_list.o: mymalloc.h +inet_addr_list.o: sock_addr.h +inet_addr_list.o: sys_defs.h +inet_addr_local.o: check_arg.h +inet_addr_local.o: hex_code.h +inet_addr_local.o: inet_addr_list.h +inet_addr_local.o: inet_addr_local.c +inet_addr_local.o: inet_addr_local.h +inet_addr_local.o: mask_addr.h +inet_addr_local.o: msg.h +inet_addr_local.o: myaddrinfo.h +inet_addr_local.o: mymalloc.h +inet_addr_local.o: sock_addr.h +inet_addr_local.o: sys_defs.h +inet_addr_local.o: vbuf.h +inet_addr_local.o: vstring.h +inet_connect.o: connect.h +inet_connect.o: host_port.h +inet_connect.o: inet_connect.c +inet_connect.o: inet_proto.h +inet_connect.o: iostuff.h +inet_connect.o: msg.h +inet_connect.o: myaddrinfo.h +inet_connect.o: mymalloc.h +inet_connect.o: sane_connect.h +inet_connect.o: sock_addr.h +inet_connect.o: sys_defs.h +inet_connect.o: timed_connect.h +inet_listen.o: host_port.h +inet_listen.o: htable.h +inet_listen.o: inet_listen.c +inet_listen.o: inet_proto.h +inet_listen.o: iostuff.h +inet_listen.o: listen.h +inet_listen.o: msg.h +inet_listen.o: myaddrinfo.h +inet_listen.o: mymalloc.h +inet_listen.o: sane_accept.h +inet_listen.o: sock_addr.h +inet_listen.o: sys_defs.h +inet_proto.o: check_arg.h +inet_proto.o: inet_proto.c +inet_proto.o: inet_proto.h +inet_proto.o: msg.h +inet_proto.o: myaddrinfo.h +inet_proto.o: mymalloc.h +inet_proto.o: name_mask.h +inet_proto.o: sys_defs.h +inet_proto.o: vbuf.h +inet_proto.o: vstring.h +inet_trigger.o: connect.h +inet_trigger.o: events.h +inet_trigger.o: inet_trigger.c +inet_trigger.o: iostuff.h +inet_trigger.o: msg.h +inet_trigger.o: mymalloc.h +inet_trigger.o: sys_defs.h +inet_trigger.o: trigger.h +inet_windowsize.o: inet_windowsize.c +inet_windowsize.o: iostuff.h +inet_windowsize.o: msg.h +inet_windowsize.o: sys_defs.h +ip_match.o: check_arg.h +ip_match.o: ip_match.c +ip_match.o: ip_match.h +ip_match.o: msg.h +ip_match.o: mymalloc.h +ip_match.o: sys_defs.h +ip_match.o: vbuf.h +ip_match.o: vstring.h +killme_after.o: killme_after.c +killme_after.o: killme_after.h +killme_after.o: sys_defs.h +known_tcp_ports.o: check_arg.h +known_tcp_ports.o: htable.h +known_tcp_ports.o: known_tcp_ports.c +known_tcp_ports.o: known_tcp_ports.h +known_tcp_ports.o: mymalloc.h +known_tcp_ports.o: stringops.h +known_tcp_ports.o: sys_defs.h +known_tcp_ports.o: vbuf.h +known_tcp_ports.o: vstring.h +ldseed.o: iostuff.h +ldseed.o: ldseed.c +ldseed.o: ldseed.h +ldseed.o: msg.h +ldseed.o: sys_defs.h +line_number.o: check_arg.h +line_number.o: line_number.c +line_number.o: line_number.h +line_number.o: sys_defs.h +line_number.o: vbuf.h +line_number.o: vstring.h +line_wrap.o: line_wrap.c +line_wrap.o: line_wrap.h +line_wrap.o: sys_defs.h +load_file.o: check_arg.h +load_file.o: iostuff.h +load_file.o: load_file.c +load_file.o: load_file.h +load_file.o: msg.h +load_file.o: sys_defs.h +load_file.o: vbuf.h +load_file.o: vstream.h +load_file.o: warn_stat.h +load_lib.o: load_lib.c +load_lib.o: load_lib.h +load_lib.o: msg.h +load_lib.o: sys_defs.h +logwriter.o: check_arg.h +logwriter.o: iostuff.h +logwriter.o: logwriter.c +logwriter.o: logwriter.h +logwriter.o: msg.h +logwriter.o: mymalloc.h +logwriter.o: safe_open.h +logwriter.o: sys_defs.h +logwriter.o: vbuf.h +logwriter.o: vstream.h +logwriter.o: vstring.h +lowercase.o: check_arg.h +lowercase.o: lowercase.c +lowercase.o: stringops.h +lowercase.o: sys_defs.h +lowercase.o: vbuf.h +lowercase.o: vstring.h +lstat_as.o: lstat_as.c +lstat_as.o: lstat_as.h +lstat_as.o: msg.h +lstat_as.o: set_eugid.h +lstat_as.o: sys_defs.h +lstat_as.o: warn_stat.h +mac_expand.o: check_arg.h +mac_expand.o: htable.h +mac_expand.o: mac_expand.c +mac_expand.o: mac_expand.h +mac_expand.o: mac_parse.h +mac_expand.o: msg.h +mac_expand.o: mymalloc.h +mac_expand.o: name_code.h +mac_expand.o: sane_strtol.h +mac_expand.o: stringops.h +mac_expand.o: sys_defs.h +mac_expand.o: vbuf.h +mac_expand.o: vstring.h +mac_parse.o: check_arg.h +mac_parse.o: mac_parse.c +mac_parse.o: mac_parse.h +mac_parse.o: msg.h +mac_parse.o: sys_defs.h +mac_parse.o: vbuf.h +mac_parse.o: vstring.h +make_dirs.o: check_arg.h +make_dirs.o: make_dirs.c +make_dirs.o: make_dirs.h +make_dirs.o: msg.h +make_dirs.o: mymalloc.h +make_dirs.o: stringops.h +make_dirs.o: sys_defs.h +make_dirs.o: vbuf.h +make_dirs.o: vstring.h +make_dirs.o: warn_stat.h +mask_addr.o: mask_addr.c +mask_addr.o: mask_addr.h +mask_addr.o: msg.h +mask_addr.o: sys_defs.h +match_list.o: argv.h +match_list.o: check_arg.h +match_list.o: dict.h +match_list.o: match_list.c +match_list.o: match_list.h +match_list.o: msg.h +match_list.o: myflock.h +match_list.o: mymalloc.h +match_list.o: stringops.h +match_list.o: sys_defs.h +match_list.o: vbuf.h +match_list.o: vstream.h +match_list.o: vstring.h +match_list.o: vstring_vstream.h +match_ops.o: argv.h +match_ops.o: check_arg.h +match_ops.o: cidr_match.h +match_ops.o: dict.h +match_ops.o: match_list.h +match_ops.o: match_ops.c +match_ops.o: msg.h +match_ops.o: myaddrinfo.h +match_ops.o: myflock.h +match_ops.o: mymalloc.h +match_ops.o: split_at.h +match_ops.o: stringops.h +match_ops.o: sys_defs.h +match_ops.o: vbuf.h +match_ops.o: vstream.h +match_ops.o: vstring.h +midna_domain.o: check_arg.h +midna_domain.o: ctable.h +midna_domain.o: midna_domain.c +midna_domain.o: midna_domain.h +midna_domain.o: msg.h +midna_domain.o: mymalloc.h +midna_domain.o: name_mask.h +midna_domain.o: stringops.h +midna_domain.o: sys_defs.h +midna_domain.o: valid_hostname.h +midna_domain.o: vbuf.h +midna_domain.o: vstring.h +msg.o: msg.c +msg.o: msg.h +msg.o: msg_output.h +msg.o: sys_defs.h +msg_logger.o: check_arg.h +msg_logger.o: connect.h +msg_logger.o: iostuff.h +msg_logger.o: logwriter.h +msg_logger.o: msg.h +msg_logger.o: msg_logger.c +msg_logger.o: msg_logger.h +msg_logger.o: msg_output.h +msg_logger.o: mymalloc.h +msg_logger.o: safe.h +msg_logger.o: sys_defs.h +msg_logger.o: vbuf.h +msg_logger.o: vstream.h +msg_logger.o: vstring.h +msg_output.o: check_arg.h +msg_output.o: msg_output.c +msg_output.o: msg_output.h +msg_output.o: msg_vstream.h +msg_output.o: mymalloc.h +msg_output.o: stringops.h +msg_output.o: sys_defs.h +msg_output.o: vbuf.h +msg_output.o: vstream.h +msg_output.o: vstring.h +msg_rate_delay.o: check_arg.h +msg_rate_delay.o: events.h +msg_rate_delay.o: msg.h +msg_rate_delay.o: msg_rate_delay.c +msg_rate_delay.o: sys_defs.h +msg_rate_delay.o: vbuf.h +msg_rate_delay.o: vstring.h +msg_syslog.o: check_arg.h +msg_syslog.o: msg.h +msg_syslog.o: msg_output.h +msg_syslog.o: msg_syslog.c +msg_syslog.o: msg_syslog.h +msg_syslog.o: mymalloc.h +msg_syslog.o: safe.h +msg_syslog.o: stringops.h +msg_syslog.o: sys_defs.h +msg_syslog.o: vbuf.h +msg_syslog.o: vstring.h +msg_vstream.o: check_arg.h +msg_vstream.o: msg.h +msg_vstream.o: msg_output.h +msg_vstream.o: msg_vstream.c +msg_vstream.o: msg_vstream.h +msg_vstream.o: sys_defs.h +msg_vstream.o: vbuf.h +msg_vstream.o: vstream.h +mvect.o: mvect.c +mvect.o: mvect.h +mvect.o: mymalloc.h +mvect.o: sys_defs.h +myaddrinfo.o: check_arg.h +myaddrinfo.o: inet_proto.h +myaddrinfo.o: known_tcp_ports.h +myaddrinfo.o: msg.h +myaddrinfo.o: myaddrinfo.c +myaddrinfo.o: myaddrinfo.h +myaddrinfo.o: mymalloc.h +myaddrinfo.o: sock_addr.h +myaddrinfo.o: split_at.h +myaddrinfo.o: stringops.h +myaddrinfo.o: sys_defs.h +myaddrinfo.o: valid_hostname.h +myaddrinfo.o: vbuf.h +myaddrinfo.o: vstring.h +myflock.o: msg.h +myflock.o: myflock.c +myflock.o: myflock.h +myflock.o: sys_defs.h +mymalloc.o: msg.h +mymalloc.o: mymalloc.c +mymalloc.o: mymalloc.h +mymalloc.o: sys_defs.h +myrand.o: myrand.c +myrand.o: myrand.h +myrand.o: sys_defs.h +mystrtok.o: check_arg.h +mystrtok.o: mystrtok.c +mystrtok.o: stringops.h +mystrtok.o: sys_defs.h +mystrtok.o: vbuf.h +mystrtok.o: vstring.h +name_code.o: name_code.c +name_code.o: name_code.h +name_code.o: sys_defs.h +name_mask.o: check_arg.h +name_mask.o: msg.h +name_mask.o: mymalloc.h +name_mask.o: name_mask.c +name_mask.o: name_mask.h +name_mask.o: stringops.h +name_mask.o: sys_defs.h +name_mask.o: vbuf.h +name_mask.o: vstring.h +nbbio.o: events.h +nbbio.o: msg.h +nbbio.o: mymalloc.h +nbbio.o: nbbio.c +nbbio.o: nbbio.h +nbbio.o: sys_defs.h +netstring.o: check_arg.h +netstring.o: compat_va_copy.h +netstring.o: msg.h +netstring.o: netstring.c +netstring.o: netstring.h +netstring.o: sys_defs.h +netstring.o: vbuf.h +netstring.o: vstream.h +netstring.o: vstring.h +neuter.o: check_arg.h +neuter.o: neuter.c +neuter.o: stringops.h +neuter.o: sys_defs.h +neuter.o: vbuf.h +neuter.o: vstring.h +non_blocking.o: iostuff.h +non_blocking.o: msg.h +non_blocking.o: non_blocking.c +non_blocking.o: sys_defs.h +nvtable.o: htable.h +nvtable.o: mymalloc.h +nvtable.o: nvtable.c +nvtable.o: nvtable.h +nvtable.o: sys_defs.h +open_as.o: msg.h +open_as.o: open_as.c +open_as.o: open_as.h +open_as.o: set_eugid.h +open_as.o: sys_defs.h +open_limit.o: iostuff.h +open_limit.o: open_limit.c +open_limit.o: sys_defs.h +open_lock.o: check_arg.h +open_lock.o: msg.h +open_lock.o: myflock.h +open_lock.o: open_lock.c +open_lock.o: open_lock.h +open_lock.o: safe_open.h +open_lock.o: sys_defs.h +open_lock.o: vbuf.h +open_lock.o: vstream.h +open_lock.o: vstring.h +pass_accept.o: attr.h +pass_accept.o: check_arg.h +pass_accept.o: htable.h +pass_accept.o: iostuff.h +pass_accept.o: listen.h +pass_accept.o: msg.h +pass_accept.o: mymalloc.h +pass_accept.o: nvtable.h +pass_accept.o: pass_accept.c +pass_accept.o: sys_defs.h +pass_accept.o: vbuf.h +pass_accept.o: vstream.h +pass_accept.o: vstring.h +pass_trigger.o: connect.h +pass_trigger.o: events.h +pass_trigger.o: iostuff.h +pass_trigger.o: msg.h +pass_trigger.o: mymalloc.h +pass_trigger.o: pass_trigger.c +pass_trigger.o: sys_defs.h +pass_trigger.o: trigger.h +peekfd.o: iostuff.h +peekfd.o: peekfd.c +peekfd.o: sys_defs.h +poll_fd.o: iostuff.h +poll_fd.o: msg.h +poll_fd.o: poll_fd.c +poll_fd.o: sys_defs.h +posix_signals.o: posix_signals.c +posix_signals.o: posix_signals.h +posix_signals.o: sys_defs.h +printable.o: check_arg.h +printable.o: printable.c +printable.o: stringops.h +printable.o: sys_defs.h +printable.o: vbuf.h +printable.o: vstring.h +rand_sleep.o: iostuff.h +rand_sleep.o: msg.h +rand_sleep.o: myrand.h +rand_sleep.o: rand_sleep.c +rand_sleep.o: sys_defs.h +readlline.o: check_arg.h +readlline.o: msg.h +readlline.o: readlline.c +readlline.o: readlline.h +readlline.o: sys_defs.h +readlline.o: vbuf.h +readlline.o: vstream.h +readlline.o: vstring.h +recv_pass_attr.o: attr.h +recv_pass_attr.o: check_arg.h +recv_pass_attr.o: htable.h +recv_pass_attr.o: iostuff.h +recv_pass_attr.o: listen.h +recv_pass_attr.o: mymalloc.h +recv_pass_attr.o: nvtable.h +recv_pass_attr.o: recv_pass_attr.c +recv_pass_attr.o: sys_defs.h +recv_pass_attr.o: vbuf.h +recv_pass_attr.o: vstream.h +recv_pass_attr.o: vstring.h +ring.o: ring.c +ring.o: ring.h +safe_getenv.o: safe.h +safe_getenv.o: safe_getenv.c +safe_getenv.o: sys_defs.h +safe_open.o: check_arg.h +safe_open.o: msg.h +safe_open.o: safe_open.c +safe_open.o: safe_open.h +safe_open.o: stringops.h +safe_open.o: sys_defs.h +safe_open.o: vbuf.h +safe_open.o: vstream.h +safe_open.o: vstring.h +safe_open.o: warn_stat.h +sane_accept.o: msg.h +sane_accept.o: sane_accept.c +sane_accept.o: sane_accept.h +sane_accept.o: sys_defs.h +sane_basename.o: check_arg.h +sane_basename.o: sane_basename.c +sane_basename.o: stringops.h +sane_basename.o: sys_defs.h +sane_basename.o: vbuf.h +sane_basename.o: vstring.h +sane_connect.o: msg.h +sane_connect.o: sane_connect.c +sane_connect.o: sane_connect.h +sane_connect.o: sys_defs.h +sane_link.o: msg.h +sane_link.o: sane_fsops.h +sane_link.o: sane_link.c +sane_link.o: sys_defs.h +sane_link.o: warn_stat.h +sane_rename.o: msg.h +sane_rename.o: sane_fsops.h +sane_rename.o: sane_rename.c +sane_rename.o: sys_defs.h +sane_rename.o: warn_stat.h +sane_socketpair.o: msg.h +sane_socketpair.o: sane_socketpair.c +sane_socketpair.o: sane_socketpair.h +sane_socketpair.o: sys_defs.h +sane_strtol.o: sane_strtol.c +sane_strtol.o: sane_strtol.h +sane_strtol.o: sys_defs.h +sane_time.o: msg.h +sane_time.o: sane_time.c +sane_time.o: sane_time.h +sane_time.o: sys_defs.h +scan_dir.o: check_arg.h +scan_dir.o: msg.h +scan_dir.o: mymalloc.h +scan_dir.o: scan_dir.c +scan_dir.o: scan_dir.h +scan_dir.o: stringops.h +scan_dir.o: sys_defs.h +scan_dir.o: vbuf.h +scan_dir.o: vstring.h +select_bug.o: check_arg.h +select_bug.o: msg.h +select_bug.o: msg_vstream.h +select_bug.o: select_bug.c +select_bug.o: sys_defs.h +select_bug.o: vbuf.h +select_bug.o: vstream.h +set_eugid.o: msg.h +set_eugid.o: set_eugid.c +set_eugid.o: set_eugid.h +set_eugid.o: sys_defs.h +set_ugid.o: msg.h +set_ugid.o: set_ugid.c +set_ugid.o: set_ugid.h +set_ugid.o: sys_defs.h +sigdelay.o: msg.h +sigdelay.o: posix_signals.h +sigdelay.o: sigdelay.c +sigdelay.o: sigdelay.h +sigdelay.o: sys_defs.h +skipblanks.o: check_arg.h +skipblanks.o: skipblanks.c +skipblanks.o: stringops.h +skipblanks.o: sys_defs.h +skipblanks.o: vbuf.h +skipblanks.o: vstring.h +slmdb.o: check_arg.h +slmdb.o: slmdb.c +slmdb.o: slmdb.h +sock_addr.o: msg.h +sock_addr.o: sock_addr.c +sock_addr.o: sock_addr.h +sock_addr.o: sys_defs.h +spawn_command.o: argv.h +spawn_command.o: check_arg.h +spawn_command.o: clean_env.h +spawn_command.o: exec_command.h +spawn_command.o: msg.h +spawn_command.o: set_ugid.h +spawn_command.o: spawn_command.c +spawn_command.o: spawn_command.h +spawn_command.o: sys_defs.h +spawn_command.o: timed_wait.h +split_at.o: split_at.c +split_at.o: split_at.h +split_at.o: sys_defs.h +split_nameval.o: check_arg.h +split_nameval.o: msg.h +split_nameval.o: split_nameval.c +split_nameval.o: stringops.h +split_nameval.o: sys_defs.h +split_nameval.o: vbuf.h +split_nameval.o: vstring.h +split_qnameval.o: check_arg.h +split_qnameval.o: msg.h +split_qnameval.o: split_qnameval.c +split_qnameval.o: stringops.h +split_qnameval.o: sys_defs.h +split_qnameval.o: vbuf.h +split_qnameval.o: vstring.h +stat_as.o: msg.h +stat_as.o: set_eugid.h +stat_as.o: stat_as.c +stat_as.o: stat_as.h +stat_as.o: sys_defs.h +stat_as.o: warn_stat.h +strcasecmp.o: strcasecmp.c +strcasecmp.o: sys_defs.h +strcasecmp_utf8.o: check_arg.h +strcasecmp_utf8.o: strcasecmp_utf8.c +strcasecmp_utf8.o: stringops.h +strcasecmp_utf8.o: sys_defs.h +strcasecmp_utf8.o: vbuf.h +strcasecmp_utf8.o: vstring.h +stream_connect.o: connect.h +stream_connect.o: iostuff.h +stream_connect.o: msg.h +stream_connect.o: stream_connect.c +stream_connect.o: sys_defs.h +stream_listen.o: htable.h +stream_listen.o: iostuff.h +stream_listen.o: listen.h +stream_listen.o: msg.h +stream_listen.o: stream_listen.c +stream_listen.o: sys_defs.h +stream_recv_fd.o: iostuff.h +stream_recv_fd.o: msg.h +stream_recv_fd.o: stream_recv_fd.c +stream_recv_fd.o: sys_defs.h +stream_send_fd.o: iostuff.h +stream_send_fd.o: msg.h +stream_send_fd.o: stream_send_fd.c +stream_send_fd.o: sys_defs.h +stream_test.o: check_arg.h +stream_test.o: connect.h +stream_test.o: htable.h +stream_test.o: iostuff.h +stream_test.o: listen.h +stream_test.o: msg.h +stream_test.o: msg_vstream.h +stream_test.o: stream_test.c +stream_test.o: sys_defs.h +stream_test.o: vbuf.h +stream_test.o: vstream.h +stream_trigger.o: connect.h +stream_trigger.o: events.h +stream_trigger.o: iostuff.h +stream_trigger.o: msg.h +stream_trigger.o: mymalloc.h +stream_trigger.o: stream_trigger.c +stream_trigger.o: sys_defs.h +stream_trigger.o: trigger.h +sys_compat.o: sys_compat.c +sys_compat.o: sys_defs.h +timecmp.o: timecmp.c +timecmp.o: timecmp.h +timed_connect.o: iostuff.h +timed_connect.o: msg.h +timed_connect.o: sane_connect.h +timed_connect.o: sys_defs.h +timed_connect.o: timed_connect.c +timed_connect.o: timed_connect.h +timed_read.o: iostuff.h +timed_read.o: msg.h +timed_read.o: sys_defs.h +timed_read.o: timed_read.c +timed_wait.o: msg.h +timed_wait.o: posix_signals.h +timed_wait.o: sys_defs.h +timed_wait.o: timed_wait.c +timed_wait.o: timed_wait.h +timed_write.o: iostuff.h +timed_write.o: msg.h +timed_write.o: sys_defs.h +timed_write.o: timed_write.c +translit.o: check_arg.h +translit.o: stringops.h +translit.o: sys_defs.h +translit.o: translit.c +translit.o: vbuf.h +translit.o: vstring.h +trimblanks.o: check_arg.h +trimblanks.o: stringops.h +trimblanks.o: sys_defs.h +trimblanks.o: trimblanks.c +trimblanks.o: vbuf.h +trimblanks.o: vstring.h +unescape.o: check_arg.h +unescape.o: stringops.h +unescape.o: sys_defs.h +unescape.o: unescape.c +unescape.o: vbuf.h +unescape.o: vstring.h +unix_connect.o: connect.h +unix_connect.o: iostuff.h +unix_connect.o: msg.h +unix_connect.o: sane_connect.h +unix_connect.o: sys_defs.h +unix_connect.o: timed_connect.h +unix_connect.o: unix_connect.c +unix_dgram_connect.o: connect.h +unix_dgram_connect.o: iostuff.h +unix_dgram_connect.o: msg.h +unix_dgram_connect.o: sys_defs.h +unix_dgram_connect.o: unix_dgram_connect.c +unix_dgram_listen.o: htable.h +unix_dgram_listen.o: iostuff.h +unix_dgram_listen.o: listen.h +unix_dgram_listen.o: msg.h +unix_dgram_listen.o: sys_defs.h +unix_dgram_listen.o: unix_dgram_listen.c +unix_listen.o: htable.h +unix_listen.o: iostuff.h +unix_listen.o: listen.h +unix_listen.o: msg.h +unix_listen.o: sane_accept.h +unix_listen.o: sys_defs.h +unix_listen.o: unix_listen.c +unix_pass_fd_fix.o: check_arg.h +unix_pass_fd_fix.o: iostuff.h +unix_pass_fd_fix.o: name_mask.h +unix_pass_fd_fix.o: sys_defs.h +unix_pass_fd_fix.o: unix_pass_fd_fix.c +unix_pass_fd_fix.o: vbuf.h +unix_pass_fd_fix.o: vstring.h +unix_recv_fd.o: iostuff.h +unix_recv_fd.o: msg.h +unix_recv_fd.o: sys_defs.h +unix_recv_fd.o: unix_recv_fd.c +unix_send_fd.o: iostuff.h +unix_send_fd.o: msg.h +unix_send_fd.o: sys_defs.h +unix_send_fd.o: unix_send_fd.c +unix_trigger.o: connect.h +unix_trigger.o: events.h +unix_trigger.o: iostuff.h +unix_trigger.o: msg.h +unix_trigger.o: mymalloc.h +unix_trigger.o: sys_defs.h +unix_trigger.o: trigger.h +unix_trigger.o: unix_trigger.c +unsafe.o: safe.h +unsafe.o: sys_defs.h +unsafe.o: unsafe.c +uppercase.o: check_arg.h +uppercase.o: stringops.h +uppercase.o: sys_defs.h +uppercase.o: uppercase.c +uppercase.o: vbuf.h +uppercase.o: vstring.h +username.o: sys_defs.h +username.o: username.c +username.o: username.h +valid_hostname.o: check_arg.h +valid_hostname.o: msg.h +valid_hostname.o: mymalloc.h +valid_hostname.o: stringops.h +valid_hostname.o: sys_defs.h +valid_hostname.o: valid_hostname.c +valid_hostname.o: valid_hostname.h +valid_hostname.o: vbuf.h +valid_hostname.o: vstring.h +valid_utf8_hostname.o: check_arg.h +valid_utf8_hostname.o: midna_domain.h +valid_utf8_hostname.o: msg.h +valid_utf8_hostname.o: mymalloc.h +valid_utf8_hostname.o: stringops.h +valid_utf8_hostname.o: sys_defs.h +valid_utf8_hostname.o: valid_hostname.h +valid_utf8_hostname.o: valid_utf8_hostname.c +valid_utf8_hostname.o: valid_utf8_hostname.h +valid_utf8_hostname.o: vbuf.h +valid_utf8_hostname.o: vstring.h +valid_utf8_string.o: check_arg.h +valid_utf8_string.o: stringops.h +valid_utf8_string.o: sys_defs.h +valid_utf8_string.o: valid_utf8_string.c +valid_utf8_string.o: vbuf.h +valid_utf8_string.o: vstring.h +vbuf.o: sys_defs.h +vbuf.o: vbuf.c +vbuf.o: vbuf.h +vbuf_print.o: check_arg.h +vbuf_print.o: msg.h +vbuf_print.o: mymalloc.h +vbuf_print.o: sys_defs.h +vbuf_print.o: vbuf.h +vbuf_print.o: vbuf_print.c +vbuf_print.o: vbuf_print.h +vbuf_print.o: vstring.h +vstream.o: check_arg.h +vstream.o: iostuff.h +vstream.o: msg.h +vstream.o: mymalloc.h +vstream.o: sys_defs.h +vstream.o: vbuf.h +vstream.o: vbuf_print.h +vstream.o: vstream.c +vstream.o: vstream.h +vstream.o: vstring.h +vstream_popen.o: argv.h +vstream_popen.o: check_arg.h +vstream_popen.o: clean_env.h +vstream_popen.o: exec_command.h +vstream_popen.o: iostuff.h +vstream_popen.o: msg.h +vstream_popen.o: set_ugid.h +vstream_popen.o: sys_defs.h +vstream_popen.o: vbuf.h +vstream_popen.o: vstream.h +vstream_popen.o: vstream_popen.c +vstream_tweak.o: check_arg.h +vstream_tweak.o: msg.h +vstream_tweak.o: sys_defs.h +vstream_tweak.o: vbuf.h +vstream_tweak.o: vstream.h +vstream_tweak.o: vstream_tweak.c +vstring.o: check_arg.h +vstring.o: msg.h +vstring.o: mymalloc.h +vstring.o: sys_defs.h +vstring.o: vbuf.h +vstring.o: vbuf_print.h +vstring.o: vstring.c +vstring.o: vstring.h +vstring_vstream.o: check_arg.h +vstring_vstream.o: msg.h +vstring_vstream.o: sys_defs.h +vstring_vstream.o: vbuf.h +vstring_vstream.o: vstream.h +vstring_vstream.o: vstring.h +vstring_vstream.o: vstring_vstream.c +vstring_vstream.o: vstring_vstream.h +warn_stat.o: msg.h +warn_stat.o: sys_defs.h +warn_stat.o: warn_stat.c +warn_stat.o: warn_stat.h +watchdog.o: events.h +watchdog.o: iostuff.h +watchdog.o: killme_after.h +watchdog.o: msg.h +watchdog.o: mymalloc.h +watchdog.o: posix_signals.h +watchdog.o: sys_defs.h +watchdog.o: watchdog.c +watchdog.o: watchdog.h +write_buf.o: iostuff.h +write_buf.o: msg.h +write_buf.o: sys_defs.h +write_buf.o: write_buf.c diff --git a/src/util/allascii.c b/src/util/allascii.c new file mode 100644 index 0000000..4ac820e --- /dev/null +++ b/src/util/allascii.c @@ -0,0 +1,65 @@ +/*++ +/* NAME +/* allascii 3 +/* SUMMARY +/* predicate if string is all ASCII +/* SYNOPSIS +/* #include <stringops.h> +/* +/* int allascii(buffer) +/* const char *buffer; +/* +/* int allascii_len(buffer, len) +/* const char *buffer; +/* ssize_t len; +/* DESCRIPTION +/* allascii() determines if its argument is an all-ASCII string. +/* +/* Arguments: +/* .IP buffer +/* The null-terminated input string. +/* .IP len +/* The string length, -1 to determine the length dynamically. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <ctype.h> +#include <string.h> + +/* Utility library. */ + +#include "stringops.h" + +/* allascii_len - return true if string is all ASCII */ + +int allascii_len(const char *string, ssize_t len) +{ + const char *cp; + int ch; + + if (len < 0) + len = strlen(string); + if (len == 0) + return (0); + for (cp = string; cp < string + len + && (ch = *(unsigned char *) cp) != 0; cp++) + if (!ISASCII(ch)) + return (0); + return (1); +} diff --git a/src/util/alldig.c b/src/util/alldig.c new file mode 100644 index 0000000..cabe5c3 --- /dev/null +++ b/src/util/alldig.c @@ -0,0 +1,73 @@ +/*++ +/* NAME +/* alldig 3 +/* SUMMARY +/* predicate if string is all numerical +/* SYNOPSIS +/* #include <stringops.h> +/* +/* int alldig(string) +/* const char *string; +/* +/* int allalnum(string) +/* const char *string; +/* DESCRIPTION +/* alldig() determines if its argument is an all-numerical string. +/* +/* allalnum() determines if its argument is an all-alphanumerical +/* string. +/* SEE ALSO +/* An alldig() routine appears in Brian W. Kernighan, P.J. Plauger: +/* "Software Tools", Addison-Wesley 1976. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <ctype.h> + +/* Utility library. */ + +#include <stringops.h> + +/* alldig - return true if string is all digits */ + +int alldig(const char *string) +{ + const char *cp; + + if (*string == 0) + return (0); + for (cp = string; *cp != 0; cp++) + if (!ISDIGIT(*cp)) + return (0); + return (1); +} + +/* allalnum - return true if string is all alphanum */ + +int allalnum(const char *string) +{ + const char *cp; + + if (*string == 0) + return (0); + for (cp = string; *cp != 0; cp++) + if (!ISALNUM(*cp)) + return (0); + return (1); +} diff --git a/src/util/allprint.c b/src/util/allprint.c new file mode 100644 index 0000000..6e9c519 --- /dev/null +++ b/src/util/allprint.c @@ -0,0 +1,50 @@ +/*++ +/* NAME +/* allprint 3 +/* SUMMARY +/* predicate if string is all printable +/* SYNOPSIS +/* #include <stringops.h> +/* +/* int allprint(buffer) +/* const char *buffer; +/* DESCRIPTION +/* allprint() determines if its argument is an all-printable string. +/* +/* Arguments: +/* .IP buffer +/* The null-terminated input string. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <ctype.h> + +/* Utility library. */ + +#include "stringops.h" + +/* allprint - return true if string is all printable */ + +int allprint(const char *string) +{ + const char *cp; + int ch; + + if (*string == 0) + return (0); + for (cp = string; (ch = *(unsigned char *) cp) != 0; cp++) + if (!ISASCII(ch) || !ISPRINT(ch)) + return (0); + return (1); +} diff --git a/src/util/allspace.c b/src/util/allspace.c new file mode 100644 index 0000000..9a05347 --- /dev/null +++ b/src/util/allspace.c @@ -0,0 +1,50 @@ +/*++ +/* NAME +/* allspace 3 +/* SUMMARY +/* predicate if string is all space +/* SYNOPSIS +/* #include <stringops.h> +/* +/* int allspace(buffer) +/* const char *buffer; +/* DESCRIPTION +/* allspace() determines if its argument is an all-space string. +/* +/* Arguments: +/* .IP buffer +/* The null-terminated input string. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <ctype.h> + +/* Utility library. */ + +#include "stringops.h" + +/* allspace - return true if string is all space */ + +int allspace(const char *string) +{ + const char *cp; + int ch; + + if (*string == 0) + return (0); + for (cp = string; (ch = *(unsigned char *) cp) != 0; cp++) + if (!ISASCII(ch) || !ISSPACE(ch)) + return (0); + return (1); +} diff --git a/src/util/argv.c b/src/util/argv.c new file mode 100644 index 0000000..a364e24 --- /dev/null +++ b/src/util/argv.c @@ -0,0 +1,326 @@ +/*++ +/* NAME +/* argv 3 +/* SUMMARY +/* string array utilities +/* SYNOPSIS +/* #include <argv.h> +/* +/* ARGV *argv_alloc(len) +/* ssize_t len; +/* +/* ARGV *argv_sort(argvp) +/* ARGV *argvp; +/* +/* ARGV *argv_free(argvp) +/* ARGV *argvp; +/* +/* void argv_add(argvp, arg, ..., ARGV_END) +/* ARGV *argvp; +/* char *arg; +/* +/* void argv_addn(argvp, arg, arg_len, ..., ARGV_END) +/* ARGV *argvp; +/* char *arg; +/* ssize_t arg_len; +/* +/* void argv_terminate(argvp); +/* ARGV *argvp; +/* +/* void argv_truncate(argvp, len); +/* ARGV *argvp; +/* ssize_t len; +/* +/* void argv_insert_one(argvp, pos, arg) +/* ARGV *argvp; +/* ssize_t pos; +/* const char *arg; +/* +/* void argv_replace_one(argvp, pos, arg) +/* ARGV *argvp; +/* ssize_t pos; +/* const char *arg; +/* +/* void argv_delete(argvp, pos, how_many) +/* ARGV *argvp; +/* ssize_t pos; +/* ssize_t how_many; +/* +/* void ARGV_FAKE_BEGIN(argv, arg) +/* const char *arg; +/* +/* void ARGV_FAKE_END +/* DESCRIPTION +/* The functions in this module manipulate arrays of string +/* pointers. An ARGV structure contains the following members: +/* .IP len +/* The length of the \fIargv\fR array member. +/* .IP argc +/* The number of \fIargv\fR elements used. +/* .IP argv +/* An array of pointers to null-terminated strings. +/* .PP +/* argv_alloc() returns an empty string array of the requested +/* length. The result is ready for use by argv_add(). The array +/* is null terminated. +/* +/* argv_sort() sorts the elements of argvp in place returning +/* the original array. +/* +/* argv_add() copies zero or more strings and adds them to the +/* specified string array. The array is null terminated. +/* Terminate the argument list with a null pointer. The manifest +/* constant ARGV_END provides a convenient notation for this. +/* +/* argv_addn() is like argv_add(), but each string is followed +/* by a string length argument. +/* +/* argv_free() releases storage for a string array, and conveniently +/* returns a null pointer. +/* +/* argv_terminate() null-terminates its string array argument. +/* +/* argv_truncate() truncates its argument to the specified +/* number of entries, but does not reallocate memory. The +/* result is null-terminated. +/* +/* argv_insert_one() inserts one string at the specified array +/* position. +/* +/* argv_replace_one() replaces one string at the specified +/* position. The old string is destroyed after the update is +/* made. +/* +/* argv_delete() deletes the specified number of elements +/* starting at the specified array position. The result is +/* null-terminated. +/* +/* ARGV_FAKE_BEGIN/END are an optimization for the case where +/* a single string needs to be passed into an ARGV-based +/* interface. ARGV_FAKE_BEGIN() opens a statement block and +/* allocates a stack-based ARGV structure named after the first +/* argument, that encapsulates the second argument. This +/* implementation allocates no heap memory and creates no copy +/* of the second argument. ARGV_FAKE_END closes the statement +/* block and thereby releases storage. +/* SEE ALSO +/* msg(3) diagnostics interface +/* DIAGNOSTICS +/* Fatal errors: memory allocation problem. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include <sys_defs.h> +#include <stdlib.h> /* 44BSD stdarg.h uses abort() */ +#include <stdarg.h> +#include <string.h> + +/* Application-specific. */ + +#include "mymalloc.h" +#include "msg.h" +#include "argv.h" + +/* argv_free - destroy string array */ + +ARGV *argv_free(ARGV *argvp) +{ + char **cpp; + + for (cpp = argvp->argv; cpp < argvp->argv + argvp->argc; cpp++) + myfree(*cpp); + myfree((void *) argvp->argv); + myfree((void *) argvp); + return (0); +} + +/* argv_alloc - initialize string array */ + +ARGV *argv_alloc(ssize_t len) +{ + ARGV *argvp; + ssize_t sane_len; + + /* + * Make sure that always argvp->argc < argvp->len. + */ + argvp = (ARGV *) mymalloc(sizeof(*argvp)); + argvp->len = 0; + sane_len = (len < 2 ? 2 : len); + argvp->argv = (char **) mymalloc((sane_len + 1) * sizeof(char *)); + argvp->len = sane_len; + argvp->argc = 0; + argvp->argv[0] = 0; + return (argvp); +} + +static int argv_cmp(const void *e1, const void *e2) +{ + const char *s1 = *(const char **) e1; + const char *s2 = *(const char **) e2; + + return strcmp(s1, s2); +} + +/* argv_sort - sort array in place */ + +ARGV *argv_sort(ARGV *argvp) +{ + qsort(argvp->argv, argvp->argc, sizeof(argvp->argv[0]), argv_cmp); + return (argvp); +} + +/* argv_extend - extend array */ + +static void argv_extend(ARGV *argvp) +{ + ssize_t new_len; + + new_len = argvp->len * 2; + argvp->argv = (char **) + myrealloc((void *) argvp->argv, (new_len + 1) * sizeof(char *)); + argvp->len = new_len; +} + +/* argv_add - add string to vector */ + +void argv_add(ARGV *argvp,...) +{ + char *arg; + va_list ap; + + /* + * Make sure that always argvp->argc < argvp->len. + */ +#define ARGV_SPACE_LEFT(a) ((a)->len - (a)->argc - 1) + + va_start(ap, argvp); + while ((arg = va_arg(ap, char *)) != 0) { + if (ARGV_SPACE_LEFT(argvp) <= 0) + argv_extend(argvp); + argvp->argv[argvp->argc++] = mystrdup(arg); + } + va_end(ap); + argvp->argv[argvp->argc] = 0; +} + +/* argv_addn - add string to vector */ + +void argv_addn(ARGV *argvp,...) +{ + char *arg; + ssize_t len; + va_list ap; + + /* + * Make sure that always argvp->argc < argvp->len. + */ + va_start(ap, argvp); + while ((arg = va_arg(ap, char *)) != 0) { + if ((len = va_arg(ap, ssize_t)) < 0) + msg_panic("argv_addn: bad string length %ld", (long) len); + if (ARGV_SPACE_LEFT(argvp) <= 0) + argv_extend(argvp); + argvp->argv[argvp->argc++] = mystrndup(arg, len); + } + va_end(ap); + argvp->argv[argvp->argc] = 0; +} + +/* argv_terminate - terminate string array */ + +void argv_terminate(ARGV *argvp) +{ + + /* + * Trust that argvp->argc < argvp->len. + */ + argvp->argv[argvp->argc] = 0; +} + +/* argv_truncate - truncate string array */ + +void argv_truncate(ARGV *argvp, ssize_t len) +{ + char **cpp; + + /* + * Sanity check. + */ + if (len < 0) + msg_panic("argv_truncate: bad length %ld", (long) len); + + if (len < argvp->argc) { + for (cpp = argvp->argv + len; cpp < argvp->argv + argvp->argc; cpp++) + myfree(*cpp); + argvp->argc = len; + argvp->argv[argvp->argc] = 0; + } +} + +/* argv_insert_one - insert one string into array */ + +void argv_insert_one(ARGV *argvp, ssize_t where, const char *arg) +{ + ssize_t pos; + + /* + * Sanity check. + */ + if (where < 0 || where > argvp->argc) + msg_panic("argv_insert_one bad position: %ld", (long) where); + + if (ARGV_SPACE_LEFT(argvp) <= 0) + argv_extend(argvp); + for (pos = argvp->argc; pos >= where; pos--) + argvp->argv[pos + 1] = argvp->argv[pos]; + argvp->argv[where] = mystrdup(arg); + argvp->argc += 1; +} + +/* argv_replace_one - replace one string in array */ + +void argv_replace_one(ARGV *argvp, ssize_t where, const char *arg) +{ + char *temp; + + /* + * Sanity check. + */ + if (where < 0 || where >= argvp->argc) + msg_panic("argv_replace_one bad position: %ld", (long) where); + + temp = argvp->argv[where]; + argvp->argv[where] = mystrdup(arg); + myfree(temp); +} + +/* argv_delete - remove string(s) from array */ + +void argv_delete(ARGV *argvp, ssize_t first, ssize_t how_many) +{ + ssize_t pos; + + /* + * Sanity check. + */ + if (first < 0 || how_many < 0 || first + how_many > argvp->argc) + msg_panic("argv_delete bad range: (start=%ld count=%ld)", + (long) first, (long) how_many); + + for (pos = first; pos < first + how_many; pos++) + myfree(argvp->argv[pos]); + for (pos = first; pos <= argvp->argc - how_many; pos++) + argvp->argv[pos] = argvp->argv[pos + how_many]; + argvp->argc -= how_many; +} diff --git a/src/util/argv.h b/src/util/argv.h new file mode 100644 index 0000000..1e3b467 --- /dev/null +++ b/src/util/argv.h @@ -0,0 +1,69 @@ +#ifndef _ARGV_H_INCLUDED_ +#define _ARGV_H_INCLUDED_ + +/*++ +/* NAME +/* argv 3h +/* SUMMARY +/* string array utilities +/* SYNOPSIS +/* #include "argv.h" +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +typedef struct ARGV { + ssize_t len; /* number of array elements */ + ssize_t argc; /* array elements in use */ + char **argv; /* string array */ +} ARGV; + +extern ARGV *argv_alloc(ssize_t); +extern ARGV *argv_sort(ARGV *); +extern void argv_add(ARGV *,...); +extern void argv_addn(ARGV *,...); +extern void argv_terminate(ARGV *); +extern void argv_truncate(ARGV *, ssize_t); +extern void argv_insert_one(ARGV *, ssize_t, const char *); +extern void argv_replace_one(ARGV *, ssize_t, const char *); +extern void argv_delete(ARGV *, ssize_t, ssize_t); +extern ARGV *argv_free(ARGV *); + +extern ARGV *argv_split(const char *, const char *); +extern ARGV *argv_split_count(const char *, const char *, ssize_t); +extern ARGV *argv_split_append(ARGV *, const char *, const char *); + +extern ARGV *argv_splitq(const char *, const char *, const char *); +extern ARGV *argv_splitq_count(const char *, const char *, const char *, ssize_t); +extern ARGV *argv_splitq_append(ARGV *, const char *, const char *, const char *); + +extern ARGV *argv_split_at(const char *, int); +extern ARGV *argv_split_at_count(const char *, int, ssize_t); +extern ARGV *argv_split_at_append(ARGV *, const char *, int); + +#define ARGV_FAKE_BEGIN(fake_argv, arg) { \ + ARGV fake_argv; \ + char *__fake_argv_args__[2]; \ + __fake_argv_args__[0] = (char *) (arg); \ + __fake_argv_args__[1] = 0; \ + fake_argv.argv = __fake_argv_args__; \ + fake_argv.argc = fake_argv.len = 1; + +#define ARGV_FAKE_END } + +#define ARGV_END ((char *) 0) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/argv_attr.h b/src/util/argv_attr.h new file mode 100644 index 0000000..53c587f --- /dev/null +++ b/src/util/argv_attr.h @@ -0,0 +1,43 @@ +#ifndef _ARGV_ATTR_H_INCLUDED_ +#define _ARGV_ATTR_H_INCLUDED_ + +/*++ +/* NAME +/* argv_attr 3h +/* SUMMARY +/* argv serialization/deserialization +/* SYNOPSIS +/* #include <argv_attr.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <argv.h> +#include <attr.h> +#include <check_arg.h> +#include <vstream.h> + + /* + * External API. + */ +#define ARGV_ATTR_SIZE "argv_size" +#define ARGV_ATTR_VALUE "argv_value" +#define ARGV_ATTR_MAX 1024 + +extern int argv_attr_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *); +extern int argv_attr_scan(ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/argv_attr_print.c b/src/util/argv_attr_print.c new file mode 100644 index 0000000..78e3315 --- /dev/null +++ b/src/util/argv_attr_print.c @@ -0,0 +1,73 @@ +/*++ +/* NAME +/* argv_attr_print +/* SUMMARY +/* write ARGV to stream +/* SYNOPSIS +/* #include <argv_attr.h> +/* +/* int argv_attr_print(print_fn, stream, flags, ptr) +/* ATTR_PRINT_COMMON_FN print_fn; +/* VSTREAM *stream; +/* int flags; +/* void *ptr; +/* DESCRIPTION +/* argv_attr_print() writes an ARGV to the named stream using +/* the specified attribute print routine. argv_attr_print() is meant +/* to be passed as a call-back to attr_print(), thusly: +/* +/* ... SEND_ATTR_FUNC(argv_attr_print, (const void *) argv), ... +/* DIAGNOSTICS +/* Fatal: out of memory. +/* +/* The result value is zero in case of success, non-zero +/* otherwise. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include <sys_defs.h> + + /* + * Utility library. + */ +#include <argv.h> +#include <argv_attr.h> +#include <attr.h> +#include <vstream.h> +#include <msg.h> + +/* argv_attr_print - write ARGV to stream */ + +int argv_attr_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp, + int flags, const void *ptr) +{ + ARGV *argv = (ARGV *) ptr; + int n; + int ret; + int argc = argv ? argv->argc : 0; + + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_INT(ARGV_ATTR_SIZE, argc), + ATTR_TYPE_END); + if (msg_verbose) + msg_info("argv_attr_print count=%d", argc); + for (n = 0; ret == 0 && n < argc; n++) + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_STR(ARGV_ATTR_VALUE, argv->argv[n]), + ATTR_TYPE_END); + if (msg_verbose) + msg_info("argv_attr_print ret=%d", ret); + /* Do not flush the stream. */ + return (ret); +} diff --git a/src/util/argv_attr_scan.c b/src/util/argv_attr_scan.c new file mode 100644 index 0000000..0987bf8 --- /dev/null +++ b/src/util/argv_attr_scan.c @@ -0,0 +1,93 @@ +/*++ +/* NAME +/* argv_attr_scan +/* SUMMARY +/* read ARGV from stream +/* SYNOPSIS +/* #include <argv_attr.h> +/* +/* int argv_attr_scan(scan_fn, stream, flags, ptr) +/* ATTR_SCAN_COMMON_FN scan_fn; +/* VSTREAM *stream; +/* int flags; +/* void *ptr; +/* DESCRIPTION +/* argv_attr_scan() creates an ARGV and reads its contents +/* from the named stream using the specified attribute scan +/* routine. argv_attr_scan() is meant to be passed as a call-back +/* to attr_scan(), thusly: +/* +/* ARGV *argv = 0; +/* ... +/* ... RECV_ATTR_FUNC(argv_attr_scan, (void *) &argv), ... +/* ... +/* if (argv) +/* argv_free(argv); +/* DIAGNOSTICS +/* Fatal: out of memory. +/* +/* In case of error, this function returns non-zero and creates +/* an ARGV null pointer. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include <sys_defs.h> + + /* + * Utility library. + */ +#include <argv.h> +#include <argv_attr.h> +#include <attr.h> +#include <msg.h> +#include <vstream.h> +#include <vstring.h> + +/* argv_attr_scan - write ARGV to stream */ + +int argv_attr_scan(ATTR_PRINT_COMMON_FN scan_fn, VSTREAM *fp, + int flags, void *ptr) +{ + ARGV *argv = 0; + int size; + int ret; + + if ((ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_INT(ARGV_ATTR_SIZE, &size), + ATTR_TYPE_END)) == 1) { + if (msg_verbose) + msg_info("argv_attr_scan count=%d", size); + if (size < 0 || size > ARGV_ATTR_MAX) { + msg_warn("invalid size %d from %s while reading ARGV", + size, VSTREAM_PATH(fp)); + ret = -1; + } else if (size > 0) { + VSTRING *buffer = vstring_alloc(100); + + argv = argv_alloc(size); + while (ret == 1 && size-- > 0) { + if ((ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_STR(ARGV_ATTR_VALUE, buffer), + ATTR_TYPE_END)) == 1) + argv_add(argv, vstring_str(buffer), ARGV_END); + } + argv_terminate(argv); + vstring_free(buffer); + } + } + *(ARGV **) ptr = argv; + if (msg_verbose) + msg_info("argv_attr_scan ret=%d", ret); + return (ret); +} diff --git a/src/util/argv_split.c b/src/util/argv_split.c new file mode 100644 index 0000000..a9f3afb --- /dev/null +++ b/src/util/argv_split.c @@ -0,0 +1,112 @@ +/*++ +/* NAME +/* argv_split 3 +/* SUMMARY +/* string array utilities +/* SYNOPSIS +/* #include <argv.h> +/* +/* ARGV *argv_split(string, delim) +/* const char *string; +/* const char *delim; +/* +/* ARGV *argv_split_count(string, delim, count) +/* const char *string; +/* const char *delim; +/* ssize_t count; +/* +/* ARGV *argv_split_append(argv, string, delim) +/* ARGV *argv; +/* const char *string; +/* const char *delim; +/* DESCRIPTION +/* argv_split() breaks up \fIstring\fR into tokens according +/* to the delimiters specified in \fIdelim\fR. The result is +/* a null-terminated string array. +/* +/* argv_split_count() is like argv_split() but stops splitting +/* input after at most \fIcount\fR -1 times and leaves the +/* remainder, if any, in the last array element. It is an error +/* to specify a count < 1. +/* +/* argv_split_append() performs the same operation as argv_split(), +/* but appends the result to an existing string array. +/* SEE ALSO +/* mystrtok(), safe string splitter. +/* DIAGNOSTICS +/* Fatal errors: memory allocation problem. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include <sys_defs.h> +#include <string.h> + +/* Application-specific. */ + +#include "mymalloc.h" +#include "stringops.h" +#include "argv.h" +#include "msg.h" + +/* argv_split - split string into token array */ + +ARGV *argv_split(const char *string, const char *delim) +{ + ARGV *argvp = argv_alloc(1); + char *saved_string = mystrdup(string); + char *bp = saved_string; + char *arg; + + while ((arg = mystrtok(&bp, delim)) != 0) + argv_add(argvp, arg, (char *) 0); + argv_terminate(argvp); + myfree(saved_string); + return (argvp); +} + +/* argv_split_count - split string into token array */ + +ARGV *argv_split_count(const char *string, const char *delim, ssize_t count) +{ + ARGV *argvp = argv_alloc(1); + char *saved_string = mystrdup(string); + char *bp = saved_string; + char *arg; + + if (count < 1) + msg_panic("argv_split_count: bad count: %ld", (long) count); + while (count-- > 1 && (arg = mystrtok(&bp, delim)) != 0) + argv_add(argvp, arg, (char *) 0); + if (*bp) + bp += strspn(bp, delim); + if (*bp) + argv_add(argvp, bp, (char *) 0); + argv_terminate(argvp); + myfree(saved_string); + return (argvp); +} + +/* argv_split_append - split string into token array, append to array */ + +ARGV *argv_split_append(ARGV *argvp, const char *string, const char *delim) +{ + char *saved_string = mystrdup(string); + char *bp = saved_string; + char *arg; + + while ((arg = mystrtok(&bp, delim)) != 0) + argv_add(argvp, arg, (char *) 0); + argv_terminate(argvp); + myfree(saved_string); + return (argvp); +} diff --git a/src/util/argv_split_at.c b/src/util/argv_split_at.c new file mode 100644 index 0000000..fcdb4ff --- /dev/null +++ b/src/util/argv_split_at.c @@ -0,0 +1,124 @@ +/*++ +/* NAME +/* argv_split_at 3 +/* SUMMARY +/* string array utilities +/* SYNOPSIS +/* #include <argv.h> +/* +/* ARGV *argv_split_at(string, sep) +/* const char *string; +/* int sep; +/* +/* ARGV *argv_split_at_count(string, sep, count) +/* const char *string; +/* int sep; +/* ssize_t count; +/* +/* ARGV *argv_split_at_append(argv, string, sep) +/* ARGV *argv; +/* const char *string; +/* int sep; +/* DESCRIPTION +/* argv_split_at() splits \fIstring\fR into fields using a +/* single separator specified in \fIsep\fR. The result is a +/* null-terminated string array. +/* +/* argv_split_at_count() is like argv_split_at() but stops +/* splitting input after at most \fIcount\fR -1 times and +/* leaves the remainder, if any, in the last array element. +/* It is an error to specify a count < 1. +/* +/* argv_split_at_append() performs the same operation as +/* argv_split_at(), but appends the result to an existing +/* string array. +/* SEE ALSO +/* split_at(), trivial string splitter. +/* DIAGNOSTICS +/* Fatal errors: memory allocation problem. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System libraries. */ + +#include <sys_defs.h> +#include <string.h> + +/* Application-specific. */ + +#include <mymalloc.h> +#include <stringops.h> +#include <argv.h> +#include <msg.h> +#include <split_at.h> + +/* argv_split_at - split string into field array */ + +ARGV *argv_split_at(const char *string, int sep) +{ + ARGV *argvp = argv_alloc(1); + char *saved_string = mystrdup(string); + char *bp = saved_string; + char *arg; + + while ((arg = split_at(bp, sep)) != 0) { + argv_add(argvp, bp, (char *) 0); + bp = arg; + } + argv_add(argvp, bp, (char *) 0); + argv_terminate(argvp); + myfree(saved_string); + return (argvp); +} + +/* argv_split_at_count - split string into field array */ + +ARGV *argv_split_at_count(const char *string, int sep, ssize_t count) +{ + ARGV *argvp = argv_alloc(1); + char *saved_string = mystrdup(string); + char *bp = saved_string; + char *arg; + + if (count < 1) + msg_panic("argv_split_at_count: bad count: %ld", (long) count); + while (count-- > 1 && (arg = split_at(bp, sep)) != 0) { + argv_add(argvp, bp, (char *) 0); + bp = arg; + } + argv_add(argvp, bp, (char *) 0); + argv_terminate(argvp); + myfree(saved_string); + return (argvp); +} + +/* argv_split_at_append - split string into field array, append to array */ + +ARGV *argv_split_at_append(ARGV *argvp, const char *string, int sep) +{ + char *saved_string = mystrdup(string); + char *bp = saved_string; + char *arg; + + while ((arg = split_at(bp, sep)) != 0) { + argv_add(argvp, bp, (char *) 0); + bp = arg; + } + argv_add(argvp, bp, (char *) 0); + argv_terminate(argvp); + myfree(saved_string); + return (argvp); +} diff --git a/src/util/argv_splitq.c b/src/util/argv_splitq.c new file mode 100644 index 0000000..3900ee1 --- /dev/null +++ b/src/util/argv_splitq.c @@ -0,0 +1,118 @@ +/*++ +/* NAME +/* argv_splitq 3 +/* SUMMARY +/* string array utilities +/* SYNOPSIS +/* #include <argv.h> +/* +/* ARGV *argv_splitq(string, delim, parens) +/* const char *string; +/* const char *delim; +/* const char *parens; +/* +/* ARGV *argv_splitq_count(string, delim, parens, count) +/* const char *string; +/* const char *delim; +/* const char *parens; +/* ssize_t count; +/* +/* ARGV *argv_splitq_append(argv, string, delim, parens) +/* ARGV *argv; +/* const char *string; +/* const char *delim; +/* const char *parens; +/* DESCRIPTION +/* argv_splitq() breaks up \fIstring\fR into tokens according +/* to the delimiters specified in \fIdelim\fR, while avoiding +/* splitting text between matching parentheses. The result is +/* a null-terminated string array. +/* +/* argv_splitq_count() is like argv_splitq() but stops splitting +/* input after at most \fIcount\fR -1 times and leaves the +/* remainder, if any, in the last array element. It is an error +/* to specify a count < 1. +/* +/* argv_splitq_append() performs the same operation as argv_splitq(), +/* but appends the result to an existing string array. +/* SEE ALSO +/* mystrtokq(), safe string splitter. +/* DIAGNOSTICS +/* Fatal errors: memory allocation problem. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include <sys_defs.h> +#include <string.h> + +/* Application-specific. */ + +#include "mymalloc.h" +#include "stringops.h" +#include "argv.h" +#include "msg.h" + +/* argv_splitq - split string into token array */ + +ARGV *argv_splitq(const char *string, const char *delim, const char *parens) +{ + ARGV *argvp = argv_alloc(1); + char *saved_string = mystrdup(string); + char *bp = saved_string; + char *arg; + + while ((arg = mystrtokq(&bp, delim, parens)) != 0) + argv_add(argvp, arg, (char *) 0); + argv_terminate(argvp); + myfree(saved_string); + return (argvp); +} + +/* argv_splitq_count - split string into token array */ + +ARGV *argv_splitq_count(const char *string, const char *delim, + const char *parens, ssize_t count) +{ + ARGV *argvp = argv_alloc(1); + char *saved_string = mystrdup(string); + char *bp = saved_string; + char *arg; + + if (count < 1) + msg_panic("argv_splitq_count: bad count: %ld", (long) count); + while (count-- > 1 && (arg = mystrtokq(&bp, delim, parens)) != 0) + argv_add(argvp, arg, (char *) 0); + if (*bp) + bp += strspn(bp, delim); + if (*bp) + argv_add(argvp, bp, (char *) 0); + argv_terminate(argvp); + myfree(saved_string); + return (argvp); +} + +/* argv_splitq_append - split string into token array, append to array */ + +ARGV *argv_splitq_append(ARGV *argvp, const char *string, const char *delim, + const char *parens) +{ + char *saved_string = mystrdup(string); + char *bp = saved_string; + char *arg; + + while ((arg = mystrtokq(&bp, delim, parens)) != 0) + argv_add(argvp, arg, (char *) 0); + argv_terminate(argvp); + myfree(saved_string); + return (argvp); +} diff --git a/src/util/attr.h b/src/util/attr.h new file mode 100644 index 0000000..067405f --- /dev/null +++ b/src/util/attr.h @@ -0,0 +1,184 @@ +#ifndef _ATTR_H_INCLUDED_ +#define _ATTR_H_INCLUDED_ + +/*++ +/* NAME +/* attr 3h +/* SUMMARY +/* attribute list manipulations +/* SYNOPSIS +/* #include "attr.h" + DESCRIPTION + .nf + + /* + * System library. + */ +#include <stdarg.h> + + /* + * Utility library. + */ +#include <vstream.h> +#include <vstring.h> +#include <htable.h> +#include <nvtable.h> +#include <check_arg.h> + + /* + * Delegation for better data abstraction. + */ +typedef int (*ATTR_SCAN_COMMON_FN) (VSTREAM *, int,...); +typedef int (*ATTR_SCAN_CUSTOM_FN) (ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *); +typedef int (*ATTR_PRINT_COMMON_FN) (VSTREAM *, int,...); +typedef int (*ATTR_PRINT_CUSTOM_FN) (ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *); + + /* + * Attribute types. See attr_scan(3) for documentation. + */ +#define ATTR_TYPE_END 0 /* end of data */ +#define ATTR_TYPE_INT 1 /* Unsigned integer */ +#define ATTR_TYPE_NUM ATTR_TYPE_INT +#define ATTR_TYPE_STR 2 /* Character string */ +#define ATTR_TYPE_HASH 3 /* Hash table */ +#define ATTR_TYPE_NV 3 /* Name-value table */ +#define ATTR_TYPE_LONG 4 /* Unsigned long */ +#define ATTR_TYPE_DATA 5 /* Binary data */ +#define ATTR_TYPE_FUNC 6 /* Function pointer */ +#define ATTR_TYPE_STREQ 7 /* Requires (name, value) match */ + + /* + * Optional sender-specified grouping for hash or nameval tables. + */ +#define ATTR_TYPE_OPEN '{' +#define ATTR_TYPE_CLOSE '}' +#define ATTR_NAME_OPEN "{" +#define ATTR_NAME_CLOSE "}" + +#define ATTR_HASH_LIMIT 1024 /* Size of hash table */ + + /* + * Typechecking support for variadic function arguments. See check_arg(3h) + * for documentation. + */ +#define SEND_ATTR_INT(name, val) ATTR_TYPE_INT, CHECK_CPTR(ATTR, char, (name)), CHECK_VAL(ATTR, int, (val)) +#define SEND_ATTR_STR(name, val) ATTR_TYPE_STR, CHECK_CPTR(ATTR, char, (name)), CHECK_CPTR(ATTR, char, (val)) +#define SEND_ATTR_HASH(val) ATTR_TYPE_HASH, CHECK_CPTR(ATTR, HTABLE, (val)) +#define SEND_ATTR_NV(val) ATTR_TYPE_NV, CHECK_CPTR(ATTR, NVTABLE, (val)) +#define SEND_ATTR_LONG(name, val) ATTR_TYPE_LONG, CHECK_CPTR(ATTR, char, (name)), CHECK_VAL(ATTR, long, (val)) +#define SEND_ATTR_DATA(name, len, val) ATTR_TYPE_DATA, CHECK_CPTR(ATTR, char, (name)), CHECK_VAL(ATTR, ssize_t, (len)), CHECK_CPTR(ATTR, void, (val)) +#define SEND_ATTR_FUNC(func, val) ATTR_TYPE_FUNC, CHECK_VAL(ATTR, ATTR_PRINT_CUSTOM_FN, (func)), CHECK_CPTR(ATTR, void, (val)) + +#define RECV_ATTR_INT(name, val) ATTR_TYPE_INT, CHECK_CPTR(ATTR, char, (name)), CHECK_PTR(ATTR, int, (val)) +#define RECV_ATTR_STR(name, val) ATTR_TYPE_STR, CHECK_CPTR(ATTR, char, (name)), CHECK_PTR(ATTR, VSTRING, (val)) +#define RECV_ATTR_STREQ(name, val) ATTR_TYPE_STREQ, CHECK_CPTR(ATTR, char, (name)), CHECK_CPTR(ATTR, char, (val)) +#define RECV_ATTR_HASH(val) ATTR_TYPE_HASH, CHECK_PTR(ATTR, HTABLE, (val)) +#define RECV_ATTR_NV(val) ATTR_TYPE_NV, CHECK_PTR(ATTR, NVTABLE, (val)) +#define RECV_ATTR_LONG(name, val) ATTR_TYPE_LONG, CHECK_CPTR(ATTR, char, (name)), CHECK_PTR(ATTR, long, (val)) +#define RECV_ATTR_DATA(name, val) ATTR_TYPE_DATA, CHECK_CPTR(ATTR, char, (name)), CHECK_PTR(ATTR, VSTRING, (val)) +#define RECV_ATTR_FUNC(func, val) ATTR_TYPE_FUNC, CHECK_VAL(ATTR, ATTR_SCAN_CUSTOM_FN, (func)), CHECK_PTR(ATTR, void, (val)) + +CHECK_VAL_HELPER_DCL(ATTR, ssize_t); +CHECK_VAL_HELPER_DCL(ATTR, long); +CHECK_VAL_HELPER_DCL(ATTR, int); +CHECK_PTR_HELPER_DCL(ATTR, void); +CHECK_PTR_HELPER_DCL(ATTR, long); +CHECK_PTR_HELPER_DCL(ATTR, int); +CHECK_PTR_HELPER_DCL(ATTR, VSTRING); +CHECK_PTR_HELPER_DCL(ATTR, NVTABLE); +CHECK_PTR_HELPER_DCL(ATTR, HTABLE); +CHECK_CPTR_HELPER_DCL(ATTR, void); +CHECK_CPTR_HELPER_DCL(ATTR, char); +CHECK_CPTR_HELPER_DCL(ATTR, NVTABLE); +CHECK_CPTR_HELPER_DCL(ATTR, HTABLE); +CHECK_VAL_HELPER_DCL(ATTR, ATTR_PRINT_CUSTOM_FN); +CHECK_VAL_HELPER_DCL(ATTR, ATTR_SCAN_CUSTOM_FN); + + /* + * Flags that control processing. See attr_scan(3) for documentation. + */ +#define ATTR_FLAG_NONE 0 +#define ATTR_FLAG_MISSING (1<<0) /* Flag missing attribute */ +#define ATTR_FLAG_EXTRA (1<<1) /* Flag spurious attribute */ +#define ATTR_FLAG_MORE (1<<2) /* Don't skip or terminate */ +#define ATTR_FLAG_PRINTABLE (1<<3) /* Sanitize received strings */ + +#define ATTR_FLAG_STRICT (ATTR_FLAG_MISSING | ATTR_FLAG_EXTRA) +#define ATTR_FLAG_ALL (017) + + /* + * Default to null-terminated, as opposed to base64-encoded. + */ +#define attr_print attr_print0 +#define attr_vprint attr_vprint0 +#define attr_scan attr_scan0 +#define attr_vscan attr_vscan0 +#define attr_scan_more attr_scan_more0 + + /* + * attr_print64.c. + */ +extern int attr_print64(VSTREAM *, int,...); +extern int attr_vprint64(VSTREAM *, int, va_list); + + /* + * attr_scan64.c. + */ +extern int WARN_UNUSED_RESULT attr_scan64(VSTREAM *, int,...); +extern int WARN_UNUSED_RESULT attr_vscan64(VSTREAM *, int, va_list); +extern int WARN_UNUSED_RESULT attr_scan_more64(VSTREAM *); + + /* + * attr_print0.c. + */ +extern int attr_print0(VSTREAM *, int,...); +extern int attr_vprint0(VSTREAM *, int, va_list); + + /* + * attr_scan0.c. + */ +extern int WARN_UNUSED_RESULT attr_scan0(VSTREAM *, int,...); +extern int WARN_UNUSED_RESULT attr_vscan0(VSTREAM *, int, va_list); +extern int WARN_UNUSED_RESULT attr_scan_more0(VSTREAM *); + + /* + * attr_scan_plain.c. + */ +extern int attr_print_plain(VSTREAM *, int,...); +extern int attr_vprint_plain(VSTREAM *, int, va_list); +extern int attr_scan_more_plain(VSTREAM *); + + /* + * attr_print_plain.c. + */ +extern int WARN_UNUSED_RESULT attr_scan_plain(VSTREAM *, int,...); +extern int WARN_UNUSED_RESULT attr_vscan_plain(VSTREAM *, int, va_list); + + /* + * Attribute names for testing the compatibility of the read and write + * routines. + */ +#ifdef TEST +#define ATTR_NAME_INT "number" +#define ATTR_NAME_STR "string" +#define ATTR_NAME_LONG "long_number" +#define ATTR_NAME_DATA "data" +#endif + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/attr_clnt.c b/src/util/attr_clnt.c new file mode 100644 index 0000000..d944be5 --- /dev/null +++ b/src/util/attr_clnt.c @@ -0,0 +1,300 @@ +/*++ +/* NAME +/* attr_clnt 3 +/* SUMMARY +/* attribute query-reply client +/* SYNOPSIS +/* #include <attr_clnt.h> +/* +/* typedef int (*ATTR_CLNT_PRINT_FN) (VSTREAM *, int, va_list); +/* typedef int (*ATTR_CLNT_SCAN_FN) (VSTREAM *, int, va_list); +/* typedef int (*ATTR_CLNT_HANDSHAKE_FN) (VSTREAM *); +/* +/* ATTR_CLNT *attr_clnt_create(server, timeout, max_idle, max_ttl) +/* const char *server; +/* int timeout; +/* int max_idle; +/* int max_ttl; +/* +/* int attr_clnt_request(client, +/* send_flags, send_type, send_name, ..., ATTR_TYPE_END, +/* recv_flags, recv_type, recv_name, ..., ATTR_TYPE_END) +/* ATTR_CLNT *client; +/* int send_flags; +/* int send_type; +/* const char *send_name; +/* int recv_flags; +/* int recv_type; +/* const char *recv_name; +/* +/* void attr_clnt_free(client) +/* ATTR_CLNT *client; +/* +/* void attr_clnt_control(client, name, value, ... ATTR_CLNT_CTL_END) +/* ATTR_CLNT *client; +/* int name; +/* DESCRIPTION +/* This module implements a client for a simple attribute-based +/* protocol. The default protocol is described in attr_scan_plain(3). +/* +/* attr_clnt_create() creates a client handle. See auto_clnt(3) for +/* a description of the arguments. +/* +/* attr_clnt_request() sends the specified request attributes and +/* receives a reply. The reply argument specifies a name-value table. +/* The other arguments are as described in attr_print_plain(3). The +/* result is the number of attributes received or -1 in case of trouble. +/* +/* attr_clnt_free() destroys a client handle and closes its connection. +/* +/* attr_clnt_control() allows the user to fine tune the behavior of +/* the specified client. The arguments are a list of (name, value) +/* terminated with ATTR_CLNT_CTL_END. +/* The following lists the names and the types of the corresponding +/* value arguments. +/* .IP "ATTR_CLNT_CTL_PROTO(ATTR_CLNT_PRINT_FN, ATTR_CLNT_SCAN_FN)" +/* Specifies alternatives for the attr_plain_print() and +/* attr_plain_scan() functions. +/* .IP "ATTR_CLNT_CTL_REQ_LIMIT(int)" +/* The maximal number of requests per connection (default: 0, +/* i.e. no limit). To enable the limit, specify a value greater +/* than zero. +/* .IP "ATTR_CLNT_CTL_TRY_LIMIT(int)" +/* The maximal number of attempts to send a request before +/* giving up (default: 2). To disable the limit, specify a +/* value equal to zero. +/* .IP "ATTR_CLNT_CTL_TRY_DELAY(int)" +/* The time in seconds between attempts to send a request +/* (default: 1). Specify a value greater than zero. +/* .IP "ATTR_CLNT_CTL_HANDSHAKE(VSTREAM *)" +/* A pointer to function that will be called at the start of a +/* new connection, and that returns 0 in case of success. +/* DIAGNOSTICS +/* Warnings: communication failure. +/* SEE ALSO +/* auto_clnt(3), client endpoint management +/* attr_scan_plain(3), attribute protocol +/* attr_print_plain(3), attribute protocol +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <unistd.h> +#include <errno.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstream.h> +#include <htable.h> +#include <attr.h> +#include <iostuff.h> +#include <compat_va_copy.h> +#include <auto_clnt.h> +#include <attr_clnt.h> + +/* Application-specific. */ + +struct ATTR_CLNT { + AUTO_CLNT *auto_clnt; + /* Remaining properties are set with attr_clnt_control(). */ + ATTR_CLNT_PRINT_FN print; + ATTR_CLNT_SCAN_FN scan; + int req_limit; + int req_count; + int try_limit; + int try_delay; +}; + +#define ATTR_CLNT_DEF_REQ_LIMIT (0) /* default per-session request limit */ +#define ATTR_CLNT_DEF_TRY_LIMIT (2) /* default request (re)try limit */ +#define ATTR_CLNT_DEF_TRY_DELAY (1) /* default request (re)try delay */ + +/* attr_clnt_free - destroy attribute client */ + +void attr_clnt_free(ATTR_CLNT *client) +{ + auto_clnt_free(client->auto_clnt); + myfree((void *) client); +} + +/* attr_clnt_create - create attribute client */ + +ATTR_CLNT *attr_clnt_create(const char *service, int timeout, + int max_idle, int max_ttl) +{ + ATTR_CLNT *client; + + client = (ATTR_CLNT *) mymalloc(sizeof(*client)); + client->auto_clnt = auto_clnt_create(service, timeout, max_idle, max_ttl); + client->scan = attr_vscan_plain; + client->print = attr_vprint_plain; + client->req_limit = ATTR_CLNT_DEF_REQ_LIMIT; + client->req_count = 0; + client->try_limit = ATTR_CLNT_DEF_TRY_LIMIT; + client->try_delay = ATTR_CLNT_DEF_TRY_DELAY; + return (client); +} + +/* attr_clnt_request - send query, receive reply */ + +int attr_clnt_request(ATTR_CLNT *client, int send_flags,...) +{ + const char *myname = "attr_clnt_request"; + VSTREAM *stream; + int count = 0; + va_list saved_ap; + va_list ap; + int type; + int recv_flags; + int err; + int ret; + + /* + * XXX If the stream is readable before we send anything, then assume the + * remote end disconnected. + * + * XXX For some reason we can't simply call the scan routine after the print + * routine, that messes up the argument list. + */ +#define SKIP_ARG(ap, type) { \ + (void) va_arg(ap, char *); \ + (void) va_arg(ap, type); \ + } +#define SKIP_ARG2(ap, t1, t2) { \ + SKIP_ARG(ap, t1); \ + (void) va_arg(ap, t2); \ + } + + /* Finalize argument lists before returning. */ + va_start(saved_ap, send_flags); + for (;;) { + errno = 0; + if ((stream = auto_clnt_access(client->auto_clnt)) != 0 + && readable(vstream_fileno(stream)) == 0) { + errno = 0; + VA_COPY(ap, saved_ap); + err = (client->print(stream, send_flags, ap) != 0 + || vstream_fflush(stream) != 0); + va_end(ap); + if (err == 0) { + VA_COPY(ap, saved_ap); + while ((type = va_arg(ap, int)) != ATTR_TYPE_END) { + switch (type) { + case ATTR_TYPE_STR: + SKIP_ARG(ap, char *); + break; + case ATTR_TYPE_DATA: + SKIP_ARG2(ap, ssize_t, char *); + break; + case ATTR_TYPE_INT: + SKIP_ARG(ap, int); + break; + case ATTR_TYPE_LONG: + SKIP_ARG(ap, long); + break; + case ATTR_TYPE_HASH: + (void) va_arg(ap, HTABLE *); + break; + default: + msg_panic("%s: unexpected attribute type %d", + myname, type); + } + } + recv_flags = va_arg(ap, int); + ret = client->scan(stream, recv_flags, ap); + va_end(ap); + /* Finalize argument lists before returning. */ + if (ret > 0) { + if (client->req_limit > 0 + && (client->req_count += 1) >= client->req_limit) { + auto_clnt_recover(client->auto_clnt); + client->req_count = 0; + } + break; + } + } + } + if ((++count >= client->try_limit && client->try_limit > 0) + || msg_verbose + || (errno && errno != EPIPE && errno != ENOENT && errno != ECONNRESET)) + msg_warn("problem talking to server %s: %m", + auto_clnt_name(client->auto_clnt)); + /* Finalize argument lists before returning. */ + if (count >= client->try_limit && client->try_limit > 0) { + ret = -1; + break; + } + sleep(client->try_delay); + auto_clnt_recover(client->auto_clnt); + client->req_count = 0; + } + /* Finalize argument lists before returning. */ + va_end(saved_ap); + return (ret); +} + +/* attr_clnt_control - fine control */ + +void attr_clnt_control(ATTR_CLNT *client, int name,...) +{ + const char *myname = "attr_clnt_control"; + va_list ap; + + for (va_start(ap, name); name != ATTR_CLNT_CTL_END; name = va_arg(ap, int)) { + switch (name) { + case ATTR_CLNT_CTL_PROTO: + client->print = va_arg(ap, ATTR_CLNT_PRINT_FN); + client->scan = va_arg(ap, ATTR_CLNT_SCAN_FN); + break; + case ATTR_CLNT_CTL_HANDSHAKE: + auto_clnt_control(client->auto_clnt, + AUTO_CLNT_CTL_HANDSHAKE, + va_arg(ap, ATTR_CLNT_HANDSHAKE_FN), + AUTO_CLNT_CTL_END); + break; + case ATTR_CLNT_CTL_REQ_LIMIT: + client->req_limit = va_arg(ap, int); + if (client->req_limit < 0) + msg_panic("%s: bad request limit: %d", + myname, client->req_limit); + if (msg_verbose) + msg_info("%s: new request limit %d", + myname, client->req_limit); + break; + case ATTR_CLNT_CTL_TRY_LIMIT: + client->try_limit = va_arg(ap, int); + if (client->try_limit < 0) + msg_panic("%s: bad retry limit: %d", myname, client->try_limit); + if (msg_verbose) + msg_info("%s: new retry limit %d", myname, client->try_limit); + break; + case ATTR_CLNT_CTL_TRY_DELAY: + client->try_delay = va_arg(ap, int); + if (client->try_delay <= 0) + msg_panic("%s: bad retry delay: %d", myname, client->try_delay); + if (msg_verbose) + msg_info("%s: new retry delay %d", myname, client->try_delay); + break; + default: + msg_panic("%s: bad name %d", myname, name); + } + } + va_end(ap); +} diff --git a/src/util/attr_clnt.h b/src/util/attr_clnt.h new file mode 100644 index 0000000..ca630cd --- /dev/null +++ b/src/util/attr_clnt.h @@ -0,0 +1,60 @@ +#ifndef _ATTR_CLNT_H_INCLUDED_ +#define _ATTR_CLNT_H_INCLUDED_ + +/*++ +/* NAME +/* attr_clnt 3h +/* SUMMARY +/* attribute query-reply client +/* SYNOPSIS +/* #include <attr_clnt.h> +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include <stdarg.h> + + /* + * Utility library. + */ +#include <attr.h> + + /* + * External interface. + */ +typedef struct ATTR_CLNT ATTR_CLNT; +typedef int (*ATTR_CLNT_PRINT_FN) (VSTREAM *, int, va_list); +typedef int (*ATTR_CLNT_SCAN_FN) (VSTREAM *, int, va_list); +typedef int (*ATTR_CLNT_HANDSHAKE_FN) (VSTREAM *); + +extern ATTR_CLNT *attr_clnt_create(const char *, int, int, int); +extern int attr_clnt_request(ATTR_CLNT *, int,...); +extern void attr_clnt_free(ATTR_CLNT *); +extern void attr_clnt_control(ATTR_CLNT *, int,...); + +#define ATTR_CLNT_CTL_END 0 +#define ATTR_CLNT_CTL_PROTO 1 /* print/scan functions */ +#define ATTR_CLNT_CTL_REQ_LIMIT 2 /* requests per connection */ +#define ATTR_CLNT_CTL_TRY_LIMIT 3 /* attempts per request */ +#define ATTR_CLNT_CTL_TRY_DELAY 4 /* pause between requests */ +#define ATTR_CLNT_CTL_HANDSHAKE 5 /* handshake before first request */ + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/attr_print0.c b/src/util/attr_print0.c new file mode 100644 index 0000000..98c5118 --- /dev/null +++ b/src/util/attr_print0.c @@ -0,0 +1,256 @@ +/*++ +/* NAME +/* attr_print0 3 +/* SUMMARY +/* send attributes over byte stream +/* SYNOPSIS +/* #include <attr.h> +/* +/* int attr_print0(fp, flags, type, name, ..., ATTR_TYPE_END) +/* VSTREAM fp; +/* int flags; +/* int type; +/* char *name; +/* +/* int attr_vprint0(fp, flags, ap) +/* VSTREAM fp; +/* int flags; +/* va_list ap; +/* DESCRIPTION +/* attr_print0() takes zero or more (name, value) simple attributes +/* and converts its input to a byte stream that can be recovered with +/* attr_scan0(). The stream is not flushed. +/* +/* attr_vprint0() provides an alternate interface that is convenient +/* for calling from within variadic functions. +/* +/* Attributes are sent in the requested order as specified with the +/* attr_print0() argument list. This routine satisfies the formatting +/* rules as outlined in attr_scan0(3). +/* +/* Arguments: +/* .IP fp +/* Stream to write the result to. +/* .IP flags +/* The bit-wise OR of zero or more of the following. +/* .RS +/* .IP ATTR_FLAG_MORE +/* After sending the requested attributes, leave the output stream in +/* a state that is usable for more attribute sending operations on +/* the same output attribute list. +/* By default, attr_print0() automatically appends an attribute list +/* terminator when it has sent the last requested attribute. +/* .RE +/* .IP List of attributes followed by terminator: +/* .RS +/* .IP "SEND_ATTR_INT(const char *name, int value)" +/* The arguments are an attribute name and an integer. +/* .IP "SEND_ATTR_LONG(const char *name, long value)" +/* The arguments are an attribute name and a long integer. +/* .IP "SEND_ATTR_STR(const char *name, const char *value)" +/* The arguments are an attribute name and a null-terminated +/* string. +/* .IP "SEND_ATTR_DATA(const char *name, ssize_t len, const void *value)" +/* The arguments are an attribute name, an attribute value +/* length, and an attribute value pointer. +/* .IP "SEND_ATTR_FUNC(ATTR_PRINT_CUSTOM_FN, const void *value)" +/* The arguments are a function pointer and generic data +/* pointer. The caller-specified function returns whatever the +/* specified attribute printing function returns. +/* .IP "SEND_ATTR_HASH(const HTABLE *table)" +/* .IP "SEND_ATTR_NAMEVAL(const NVTABLE *table)" +/* The content of the table is sent as a sequence of string-valued +/* attributes with names equal to the table lookup keys. +/* .IP ATTR_TYPE_END +/* This terminates the attribute list. +/* .RE +/* DIAGNOSTICS +/* The result value is 0 in case of success, VSTREAM_EOF in case +/* of trouble. +/* +/* Panic: interface violation. All system call errors are fatal. +/* SEE ALSO +/* attr_scan0(3) recover attributes from byte stream +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdarg.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstream.h> +#include <htable.h> +#include <attr.h> +#include <base64_code.h> + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* attr_vprint0 - send attribute list to stream */ + +int attr_vprint0(VSTREAM *fp, int flags, va_list ap) +{ + const char *myname = "attr_print0"; + int attr_type; + char *attr_name; + unsigned int_val; + unsigned long long_val; + char *str_val; + HTABLE_INFO **ht_info_list; + HTABLE_INFO **ht; + ssize_t len_val; + static VSTRING *base64_buf; + ATTR_PRINT_CUSTOM_FN print_fn; + void *print_arg; + + /* + * Sanity check. + */ + if (flags & ~ATTR_FLAG_ALL) + msg_panic("%s: bad flags: 0x%x", myname, flags); + + /* + * Iterate over all (type, name, value) triples, and produce output on + * the fly. + */ + while ((attr_type = va_arg(ap, int)) != ATTR_TYPE_END) { + switch (attr_type) { + case ATTR_TYPE_INT: + attr_name = va_arg(ap, char *); + vstream_fwrite(fp, attr_name, strlen(attr_name) + 1); + int_val = va_arg(ap, int); + vstream_fprintf(fp, "%u", (unsigned) int_val); + VSTREAM_PUTC('\0', fp); + if (msg_verbose) + msg_info("send attr %s = %u", attr_name, int_val); + break; + case ATTR_TYPE_LONG: + attr_name = va_arg(ap, char *); + vstream_fwrite(fp, attr_name, strlen(attr_name) + 1); + long_val = va_arg(ap, unsigned long); + vstream_fprintf(fp, "%lu", (unsigned long) long_val); + VSTREAM_PUTC('\0', fp); + if (msg_verbose) + msg_info("send attr %s = %lu", attr_name, long_val); + break; + case ATTR_TYPE_STR: + attr_name = va_arg(ap, char *); + vstream_fwrite(fp, attr_name, strlen(attr_name) + 1); + str_val = va_arg(ap, char *); + vstream_fwrite(fp, str_val, strlen(str_val) + 1); + if (msg_verbose) + msg_info("send attr %s = %s", attr_name, str_val); + break; + case ATTR_TYPE_DATA: + attr_name = va_arg(ap, char *); + vstream_fwrite(fp, attr_name, strlen(attr_name) + 1); + len_val = va_arg(ap, ssize_t); + str_val = va_arg(ap, char *); + if (base64_buf == 0) + base64_buf = vstring_alloc(10); + base64_encode(base64_buf, str_val, len_val); + vstream_fwrite(fp, STR(base64_buf), LEN(base64_buf) + 1); + if (msg_verbose) + msg_info("send attr %s = [data %ld bytes]", + attr_name, (long) len_val); + break; + case ATTR_TYPE_FUNC: + print_fn = va_arg(ap, ATTR_PRINT_CUSTOM_FN); + print_arg = va_arg(ap, void *); + print_fn(attr_print0, fp, flags | ATTR_FLAG_MORE, print_arg); + break; + case ATTR_TYPE_HASH: + vstream_fwrite(fp, ATTR_NAME_OPEN, sizeof(ATTR_NAME_OPEN)); + ht_info_list = htable_list(va_arg(ap, HTABLE *)); + for (ht = ht_info_list; *ht; ht++) { + vstream_fwrite(fp, ht[0]->key, strlen(ht[0]->key) + 1); + vstream_fwrite(fp, ht[0]->value, strlen(ht[0]->value) + 1); + if (msg_verbose) + msg_info("send attr name %s value %s", + ht[0]->key, (char *) ht[0]->value); + } + myfree((void *) ht_info_list); + vstream_fwrite(fp, ATTR_NAME_CLOSE, sizeof(ATTR_NAME_CLOSE)); + break; + default: + msg_panic("%s: unknown type code: %d", myname, attr_type); + } + } + if ((flags & ATTR_FLAG_MORE) == 0) + VSTREAM_PUTC('\0', fp); + return (vstream_ferror(fp)); +} + +int attr_print0(VSTREAM *fp, int flags,...) +{ + va_list ap; + int ret; + + va_start(ap, flags); + ret = attr_vprint0(fp, flags, ap); + va_end(ap); + return (ret); +} + +#ifdef TEST + + /* + * Proof of concept test program. Mirror image of the attr_scan0 test + * program. + */ +#include <msg_vstream.h> + +int main(int unused_argc, char **argv) +{ + HTABLE *table = htable_create(1); + + msg_vstream_init(argv[0], VSTREAM_ERR); + msg_verbose = 1; + htable_enter(table, "foo-name", mystrdup("foo-value")); + htable_enter(table, "bar-name", mystrdup("bar-value")); + attr_print0(VSTREAM_OUT, ATTR_FLAG_NONE, + SEND_ATTR_STR("protocol", "test"), + SEND_ATTR_INT(ATTR_NAME_INT, 4711), + SEND_ATTR_LONG(ATTR_NAME_LONG, 1234L), + SEND_ATTR_STR(ATTR_NAME_STR, "whoopee"), + SEND_ATTR_DATA(ATTR_NAME_DATA, strlen("whoopee"), "whoopee"), + SEND_ATTR_HASH(table), + SEND_ATTR_LONG(ATTR_NAME_LONG, 4321L), + ATTR_TYPE_END); + attr_print0(VSTREAM_OUT, ATTR_FLAG_NONE, + SEND_ATTR_STR("protocol", "test"), + SEND_ATTR_INT(ATTR_NAME_INT, 4711), + SEND_ATTR_LONG(ATTR_NAME_LONG, 1234L), + SEND_ATTR_STR(ATTR_NAME_STR, "whoopee"), + SEND_ATTR_DATA(ATTR_NAME_DATA, strlen("whoopee"), "whoopee"), + ATTR_TYPE_END); + attr_print0(VSTREAM_OUT, ATTR_FLAG_NONE, + SEND_ATTR_STR("protocol", "not-test"), + ATTR_TYPE_END); + if (vstream_fflush(VSTREAM_OUT) != 0) + msg_fatal("write error: %m"); + + htable_free(table, myfree); + return (0); +} + +#endif diff --git a/src/util/attr_print64.c b/src/util/attr_print64.c new file mode 100644 index 0000000..085ba33 --- /dev/null +++ b/src/util/attr_print64.c @@ -0,0 +1,297 @@ +/*++ +/* NAME +/* attr_print64 3 +/* SUMMARY +/* send attributes over byte stream +/* SYNOPSIS +/* #include <attr.h> +/* +/* int attr_print64(fp, flags, type, name, ..., ATTR_TYPE_END) +/* VSTREAM fp; +/* int flags; +/* int type; +/* char *name; +/* +/* int attr_vprint64(fp, flags, ap) +/* VSTREAM fp; +/* int flags; +/* va_list ap; +/* DESCRIPTION +/* attr_print64() takes zero or more (name, value) simple attributes +/* and converts its input to a byte stream that can be recovered with +/* attr_scan64(). The stream is not flushed. +/* +/* attr_vprint64() provides an alternate interface that is convenient +/* for calling from within variadic functions. +/* +/* Attributes are sent in the requested order as specified with the +/* attr_print64() argument list. This routine satisfies the formatting +/* rules as outlined in attr_scan64(3). +/* +/* Arguments: +/* .IP fp +/* Stream to write the result to. +/* .IP flags +/* The bit-wise OR of zero or more of the following. +/* .RS +/* .IP ATTR_FLAG_MORE +/* After sending the requested attributes, leave the output stream in +/* a state that is usable for more attribute sending operations on +/* the same output attribute list. +/* By default, attr_print64() automatically appends an attribute list +/* terminator when it has sent the last requested attribute. +/* .RE +/* .IP List of attributes followed by terminator: +/* .RS +/* .IP "SEND_ATTR_INT(const char *name, int value)" +/* The arguments are an attribute name and an integer. +/* .IP "SEND_ATTR_LONG(const char *name, long value)" +/* The arguments are an attribute name and a long integer. +/* .IP "SEND_ATTR_STR(const char *name, const char *value)" +/* The arguments are an attribute name and a null-terminated +/* string. +/* .IP "SEND_ATTR_DATA(const char *name, ssize_t len, const void *value)" +/* The arguments are an attribute name, an attribute value +/* length, and an attribute value pointer. +/* .IP "SEND_ATTR_FUNC(ATTR_PRINT_CUSTOM_FN, const void *value)" +/* The arguments are a function pointer and generic data +/* pointer. The caller-specified function returns whatever the +/* specified attribute printing function returns. +/* .IP "SEND_ATTR_HASH(const HTABLE *table)" +/* .IP "SEND_ATTR_NAMEVAL(const NVTABLE *table)" +/* The content of the table is sent as a sequence of string-valued +/* attributes with names equal to the table lookup keys. +/* .IP ATTR_TYPE_END +/* This terminates the attribute list. +/* .RE +/* DIAGNOSTICS +/* The result value is 0 in case of success, VSTREAM_EOF in case +/* of trouble. +/* +/* Panic: interface violation. All system call errors are fatal. +/* SEE ALSO +/* attr_scan64(3) recover attributes from byte stream +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdarg.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstream.h> +#include <htable.h> +#include <base64_code.h> +#include <attr.h> + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* attr_print64_str - encode and send attribute information */ + +static void attr_print64_str(VSTREAM *fp, const char *str, ssize_t len) +{ + static VSTRING *base64_buf; + + if (base64_buf == 0) + base64_buf = vstring_alloc(10); + + base64_encode(base64_buf, str, len); + vstream_fputs(STR(base64_buf), fp); +} + +static void attr_print64_num(VSTREAM *fp, unsigned num) +{ + static VSTRING *plain; + + if (plain == 0) + plain = vstring_alloc(10); + + vstring_sprintf(plain, "%u", num); + attr_print64_str(fp, STR(plain), LEN(plain)); +} + +static void attr_print64_long_num(VSTREAM *fp, unsigned long long_num) +{ + static VSTRING *plain; + + if (plain == 0) + plain = vstring_alloc(10); + + vstring_sprintf(plain, "%lu", long_num); + attr_print64_str(fp, STR(plain), LEN(plain)); +} + +/* attr_vprint64 - send attribute list to stream */ + +int attr_vprint64(VSTREAM *fp, int flags, va_list ap) +{ + const char *myname = "attr_print64"; + int attr_type; + char *attr_name; + unsigned int_val; + unsigned long long_val; + char *str_val; + HTABLE_INFO **ht_info_list; + HTABLE_INFO **ht; + ssize_t len_val; + ATTR_PRINT_CUSTOM_FN print_fn; + void *print_arg; + + /* + * Sanity check. + */ + if (flags & ~ATTR_FLAG_ALL) + msg_panic("%s: bad flags: 0x%x", myname, flags); + + /* + * Iterate over all (type, name, value) triples, and produce output on + * the fly. + */ + while ((attr_type = va_arg(ap, int)) != ATTR_TYPE_END) { + switch (attr_type) { + case ATTR_TYPE_INT: + attr_name = va_arg(ap, char *); + attr_print64_str(fp, attr_name, strlen(attr_name)); + int_val = va_arg(ap, int); + VSTREAM_PUTC(':', fp); + attr_print64_num(fp, (unsigned) int_val); + VSTREAM_PUTC('\n', fp); + if (msg_verbose) + msg_info("send attr %s = %u", attr_name, int_val); + break; + case ATTR_TYPE_LONG: + attr_name = va_arg(ap, char *); + attr_print64_str(fp, attr_name, strlen(attr_name)); + long_val = va_arg(ap, long); + VSTREAM_PUTC(':', fp); + attr_print64_long_num(fp, (unsigned long) long_val); + VSTREAM_PUTC('\n', fp); + if (msg_verbose) + msg_info("send attr %s = %lu", attr_name, long_val); + break; + case ATTR_TYPE_STR: + attr_name = va_arg(ap, char *); + attr_print64_str(fp, attr_name, strlen(attr_name)); + str_val = va_arg(ap, char *); + VSTREAM_PUTC(':', fp); + attr_print64_str(fp, str_val, strlen(str_val)); + VSTREAM_PUTC('\n', fp); + if (msg_verbose) + msg_info("send attr %s = %s", attr_name, str_val); + break; + case ATTR_TYPE_DATA: + attr_name = va_arg(ap, char *); + attr_print64_str(fp, attr_name, strlen(attr_name)); + len_val = va_arg(ap, ssize_t); + str_val = va_arg(ap, char *); + VSTREAM_PUTC(':', fp); + attr_print64_str(fp, str_val, len_val); + VSTREAM_PUTC('\n', fp); + if (msg_verbose) + msg_info("send attr %s = [data %ld bytes]", + attr_name, (long) len_val); + break; + case ATTR_TYPE_FUNC: + print_fn = va_arg(ap, ATTR_PRINT_CUSTOM_FN); + print_arg = va_arg(ap, void *); + print_fn(attr_print64, fp, flags | ATTR_FLAG_MORE, print_arg); + break; + case ATTR_TYPE_HASH: + attr_print64_str(fp, ATTR_NAME_OPEN, sizeof(ATTR_NAME_OPEN) - 1); + VSTREAM_PUTC('\n', fp); + ht_info_list = htable_list(va_arg(ap, HTABLE *)); + for (ht = ht_info_list; *ht; ht++) { + attr_print64_str(fp, ht[0]->key, strlen(ht[0]->key)); + VSTREAM_PUTC(':', fp); + attr_print64_str(fp, ht[0]->value, strlen(ht[0]->value)); + VSTREAM_PUTC('\n', fp); + if (msg_verbose) + msg_info("send attr name %s value %s", + ht[0]->key, (char *) ht[0]->value); + } + myfree((void *) ht_info_list); + attr_print64_str(fp, ATTR_NAME_CLOSE, sizeof(ATTR_NAME_CLOSE) - 1); + VSTREAM_PUTC('\n', fp); + break; + default: + msg_panic("%s: unknown type code: %d", myname, attr_type); + } + } + if ((flags & ATTR_FLAG_MORE) == 0) + VSTREAM_PUTC('\n', fp); + return (vstream_ferror(fp)); +} + +int attr_print64(VSTREAM *fp, int flags,...) +{ + va_list ap; + int ret; + + va_start(ap, flags); + ret = attr_vprint64(fp, flags, ap); + va_end(ap); + return (ret); +} + +#ifdef TEST + + /* + * Proof of concept test program. Mirror image of the attr_scan64 test + * program. + */ +#include <msg_vstream.h> + +int main(int unused_argc, char **argv) +{ + HTABLE *table = htable_create(1); + + msg_vstream_init(argv[0], VSTREAM_ERR); + msg_verbose = 1; + htable_enter(table, "foo-name", mystrdup("foo-value")); + htable_enter(table, "bar-name", mystrdup("bar-value")); + attr_print64(VSTREAM_OUT, ATTR_FLAG_NONE, + SEND_ATTR_STR("protocol", "test"), + SEND_ATTR_INT(ATTR_NAME_INT, 4711), + SEND_ATTR_LONG(ATTR_NAME_LONG, 1234L), + SEND_ATTR_STR(ATTR_NAME_STR, "whoopee"), + SEND_ATTR_DATA(ATTR_NAME_DATA, strlen("whoopee"), "whoopee"), + SEND_ATTR_HASH(table), + SEND_ATTR_LONG(ATTR_NAME_LONG, 4321L), + ATTR_TYPE_END); + attr_print64(VSTREAM_OUT, ATTR_FLAG_NONE, + SEND_ATTR_STR("protocol", "test"), + SEND_ATTR_INT(ATTR_NAME_INT, 4711), + SEND_ATTR_LONG(ATTR_NAME_LONG, 1234L), + SEND_ATTR_STR(ATTR_NAME_STR, "whoopee"), + SEND_ATTR_DATA(ATTR_NAME_DATA, strlen("whoopee"), "whoopee"), + ATTR_TYPE_END); + attr_print64(VSTREAM_OUT, ATTR_FLAG_NONE, + SEND_ATTR_STR("protocol", "not-test"), + ATTR_TYPE_END); + if (vstream_fflush(VSTREAM_OUT) != 0) + msg_fatal("write error: %m"); + + htable_free(table, myfree); + return (0); +} + +#endif diff --git a/src/util/attr_print_plain.c b/src/util/attr_print_plain.c new file mode 100644 index 0000000..7d2d02f --- /dev/null +++ b/src/util/attr_print_plain.c @@ -0,0 +1,252 @@ +/*++ +/* NAME +/* attr_print_plain 3 +/* SUMMARY +/* send attributes over byte stream +/* SYNOPSIS +/* #include <attr.h> +/* +/* int attr_print_plain(fp, flags, type, name, ..., ATTR_TYPE_END) +/* VSTREAM fp; +/* int flags; +/* int type; +/* char *name; +/* +/* int attr_vprint_plain(fp, flags, ap) +/* VSTREAM fp; +/* int flags; +/* va_list ap; +/* DESCRIPTION +/* attr_print_plain() takes zero or more (name, value) simple attributes +/* and converts its input to a byte stream that can be recovered with +/* attr_scan_plain(). The stream is not flushed. +/* +/* attr_vprint_plain() provides an alternate interface that is convenient +/* for calling from within variadic functions. +/* +/* Attributes are sent in the requested order as specified with the +/* attr_print_plain() argument list. This routine satisfies the formatting +/* rules as outlined in attr_scan_plain(3). +/* +/* Arguments: +/* .IP fp +/* Stream to write the result to. +/* .IP flags +/* The bit-wise OR of zero or more of the following. +/* .RS +/* .IP ATTR_FLAG_MORE +/* After sending the requested attributes, leave the output stream in +/* a state that is usable for more attribute sending operations on +/* the same output attribute list. +/* By default, attr_print_plain() automatically appends an attribute list +/* terminator when it has sent the last requested attribute. +/* .RE +/* .IP List of attributes followed by terminator: +/* .RS +/* .IP "SEND_ATTR_INT(const char *name, int value)" +/* The arguments are an attribute name and an integer. +/* .IP "SEND_ATTR_LONG(const char *name, long value)" +/* The arguments are an attribute name and a long integer. +/* .IP "SEND_ATTR_STR(const char *name, const char *value)" +/* The arguments are an attribute name and a null-terminated +/* string. +/* .IP "SEND_ATTR_DATA(const char *name, ssize_t len, const void *value)" +/* The arguments are an attribute name, an attribute value +/* length, and an attribute value pointer. +/* .IP "SEND_ATTR_FUNC(ATTR_PRINT_CUSTOM_FN, const void *value)" +/* The arguments are a function pointer and generic data +/* pointer. The caller-specified function returns whatever the +/* specified attribute printing function returns. +/* .IP "SEND_ATTR_HASH(const HTABLE *table)" +/* .IP "SEND_ATTR_NAMEVAL(const NVTABLE *table)" +/* The content of the table is sent as a sequence of string-valued +/* attributes with names equal to the table lookup keys. +/* .IP ATTR_TYPE_END +/* This terminates the attribute list. +/* .RE +/* DIAGNOSTICS +/* The result value is 0 in case of success, VSTREAM_EOF in case +/* of trouble. +/* +/* Panic: interface violation. All system call errors are fatal. +/* SEE ALSO +/* attr_scan_plain(3) recover attributes from byte stream +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdarg.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstream.h> +#include <htable.h> +#include <base64_code.h> +#include <vstring.h> +#include <attr.h> + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* attr_vprint_plain - send attribute list to stream */ + +int attr_vprint_plain(VSTREAM *fp, int flags, va_list ap) +{ + const char *myname = "attr_print_plain"; + int attr_type; + char *attr_name; + unsigned int_val; + unsigned long long_val; + char *str_val; + HTABLE_INFO **ht_info_list; + HTABLE_INFO **ht; + static VSTRING *base64_buf; + ssize_t len_val; + ATTR_PRINT_CUSTOM_FN print_fn; + void *print_arg; + + /* + * Sanity check. + */ + if (flags & ~ATTR_FLAG_ALL) + msg_panic("%s: bad flags: 0x%x", myname, flags); + + /* + * Iterate over all (type, name, value) triples, and produce output on + * the fly. + */ + while ((attr_type = va_arg(ap, int)) != ATTR_TYPE_END) { + switch (attr_type) { + case ATTR_TYPE_INT: + attr_name = va_arg(ap, char *); + int_val = va_arg(ap, int); + vstream_fprintf(fp, "%s=%u\n", attr_name, (unsigned) int_val); + if (msg_verbose) + msg_info("send attr %s = %u", attr_name, (unsigned) int_val); + break; + case ATTR_TYPE_LONG: + attr_name = va_arg(ap, char *); + long_val = va_arg(ap, long); + vstream_fprintf(fp, "%s=%lu\n", attr_name, long_val); + if (msg_verbose) + msg_info("send attr %s = %lu", attr_name, long_val); + break; + case ATTR_TYPE_STR: + attr_name = va_arg(ap, char *); + str_val = va_arg(ap, char *); + vstream_fprintf(fp, "%s=%s\n", attr_name, str_val); + if (msg_verbose) + msg_info("send attr %s = %s", attr_name, str_val); + break; + case ATTR_TYPE_DATA: + attr_name = va_arg(ap, char *); + len_val = va_arg(ap, ssize_t); + str_val = va_arg(ap, char *); + if (base64_buf == 0) + base64_buf = vstring_alloc(10); + base64_encode(base64_buf, str_val, len_val); + vstream_fprintf(fp, "%s=%s\n", attr_name, STR(base64_buf)); + if (msg_verbose) + msg_info("send attr %s = [data %ld bytes]", + attr_name, (long) len_val); + break; + case ATTR_TYPE_FUNC: + print_fn = va_arg(ap, ATTR_PRINT_CUSTOM_FN); + print_arg = va_arg(ap, void *); + print_fn(attr_print_plain, fp, flags | ATTR_FLAG_MORE, print_arg); + break; + case ATTR_TYPE_HASH: + vstream_fwrite(fp, ATTR_NAME_OPEN, sizeof(ATTR_NAME_OPEN)); + VSTREAM_PUTC('\n', fp); + ht_info_list = htable_list(va_arg(ap, HTABLE *)); + for (ht = ht_info_list; *ht; ht++) { + vstream_fprintf(fp, "%s=%s\n", ht[0]->key, (char *) ht[0]->value); + if (msg_verbose) + msg_info("send attr name %s value %s", + ht[0]->key, (char *) ht[0]->value); + } + myfree((void *) ht_info_list); + vstream_fwrite(fp, ATTR_NAME_CLOSE, sizeof(ATTR_NAME_CLOSE)); + VSTREAM_PUTC('\n', fp); + break; + default: + msg_panic("%s: unknown type code: %d", myname, attr_type); + } + } + if ((flags & ATTR_FLAG_MORE) == 0) + VSTREAM_PUTC('\n', fp); + return (vstream_ferror(fp)); +} + +int attr_print_plain(VSTREAM *fp, int flags,...) +{ + va_list ap; + int ret; + + va_start(ap, flags); + ret = attr_vprint_plain(fp, flags, ap); + va_end(ap); + return (ret); +} + +#ifdef TEST + + /* + * Proof of concept test program. Mirror image of the attr_scan_plain test + * program. + */ +#include <msg_vstream.h> + +int main(int unused_argc, char **argv) +{ + HTABLE *table = htable_create(1); + + msg_vstream_init(argv[0], VSTREAM_ERR); + msg_verbose = 1; + htable_enter(table, "foo-name", mystrdup("foo-value")); + htable_enter(table, "bar-name", mystrdup("bar-value")); + attr_print_plain(VSTREAM_OUT, ATTR_FLAG_NONE, + SEND_ATTR_STR("protocol", "test"), + SEND_ATTR_INT(ATTR_NAME_INT, 4711), + SEND_ATTR_LONG(ATTR_NAME_LONG, 1234L), + SEND_ATTR_STR(ATTR_NAME_STR, "whoopee"), + SEND_ATTR_DATA(ATTR_NAME_DATA, strlen("whoopee"), "whoopee"), + SEND_ATTR_HASH(table), + SEND_ATTR_LONG(ATTR_NAME_LONG, 4321L), + ATTR_TYPE_END); + attr_print_plain(VSTREAM_OUT, ATTR_FLAG_NONE, + SEND_ATTR_STR("protocol", "test"), + SEND_ATTR_INT(ATTR_NAME_INT, 4711), + SEND_ATTR_LONG(ATTR_NAME_LONG, 1234L), + SEND_ATTR_STR(ATTR_NAME_STR, "whoopee"), + SEND_ATTR_DATA(ATTR_NAME_DATA, strlen("whoopee"), "whoopee"), + ATTR_TYPE_END); + attr_print_plain(VSTREAM_OUT, ATTR_FLAG_NONE, + SEND_ATTR_STR("protocol", "not-test"), + ATTR_TYPE_END); + if (vstream_fflush(VSTREAM_OUT) != 0) + msg_fatal("write error: %m"); + + htable_free(table, myfree); + return (0); +} + +#endif diff --git a/src/util/attr_scan.ref b/src/util/attr_scan.ref new file mode 100644 index 0000000..cd06a27 --- /dev/null +++ b/src/util/attr_scan.ref @@ -0,0 +1,36 @@ +./attr_print: send attr number = 4711 +./attr_print: send attr string = whoopee +./attr_print: send attr name foo-name value foo-value +./attr_print: send attr name bar-name value bar-value +./attr_print: send attr number = 4711 +./attr_print: send attr string = whoopee +./attr_scan: unknown_stream: wanted attribute: number +./attr_scan: input attribute name: number +./attr_scan: input attribute value: 4711 +./attr_scan: unknown_stream: wanted attribute: string +./attr_scan: input attribute name: string +./attr_scan: input attribute value: whoopee +./attr_scan: unknown_stream: wanted attribute: (any attribute name or list terminator) +./attr_scan: input attribute name: foo-name +./attr_scan: input attribute value: foo-value +./attr_scan: unknown_stream: wanted attribute: (any attribute name or list terminator) +./attr_scan: input attribute name: bar-name +./attr_scan: input attribute value: bar-value +./attr_scan: unknown_stream: wanted attribute: (any attribute name or list terminator) +./attr_scan: input attribute name: (end) +./attr_scan: unknown_stream: wanted attribute: number +./attr_scan: input attribute name: number +./attr_scan: input attribute value: 4711 +./attr_scan: unknown_stream: wanted attribute: string +./attr_scan: input attribute name: string +./attr_scan: input attribute value: whoopee +./attr_scan: unknown_stream: wanted attribute: (list terminator) +./attr_scan: input attribute name: (end) +number 4711 +string whoopee +(hash) foo-name foo-value +(hash) bar-name bar-value +number 4711 +string whoopee +(hash) foo-name foo-value +(hash) bar-name bar-value diff --git a/src/util/attr_scan0.c b/src/util/attr_scan0.c new file mode 100644 index 0000000..13aa125 --- /dev/null +++ b/src/util/attr_scan0.c @@ -0,0 +1,596 @@ +/*++ +/* NAME +/* attr_scan0 3 +/* SUMMARY +/* recover attributes from byte stream +/* SYNOPSIS +/* #include <attr.h> +/* +/* int attr_scan0(fp, flags, type, name, ..., ATTR_TYPE_END) +/* VSTREAM *fp; +/* int flags; +/* int type; +/* char *name; +/* +/* int attr_vscan0(fp, flags, ap) +/* VSTREAM *fp; +/* int flags; +/* va_list ap; +/* +/* int attr_scan_more0(fp) +/* VSTREAM *fp; +/* DESCRIPTION +/* attr_scan0() takes zero or more (name, value) request attributes +/* and recovers the attribute values from the byte stream that was +/* possibly generated by attr_print0(). +/* +/* attr_vscan0() provides an alternative interface that is convenient +/* for calling from within a variadic function. +/* +/* attr_scan_more0() returns 0 when a terminator is found (and +/* consumes that terminator), returns 1 when more input is +/* expected (without consuming input), and returns -1 otherwise +/* (error). +/* +/* The input stream is formatted as follows, where (item)* stands +/* for zero or more instances of the specified item, and where +/* (item1 | item2) stands for choice: +/* +/* .in +5 +/* attr-list :== (simple-attr | multi-attr)* null +/* .br +/* multi-attr :== "{" null simple-attr* "}" null +/* .br +/* simple-attr :== attr-name null attr-value null +/* .br +/* attr-name :== any string not containing null +/* .br +/* attr-value :== any string not containing null +/* .br +/* null :== the ASCII null character +/* .in +/* +/* All attribute names and attribute values are sent as null terminated +/* strings. Each string must be no longer than 4*var_line_limit +/* characters including the terminator. +/* These formatting rules favor implementations in C. +/* +/* Normally, attributes must be received in the sequence as specified with +/* the attr_scan0() argument list. The input stream may contain additional +/* attributes at any point in the input stream, including additional +/* instances of requested attributes. +/* +/* Additional input attributes or input attribute instances are silently +/* skipped over, unless the ATTR_FLAG_EXTRA processing flag is specified +/* (see below). This allows for some flexibility in the evolution of +/* protocols while still providing the option of being strict where +/* this is desirable. +/* +/* Arguments: +/* .IP fp +/* Stream to recover the input attributes from. +/* .IP flags +/* The bit-wise OR of zero or more of the following. +/* .RS +/* .IP ATTR_FLAG_MISSING +/* Log a warning when the input attribute list terminates before all +/* requested attributes are recovered. It is always an error when the +/* input stream ends without the newline attribute list terminator. +/* .IP ATTR_FLAG_EXTRA +/* Log a warning and stop attribute recovery when the input stream +/* contains an attribute that was not requested. This includes the +/* case of additional instances of a requested attribute. +/* .IP ATTR_FLAG_MORE +/* After recovering the requested attributes, leave the input stream +/* in a state that is usable for more attr_scan0() operations from the +/* same input attribute list. +/* By default, attr_scan0() skips forward past the input attribute list +/* terminator. +/* .IP ATTR_FLAG_PRINTABLE +/* Santize received string values with printable(_, '?'). +/* .IP ATTR_FLAG_STRICT +/* For convenience, this value combines both ATTR_FLAG_MISSING and +/* ATTR_FLAG_EXTRA. +/* .IP ATTR_FLAG_NONE +/* For convenience, this value requests none of the above. +/* .RE +/* .IP List of attributes followed by terminator: +/* .RS +/* .IP "RECV_ATTR_INT(const char *name, int *ptr)" +/* This argument is followed by an attribute name and an integer pointer. +/* .IP "RECV_ATTR_LONG(const char *name, long *ptr)" +/* This argument is followed by an attribute name and a long pointer. +/* .IP "RECV_ATTR_STR(const char *name, VSTRING *vp)" +/* This argument is followed by an attribute name and a VSTRING pointer. +/* .IP "RECV_ATTR_STREQ(const char *name, const char *value)" +/* The name and value must match what the client sends. +/* This attribute does not increment the result value. +/* .IP "RECV_ATTR_DATA(const char *name, VSTRING *vp)" +/* This argument is followed by an attribute name and a VSTRING pointer. +/* .IP "RECV_ATTR_FUNC(ATTR_SCAN_CUSTOM_FN, void *data)" +/* This argument is followed by a function pointer and a generic data +/* pointer. The caller-specified function returns < 0 in case of +/* error. +/* .IP "RECV_ATTR_HASH(HTABLE *table)" +/* .IP "RECV_ATTR_NAMEVAL(NVTABLE *table)" +/* Receive a sequence of attribute names and string values. +/* There can be no more than 1024 attributes in a hash table. +/* .sp +/* The attribute string values are stored in the hash table under +/* keys equal to the attribute name (obtained from the input stream). +/* Values from the input stream are added to the hash table. Existing +/* hash table entries are not replaced. +/* .sp +/* Note: the SEND_ATTR_HASH or SEND_ATTR_NAMEVAL requests +/* format their payload as a multi-attr sequence (see syntax +/* above). When the receiver's input does not start with a +/* multi-attr delimiter (i.e. the sender did not request +/* SEND_ATTR_HASH or SEND_ATTR_NAMEVAL), the receiver will +/* store all attribute names and values up to the attribute +/* list terminator. In terms of code, this means that the +/* RECV_ATTR_HASH or RECV_ATTR_NAMEVAL request must be followed +/* by ATTR_TYPE_END. +/* .IP ATTR_TYPE_END +/* This argument terminates the requested attribute list. +/* .RE +/* BUGS +/* RECV_ATTR_HASH (RECV_ATTR_NAMEVAL) accepts attributes with arbitrary +/* names from possibly untrusted sources. +/* This is unsafe, unless the resulting table is queried only with +/* known to be good attribute names. +/* DIAGNOSTICS +/* attr_scan0() and attr_vscan0() return -1 when malformed input is +/* detected (string too long, incomplete line, missing end marker). +/* Otherwise, the result value is the number of attributes that were +/* successfully recovered from the input stream (a hash table counts +/* as the number of entries stored into the table). +/* +/* Panic: interface violation. All system call errors are fatal. +/* SEE ALSO +/* attr_print0(3) send attributes over byte stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdarg.h> +#include <string.h> +#include <stdio.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstream.h> +#include <vstring.h> +#include <vstring_vstream.h> +#include <htable.h> +#include <base64_code.h> +#include <stringops.h> +#include <attr.h> + +/* Application specific. */ + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* attr_scan0_string - pull a string from the input stream */ + +static int attr_scan0_string(VSTREAM *fp, VSTRING *plain_buf, const char *context) +{ + int ch; + + if ((ch = vstring_get_null(plain_buf, fp)) == VSTREAM_EOF) { + msg_warn("%s on %s while reading %s", + vstream_ftimeout(fp) ? "timeout" : "premature end-of-input", + VSTREAM_PATH(fp), context); + return (-1); + } + if (ch != 0) { + msg_warn("unexpected end-of-input from %s while reading %s", + VSTREAM_PATH(fp), context); + return (-1); + } + if (msg_verbose) + msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)"); + return (ch); +} + +/* attr_scan0_data - pull a data blob from the input stream */ + +static int attr_scan0_data(VSTREAM *fp, VSTRING *str_buf, + const char *context) +{ + static VSTRING *base64_buf = 0; + int ch; + + if (base64_buf == 0) + base64_buf = vstring_alloc(10); + if ((ch = attr_scan0_string(fp, base64_buf, context)) < 0) + return (-1); + if (base64_decode(str_buf, STR(base64_buf), LEN(base64_buf)) == 0) { + msg_warn("malformed base64 data from %s while reading %s: %.100s", + VSTREAM_PATH(fp), context, STR(base64_buf)); + return (-1); + } + return (ch); +} + +/* attr_scan0_number - pull a number from the input stream */ + +static int attr_scan0_number(VSTREAM *fp, unsigned *ptr, VSTRING *str_buf, + const char *context) +{ + char junk = 0; + int ch; + + if ((ch = attr_scan0_string(fp, str_buf, context)) < 0) + return (-1); + if (sscanf(STR(str_buf), "%u%c", ptr, &junk) != 1 || junk != 0) { + msg_warn("malformed numerical data from %s while reading %s: %.100s", + VSTREAM_PATH(fp), context, STR(str_buf)); + return (-1); + } + return (ch); +} + +/* attr_scan0_long_number - pull a number from the input stream */ + +static int attr_scan0_long_number(VSTREAM *fp, unsigned long *ptr, + VSTRING *str_buf, + const char *context) +{ + char junk = 0; + int ch; + + if ((ch = attr_scan0_string(fp, str_buf, context)) < 0) + return (-1); + if (sscanf(STR(str_buf), "%lu%c", ptr, &junk) != 1 || junk != 0) { + msg_warn("malformed numerical data from %s while reading %s: %.100s", + VSTREAM_PATH(fp), context, STR(str_buf)); + return (-1); + } + return (ch); +} + +/* attr_vscan0 - receive attribute list from stream */ + +int attr_vscan0(VSTREAM *fp, int flags, va_list ap) +{ + const char *myname = "attr_scan0"; + static VSTRING *str_buf = 0; + static VSTRING *name_buf = 0; + int wanted_type = -1; + char *wanted_name; + unsigned int *number; + unsigned long *long_number; + VSTRING *string; + HTABLE *hash_table; + int ch; + int conversions; + ATTR_SCAN_CUSTOM_FN scan_fn; + void *scan_arg; + const char *expect_val; + + /* + * Sanity check. + */ + if (flags & ~ATTR_FLAG_ALL) + msg_panic("%s: bad flags: 0x%x", myname, flags); + + /* + * EOF check. + */ + if ((ch = VSTREAM_GETC(fp)) == VSTREAM_EOF) + return (0); + vstream_ungetc(fp, ch); + + /* + * Initialize. + */ + if (str_buf == 0) { + str_buf = vstring_alloc(10); + name_buf = vstring_alloc(10); + } + + /* + * Iterate over all (type, name, value) triples. + */ + for (conversions = 0; /* void */ ; conversions++) { + + /* + * Determine the next attribute type and attribute name on the + * caller's wish list. + * + * If we're reading into a hash table, we already know that the + * attribute value is string-valued, and we get the attribute name + * from the input stream instead. This is secure only when the + * resulting table is queried with known to be good attribute names. + */ + if (wanted_type != ATTR_TYPE_HASH + && wanted_type != ATTR_TYPE_CLOSE) { + wanted_type = va_arg(ap, int); + if (wanted_type == ATTR_TYPE_END) { + if ((flags & ATTR_FLAG_MORE) != 0) + return (conversions); + wanted_name = "(list terminator)"; + } else if (wanted_type == ATTR_TYPE_HASH) { + wanted_name = "(any attribute name or list terminator)"; + hash_table = va_arg(ap, HTABLE *); + } else if (wanted_type != ATTR_TYPE_FUNC) { + wanted_name = va_arg(ap, char *); + } + } + + /* + * Locate the next attribute of interest in the input stream. + */ + while (wanted_type != ATTR_TYPE_FUNC) { + + /* + * Get the name of the next attribute. Hitting EOF is always bad. + * Hitting the end-of-input early is OK if the caller is prepared + * to deal with missing inputs. + */ + if (msg_verbose) + msg_info("%s: wanted attribute: %s", + VSTREAM_PATH(fp), wanted_name); + if ((ch = attr_scan0_string(fp, name_buf, + "input attribute name")) == VSTREAM_EOF) + return (-1); + if (LEN(name_buf) == 0) { + if (wanted_type == ATTR_TYPE_END + || wanted_type == ATTR_TYPE_HASH) + return (conversions); + if ((flags & ATTR_FLAG_MISSING) != 0) + msg_warn("missing attribute %s in input from %s", + wanted_name, VSTREAM_PATH(fp)); + return (conversions); + } + + /* + * See if the caller asks for this attribute. + */ + if (wanted_type == ATTR_TYPE_HASH + && strcmp(ATTR_NAME_OPEN, STR(name_buf)) == 0) { + wanted_type = ATTR_TYPE_CLOSE; + wanted_name = "(any attribute name or '}')"; + /* Advance in the input stream. */ + continue; + } else if (wanted_type == ATTR_TYPE_CLOSE + && strcmp(ATTR_NAME_CLOSE, STR(name_buf)) == 0) { + /* Advance in the argument list. */ + wanted_type = -1; + break; + } + if (wanted_type == ATTR_TYPE_HASH + || wanted_type == ATTR_TYPE_CLOSE + || (wanted_type != ATTR_TYPE_END + && strcmp(wanted_name, STR(name_buf)) == 0)) + break; + if ((flags & ATTR_FLAG_EXTRA) != 0) { + msg_warn("unexpected attribute %s from %s (expecting: %s)", + STR(name_buf), VSTREAM_PATH(fp), wanted_name); + return (conversions); + } + + /* + * Skip over this attribute. The caller does not ask for it. + */ + (void) attr_scan0_string(fp, str_buf, "input attribute value"); + } + + /* + * Do the requested conversion. + */ + switch (wanted_type) { + case ATTR_TYPE_INT: + number = va_arg(ap, unsigned int *); + if ((ch = attr_scan0_number(fp, number, str_buf, + "input attribute value")) < 0) + return (-1); + break; + case ATTR_TYPE_LONG: + long_number = va_arg(ap, unsigned long *); + if ((ch = attr_scan0_long_number(fp, long_number, str_buf, + "input attribute value")) < 0) + return (-1); + break; + case ATTR_TYPE_STR: + string = va_arg(ap, VSTRING *); + if ((ch = attr_scan0_string(fp, string, + "input attribute value")) < 0) + return (-1); + if (flags & ATTR_FLAG_PRINTABLE) + (void) printable(STR(string), '?'); + break; + case ATTR_TYPE_DATA: + string = va_arg(ap, VSTRING *); + if ((ch = attr_scan0_data(fp, string, + "input attribute value")) < 0) + return (-1); + break; + case ATTR_TYPE_FUNC: + scan_fn = va_arg(ap, ATTR_SCAN_CUSTOM_FN); + scan_arg = va_arg(ap, void *); + if (scan_fn(attr_scan0, fp, flags | ATTR_FLAG_MORE, scan_arg) < 0) + return (-1); + break; + case ATTR_TYPE_STREQ: + expect_val = va_arg(ap, const char *); + if ((ch = attr_scan0_string(fp, str_buf, + "input attribute value")) < 0) + return (-1); + if (strcmp(expect_val, STR(str_buf)) != 0) { + msg_warn("unexpected %s %s from %s (expected: %s)", + STR(name_buf), STR(str_buf), VSTREAM_PATH(fp), + expect_val); + return (-1); + } + conversions -= 1; + break; + case ATTR_TYPE_HASH: + case ATTR_TYPE_CLOSE: + if ((ch = attr_scan0_string(fp, str_buf, + "input attribute value")) < 0) + return (-1); + if (flags & ATTR_FLAG_PRINTABLE) { + (void) printable(STR(name_buf), '?'); + (void) printable(STR(str_buf), '?'); + } + if (htable_locate(hash_table, STR(name_buf)) != 0) { + if ((flags & ATTR_FLAG_EXTRA) != 0) { + msg_warn("duplicate attribute %s in input from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (conversions); + } + } else if (hash_table->used >= ATTR_HASH_LIMIT) { + msg_warn("attribute count exceeds limit %d in input from %s", + ATTR_HASH_LIMIT, VSTREAM_PATH(fp)); + return (conversions); + } else { + htable_enter(hash_table, STR(name_buf), + mystrdup(STR(str_buf))); + } + break; + case -1: + conversions -= 1; + break; + default: + msg_panic("%s: unknown type code: %d", myname, wanted_type); + } + } +} + +/* attr_scan0 - read attribute list from stream */ + +int attr_scan0(VSTREAM *fp, int flags,...) +{ + va_list ap; + int ret; + + va_start(ap, flags); + ret = attr_vscan0(fp, flags, ap); + va_end(ap); + return (ret); +} + +/* attr_scan_more0 - look ahead for more */ + +int attr_scan_more0(VSTREAM *fp) +{ + int ch; + + switch (ch = VSTREAM_GETC(fp)) { + case 0: + if (msg_verbose) + msg_info("%s: terminator (consumed)", VSTREAM_PATH(fp)); + return (0); + case VSTREAM_EOF: + if (msg_verbose) + msg_info("%s: EOF", VSTREAM_PATH(fp)); + return (-1); + default: + if (msg_verbose) + msg_info("%s: non-terminator '%c' (lookahead)", + VSTREAM_PATH(fp), ch); + (void) vstream_ungetc(fp, ch); + return (1); + } +} + +#ifdef TEST + + /* + * Proof of concept test program. Mirror image of the attr_scan0 test + * program. + */ +#include <msg_vstream.h> + +int var_line_limit = 2048; + +int main(int unused_argc, char **used_argv) +{ + VSTRING *data_val = vstring_alloc(1); + VSTRING *str_val = vstring_alloc(1); + HTABLE *table = htable_create(1); + HTABLE_INFO **ht_info_list; + HTABLE_INFO **ht; + int int_val; + long long_val; + long long_val2; + int ret; + + msg_verbose = 1; + msg_vstream_init(used_argv[0], VSTREAM_ERR); + if ((ret = attr_scan0(VSTREAM_IN, + ATTR_FLAG_STRICT, + RECV_ATTR_STREQ("protocol", "test"), + RECV_ATTR_INT(ATTR_NAME_INT, &int_val), + RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val), + RECV_ATTR_STR(ATTR_NAME_STR, str_val), + RECV_ATTR_DATA(ATTR_NAME_DATA, data_val), + RECV_ATTR_HASH(table), + RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val2), + ATTR_TYPE_END)) > 4) { + vstream_printf("%s %d\n", ATTR_NAME_INT, int_val); + vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val); + vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val)); + vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(str_val)); + ht_info_list = htable_list(table); + for (ht = ht_info_list; *ht; ht++) + vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value); + myfree((void *) ht_info_list); + vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val2); + } else { + vstream_printf("return: %d\n", ret); + } + if ((ret = attr_scan0(VSTREAM_IN, + ATTR_FLAG_STRICT, + RECV_ATTR_STREQ("protocol", "test"), + RECV_ATTR_INT(ATTR_NAME_INT, &int_val), + RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val), + RECV_ATTR_STR(ATTR_NAME_STR, str_val), + RECV_ATTR_DATA(ATTR_NAME_DATA, data_val), + ATTR_TYPE_END)) == 4) { + vstream_printf("%s %d\n", ATTR_NAME_INT, int_val); + vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val); + vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val)); + vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val)); + ht_info_list = htable_list(table); + for (ht = ht_info_list; *ht; ht++) + vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value); + myfree((void *) ht_info_list); + } else { + vstream_printf("return: %d\n", ret); + } + if ((ret = attr_scan0(VSTREAM_IN, + ATTR_FLAG_STRICT, + RECV_ATTR_STREQ("protocol", "test"), + ATTR_TYPE_END)) != 0) + vstream_printf("return: %d\n", ret); + if (vstream_fflush(VSTREAM_OUT) != 0) + msg_fatal("write error: %m"); + + vstring_free(data_val); + vstring_free(str_val); + htable_free(table, myfree); + + return (0); +} + +#endif diff --git a/src/util/attr_scan0.ref b/src/util/attr_scan0.ref new file mode 100644 index 0000000..9055d79 --- /dev/null +++ b/src/util/attr_scan0.ref @@ -0,0 +1,79 @@ +./attr_print0: send attr protocol = test +./attr_print0: send attr number = 4711 +./attr_print0: send attr long_number = 1234 +./attr_print0: send attr string = whoopee +./attr_print0: send attr data = [data 7 bytes] +./attr_print0: send attr name bar-name value bar-value +./attr_print0: send attr name foo-name value foo-value +./attr_print0: send attr long_number = 4321 +./attr_print0: send attr protocol = test +./attr_print0: send attr number = 4711 +./attr_print0: send attr long_number = 1234 +./attr_print0: send attr string = whoopee +./attr_print0: send attr data = [data 7 bytes] +./attr_print0: send attr protocol = not-test +./attr_scan0: unknown_stream: wanted attribute: protocol +./attr_scan0: input attribute name: protocol +./attr_scan0: input attribute value: test +./attr_scan0: unknown_stream: wanted attribute: number +./attr_scan0: input attribute name: number +./attr_scan0: input attribute value: 4711 +./attr_scan0: unknown_stream: wanted attribute: long_number +./attr_scan0: input attribute name: long_number +./attr_scan0: input attribute value: 1234 +./attr_scan0: unknown_stream: wanted attribute: string +./attr_scan0: input attribute name: string +./attr_scan0: input attribute value: whoopee +./attr_scan0: unknown_stream: wanted attribute: data +./attr_scan0: input attribute name: data +./attr_scan0: input attribute value: d2hvb3BlZQ== +./attr_scan0: unknown_stream: wanted attribute: (any attribute name or list terminator) +./attr_scan0: input attribute name: { +./attr_scan0: unknown_stream: wanted attribute: (any attribute name or '}') +./attr_scan0: input attribute name: bar-name +./attr_scan0: input attribute value: bar-value +./attr_scan0: unknown_stream: wanted attribute: (any attribute name or '}') +./attr_scan0: input attribute name: foo-name +./attr_scan0: input attribute value: foo-value +./attr_scan0: unknown_stream: wanted attribute: (any attribute name or '}') +./attr_scan0: input attribute name: } +./attr_scan0: unknown_stream: wanted attribute: long_number +./attr_scan0: input attribute name: long_number +./attr_scan0: input attribute value: 4321 +./attr_scan0: unknown_stream: wanted attribute: (list terminator) +./attr_scan0: input attribute name: (end) +./attr_scan0: unknown_stream: wanted attribute: protocol +./attr_scan0: input attribute name: protocol +./attr_scan0: input attribute value: test +./attr_scan0: unknown_stream: wanted attribute: number +./attr_scan0: input attribute name: number +./attr_scan0: input attribute value: 4711 +./attr_scan0: unknown_stream: wanted attribute: long_number +./attr_scan0: input attribute name: long_number +./attr_scan0: input attribute value: 1234 +./attr_scan0: unknown_stream: wanted attribute: string +./attr_scan0: input attribute name: string +./attr_scan0: input attribute value: whoopee +./attr_scan0: unknown_stream: wanted attribute: data +./attr_scan0: input attribute name: data +./attr_scan0: input attribute value: d2hvb3BlZQ== +./attr_scan0: unknown_stream: wanted attribute: (list terminator) +./attr_scan0: input attribute name: (end) +./attr_scan0: unknown_stream: wanted attribute: protocol +./attr_scan0: input attribute name: protocol +./attr_scan0: input attribute value: not-test +./attr_scan0: warning: unexpected protocol not-test from unknown_stream (expected: test) +number 4711 +long_number 1234 +string whoopee +data whoopee +(hash) bar-name bar-value +(hash) foo-name foo-value +long_number 4321 +number 4711 +long_number 1234 +string whoopee +data whoopee +(hash) bar-name bar-value +(hash) foo-name foo-value +return: -1 diff --git a/src/util/attr_scan64.c b/src/util/attr_scan64.c new file mode 100644 index 0000000..0d9b114 --- /dev/null +++ b/src/util/attr_scan64.c @@ -0,0 +1,665 @@ +/*++ +/* NAME +/* attr_scan64 3 +/* SUMMARY +/* recover attributes from byte stream +/* SYNOPSIS +/* #include <attr.h> +/* +/* int attr_scan64(fp, flags, type, name, ..., ATTR_TYPE_END) +/* VSTREAM *fp; +/* int flags; +/* int type; +/* char *name; +/* +/* int attr_vscan64(fp, flags, ap) +/* VSTREAM *fp; +/* int flags; +/* va_list ap; +/* +/* int attr_scan_more64(fp) +/* VSTREAM *fp; +/* DESCRIPTION +/* attr_scan64() takes zero or more (name, value) request attributes +/* and recovers the attribute values from the byte stream that was +/* possibly generated by attr_print64(). +/* +/* attr_vscan64() provides an alternative interface that is convenient +/* for calling from within a variadic function. +/* +/* attr_scan_more64() returns 0 when a terminator is found +/* (and consumes that terminator), returns 1 when more input +/* is expected (without consuming input), and returns -1 +/* otherwise (error). +/* +/* The input stream is formatted as follows, where (item)* stands +/* for zero or more instances of the specified item, and where +/* (item1 | item2) stands for choice: +/* +/* .in +5 +/* attr-list :== (simple-attr | multi-attr)* newline +/* .br +/* multi-attr :== "{" newline simple-attr* "}" newline +/* .br +/* simple-attr :== attr-name colon attr-value newline +/* .br +/* attr-name :== any base64 encoded string +/* .br +/* attr-value :== any base64 encoded string +/* .br +/* colon :== the ASCII colon character +/* .br +/* newline :== the ASCII newline character +/* .in +/* +/* All attribute names and attribute values are sent as base64-encoded +/* strings. Each base64 encoding must be no longer than 4*var_line_limit +/* characters. The formatting rules aim to make implementations in PERL +/* and other languages easy. +/* +/* Normally, attributes must be received in the sequence as specified with +/* the attr_scan64() argument list. The input stream may contain additional +/* attributes at any point in the input stream, including additional +/* instances of requested attributes. +/* +/* Additional input attributes or input attribute instances are silently +/* skipped over, unless the ATTR_FLAG_EXTRA processing flag is specified +/* (see below). This allows for some flexibility in the evolution of +/* protocols while still providing the option of being strict where +/* this is desirable. +/* +/* Arguments: +/* .IP fp +/* Stream to recover the input attributes from. +/* .IP flags +/* The bit-wise OR of zero or more of the following. +/* .RS +/* .IP ATTR_FLAG_MISSING +/* Log a warning when the input attribute list terminates before all +/* requested attributes are recovered. It is always an error when the +/* input stream ends without the newline attribute list terminator. +/* .IP ATTR_FLAG_EXTRA +/* Log a warning and stop attribute recovery when the input stream +/* contains an attribute that was not requested. This includes the +/* case of additional instances of a requested attribute. +/* .IP ATTR_FLAG_MORE +/* After recovering the requested attributes, leave the input stream +/* in a state that is usable for more attr_scan64() operations from the +/* same input attribute list. +/* By default, attr_scan64() skips forward past the input attribute list +/* terminator. +/* .IP ATTR_FLAG_PRINTABLE +/* Santize received string values with printable(_, '?'). +/* .IP ATTR_FLAG_STRICT +/* For convenience, this value combines both ATTR_FLAG_MISSING and +/* ATTR_FLAG_EXTRA. +/* .IP ATTR_FLAG_NONE +/* For convenience, this value requests none of the above. +/* .RE +/* .IP List of attributes followed by terminator: +/* .RS +/* .IP "RECV_ATTR_INT(const char *name, int *ptr)" +/* This argument is followed by an attribute name and an integer pointer. +/* .IP "RECV_ATTR_LONG(const char *name, long *ptr)" +/* This argument is followed by an attribute name and a long pointer. +/* .IP "RECV_ATTR_STR(const char *name, VSTRING *vp)" +/* This argument is followed by an attribute name and a VSTRING pointer. +/* .IP "RECV_ATTR_STREQ(const char *name, const char *value)" +/* The name and value must match what the client sends. +/* This attribute does not increment the result value. +/* .IP "RECV_ATTR_DATA(const char *name, VSTRING *vp)" +/* This argument is followed by an attribute name and a VSTRING pointer. +/* .IP "RECV_ATTR_FUNC(ATTR_SCAN_CUSTOM_FN, void *data)" +/* This argument is followed by a function pointer and a generic data +/* pointer. The caller-specified function returns < 0 in case of +/* error. +/* .IP "RECV_ATTR_HASH(HTABLE *table)" +/* .IP "RECV_ATTR_NAMEVAL(NVTABLE *table)" +/* Receive a sequence of attribute names and string values. +/* There can be no more than 1024 attributes in a hash table. +/* .sp +/* The attribute string values are stored in the hash table under +/* keys equal to the attribute name (obtained from the input stream). +/* Values from the input stream are added to the hash table. Existing +/* hash table entries are not replaced. +/* .sp +/* Note: the SEND_ATTR_HASH or SEND_ATTR_NAMEVAL requests +/* format their payload as a multi-attr sequence (see syntax +/* above). When the receiver's input does not start with a +/* multi-attr delimiter (i.e. the sender did not request +/* SEND_ATTR_HASH or SEND_ATTR_NAMEVAL), the receiver will +/* store all attribute names and values up to the attribute +/* list terminator. In terms of code, this means that the +/* RECV_ATTR_HASH or RECV_ATTR_NAMEVAL request must be followed +/* by ATTR_TYPE_END. +/* .IP ATTR_TYPE_END +/* This argument terminates the requested attribute list. +/* .RE +/* BUGS +/* RECV_ATTR_HASH (RECV_ATTR_NAMEVAL) accepts attributes with arbitrary +/* names from possibly untrusted sources. +/* This is unsafe, unless the resulting table is queried only with +/* known to be good attribute names. +/* DIAGNOSTICS +/* attr_scan64() and attr_vscan64() return -1 when malformed input is +/* detected (string too long, incomplete line, missing end marker). +/* Otherwise, the result value is the number of attributes that were +/* successfully recovered from the input stream (a hash table counts +/* as the number of entries stored into the table). +/* +/* Panic: interface violation. All system call errors are fatal. +/* SEE ALSO +/* attr_print64(3) send attributes over byte stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdarg.h> +#include <string.h> +#include <stdio.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstream.h> +#include <vstring.h> +#include <htable.h> +#include <base64_code.h> +#include <stringops.h> +#include <attr.h> + +/* Application specific. */ + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* attr_scan64_string - pull a string from the input stream */ + +static int attr_scan64_string(VSTREAM *fp, VSTRING *plain_buf, const char *context) +{ + static VSTRING *base64_buf = 0; + +#if 0 + extern int var_line_limit; /* XXX */ + int limit = var_line_limit * 4; + +#endif + int ch; + + if (base64_buf == 0) + base64_buf = vstring_alloc(10); + + VSTRING_RESET(base64_buf); + while ((ch = VSTREAM_GETC(fp)) != ':' && ch != '\n') { + if (ch == VSTREAM_EOF) { + msg_warn("%s on %s while reading %s", + vstream_ftimeout(fp) ? "timeout" : "premature end-of-input", + VSTREAM_PATH(fp), context); + return (-1); + } + VSTRING_ADDCH(base64_buf, ch); +#if 0 + if (LEN(base64_buf) > limit) { + msg_warn("string length > %d characters from %s while reading %s", + limit, VSTREAM_PATH(fp), context); + return (-1); + } +#endif + } + VSTRING_TERMINATE(base64_buf); + if (base64_decode(plain_buf, STR(base64_buf), LEN(base64_buf)) == 0) { + msg_warn("malformed base64 data from %s: %.100s", + VSTREAM_PATH(fp), STR(base64_buf)); + return (-1); + } + if (msg_verbose) + msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)"); + return (ch); +} + +/* attr_scan64_number - pull a number from the input stream */ + +static int attr_scan64_number(VSTREAM *fp, unsigned *ptr, VSTRING *str_buf, + const char *context) +{ + char junk = 0; + int ch; + + if ((ch = attr_scan64_string(fp, str_buf, context)) < 0) + return (-1); + if (sscanf(STR(str_buf), "%u%c", ptr, &junk) != 1 || junk != 0) { + msg_warn("malformed numerical data from %s while reading %s: %.100s", + VSTREAM_PATH(fp), context, STR(str_buf)); + return (-1); + } + return (ch); +} + +/* attr_scan64_long_number - pull a number from the input stream */ + +static int attr_scan64_long_number(VSTREAM *fp, unsigned long *ptr, + VSTRING *str_buf, + const char *context) +{ + char junk = 0; + int ch; + + if ((ch = attr_scan64_string(fp, str_buf, context)) < 0) + return (-1); + if (sscanf(STR(str_buf), "%lu%c", ptr, &junk) != 1 || junk != 0) { + msg_warn("malformed numerical data from %s while reading %s: %.100s", + VSTREAM_PATH(fp), context, STR(str_buf)); + return (-1); + } + return (ch); +} + +/* attr_vscan64 - receive attribute list from stream */ + +int attr_vscan64(VSTREAM *fp, int flags, va_list ap) +{ + const char *myname = "attr_scan64"; + static VSTRING *str_buf = 0; + static VSTRING *name_buf = 0; + int wanted_type = -1; + char *wanted_name; + unsigned int *number; + unsigned long *long_number; + VSTRING *string; + HTABLE *hash_table; + int ch; + int conversions; + ATTR_SCAN_CUSTOM_FN scan_fn; + void *scan_arg; + const char *expect_val; + + /* + * Sanity check. + */ + if (flags & ~ATTR_FLAG_ALL) + msg_panic("%s: bad flags: 0x%x", myname, flags); + + /* + * EOF check. + */ + if ((ch = VSTREAM_GETC(fp)) == VSTREAM_EOF) + return (0); + vstream_ungetc(fp, ch); + + /* + * Initialize. + */ + if (str_buf == 0) { + str_buf = vstring_alloc(10); + name_buf = vstring_alloc(10); + } + + /* + * Iterate over all (type, name, value) triples. + */ + for (conversions = 0; /* void */ ; conversions++) { + + /* + * Determine the next attribute type and attribute name on the + * caller's wish list. + * + * If we're reading into a hash table, we already know that the + * attribute value is string-valued, and we get the attribute name + * from the input stream instead. This is secure only when the + * resulting table is queried with known to be good attribute names. + */ + if (wanted_type != ATTR_TYPE_HASH + && wanted_type != ATTR_TYPE_CLOSE) { + wanted_type = va_arg(ap, int); + if (wanted_type == ATTR_TYPE_END) { + if ((flags & ATTR_FLAG_MORE) != 0) + return (conversions); + wanted_name = "(list terminator)"; + } else if (wanted_type == ATTR_TYPE_HASH) { + wanted_name = "(any attribute name or list terminator)"; + hash_table = va_arg(ap, HTABLE *); + } else if (wanted_type != ATTR_TYPE_FUNC) { + wanted_name = va_arg(ap, char *); + } + } + + /* + * Locate the next attribute of interest in the input stream. + */ + while (wanted_type != ATTR_TYPE_FUNC) { + + /* + * Get the name of the next attribute. Hitting EOF is always bad. + * Hitting the end-of-input early is OK if the caller is prepared + * to deal with missing inputs. + */ + if (msg_verbose) + msg_info("%s: wanted attribute: %s", + VSTREAM_PATH(fp), wanted_name); + if ((ch = attr_scan64_string(fp, name_buf, + "input attribute name")) == VSTREAM_EOF) + return (-1); + if (ch == '\n' && LEN(name_buf) == 0) { + if (wanted_type == ATTR_TYPE_END + || wanted_type == ATTR_TYPE_HASH) + return (conversions); + if ((flags & ATTR_FLAG_MISSING) != 0) + msg_warn("missing attribute %s in input from %s", + wanted_name, VSTREAM_PATH(fp)); + return (conversions); + } + + /* + * See if the caller asks for this attribute. + */ + if (wanted_type == ATTR_TYPE_HASH + && ch == '\n' && strcmp(ATTR_NAME_OPEN, STR(name_buf)) == 0) { + wanted_type = ATTR_TYPE_CLOSE; + wanted_name = "(any attribute name or '}')"; + /* Advance in the input stream. */ + continue; + } else if (wanted_type == ATTR_TYPE_CLOSE + && ch == '\n' && strcmp(ATTR_NAME_CLOSE, STR(name_buf)) == 0) { + /* Advance in the argument list. */ + wanted_type = -1; + break; + } + if (wanted_type == ATTR_TYPE_HASH + || wanted_type == ATTR_TYPE_CLOSE + || (wanted_type != ATTR_TYPE_END + && strcmp(wanted_name, STR(name_buf)) == 0)) + break; + if ((flags & ATTR_FLAG_EXTRA) != 0) { + msg_warn("unexpected attribute %s from %s (expecting: %s)", + STR(name_buf), VSTREAM_PATH(fp), wanted_name); + return (conversions); + } + + /* + * Skip over this attribute. The caller does not ask for it. + */ + while (ch != '\n' && (ch = VSTREAM_GETC(fp)) != VSTREAM_EOF) + /* void */ ; + } + + /* + * Do the requested conversion. If the target attribute is a + * non-array type, disallow sending a multi-valued attribute, and + * disallow sending no value. If the target attribute is an array + * type, allow the sender to send a zero-element array (i.e. no value + * at all). XXX Need to impose a bound on the number of array + * elements. + */ + switch (wanted_type) { + case ATTR_TYPE_INT: + if (ch != ':') { + msg_warn("missing value for number attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + number = va_arg(ap, unsigned int *); + if ((ch = attr_scan64_number(fp, number, str_buf, + "input attribute value")) < 0) + return (-1); + if (ch != '\n') { + msg_warn("multiple values for attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + break; + case ATTR_TYPE_LONG: + if (ch != ':') { + msg_warn("missing value for number attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + long_number = va_arg(ap, unsigned long *); + if ((ch = attr_scan64_long_number(fp, long_number, str_buf, + "input attribute value")) < 0) + return (-1); + if (ch != '\n') { + msg_warn("multiple values for attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + break; + case ATTR_TYPE_STR: + if (ch != ':') { + msg_warn("missing value for string attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + string = va_arg(ap, VSTRING *); + if ((ch = attr_scan64_string(fp, string, + "input attribute value")) < 0) + return (-1); + if (ch != '\n') { + msg_warn("multiple values for attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + if (flags & ATTR_FLAG_PRINTABLE) + (void) printable(STR(string), '?'); + break; + case ATTR_TYPE_DATA: + if (ch != ':') { + msg_warn("missing value for data attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + string = va_arg(ap, VSTRING *); + if ((ch = attr_scan64_string(fp, string, + "input attribute value")) < 0) + return (-1); + if (ch != '\n') { + msg_warn("multiple values for attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + break; + case ATTR_TYPE_FUNC: + scan_fn = va_arg(ap, ATTR_SCAN_CUSTOM_FN); + scan_arg = va_arg(ap, void *); + if (scan_fn(attr_scan64, fp, flags | ATTR_FLAG_MORE, scan_arg) < 0) + return (-1); + break; + case ATTR_TYPE_STREQ: + if (ch != ':') { + msg_warn("missing value for string attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + expect_val = va_arg(ap, const char *); + if ((ch = attr_scan64_string(fp, str_buf, + "input attribute value")) < 0) + return (-1); + if (ch != '\n') { + msg_warn("multiple values for attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + if (strcmp(expect_val, STR(str_buf)) != 0) { + msg_warn("unexpected %s %s from %s (expected: %s)", + STR(name_buf), STR(str_buf), VSTREAM_PATH(fp), + expect_val); + return (-1); + } + conversions -= 1; + break; + case ATTR_TYPE_HASH: + case ATTR_TYPE_CLOSE: + if (ch != ':') { + msg_warn("missing value for string attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + if ((ch = attr_scan64_string(fp, str_buf, + "input attribute value")) < 0) + return (-1); + if (ch != '\n') { + msg_warn("multiple values for attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + if (flags & ATTR_FLAG_PRINTABLE) { + (void) printable(STR(name_buf), '?'); + (void) printable(STR(str_buf), '?'); + } + if (htable_locate(hash_table, STR(name_buf)) != 0) { + if ((flags & ATTR_FLAG_EXTRA) != 0) { + msg_warn("duplicate attribute %s in input from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (conversions); + } + } else if (hash_table->used >= ATTR_HASH_LIMIT) { + msg_warn("attribute count exceeds limit %d in input from %s", + ATTR_HASH_LIMIT, VSTREAM_PATH(fp)); + return (conversions); + } else { + htable_enter(hash_table, STR(name_buf), + mystrdup(STR(str_buf))); + } + break; + case -1: + conversions -= 1; + break; + default: + msg_panic("%s: unknown type code: %d", myname, wanted_type); + } + } +} + +/* attr_scan64 - read attribute list from stream */ + +int attr_scan64(VSTREAM *fp, int flags,...) +{ + va_list ap; + int ret; + + va_start(ap, flags); + ret = attr_vscan64(fp, flags, ap); + va_end(ap); + return (ret); +} + +/* attr_scan_more64 - look ahead for more */ + +int attr_scan_more64(VSTREAM *fp) +{ + int ch; + + switch (ch = VSTREAM_GETC(fp)) { + case '\n': + if (msg_verbose) + msg_info("%s: terminator (consumed)", VSTREAM_PATH(fp)); + return (0); + case VSTREAM_EOF: + if (msg_verbose) + msg_info("%s: EOF", VSTREAM_PATH(fp)); + return (-1); + default: + if (msg_verbose) + msg_info("%s: non-terminator '%c' (lookahead)", + VSTREAM_PATH(fp), ch); + (void) vstream_ungetc(fp, ch); + return (1); + } +} + +#ifdef TEST + + /* + * Proof of concept test program. Mirror image of the attr_scan64 test + * program. + */ +#include <msg_vstream.h> + +int var_line_limit = 2048; + +int main(int unused_argc, char **used_argv) +{ + VSTRING *data_val = vstring_alloc(1); + VSTRING *str_val = vstring_alloc(1); + HTABLE *table = htable_create(1); + HTABLE_INFO **ht_info_list; + HTABLE_INFO **ht; + int int_val; + long long_val; + long long_val2; + int ret; + + msg_verbose = 1; + msg_vstream_init(used_argv[0], VSTREAM_ERR); + if ((ret = attr_scan64(VSTREAM_IN, + ATTR_FLAG_STRICT, + RECV_ATTR_STREQ("protocol", "test"), + RECV_ATTR_INT(ATTR_NAME_INT, &int_val), + RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val), + RECV_ATTR_STR(ATTR_NAME_STR, str_val), + RECV_ATTR_DATA(ATTR_NAME_DATA, data_val), + RECV_ATTR_HASH(table), + RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val2), + ATTR_TYPE_END)) > 4) { + vstream_printf("%s %d\n", ATTR_NAME_INT, int_val); + vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val); + vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val)); + vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val)); + ht_info_list = htable_list(table); + for (ht = ht_info_list; *ht; ht++) + vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value); + myfree((void *) ht_info_list); + vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val2); + } else { + vstream_printf("return: %d\n", ret); + } + if ((ret = attr_scan64(VSTREAM_IN, + ATTR_FLAG_STRICT, + RECV_ATTR_STREQ("protocol", "test"), + RECV_ATTR_INT(ATTR_NAME_INT, &int_val), + RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val), + RECV_ATTR_STR(ATTR_NAME_STR, str_val), + RECV_ATTR_DATA(ATTR_NAME_DATA, data_val), + ATTR_TYPE_END)) == 4) { + vstream_printf("%s %d\n", ATTR_NAME_INT, int_val); + vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val); + vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val)); + vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val)); + ht_info_list = htable_list(table); + for (ht = ht_info_list; *ht; ht++) + vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value); + myfree((void *) ht_info_list); + } else { + vstream_printf("return: %d\n", ret); + } + if ((ret = attr_scan64(VSTREAM_IN, + ATTR_FLAG_STRICT, + RECV_ATTR_STREQ("protocol", "test"), + ATTR_TYPE_END)) != 0) + vstream_printf("return: %d\n", ret); + if (vstream_fflush(VSTREAM_OUT) != 0) + msg_fatal("write error: %m"); + + vstring_free(data_val); + vstring_free(str_val); + htable_free(table, myfree); + + return (0); +} + +#endif diff --git a/src/util/attr_scan64.ref b/src/util/attr_scan64.ref new file mode 100644 index 0000000..ccf27f1 --- /dev/null +++ b/src/util/attr_scan64.ref @@ -0,0 +1,79 @@ +./attr_print64: send attr protocol = test +./attr_print64: send attr number = 4711 +./attr_print64: send attr long_number = 1234 +./attr_print64: send attr string = whoopee +./attr_print64: send attr data = [data 7 bytes] +./attr_print64: send attr name bar-name value bar-value +./attr_print64: send attr name foo-name value foo-value +./attr_print64: send attr long_number = 4321 +./attr_print64: send attr protocol = test +./attr_print64: send attr number = 4711 +./attr_print64: send attr long_number = 1234 +./attr_print64: send attr string = whoopee +./attr_print64: send attr data = [data 7 bytes] +./attr_print64: send attr protocol = not-test +./attr_scan64: unknown_stream: wanted attribute: protocol +./attr_scan64: input attribute name: protocol +./attr_scan64: input attribute value: test +./attr_scan64: unknown_stream: wanted attribute: number +./attr_scan64: input attribute name: number +./attr_scan64: input attribute value: 4711 +./attr_scan64: unknown_stream: wanted attribute: long_number +./attr_scan64: input attribute name: long_number +./attr_scan64: input attribute value: 1234 +./attr_scan64: unknown_stream: wanted attribute: string +./attr_scan64: input attribute name: string +./attr_scan64: input attribute value: whoopee +./attr_scan64: unknown_stream: wanted attribute: data +./attr_scan64: input attribute name: data +./attr_scan64: input attribute value: whoopee +./attr_scan64: unknown_stream: wanted attribute: (any attribute name or list terminator) +./attr_scan64: input attribute name: { +./attr_scan64: unknown_stream: wanted attribute: (any attribute name or '}') +./attr_scan64: input attribute name: bar-name +./attr_scan64: input attribute value: bar-value +./attr_scan64: unknown_stream: wanted attribute: (any attribute name or '}') +./attr_scan64: input attribute name: foo-name +./attr_scan64: input attribute value: foo-value +./attr_scan64: unknown_stream: wanted attribute: (any attribute name or '}') +./attr_scan64: input attribute name: } +./attr_scan64: unknown_stream: wanted attribute: long_number +./attr_scan64: input attribute name: long_number +./attr_scan64: input attribute value: 4321 +./attr_scan64: unknown_stream: wanted attribute: (list terminator) +./attr_scan64: input attribute name: (end) +./attr_scan64: unknown_stream: wanted attribute: protocol +./attr_scan64: input attribute name: protocol +./attr_scan64: input attribute value: test +./attr_scan64: unknown_stream: wanted attribute: number +./attr_scan64: input attribute name: number +./attr_scan64: input attribute value: 4711 +./attr_scan64: unknown_stream: wanted attribute: long_number +./attr_scan64: input attribute name: long_number +./attr_scan64: input attribute value: 1234 +./attr_scan64: unknown_stream: wanted attribute: string +./attr_scan64: input attribute name: string +./attr_scan64: input attribute value: whoopee +./attr_scan64: unknown_stream: wanted attribute: data +./attr_scan64: input attribute name: data +./attr_scan64: input attribute value: whoopee +./attr_scan64: unknown_stream: wanted attribute: (list terminator) +./attr_scan64: input attribute name: (end) +./attr_scan64: unknown_stream: wanted attribute: protocol +./attr_scan64: input attribute name: protocol +./attr_scan64: input attribute value: not-test +./attr_scan64: warning: unexpected protocol not-test from unknown_stream (expected: test) +number 4711 +long_number 1234 +string whoopee +data whoopee +(hash) bar-name bar-value +(hash) foo-name foo-value +long_number 4321 +number 4711 +long_number 1234 +string whoopee +data whoopee +(hash) bar-name bar-value +(hash) foo-name foo-value +return: -1 diff --git a/src/util/attr_scan_plain.c b/src/util/attr_scan_plain.c new file mode 100644 index 0000000..d7e2f66 --- /dev/null +++ b/src/util/attr_scan_plain.c @@ -0,0 +1,643 @@ +/*++ +/* NAME +/* attr_scan_plain 3 +/* SUMMARY +/* recover attributes from byte stream +/* SYNOPSIS +/* #include <attr.h> +/* +/* int attr_scan_plain(fp, flags, type, name, ..., ATTR_TYPE_END) +/* VSTREAM *fp; +/* int flags; +/* int type; +/* char *name; +/* +/* int attr_vscan_plain(fp, flags, ap) +/* VSTREAM *fp; +/* int flags; +/* va_list ap; +/* +/* int attr_scan_more_plain(fp) +/* VSTREAM *fp; +/* DESCRIPTION +/* attr_scan_plain() takes zero or more (name, value) request attributes +/* and recovers the attribute values from the byte stream that was +/* possibly generated by attr_print_plain(). +/* +/* attr_vscan_plain() provides an alternative interface that is convenient +/* for calling from within a variadic function. +/* +/* attr_scan_more_plain() returns 0 when a terminator is found +/* (and consumes that terminator), returns 1 when more input +/* is expected (without consuming input), and returns -1 +/* otherwise (error). +/* +/* The input stream is formatted as follows, where (item)* stands +/* for zero or more instances of the specified item, and where +/* (item1 | item2) stands for choice: +/* +/* .in +5 +/* attr-list :== (simple-attr | multi-attr)* newline +/* .br +/* multi-attr :== "{" newline simple-attr* "}" newline +/* .br +/* simple-attr :== attr-name "=" attr-value newline +/* .br +/* attr-name :== any string without null or "=" or newline. +/* .br +/* attr-value :== any string without null or newline. +/* .br +/* newline :== the ASCII newline character +/* .in +/* +/* All attribute names and attribute values are sent as plain +/* strings. Each string must be no longer than 4*var_line_limit +/* characters. The formatting rules aim to make implementations in PERL +/* and other languages easy. +/* +/* Normally, attributes must be received in the sequence as specified +/* with the attr_scan_plain() argument list. The input stream may +/* contain additional attributes at any point in the input stream, +/* including additional instances of requested attributes. +/* +/* Additional input attributes or input attribute instances are silently +/* skipped over, unless the ATTR_FLAG_EXTRA processing flag is specified +/* (see below). This allows for some flexibility in the evolution of +/* protocols while still providing the option of being strict where +/* this is desirable. +/* +/* Arguments: +/* .IP fp +/* Stream to recover the input attributes from. +/* .IP flags +/* The bit-wise OR of zero or more of the following. +/* .RS +/* .IP ATTR_FLAG_MISSING +/* Log a warning when the input attribute list terminates before all +/* requested attributes are recovered. It is always an error when the +/* input stream ends without the newline attribute list terminator. +/* .IP ATTR_FLAG_EXTRA +/* Log a warning and stop attribute recovery when the input stream +/* contains an attribute that was not requested. This includes the +/* case of additional instances of a requested attribute. +/* .IP ATTR_FLAG_MORE +/* After recovering the requested attributes, leave the input stream +/* in a state that is usable for more attr_scan_plain() operations +/* from the same input attribute list. +/* By default, attr_scan_plain() skips forward past the input attribute +/* list terminator. +/* .IP ATTR_FLAG_PRINTABLE +/* Santize received string values with printable(_, '?'). +/* .IP ATTR_FLAG_STRICT +/* For convenience, this value combines both ATTR_FLAG_MISSING and +/* ATTR_FLAG_EXTRA. +/* .IP ATTR_FLAG_NONE +/* For convenience, this value requests none of the above. +/* .RE +/* .IP List of attributes followed by terminator: +/* .RS +/* .IP "RECV_ATTR_INT(const char *name, int *ptr)" +/* This argument is followed by an attribute name and an integer pointer. +/* .IP "RECV_ATTR_LONG(const char *name, long *ptr)" +/* This argument is followed by an attribute name and a long pointer. +/* .IP "RECV_ATTR_STR(const char *name, VSTRING *vp)" +/* This argument is followed by an attribute name and a VSTRING pointer. +/* .IP "RECV_ATTR_STREQ(const char *name, const char *value)" +/* The name and value must match what the client sends. +/* This attribute does not increment the result value. +/* .IP "RECV_ATTR_DATA(const char *name, VSTRING *vp)" +/* This argument is followed by an attribute name and a VSTRING pointer. +/* .IP "RECV_ATTR_FUNC(ATTR_SCAN_CUSTOM_FN, void *data)" +/* This argument is followed by a function pointer and a generic data +/* pointer. The caller-specified function returns < 0 in case of +/* error. +/* .IP "RECV_ATTR_HASH(HTABLE *table)" +/* .IP "RECV_ATTR_NAMEVAL(NVTABLE *table)" +/* Receive a sequence of attribute names and string values. +/* There can be no more than 1024 attributes in a hash table. +/* .sp +/* The attribute string values are stored in the hash table under +/* keys equal to the attribute name (obtained from the input stream). +/* Values from the input stream are added to the hash table. Existing +/* hash table entries are not replaced. +/* .sp +/* Note: the SEND_ATTR_HASH or SEND_ATTR_NAMEVAL requests +/* format their payload as a multi-attr sequence (see syntax +/* above). When the receiver's input does not start with a +/* multi-attr delimiter (i.e. the sender did not request +/* SEND_ATTR_HASH or SEND_ATTR_NAMEVAL), the receiver will +/* store all attribute names and values up to the attribute +/* list terminator. In terms of code, this means that the +/* RECV_ATTR_HASH or RECV_ATTR_NAMEVAL request must be followed +/* by ATTR_TYPE_END. +/* .IP ATTR_TYPE_END +/* This argument terminates the requested attribute list. +/* .RE +/* BUGS +/* RECV_ATTR_HASH (RECV_ATTR_NAMEVAL) accepts attributes with arbitrary +/* names from possibly untrusted sources. +/* This is unsafe, unless the resulting table is queried only with +/* known to be good attribute names. +/* DIAGNOSTICS +/* attr_scan_plain() and attr_vscan_plain() return -1 when malformed input +/* is detected (string too long, incomplete line, missing end marker). +/* Otherwise, the result value is the number of attributes that were +/* successfully recovered from the input stream (a hash table counts +/* as the number of entries stored into the table). +/* +/* Panic: interface violation. All system call errors are fatal. +/* SEE ALSO +/* attr_print_plain(3) send attributes over byte stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdarg.h> +#include <string.h> +#include <stdio.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstream.h> +#include <vstring.h> +#include <htable.h> +#include <base64_code.h> +#include <stringops.h> +#include <attr.h> + +/* Application specific. */ + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* attr_scan_plain_string - pull a string from the input stream */ + +static int attr_scan_plain_string(VSTREAM *fp, VSTRING *plain_buf, + int terminator, const char *context) +{ +#if 0 + extern int var_line_limit; /* XXX */ + int limit = var_line_limit * 4; + +#endif + int ch; + + VSTRING_RESET(plain_buf); + while ((ch = VSTREAM_GETC(fp)) != '\n' + && (terminator == 0 || ch != terminator)) { + if (ch == VSTREAM_EOF) { + msg_warn("%s on %s while reading %s", + vstream_ftimeout(fp) ? "timeout" : "premature end-of-input", + VSTREAM_PATH(fp), context); + return (-1); + } + VSTRING_ADDCH(plain_buf, ch); +#if 0 + if (LEN(plain_buf) > limit) { + msg_warn("string length > %d characters from %s while reading %s", + limit, VSTREAM_PATH(fp), context); + return (-1); + } +#endif + } + VSTRING_TERMINATE(plain_buf); + + if (msg_verbose) + msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)"); + return (ch); +} + +/* attr_scan_plain_data - pull a data blob from the input stream */ + +static int attr_scan_plain_data(VSTREAM *fp, VSTRING *str_buf, + int terminator, + const char *context) +{ + static VSTRING *base64_buf = 0; + int ch; + + if (base64_buf == 0) + base64_buf = vstring_alloc(10); + if ((ch = attr_scan_plain_string(fp, base64_buf, terminator, context)) < 0) + return (-1); + if (base64_decode(str_buf, STR(base64_buf), LEN(base64_buf)) == 0) { + msg_warn("malformed base64 data from %s while reading %s: %.100s", + VSTREAM_PATH(fp), context, STR(base64_buf)); + return (-1); + } + return (ch); +} + +/* attr_scan_plain_number - pull a number from the input stream */ + +static int attr_scan_plain_number(VSTREAM *fp, unsigned *ptr, VSTRING *str_buf, + int terminator, const char *context) +{ + char junk = 0; + int ch; + + if ((ch = attr_scan_plain_string(fp, str_buf, terminator, context)) < 0) + return (-1); + if (sscanf(STR(str_buf), "%u%c", ptr, &junk) != 1 || junk != 0) { + msg_warn("malformed numerical data from %s while reading %s: %.100s", + VSTREAM_PATH(fp), context, STR(str_buf)); + return (-1); + } + return (ch); +} + +/* attr_scan_plain_long_number - pull a number from the input stream */ + +static int attr_scan_plain_long_number(VSTREAM *fp, unsigned long *ptr, + VSTRING *str_buf, + int terminator, + const char *context) +{ + char junk = 0; + int ch; + + if ((ch = attr_scan_plain_string(fp, str_buf, terminator, context)) < 0) + return (-1); + if (sscanf(STR(str_buf), "%lu%c", ptr, &junk) != 1 || junk != 0) { + msg_warn("malformed numerical data from %s while reading %s: %.100s", + VSTREAM_PATH(fp), context, STR(str_buf)); + return (-1); + } + return (ch); +} + +/* attr_vscan_plain - receive attribute list from stream */ + +int attr_vscan_plain(VSTREAM *fp, int flags, va_list ap) +{ + const char *myname = "attr_scan_plain"; + static VSTRING *str_buf = 0; + static VSTRING *name_buf = 0; + int wanted_type = -1; + char *wanted_name; + unsigned int *number; + unsigned long *long_number; + VSTRING *string; + HTABLE *hash_table; + int ch; + int conversions; + ATTR_SCAN_CUSTOM_FN scan_fn; + void *scan_arg; + const char *expect_val; + + /* + * Sanity check. + */ + if (flags & ~ATTR_FLAG_ALL) + msg_panic("%s: bad flags: 0x%x", myname, flags); + + /* + * EOF check. + */ + if ((ch = VSTREAM_GETC(fp)) == VSTREAM_EOF) + return (0); + vstream_ungetc(fp, ch); + + /* + * Initialize. + */ + if (str_buf == 0) { + str_buf = vstring_alloc(10); + name_buf = vstring_alloc(10); + } + + /* + * Iterate over all (type, name, value) triples. + */ + for (conversions = 0; /* void */ ; conversions++) { + + /* + * Determine the next attribute type and attribute name on the + * caller's wish list. + * + * If we're reading into a hash table, we already know that the + * attribute value is string-valued, and we get the attribute name + * from the input stream instead. This is secure only when the + * resulting table is queried with known to be good attribute names. + */ + if (wanted_type != ATTR_TYPE_HASH + && wanted_type != ATTR_TYPE_CLOSE) { + wanted_type = va_arg(ap, int); + if (wanted_type == ATTR_TYPE_END) { + if ((flags & ATTR_FLAG_MORE) != 0) + return (conversions); + wanted_name = "(list terminator)"; + } else if (wanted_type == ATTR_TYPE_HASH) { + wanted_name = "(any attribute name or list terminator)"; + hash_table = va_arg(ap, HTABLE *); + } else if (wanted_type != ATTR_TYPE_FUNC) { + wanted_name = va_arg(ap, char *); + } + } + + /* + * Locate the next attribute of interest in the input stream. + */ + while (wanted_type != ATTR_TYPE_FUNC) { + + /* + * Get the name of the next attribute. Hitting EOF is always bad. + * Hitting the end-of-input early is OK if the caller is prepared + * to deal with missing inputs. + */ + if (msg_verbose) + msg_info("%s: wanted attribute: %s", + VSTREAM_PATH(fp), wanted_name); + if ((ch = attr_scan_plain_string(fp, name_buf, '=', + "input attribute name")) == VSTREAM_EOF) + return (-1); + if (ch == '\n' && LEN(name_buf) == 0) { + if (wanted_type == ATTR_TYPE_END + || wanted_type == ATTR_TYPE_HASH) + return (conversions); + if ((flags & ATTR_FLAG_MISSING) != 0) + msg_warn("missing attribute %s in input from %s", + wanted_name, VSTREAM_PATH(fp)); + return (conversions); + } + + /* + * See if the caller asks for this attribute. + */ + if (wanted_type == ATTR_TYPE_HASH + && ch == '\n' && strcmp(ATTR_NAME_OPEN, STR(name_buf)) == 0) { + wanted_type = ATTR_TYPE_CLOSE; + wanted_name = "(any attribute name or '}')"; + /* Advance in the input stream. */ + continue; + } else if (wanted_type == ATTR_TYPE_CLOSE + && ch == '\n' && strcmp(ATTR_NAME_CLOSE, STR(name_buf)) == 0) { + /* Advance in the argument list. */ + wanted_type = -1; + break; + } + if (wanted_type == ATTR_TYPE_HASH + || wanted_type == ATTR_TYPE_CLOSE + || (wanted_type != ATTR_TYPE_END + && strcmp(wanted_name, STR(name_buf)) == 0)) + break; + if ((flags & ATTR_FLAG_EXTRA) != 0) { + msg_warn("unexpected attribute %s from %s (expecting: %s)", + STR(name_buf), VSTREAM_PATH(fp), wanted_name); + return (conversions); + } + + /* + * Skip over this attribute. The caller does not ask for it. + */ + while (ch != '\n' && (ch = VSTREAM_GETC(fp)) != VSTREAM_EOF) + /* void */ ; + } + + /* + * Do the requested conversion. + */ + switch (wanted_type) { + case ATTR_TYPE_INT: + if (ch != '=') { + msg_warn("missing value for number attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + number = va_arg(ap, unsigned int *); + if ((ch = attr_scan_plain_number(fp, number, str_buf, 0, + "input attribute value")) < 0) + return (-1); + break; + case ATTR_TYPE_LONG: + if (ch != '=') { + msg_warn("missing value for number attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + long_number = va_arg(ap, unsigned long *); + if ((ch = attr_scan_plain_long_number(fp, long_number, str_buf, + 0, "input attribute value")) < 0) + return (-1); + break; + case ATTR_TYPE_STR: + if (ch != '=') { + msg_warn("missing value for string attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + string = va_arg(ap, VSTRING *); + if ((ch = attr_scan_plain_string(fp, string, 0, + "input attribute value")) < 0) + return (-1); + if (flags & ATTR_FLAG_PRINTABLE) + (void) printable(STR(string), '?'); + break; + case ATTR_TYPE_DATA: + if (ch != '=') { + msg_warn("missing value for data attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + string = va_arg(ap, VSTRING *); + if ((ch = attr_scan_plain_data(fp, string, 0, + "input attribute value")) < 0) + return (-1); + break; + case ATTR_TYPE_FUNC: + scan_fn = va_arg(ap, ATTR_SCAN_CUSTOM_FN); + scan_arg = va_arg(ap, void *); + if (scan_fn(attr_scan_plain, fp, flags | ATTR_FLAG_MORE, scan_arg) < 0) + return (-1); + break; + case ATTR_TYPE_STREQ: + if (ch != '=') { + msg_warn("missing value for string attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + expect_val = va_arg(ap, const char *); + if ((ch = attr_scan_plain_string(fp, str_buf, 0, + "input attribute value")) < 0) + return (-1); + if (strcmp(expect_val, STR(str_buf)) != 0) { + msg_warn("unexpected %s %s from %s (expected: %s)", + STR(name_buf), STR(str_buf), VSTREAM_PATH(fp), + expect_val); + return (-1); + } + conversions -= 1; + break; + case ATTR_TYPE_HASH: + case ATTR_TYPE_CLOSE: + if (ch != '=') { + msg_warn("missing value for string attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + if ((ch = attr_scan_plain_string(fp, str_buf, 0, + "input attribute value")) < 0) + return (-1); + if (flags & ATTR_FLAG_PRINTABLE) { + (void) printable(STR(name_buf), '?'); + (void) printable(STR(str_buf), '?'); + } + if (htable_locate(hash_table, STR(name_buf)) != 0) { + if ((flags & ATTR_FLAG_EXTRA) != 0) { + msg_warn("duplicate attribute %s in input from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (conversions); + } + } else if (hash_table->used >= ATTR_HASH_LIMIT) { + msg_warn("attribute count exceeds limit %d in input from %s", + ATTR_HASH_LIMIT, VSTREAM_PATH(fp)); + return (conversions); + } else { + htable_enter(hash_table, STR(name_buf), + mystrdup(STR(str_buf))); + } + break; + case -1: + conversions -= 1; + break; + default: + msg_panic("%s: unknown type code: %d", myname, wanted_type); + } + } +} + +/* attr_scan_plain - read attribute list from stream */ + +int attr_scan_plain(VSTREAM *fp, int flags,...) +{ + va_list ap; + int ret; + + va_start(ap, flags); + ret = attr_vscan_plain(fp, flags, ap); + va_end(ap); + return (ret); +} + +/* attr_scan_more_plain - look ahead for more */ + +int attr_scan_more_plain(VSTREAM *fp) +{ + int ch; + + switch (ch = VSTREAM_GETC(fp)) { + case '\n': + if (msg_verbose) + msg_info("%s: terminator (consumed)", VSTREAM_PATH(fp)); + return (0); + case VSTREAM_EOF: + if (msg_verbose) + msg_info("%s: EOF", VSTREAM_PATH(fp)); + return (-1); + default: + if (msg_verbose) + msg_info("%s: non-terminator '%c' (lookahead)", + VSTREAM_PATH(fp), ch); + (void) vstream_ungetc(fp, ch); + return (1); + } +} + +#ifdef TEST + + /* + * Proof of concept test program. Mirror image of the attr_scan_plain test + * program. + */ +#include <msg_vstream.h> + +int var_line_limit = 2048; + +int main(int unused_argc, char **used_argv) +{ + VSTRING *data_val = vstring_alloc(1); + VSTRING *str_val = vstring_alloc(1); + HTABLE *table = htable_create(1); + HTABLE_INFO **ht_info_list; + HTABLE_INFO **ht; + int int_val; + long long_val; + long long_val2; + int ret; + + msg_verbose = 1; + msg_vstream_init(used_argv[0], VSTREAM_ERR); + if ((ret = attr_scan_plain(VSTREAM_IN, + ATTR_FLAG_STRICT, + RECV_ATTR_STREQ("protocol", "test"), + RECV_ATTR_INT(ATTR_NAME_INT, &int_val), + RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val), + RECV_ATTR_STR(ATTR_NAME_STR, str_val), + RECV_ATTR_DATA(ATTR_NAME_DATA, data_val), + RECV_ATTR_HASH(table), + RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val2), + ATTR_TYPE_END)) > 4) { + vstream_printf("%s %d\n", ATTR_NAME_INT, int_val); + vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val); + vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val)); + vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val)); + ht_info_list = htable_list(table); + for (ht = ht_info_list; *ht; ht++) + vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value); + myfree((void *) ht_info_list); + vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val2); + } else { + vstream_printf("return: %d\n", ret); + } + if ((ret = attr_scan_plain(VSTREAM_IN, + ATTR_FLAG_STRICT, + RECV_ATTR_STREQ("protocol", "test"), + RECV_ATTR_INT(ATTR_NAME_INT, &int_val), + RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val), + RECV_ATTR_STR(ATTR_NAME_STR, str_val), + RECV_ATTR_DATA(ATTR_NAME_DATA, data_val), + ATTR_TYPE_END)) == 4) { + vstream_printf("%s %d\n", ATTR_NAME_INT, int_val); + vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val); + vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val)); + vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val)); + ht_info_list = htable_list(table); + for (ht = ht_info_list; *ht; ht++) + vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value); + myfree((void *) ht_info_list); + } else { + vstream_printf("return: %d\n", ret); + } + if ((ret = attr_scan_plain(VSTREAM_IN, + ATTR_FLAG_STRICT, + RECV_ATTR_STREQ("protocol", "test"), + ATTR_TYPE_END)) != 0) + vstream_printf("return: %d\n", ret); + if (vstream_fflush(VSTREAM_OUT) != 0) + msg_fatal("write error: %m"); + + vstring_free(data_val); + vstring_free(str_val); + htable_free(table, myfree); + + return (0); +} + +#endif diff --git a/src/util/attr_scan_plain.ref b/src/util/attr_scan_plain.ref new file mode 100644 index 0000000..1c0f358 --- /dev/null +++ b/src/util/attr_scan_plain.ref @@ -0,0 +1,79 @@ +./attr_print_plain: send attr protocol = test +./attr_print_plain: send attr number = 4711 +./attr_print_plain: send attr long_number = 1234 +./attr_print_plain: send attr string = whoopee +./attr_print_plain: send attr data = [data 7 bytes] +./attr_print_plain: send attr name bar-name value bar-value +./attr_print_plain: send attr name foo-name value foo-value +./attr_print_plain: send attr long_number = 4321 +./attr_print_plain: send attr protocol = test +./attr_print_plain: send attr number = 4711 +./attr_print_plain: send attr long_number = 1234 +./attr_print_plain: send attr string = whoopee +./attr_print_plain: send attr data = [data 7 bytes] +./attr_print_plain: send attr protocol = not-test +./attr_scan_plain: unknown_stream: wanted attribute: protocol +./attr_scan_plain: input attribute name: protocol +./attr_scan_plain: input attribute value: test +./attr_scan_plain: unknown_stream: wanted attribute: number +./attr_scan_plain: input attribute name: number +./attr_scan_plain: input attribute value: 4711 +./attr_scan_plain: unknown_stream: wanted attribute: long_number +./attr_scan_plain: input attribute name: long_number +./attr_scan_plain: input attribute value: 1234 +./attr_scan_plain: unknown_stream: wanted attribute: string +./attr_scan_plain: input attribute name: string +./attr_scan_plain: input attribute value: whoopee +./attr_scan_plain: unknown_stream: wanted attribute: data +./attr_scan_plain: input attribute name: data +./attr_scan_plain: input attribute value: d2hvb3BlZQ== +./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or list terminator) +./attr_scan_plain: input attribute name: { +./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or '}') +./attr_scan_plain: input attribute name: bar-name +./attr_scan_plain: input attribute value: bar-value +./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or '}') +./attr_scan_plain: input attribute name: foo-name +./attr_scan_plain: input attribute value: foo-value +./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or '}') +./attr_scan_plain: input attribute name: } +./attr_scan_plain: unknown_stream: wanted attribute: long_number +./attr_scan_plain: input attribute name: long_number +./attr_scan_plain: input attribute value: 4321 +./attr_scan_plain: unknown_stream: wanted attribute: (list terminator) +./attr_scan_plain: input attribute name: (end) +./attr_scan_plain: unknown_stream: wanted attribute: protocol +./attr_scan_plain: input attribute name: protocol +./attr_scan_plain: input attribute value: test +./attr_scan_plain: unknown_stream: wanted attribute: number +./attr_scan_plain: input attribute name: number +./attr_scan_plain: input attribute value: 4711 +./attr_scan_plain: unknown_stream: wanted attribute: long_number +./attr_scan_plain: input attribute name: long_number +./attr_scan_plain: input attribute value: 1234 +./attr_scan_plain: unknown_stream: wanted attribute: string +./attr_scan_plain: input attribute name: string +./attr_scan_plain: input attribute value: whoopee +./attr_scan_plain: unknown_stream: wanted attribute: data +./attr_scan_plain: input attribute name: data +./attr_scan_plain: input attribute value: d2hvb3BlZQ== +./attr_scan_plain: unknown_stream: wanted attribute: (list terminator) +./attr_scan_plain: input attribute name: (end) +./attr_scan_plain: unknown_stream: wanted attribute: protocol +./attr_scan_plain: input attribute name: protocol +./attr_scan_plain: input attribute value: not-test +./attr_scan_plain: warning: unexpected protocol not-test from unknown_stream (expected: test) +number 4711 +long_number 1234 +string whoopee +data whoopee +(hash) bar-name bar-value +(hash) foo-name foo-value +long_number 4321 +number 4711 +long_number 1234 +string whoopee +data whoopee +(hash) bar-name bar-value +(hash) foo-name foo-value +return: -1 diff --git a/src/util/auto_clnt.c b/src/util/auto_clnt.c new file mode 100644 index 0000000..cdbbe22 --- /dev/null +++ b/src/util/auto_clnt.c @@ -0,0 +1,372 @@ +/*++ +/* NAME +/* auto_clnt 3 +/* SUMMARY +/* client endpoint maintenance +/* SYNOPSIS +/* #include <auto_clnt.h> +/* +/* typedef void (*AUTO_CLNT_HANDSHAKE_FN)(VSTREAM *); +/* +/* AUTO_CLNT *auto_clnt_create(service, timeout, max_idle, max_ttl) +/* const char *service; +/* int timeout; +/* int max_idle; +/* int max_ttl; +/* +/* VSTREAM *auto_clnt_access(auto_clnt) +/* AUTO_CLNT *auto_clnt; +/* +/* void auto_clnt_recover(auto_clnt) +/* AUTO_CLNT *auto_clnt; +/* +/* const char *auto_clnt_name(auto_clnt) +/* AUTO_CLNT *auto_clnt; +/* +/* void auto_clnt_free(auto_clnt) +/* AUTO_CLNT *auto_clnt; +/* +/* void auto_clnt_control(auto_clnt, name, value, ... AUTO_CLNT_CTL_END) +/* AUTO_CLNT *auto_clnt; +/* int name; +/* DESCRIPTION +/* This module maintains IPC client endpoints that automatically +/* disconnect after a being idle for a configurable amount of time, +/* that disconnect after a configurable time to live, +/* and that transparently handle most server-initiated disconnects. +/* +/* This module tries each operation only a limited number of +/* times and then reports an error. This is unlike the +/* clnt_stream(3) module which will retry forever, so that +/* the application never experiences an error. +/* +/* auto_clnt_create() instantiates a client endpoint. +/* +/* auto_clnt_access() returns an open stream to the service specified +/* to auto_clnt_create(). The stream instance may change between calls. +/* The result is a null pointer in case of failure. +/* +/* auto_clnt_recover() recovers from a server-initiated disconnect +/* that happened in the middle of an I/O operation. +/* +/* auto_clnt_name() returns the name of the specified client endpoint. +/* +/* auto_clnt_free() destroys of the specified client endpoint. +/* +/* auto_clnt_control() allows the user to fine tune the behavior of +/* the specified client. The arguments are a list of (name, value) +/* terminated with AUTO_CLNT_CTL_END. +/* The following lists the names and the types of the corresponding +/* value arguments. +/* .IP "AUTO_CLNT_CTL_HANDSHAKE(VSTREAM *)" +/* A pointer to function that will be called at the start of a +/* new connection, and that returns 0 in case of success. +/* .PP +/* Arguments: +/* .IP service +/* The service argument specifies "transport:servername" where +/* transport is currently limited to one of the following: +/* .RS +/* .IP inet +/* servername has the form "inet:host:port". +/* .IP local +/* servername has the form "local:private/servicename" or +/* "local:public/servicename". This is the preferred way to +/* specify Postfix daemons that are configured as "unix" in +/* master.cf. +/* .IP unix +/* servername has the form "unix:private/servicename" or +/* "unix:public/servicename". This does not work on Solaris, +/* where Postfix uses STREAMS instead of UNIX-domain sockets. +/* .RE +/* .IP timeout +/* The time limit for sending, receiving, or for connecting +/* to a server. Specify a value <=0 to disable the time limit. +/* .IP max_idle +/* Idle time after which the client disconnects. Specify 0 to +/* disable the limit. +/* .IP max_ttl +/* Upper bound on the time that a connection is allowed to persist. +/* Specify 0 to disable the limit. +/* .IP open_action +/* Application call-back routine that opens a stream or returns a +/* null pointer upon failure. In case of success, the call-back routine +/* is expected to set the stream pathname to the server endpoint name. +/* .IP context +/* Application context that is passed to the open_action routine. +/* .IP handshake +/* A null pointer, or a pointer to function that will be called +/* at the start of a new connection and that returns 0 in case +/* of success. +/* DIAGNOSTICS +/* Warnings: communication failure. Fatal error: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstream.h> +#include <events.h> +#include <iostuff.h> +#include <connect.h> +#include <split_at.h> +#include <auto_clnt.h> + +/* Application-specific. */ + + /* + * AUTO_CLNT is an opaque structure. None of the access methods can easily + * be implemented as a macro, and access is not performance critical anyway. + */ +struct AUTO_CLNT { + VSTREAM *vstream; /* buffered I/O */ + char *endpoint; /* host:port or pathname */ + int timeout; /* I/O time limit */ + int max_idle; /* time before client disconnect */ + int max_ttl; /* time before client disconnect */ + AUTO_CLNT_HANDSHAKE_FN handshake; /* new connection only */ + int (*connect) (const char *, int, int); /* unix, local, inet */ +}; + +static void auto_clnt_close(AUTO_CLNT *); + +/* auto_clnt_event - server-initiated disconnect or client-side max_idle */ + +static void auto_clnt_event(int unused_event, void *context) +{ + AUTO_CLNT *auto_clnt = (AUTO_CLNT *) context; + + /* + * Sanity check. This routine causes the stream to be closed, so it + * cannot be called when the stream is already closed. + */ + if (auto_clnt->vstream == 0) + msg_panic("auto_clnt_event: stream is closed"); + + auto_clnt_close(auto_clnt); +} + +/* auto_clnt_ttl_event - client-side expiration */ + +static void auto_clnt_ttl_event(int event, void *context) +{ + + /* + * XXX This function is needed only because event_request_timer() cannot + * distinguish between requests that specify the same call-back routine + * and call-back context. The fix is obvious: specify a request ID along + * with the call-back routine, but there is too much code that would have + * to be changed. + * + * XXX Should we be concerned that an overly aggressive optimizer will + * eliminate this function and replace calls to auto_clnt_ttl_event() by + * direct calls to auto_clnt_event()? It should not, because there exists + * code that takes the address of both functions. + */ + auto_clnt_event(event, context); +} + +/* auto_clnt_open - connect to service */ + +static void auto_clnt_open(AUTO_CLNT *auto_clnt) +{ + const char *myname = "auto_clnt_open"; + int fd; + + /* + * Sanity check. + */ + if (auto_clnt->vstream) + msg_panic("auto_clnt_open: stream is open"); + + /* + * Schedule a read event so that we can clean up when the remote side + * disconnects, and schedule a timer event so that we can cleanup an idle + * connection. Note that both events are handled by the same routine. + * + * Finally, schedule an event to force disconnection even when the + * connection is not idle. This is to prevent one client from clinging on + * to a server forever. + */ + fd = auto_clnt->connect(auto_clnt->endpoint, BLOCKING, auto_clnt->timeout); + if (fd < 0) { + msg_warn("connect to %s: %m", auto_clnt->endpoint); + } else { + if (msg_verbose) + msg_info("%s: connected to %s", myname, auto_clnt->endpoint); + auto_clnt->vstream = vstream_fdopen(fd, O_RDWR); + vstream_control(auto_clnt->vstream, + CA_VSTREAM_CTL_PATH(auto_clnt->endpoint), + CA_VSTREAM_CTL_TIMEOUT(auto_clnt->timeout), + CA_VSTREAM_CTL_END); + } + + if (auto_clnt->vstream != 0) { + close_on_exec(vstream_fileno(auto_clnt->vstream), CLOSE_ON_EXEC); + event_enable_read(vstream_fileno(auto_clnt->vstream), auto_clnt_event, + (void *) auto_clnt); + if (auto_clnt->max_idle > 0) + event_request_timer(auto_clnt_event, (void *) auto_clnt, + auto_clnt->max_idle); + if (auto_clnt->max_ttl > 0) + event_request_timer(auto_clnt_ttl_event, (void *) auto_clnt, + auto_clnt->max_ttl); + } +} + +/* auto_clnt_close - disconnect from service */ + +static void auto_clnt_close(AUTO_CLNT *auto_clnt) +{ + const char *myname = "auto_clnt_close"; + + /* + * Sanity check. + */ + if (auto_clnt->vstream == 0) + msg_panic("%s: stream is closed", myname); + + /* + * Be sure to disable read and timer events. + */ + if (msg_verbose) + msg_info("%s: disconnect %s stream", + myname, VSTREAM_PATH(auto_clnt->vstream)); + event_disable_readwrite(vstream_fileno(auto_clnt->vstream)); + event_cancel_timer(auto_clnt_event, (void *) auto_clnt); + event_cancel_timer(auto_clnt_ttl_event, (void *) auto_clnt); + (void) vstream_fclose(auto_clnt->vstream); + auto_clnt->vstream = 0; +} + +/* auto_clnt_recover - recover from server-initiated disconnect */ + +void auto_clnt_recover(AUTO_CLNT *auto_clnt) +{ + + /* + * Clean up. Don't re-connect until the caller needs it. + */ + if (auto_clnt->vstream) + auto_clnt_close(auto_clnt); +} + +/* auto_clnt_access - access a client stream */ + +VSTREAM *auto_clnt_access(AUTO_CLNT *auto_clnt) +{ + AUTO_CLNT_HANDSHAKE_FN handshake; + + /* + * Open a stream or restart the idle timer. + * + * Important! Do not restart the TTL timer! + */ + if (auto_clnt->vstream == 0) { + auto_clnt_open(auto_clnt); + handshake = (auto_clnt->vstream ? auto_clnt->handshake : 0); + } else { + if (auto_clnt->max_idle > 0) + event_request_timer(auto_clnt_event, (void *) auto_clnt, + auto_clnt->max_idle); + handshake = 0; + } + if (handshake != 0 && handshake(auto_clnt->vstream) != 0) + return (0); + return (auto_clnt->vstream); +} + +/* auto_clnt_create - create client stream object */ + +AUTO_CLNT *auto_clnt_create(const char *service, int timeout, + int max_idle, int max_ttl) +{ + const char *myname = "auto_clnt_create"; + char *transport = mystrdup(service); + char *endpoint; + AUTO_CLNT *auto_clnt; + + /* + * Don't open the stream until the caller needs it. + */ + if ((endpoint = split_at(transport, ':')) == 0 + || *endpoint == 0 || *transport == 0) + msg_fatal("need service transport:endpoint instead of \"%s\"", service); + if (msg_verbose) + msg_info("%s: transport=%s endpoint=%s", myname, transport, endpoint); + auto_clnt = (AUTO_CLNT *) mymalloc(sizeof(*auto_clnt)); + auto_clnt->vstream = 0; + auto_clnt->endpoint = mystrdup(endpoint); + auto_clnt->timeout = timeout; + auto_clnt->max_idle = max_idle; + auto_clnt->max_ttl = max_ttl; + auto_clnt->handshake = 0; + if (strcmp(transport, "inet") == 0) { + auto_clnt->connect = inet_connect; + } else if (strcmp(transport, "local") == 0) { + auto_clnt->connect = LOCAL_CONNECT; + } else if (strcmp(transport, "unix") == 0) { + auto_clnt->connect = unix_connect; + } else { + msg_fatal("invalid transport name: %s in service: %s", + transport, service); + } + myfree(transport); + return (auto_clnt); +} + +/* auto_clnt_name - return client stream name */ + +const char *auto_clnt_name(AUTO_CLNT *auto_clnt) +{ + return (auto_clnt->endpoint); +} + +/* auto_clnt_free - destroy client stream instance */ + +void auto_clnt_free(AUTO_CLNT *auto_clnt) +{ + if (auto_clnt->vstream) + auto_clnt_close(auto_clnt); + myfree(auto_clnt->endpoint); + myfree((void *) auto_clnt); +} + +/* auto_clnt_control - fine control */ + +void auto_clnt_control(AUTO_CLNT *client, int name,...) +{ + const char *myname = "auto_clnt_control"; + va_list ap; + + for (va_start(ap, name); name != AUTO_CLNT_CTL_END; name = va_arg(ap, int)) { + switch (name) { + case AUTO_CLNT_CTL_HANDSHAKE: + client->handshake = va_arg(ap, AUTO_CLNT_HANDSHAKE_FN); + break; + default: + msg_panic("%s: bad name %d", myname, name); + } + } + va_end(ap); +} diff --git a/src/util/auto_clnt.h b/src/util/auto_clnt.h new file mode 100644 index 0000000..61c6680 --- /dev/null +++ b/src/util/auto_clnt.h @@ -0,0 +1,51 @@ +#ifndef _AUTO_CLNT_H_INCLUDED_ +#define _AUTO_CLNT_H_INCLUDED_ + +/*++ +/* NAME +/* auto_clnt 3h +/* SUMMARY +/* client endpoint maintenance +/* SYNOPSIS +/* #include <auto_clnt.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <vstream.h> + + /* + * External interface. + */ +typedef struct AUTO_CLNT AUTO_CLNT; +typedef int (*AUTO_CLNT_HANDSHAKE_FN) (VSTREAM *); + +extern AUTO_CLNT *auto_clnt_create(const char *, int, int, int); +extern VSTREAM *auto_clnt_access(AUTO_CLNT *); +extern void auto_clnt_recover(AUTO_CLNT *); +extern const char *auto_clnt_name(AUTO_CLNT *); +extern void auto_clnt_free(AUTO_CLNT *); +extern void auto_clnt_control(AUTO_CLNT *, int,...); + +#define AUTO_CLNT_CTL_END 0 +#define AUTO_CLNT_CTL_HANDSHAKE 1 /* handshake before first request */ + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/balpar.c b/src/util/balpar.c new file mode 100644 index 0000000..6ff97eb --- /dev/null +++ b/src/util/balpar.c @@ -0,0 +1,56 @@ +/*++ +/* NAME +/* balpar 3 +/* SUMMARY +/* determine length of string in parentheses +/* SYNOPSIS +/* #include <stringops.h> +/* +/* size_t balpar(string, parens) +/* const char *string; +/* const char *parens; +/* DESCRIPTION +/* balpar() determines the length of a string enclosed in +/* the specified parentheses, zero in case of error. +/* SEE ALSO +/* A balpar() routine appears in Brian W. Kernighan, P.J. Plauger: +/* "Software Tools", Addison-Wesley 1976. This function is different. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +/* Utility library. */ + +#include <stringops.h> + +/* balpar - return length of {text} */ + +size_t balpar(const char *string, const char *parens) +{ + const char *cp; + int level; + int ch; + + if (*string != parens[0]) + return (0); + for (level = 1, cp = string + 1; (ch = *cp) != 0; cp++) { + if (ch == parens[1]) { + if (--level == 0) + return (cp - string + 1); + } else if (ch == parens[0]) { + level++; + } + } + return (0); +} diff --git a/src/util/base32_code.c b/src/util/base32_code.c new file mode 100644 index 0000000..31566ea --- /dev/null +++ b/src/util/base32_code.c @@ -0,0 +1,266 @@ +/*++ +/* NAME +/* base32_code 3 +/* SUMMARY +/* encode/decode data, base 32 style +/* SYNOPSIS +/* #include <base32_code.h> +/* +/* VSTRING *base32_encode(result, in, len) +/* VSTRING *result; +/* const char *in; +/* ssize_t len; +/* +/* VSTRING *base32_decode(result, in, len) +/* VSTRING *result; +/* const char *in; +/* ssize_t len; +/* DESCRIPTION +/* base32_encode() takes a block of len bytes and encodes it as one +/* null-terminated string. The result value is the result argument. +/* +/* base32_decode() performs the opposite transformation. The result +/* value is the result argument. The result is null terminated, whether +/* or not that makes sense. +/* DIAGNOSTICS +/* base32_decode() returns a null pointer when the input contains +/* characters not in the base 32 alphabet. +/* SEE ALSO +/* RFC 4648; padding is strictly enforced +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <ctype.h> +#include <string.h> +#include <limits.h> + +#ifndef UCHAR_MAX +#define UCHAR_MAX 0xff +#endif + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstring.h> +#include <base32_code.h> + +/* Application-specific. */ + +static unsigned char to_b32[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + +#define UNSIG_CHAR_PTR(x) ((unsigned char *)(x)) + +/* base32_encode - raw data to encoded */ + +VSTRING *base32_encode(VSTRING *result, const char *in, ssize_t len) +{ + const unsigned char *cp; + ssize_t count; + static int pad_count[] = {0, 6, 4, 3, 1}; + + /* + * Encode 5 -> 8. + */ + VSTRING_RESET(result); + for (cp = UNSIG_CHAR_PTR(in), count = len; count > 0; count -= 5, cp += 5) { + VSTRING_ADDCH(result, to_b32[cp[0] >> 3]); + if (count < 2) { + VSTRING_ADDCH(result, to_b32[(cp[0] & 0x7) << 2]); + break; + } + VSTRING_ADDCH(result, to_b32[(cp[0] & 0x7) << 2 | cp[1] >> 6]); + VSTRING_ADDCH(result, to_b32[(cp[1] & 0x3f) >> 1]); + if (count < 3) { + VSTRING_ADDCH(result, to_b32[(cp[1] & 0x1) << 4]); + break; + } + VSTRING_ADDCH(result, to_b32[(cp[1] & 0x1) << 4 | cp[2] >> 4]); + if (count < 4) { + VSTRING_ADDCH(result, to_b32[(cp[2] & 0xf) << 1]); + break; + } + VSTRING_ADDCH(result, to_b32[(cp[2] & 0xf) << 1 | cp[3] >> 7]); + VSTRING_ADDCH(result, to_b32[(cp[3] & 0x7f) >> 2]); + if (count < 5) { + VSTRING_ADDCH(result, to_b32[(cp[3] & 0x3) << 3]); + break; + } + VSTRING_ADDCH(result, to_b32[(cp[3] & 0x3) << 3 | cp[4] >> 5]); + VSTRING_ADDCH(result, to_b32[cp[4] & 0x1f]); + } + if (count > 0) + vstring_strncat(result, "======", pad_count[count]); + VSTRING_TERMINATE(result); + return (result); +} + +/* base32_decode - encoded data to raw */ + +VSTRING *base32_decode(VSTRING *result, const char *in, ssize_t len) +{ + static unsigned char *un_b32 = 0; + const unsigned char *cp; + ssize_t count; + unsigned int ch0; + unsigned int ch1; + unsigned int ch2; + unsigned int ch3; + unsigned int ch4; + unsigned int ch5; + unsigned int ch6; + unsigned int ch7; + +#define CHARS_PER_BYTE (UCHAR_MAX + 1) +#define INVALID 0xff +#if 1 +#define ENFORCE_LENGTH(x) (x) +#define ENFORCE_PADDING(x) (x) +#define ENFORCE_NULL_BITS(x) (x) +#else +#define ENFORCE_LENGTH(x) (1) +#define ENFORCE_PADDING(x) (1) +#define ENFORCE_NULL_BITS(x) (1) +#endif + + /* + * Sanity check. + */ + if (ENFORCE_LENGTH(len % 8)) + return (0); + + /* + * Once: initialize the decoding lookup table on the fly. + */ + if (un_b32 == 0) { + un_b32 = (unsigned char *) mymalloc(CHARS_PER_BYTE); + memset(un_b32, INVALID, CHARS_PER_BYTE); + for (cp = to_b32; cp < to_b32 + sizeof(to_b32) - 1; cp++) + un_b32[*cp] = cp - to_b32; + } + + /* + * Decode 8 -> 5. + */ + VSTRING_RESET(result); + for (cp = UNSIG_CHAR_PTR(in), count = 0; count < len; count += 8) { + if ((ch0 = un_b32[*cp++]) == INVALID + || (ch1 = un_b32[*cp++]) == INVALID) + return (0); + VSTRING_ADDCH(result, ch0 << 3 | ch1 >> 2); + if ((ch2 = *cp++) == '=' + && ENFORCE_PADDING(strcmp((char *) cp, "=====") == 0) + && ENFORCE_NULL_BITS((ch1 & 0x3) == 0)) + break; + if ((ch2 = un_b32[ch2]) == INVALID) + return (0); + if ((ch3 = un_b32[*cp++]) == INVALID) + return (0); + VSTRING_ADDCH(result, ch1 << 6 | ch2 << 1 | ch3 >> 4); + if ((ch4 = *cp++) == '=' + && ENFORCE_PADDING(strcmp((char *) cp, "===") == 0) + && ENFORCE_NULL_BITS((ch3 & 0xf) == 0)) + break; + if ((ch4 = un_b32[ch4]) == INVALID) + return (0); + VSTRING_ADDCH(result, ch3 << 4 | ch4 >> 1); + if ((ch5 = *cp++) == '=' + && ENFORCE_PADDING(strcmp((char *) cp, "==") == 0) + && ENFORCE_NULL_BITS((ch4 & 0x1) == 0)) + break; + if ((ch5 = un_b32[ch5]) == INVALID) + return (0); + if ((ch6 = un_b32[*cp++]) == INVALID) + return (0); + VSTRING_ADDCH(result, ch4 << 7 | ch5 << 2 | ch6 >> 3); + if ((ch7 = *cp++) == '=' + && ENFORCE_NULL_BITS((ch6 & 0x7) == 0)) + break; + if ((ch7 = un_b32[ch7]) == INVALID) + return (0); + VSTRING_ADDCH(result, ch6 << 5 | ch7); + } + VSTRING_TERMINATE(result); + return (result); +} + +#ifdef TEST + + /* + * Proof-of-concept test program: convert to base 32 and back. + */ + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +int main(int unused_argc, char **unused_argv) +{ + VSTRING *b1 = vstring_alloc(1); + VSTRING *b2 = vstring_alloc(1); + VSTRING *test = vstring_alloc(1); + int i, j; + + /* + * Test all byte values (except null) on all byte positions. + */ + for (j = 0; j < 256; j++) + for (i = 1; i < 256; i++) + VSTRING_ADDCH(test, i); + VSTRING_TERMINATE(test); + +#define DECODE(b,x,l) { \ + if (base32_decode((b),(x),(l)) == 0) \ + msg_panic("bad base32: %s", (x)); \ + } +#define VERIFY(b,t,l) { \ + if (memcmp((b), (t), (l)) != 0) \ + msg_panic("bad test: %s", (b)); \ + } + + /* + * Test all padding variants. + */ + for (i = 1; i <= 8; i++) { + base32_encode(b1, STR(test), LEN(test)); + DECODE(b2, STR(b1), LEN(b1)); + VERIFY(STR(b2), STR(test), LEN(test)); + + base32_encode(b1, STR(test), LEN(test)); + base32_encode(b2, STR(b1), LEN(b1)); + base32_encode(b1, STR(b2), LEN(b2)); + DECODE(b2, STR(b1), LEN(b1)); + DECODE(b1, STR(b2), LEN(b2)); + DECODE(b2, STR(b1), LEN(b1)); + VERIFY(STR(b2), STR(test), LEN(test)); + + base32_encode(b1, STR(test), LEN(test)); + base32_encode(b2, STR(b1), LEN(b1)); + base32_encode(b1, STR(b2), LEN(b2)); + base32_encode(b2, STR(b1), LEN(b1)); + base32_encode(b1, STR(b2), LEN(b2)); + DECODE(b2, STR(b1), LEN(b1)); + DECODE(b1, STR(b2), LEN(b2)); + DECODE(b2, STR(b1), LEN(b1)); + DECODE(b1, STR(b2), LEN(b2)); + DECODE(b2, STR(b1), LEN(b1)); + VERIFY(STR(b2), STR(test), LEN(test)); + vstring_truncate(test, LEN(test) - 1); + } + vstring_free(test); + vstring_free(b1); + vstring_free(b2); + return (0); +} + +#endif diff --git a/src/util/base32_code.h b/src/util/base32_code.h new file mode 100644 index 0000000..aa258fe --- /dev/null +++ b/src/util/base32_code.h @@ -0,0 +1,41 @@ +#ifndef _BASE32_CODE_H_INCLUDED_ +#define _BASE32_CODE_H_INCLUDED_ + +/*++ +/* NAME +/* base32_code 3h +/* SUMMARY +/* encode/decode data, base 32 style +/* SYNOPSIS +/* #include <base32_code.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <vstring.h> + + /* + * External interface. + */ +extern VSTRING *base32_encode(VSTRING *, const char *, ssize_t); +extern VSTRING *WARN_UNUSED_RESULT base32_decode(VSTRING *, const char *, ssize_t); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/base64_code.c b/src/util/base64_code.c new file mode 100644 index 0000000..e607218 --- /dev/null +++ b/src/util/base64_code.c @@ -0,0 +1,228 @@ +/*++ +/* NAME +/* base64_code 3 +/* SUMMARY +/* encode/decode data, base 64 style +/* SYNOPSIS +/* #include <base64_code.h> +/* +/* VSTRING *base64_encode(result, in, len) +/* VSTRING *result; +/* const char *in; +/* ssize_t len; +/* +/* VSTRING *base64_decode(result, in, len) +/* VSTRING *result; +/* const char *in; +/* ssize_t len; +/* +/* VSTRING *base64_encode_opt(result, in, len, flags) +/* VSTRING *result; +/* const char *in; +/* ssize_t len; +/* int flags; +/* +/* VSTRING *base64_decode_opt(result, in, len, flags) +/* VSTRING *result; +/* const char *in; +/* ssize_t len; +/* int flags; +/* DESCRIPTION +/* base64_encode() takes a block of len bytes and encodes it as one +/* null-terminated string. The result value is the result argument. +/* +/* base64_decode() performs the opposite transformation. The result +/* value is the result argument. The result is null terminated, whether +/* or not that makes sense. +/* +/* base64_encode_opt() and base64_decode_opt() provide extended +/* interfaces. In both cases the flags arguments is the bit-wise +/* OR of zero or more the following: +/* .IP BASE64_FLAG_APPEND +/* Append the result, instead of overwriting the result buffer. +/* .PP +/* For convenience, BASE64_FLAG_NONE specifies none of the above. +/* DIAGNOSTICS +/* base64_decode () returns a null pointer when the input contains +/* characters not in the base 64 alphabet. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <ctype.h> +#include <string.h> +#include <limits.h> + +#ifndef UCHAR_MAX +#define UCHAR_MAX 0xff +#endif + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstring.h> +#include <base64_code.h> + +/* Application-specific. */ + +static unsigned char to_b64[] = +"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +#define UNSIG_CHAR_PTR(x) ((unsigned char *)(x)) + +/* base64_encode - raw data to encoded */ + +#undef base64_encode + +extern VSTRING *base64_encode(VSTRING *, const char *, ssize_t); + +VSTRING *base64_encode(VSTRING *result, const char *in, ssize_t len) +{ + return (base64_encode_opt(result, in, len, BASE64_FLAG_NONE)); +} + +VSTRING *base64_encode_opt(VSTRING *result, const char *in, ssize_t len, + int flags) +{ + const unsigned char *cp; + ssize_t count; + + /* + * Encode 3 -> 4. + */ + if ((flags & BASE64_FLAG_APPEND) == 0) + VSTRING_RESET(result); + for (cp = UNSIG_CHAR_PTR(in), count = len; count > 0; count -= 3, cp += 3) { + VSTRING_ADDCH(result, to_b64[cp[0] >> 2]); + if (count > 1) { + VSTRING_ADDCH(result, to_b64[(cp[0] & 0x3) << 4 | cp[1] >> 4]); + if (count > 2) { + VSTRING_ADDCH(result, to_b64[(cp[1] & 0xf) << 2 | cp[2] >> 6]); + VSTRING_ADDCH(result, to_b64[cp[2] & 0x3f]); + } else { + VSTRING_ADDCH(result, to_b64[(cp[1] & 0xf) << 2]); + VSTRING_ADDCH(result, '='); + break; + } + } else { + VSTRING_ADDCH(result, to_b64[(cp[0] & 0x3) << 4]); + VSTRING_ADDCH(result, '='); + VSTRING_ADDCH(result, '='); + break; + } + } + VSTRING_TERMINATE(result); + return (result); +} + +/* base64_decode - encoded data to raw */ + +#undef base64_decode + +extern VSTRING *base64_decode(VSTRING *, const char *, ssize_t); + +VSTRING *base64_decode(VSTRING *result, const char *in, ssize_t len) +{ + return (base64_decode_opt(result, in, len, BASE64_FLAG_NONE)); +} + +VSTRING *base64_decode_opt(VSTRING *result, const char *in, ssize_t len, + int flags) +{ + static unsigned char *un_b64 = 0; + const unsigned char *cp; + ssize_t count; + unsigned int ch0; + unsigned int ch1; + unsigned int ch2; + unsigned int ch3; + +#define CHARS_PER_BYTE (UCHAR_MAX + 1) +#define INVALID 0xff + + /* + * Sanity check. + */ + if (len % 4) + return (0); + + /* + * Once: initialize the decoding lookup table on the fly. + */ + if (un_b64 == 0) { + un_b64 = (unsigned char *) mymalloc(CHARS_PER_BYTE); + memset(un_b64, INVALID, CHARS_PER_BYTE); + for (cp = to_b64; cp < to_b64 + sizeof(to_b64) - 1; cp++) + un_b64[*cp] = cp - to_b64; + } + + /* + * Decode 4 -> 3. + */ + if ((flags & BASE64_FLAG_APPEND) == 0) + VSTRING_RESET(result); + for (cp = UNSIG_CHAR_PTR(in), count = 0; count < len; count += 4) { + if ((ch0 = un_b64[*cp++]) == INVALID + || (ch1 = un_b64[*cp++]) == INVALID) + return (0); + VSTRING_ADDCH(result, ch0 << 2 | ch1 >> 4); + if ((ch2 = *cp++) == '=') + break; + if ((ch2 = un_b64[ch2]) == INVALID) + return (0); + VSTRING_ADDCH(result, ch1 << 4 | ch2 >> 2); + if ((ch3 = *cp++) == '=') + break; + if ((ch3 = un_b64[ch3]) == INVALID) + return (0); + VSTRING_ADDCH(result, ch2 << 6 | ch3); + } + VSTRING_TERMINATE(result); + return (result); +} + +#ifdef TEST + + /* + * Proof-of-concept test program: convert to base 64 and back. + */ + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +int main(int unused_argc, char **unused_argv) +{ + VSTRING *b1 = vstring_alloc(1); + VSTRING *b2 = vstring_alloc(1); + char test[256]; + int n; + + for (n = 0; n < sizeof(test); n++) + test[n] = n; + base64_encode(b1, test, sizeof(test)); + if (base64_decode(b2, STR(b1), LEN(b1)) == 0) + msg_panic("bad base64: %s", STR(b1)); + if (LEN(b2) != sizeof(test)) + msg_panic("bad decode length: %ld != %ld", + (long) LEN(b2), (long) sizeof(test)); + for (n = 0; n < sizeof(test); n++) + if (STR(b2)[n] != test[n]) + msg_panic("bad decode value %d != %d", + (unsigned char) STR(b2)[n], (unsigned char) test[n]); + vstring_free(b1); + vstring_free(b2); + return (0); +} + +#endif diff --git a/src/util/base64_code.h b/src/util/base64_code.h new file mode 100644 index 0000000..fefdf4e --- /dev/null +++ b/src/util/base64_code.h @@ -0,0 +1,49 @@ +#ifndef _BASE64_CODE_H_INCLUDED_ +#define _BASE64_CODE_H_INCLUDED_ + +/*++ +/* NAME +/* base64_code 3h +/* SUMMARY +/* encode/decode data, base 64 style +/* SYNOPSIS +/* #include <base64_code.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <vstring.h> + + /* + * External interface. + */ +extern VSTRING *base64_encode_opt(VSTRING *, const char *, ssize_t, int); +extern VSTRING *WARN_UNUSED_RESULT base64_decode_opt(VSTRING *, const char *, ssize_t, int); + +#define BASE64_FLAG_NONE 0 +#define BASE64_FLAG_APPEND (1<<0) + +#define base64_encode(bp, cp, ln) \ + base64_encode_opt((bp), (cp), (ln), BASE64_FLAG_NONE) +#define base64_decode(bp, cp, ln) \ + base64_decode_opt((bp), (cp), (ln), BASE64_FLAG_NONE) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/basename.c b/src/util/basename.c new file mode 100644 index 0000000..429e6cf --- /dev/null +++ b/src/util/basename.c @@ -0,0 +1,49 @@ +/*++ +/* NAME +/* basename 3 +/* SUMMARY +/* extract file basename +/* SYNOPSIS +/* #include <stringops.h> +/* +/* char *basename(path) +/* const char *path; +/* DESCRIPTION +/* The \fBbasename\fR routine skips over the last '/' in +/* \fIpath\fR and returns a pointer to the result. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> + +#ifndef HAVE_BASENAME + +/* Utility library. */ + +#include "stringops.h" + +/* basename - skip directory prefix */ + +char *basename(const char *path) +{ + char *result; + + if ((result = strrchr(path, '/')) == 0) + result = (char *) path; + else + result += 1; + return (result); +} + +#endif diff --git a/src/util/binhash.c b/src/util/binhash.c new file mode 100644 index 0000000..fd9bc87 --- /dev/null +++ b/src/util/binhash.c @@ -0,0 +1,447 @@ +/*++ +/* NAME +/* binhash 3 +/* SUMMARY +/* hash table manager +/* SYNOPSIS +/* #include <binhash.h> +/* +/* typedef struct { +/* .in +4 +/* void *key; +/* ssize_t key_len; +/* void *value; +/* /* private fields... */ +/* .in -4 +/* } BINHASH_INFO; +/* +/* BINHASH *binhash_create(size) +/* ssize_t size; +/* +/* BINHASH_INFO *binhash_enter(table, key, key_len, value) +/* BINHASH *table; +/* const void *key; +/* ssize_t key_len; +/* void *value; +/* +/* char *binhash_find(table, key, key_len) +/* BINHASH *table; +/* const void *key; +/* ssize_t key_len; +/* +/* BINHASH_INFO *binhash_locate(table, key, key_len) +/* BINHASH *table; +/* const void *key; +/* ssize_t key_len; +/* +/* void binhash_delete(table, key, key_len, free_fn) +/* BINHASH *table; +/* const void *key; +/* ssize_t key_len; +/* void (*free_fn)(void *); +/* +/* void binhash_free(table, free_fn) +/* BINHASH *table; +/* void (*free_fn)(void *); +/* +/* void binhash_walk(table, action, ptr) +/* BINHASH *table; +/* void (*action)(BINHASH_INFO *info, void *ptr); +/* void *ptr; +/* +/* BINHASH_INFO **binhash_list(table) +/* BINHASH *table; +/* +/* BINHASH_INFO *binhash_sequence(table, how) +/* BINHASH *table; +/* int how; +/* DESCRIPTION +/* This module maintains one or more hash tables. Each table entry +/* consists of a unique binary-valued lookup key and a generic +/* character-pointer value. +/* The tables are automatically resized when they fill up. When the +/* values to be remembered are not character pointers, proper casts +/* should be used or the code will not be portable. +/* +/* binhash_create() creates a table of the specified size and returns a +/* pointer to the result. The lookup keys are saved with mymemdup(). +/* +/* binhash_enter() stores a (key, value) pair into the specified table +/* and returns a pointer to the resulting entry. The code does not +/* check if an entry with that key already exists: use binhash_locate() +/* for updating an existing entry. The key is copied; the value is not. +/* +/* binhash_find() returns the value that was stored under the given key, +/* or a null pointer if it was not found. In order to distinguish +/* a null value from a non-existent value, use binhash_locate(). +/* +/* binhash_locate() returns a pointer to the entry that was stored +/* for the given key, or a null pointer if it was not found. +/* +/* binhash_delete() removes one entry that was stored under the given key. +/* If the free_fn argument is not a null pointer, the corresponding +/* function is called with as argument the value that was stored under +/* the key. +/* +/* binhash_free() destroys a hash table, including contents. If the free_fn +/* argument is not a null pointer, the corresponding function is called +/* for each table entry, with as argument the value that was stored +/* with the entry. +/* +/* binhash_walk() invokes the action function for each table entry, with +/* a pointer to the entry as its argument. The ptr argument is passed +/* on to the action function. +/* +/* binhash_list() returns a null-terminated list of pointers to +/* all elements in the named table. The list should be passed to +/* myfree(). +/* +/* binhash_sequence() returns the first or next element +/* depending on the value of the "how" argument. Specify +/* BINHASH_SEQ_FIRST to start a new sequence, BINHASH_SEQ_NEXT +/* to continue, and BINHASH_SEQ_STOP to terminate a sequence +/* early. The caller must not delete an element before it is +/* visited. +/* RESTRICTIONS +/* A callback function should not modify the hash table that is +/* specified to its caller. +/* DIAGNOSTICS +/* The following conditions are reported and cause the program to +/* terminate immediately: memory allocation failure; an attempt +/* to delete a non-existent entry. +/* SEE ALSO +/* mymalloc(3) memory management wrapper +/* hash_fnv(3) Fowler/Noll/Vo hash function +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* C library */ + +#include <sys_defs.h> +#include <string.h> + +/* Local stuff */ + +#include "mymalloc.h" +#include "msg.h" +#include "binhash.h" + +/* binhash_hash - hash a string */ + +#ifndef NO_HASH_FNV +#include "hash_fnv.h" + +#define binhash_hash(key, len, size) (hash_fnv((key), (len)) % (size)) + +#else + +static size_t binhash_hash(const void *key, ssize_t len, size_t size) +{ + size_t h = 0; + size_t g; + + /* + * From the "Dragon" book by Aho, Sethi and Ullman. + */ + + while (len-- > 0) { + h = (h << 4U) + *(unsigned const char *) key++; + if ((g = (h & 0xf0000000)) != 0) { + h ^= (g >> 24U); + h ^= g; + } + } + return (h % size); +} + +#endif + +/* binhash_link - insert element into table */ + +#define binhash_link(table, elm) { \ + BINHASH_INFO **_h = table->data + binhash_hash(elm->key, elm->key_len, table->size);\ + elm->prev = 0; \ + if ((elm->next = *_h) != 0) \ + (*_h)->prev = elm; \ + *_h = elm; \ + table->used++; \ +} + +/* binhash_size - allocate and initialize hash table */ + +static void binhash_size(BINHASH *table, size_t size) +{ + BINHASH_INFO **h; + + size |= 1; + + table->data = h = (BINHASH_INFO **) mymalloc(size * sizeof(BINHASH_INFO *)); + table->size = size; + table->used = 0; + + while (size-- > 0) + *h++ = 0; +} + +/* binhash_create - create initial hash table */ + +BINHASH *binhash_create(ssize_t size) +{ + BINHASH *table; + + table = (BINHASH *) mymalloc(sizeof(BINHASH)); + binhash_size(table, size < 13 ? 13 : size); + table->seq_bucket = table->seq_element = 0; + return (table); +} + +/* binhash_grow - extend existing table */ + +static void binhash_grow(BINHASH *table) +{ + BINHASH_INFO *ht; + BINHASH_INFO *next; + ssize_t old_size = table->size; + BINHASH_INFO **h = table->data; + BINHASH_INFO **old_entries = h; + + binhash_size(table, 2 * old_size); + + while (old_size-- > 0) { + for (ht = *h++; ht; ht = next) { + next = ht->next; + binhash_link(table, ht); + } + } + myfree((void *) old_entries); +} + +/* binhash_enter - enter (key, value) pair */ + +BINHASH_INFO *binhash_enter(BINHASH *table, const void *key, ssize_t key_len, void *value) +{ + BINHASH_INFO *ht; + + if (table->used >= table->size) + binhash_grow(table); + ht = (BINHASH_INFO *) mymalloc(sizeof(BINHASH_INFO)); + ht->key = mymemdup(key, key_len); + ht->key_len = key_len; + ht->value = value; + binhash_link(table, ht); + return (ht); +} + +/* binhash_find - lookup value */ + +void *binhash_find(BINHASH *table, const void *key, ssize_t key_len) +{ + BINHASH_INFO *ht; + +#define KEY_EQ(x,y,l) (((unsigned char *) x)[0] == ((unsigned char *) y)[0] && memcmp(x,y,l) == 0) + + if (table != 0) + for (ht = table->data[binhash_hash(key, key_len, table->size)]; ht; ht = ht->next) + if (key_len == ht->key_len && KEY_EQ(key, ht->key, key_len)) + return (ht->value); + return (0); +} + +/* binhash_locate - lookup entry */ + +BINHASH_INFO *binhash_locate(BINHASH *table, const void *key, ssize_t key_len) +{ + BINHASH_INFO *ht; + + if (table != 0) + for (ht = table->data[binhash_hash(key, key_len, table->size)]; ht; ht = ht->next) + if (key_len == ht->key_len && KEY_EQ(key, ht->key, key_len)) + return (ht); + return (0); +} + +/* binhash_delete - delete one entry */ + +void binhash_delete(BINHASH *table, const void *key, ssize_t key_len, void (*free_fn) (void *)) +{ + if (table != 0) { + BINHASH_INFO *ht; + BINHASH_INFO **h = table->data + binhash_hash(key, key_len, table->size); + + for (ht = *h; ht; ht = ht->next) { + if (key_len == ht->key_len && KEY_EQ(key, ht->key, key_len)) { + if (ht->next) + ht->next->prev = ht->prev; + if (ht->prev) + ht->prev->next = ht->next; + else + *h = ht->next; + table->used--; + myfree(ht->key); + if (free_fn) + (*free_fn) (ht->value); + myfree((void *) ht); + return; + } + } + msg_panic("binhash_delete: unknown_key: \"%s\"", (char *) key); + } +} + +/* binhash_free - destroy hash table */ + +void binhash_free(BINHASH *table, void (*free_fn) (void *)) +{ + if (table != 0) { + ssize_t i = table->size; + BINHASH_INFO *ht; + BINHASH_INFO *next; + BINHASH_INFO **h = table->data; + + while (i-- > 0) { + for (ht = *h++; ht; ht = next) { + next = ht->next; + myfree(ht->key); + if (free_fn) + (*free_fn) (ht->value); + myfree((void *) ht); + } + } + myfree((void *) table->data); + table->data = 0; + if (table->seq_bucket) + myfree((void *) table->seq_bucket); + table->seq_bucket = 0; + myfree((void *) table); + } +} + +/* binhash_walk - iterate over hash table */ + +void binhash_walk(BINHASH *table, void (*action) (BINHASH_INFO *, void *), + void *ptr) { + if (table != 0) { + ssize_t i = table->size; + BINHASH_INFO **h = table->data; + BINHASH_INFO *ht; + + while (i-- > 0) + for (ht = *h++; ht; ht = ht->next) + (*action) (ht, ptr); + } +} + +/* binhash_list - list all table members */ + +BINHASH_INFO **binhash_list(table) +BINHASH *table; +{ + BINHASH_INFO **list; + BINHASH_INFO *member; + ssize_t count = 0; + ssize_t i; + + if (table != 0) { + list = (BINHASH_INFO **) mymalloc(sizeof(*list) * (table->used + 1)); + for (i = 0; i < table->size; i++) + for (member = table->data[i]; member != 0; member = member->next) + list[count++] = member; + } else { + list = (BINHASH_INFO **) mymalloc(sizeof(*list)); + } + list[count] = 0; + return (list); +} + +/* binhash_sequence - dict(3) compatibility iterator */ + +BINHASH_INFO *binhash_sequence(BINHASH *table, int how) +{ + if (table == 0) + return (0); + + switch (how) { + case BINHASH_SEQ_FIRST: /* start new sequence */ + if (table->seq_bucket) + myfree((void *) table->seq_bucket); + table->seq_bucket = binhash_list(table); + table->seq_element = table->seq_bucket; + return (*(table->seq_element)++); + case BINHASH_SEQ_NEXT: /* next element */ + if (table->seq_element && *table->seq_element) + return (*(table->seq_element)++); + /* FALLTHROUGH */ + default: /* terminate sequence */ + if (table->seq_bucket) { + myfree((void *) table->seq_bucket); + table->seq_bucket = table->seq_element = 0; + } + return (0); + } +} + +#ifdef TEST +#include <vstring_vstream.h> +#include <myrand.h> + +int main(int unused_argc, char **unused_argv) +{ + VSTRING *buf = vstring_alloc(10); + ssize_t count = 0; + BINHASH *hash; + BINHASH_INFO **ht_info; + BINHASH_INFO **ht; + BINHASH_INFO *info; + ssize_t i; + ssize_t r; + int op; + + /* + * Load a large number of strings including terminator, and delete them + * in a random order. + */ + hash = binhash_create(10); + while (vstring_get(buf, VSTREAM_IN) != VSTREAM_EOF) + binhash_enter(hash, vstring_str(buf), VSTRING_LEN(buf) + 1, + CAST_INT_TO_VOID_PTR(count++)); + if (count != hash->used) + msg_panic("%ld entries stored, but %lu entries exist", + (long) count, (unsigned long) hash->used); + for (i = 0, op = BINHASH_SEQ_FIRST; (info = binhash_sequence(hash, op)) != 0; + i++, op = BINHASH_SEQ_NEXT) + if (memchr(info->key, 0, info->key_len) == 0) + msg_panic("no null byte in lookup key"); + if (i != hash->used) + msg_panic("%ld entries found, but %lu entries exist", + (long) i, (unsigned long) hash->used); + ht_info = binhash_list(hash); + for (i = 0; i < hash->used; i++) { + r = myrand() % hash->used; + info = ht_info[i]; + ht_info[i] = ht_info[r]; + ht_info[r] = info; + } + for (ht = ht_info; *ht; ht++) + binhash_delete(hash, ht[0]->key, ht[0]->key_len, (void (*) (void *)) 0); + if (hash->used > 0) + msg_panic("%ld entries not deleted", (long) hash->used); + myfree((void *) ht_info); + binhash_free(hash, (void (*) (void *)) 0); + vstring_free(buf); + return (0); +} + +#endif diff --git a/src/util/binhash.h b/src/util/binhash.h new file mode 100644 index 0000000..3b4e1a4 --- /dev/null +++ b/src/util/binhash.h @@ -0,0 +1,65 @@ +#ifndef _BINHASH_H_INCLUDED_ +#define _BINHASH_H_INCLUDED_ + +/*++ +/* NAME +/* binhash 3h +/* SUMMARY +/* hash table manager +/* SYNOPSIS +/* #include <binhash.h> +/* DESCRIPTION +/* .nf + + /* Structure of one hash table entry. */ + +typedef struct BINHASH_INFO { + void *key; /* lookup key */ + ssize_t key_len; /* key length */ + void *value; /* associated value */ + struct BINHASH_INFO *next; /* colliding entry */ + struct BINHASH_INFO *prev; /* colliding entry */ +} BINHASH_INFO; + + /* Structure of one hash table. */ + +typedef struct BINHASH { + ssize_t size; /* length of entries array */ + ssize_t used; /* number of entries in table */ + BINHASH_INFO **data; /* entries array, auto-resized */ + BINHASH_INFO **seq_bucket; /* current sequence hash bucket */ + BINHASH_INFO **seq_element; /* current sequence element */ +} BINHASH; + +extern BINHASH *binhash_create(ssize_t); +extern BINHASH_INFO *binhash_enter(BINHASH *, const void *, ssize_t, void *); +extern BINHASH_INFO *binhash_locate(BINHASH *, const void *, ssize_t); +extern void *binhash_find(BINHASH *, const void *, ssize_t); +extern void binhash_delete(BINHASH *, const void *, ssize_t, void (*) (void *)); +extern void binhash_free(BINHASH *, void (*) (void *)); +extern void binhash_walk(BINHASH *, void (*) (BINHASH_INFO *, void *), void *); +extern BINHASH_INFO **binhash_list(BINHASH *); +extern BINHASH_INFO *binhash_sequence(BINHASH *, int); + +#define BINHASH_SEQ_FIRST 0 +#define BINHASH_SEQ_NEXT 1 +#define BINHASH_SEQ_STOP (-1) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* CREATION DATE +/* Thu Feb 20 16:54:29 EST 1997 +/* LAST MODIFICATION +/* %E% %U% +/* VERSION/RELEASE +/* %I% +/*--*/ + +#endif diff --git a/src/util/byte_mask.c b/src/util/byte_mask.c new file mode 100644 index 0000000..e818476 --- /dev/null +++ b/src/util/byte_mask.c @@ -0,0 +1,306 @@ +/*++ +/* NAME +/* byte_mask 3 +/* SUMMARY +/* map byte sequence to bit mask +/* SYNOPSIS +/* #include <byte_mask.h> +/* +/* typedef struct { +/* .in +4 +/* int byte_val; +/* int mask; +/* .in -4 +/* } BYTE_MASK; +/* +/* int byte_mask( +/* const char *context, +/* const BYTE_MASK *table, +/* const char *bytes); +/* +/* const char *str_byte_mask( +/* const char *context, +/* const BYTE_MASK *table, +/* int mask); +/* +/* int byte_mask_opt( +/* const char *context; +/* const BYTE_MASK *table, +/* const char *bytes, +/* int flags); +/* +/* const char *str_byte_mask_opt( +/* VSTRING *buf, +/* const char *context, +/* const BYTE_MASK *table, +/* int mask, +/* int flags); +/* DESCRIPTION +/* byte_mask() takes a null-terminated \fItable\fR with (byte +/* value, single-bit mask) pairs and computes the bit-wise OR +/* of the masks that correspond to the byte values pointed to +/* by the \fIbytes\fR argument. +/* +/* str_byte_mask() translates a bit mask into its equivalent +/* bytes. The result is written to a static buffer that is +/* overwritten upon each call. +/* +/* byte_mask_opt() and str_byte_mask_opt() are extended versions +/* with additional fine control. +/* +/* Arguments: +/* .IP buf +/* Null pointer or pointer to buffer storage. +/* .IP context +/* What kind of byte values and bit masks are being manipulated, +/* reported in error messages. Typically, this would be the +/* name of a user-configurable parameter or command-line +/* attribute. +/* .IP table +/* Table with (byte value, single-bit mask) pairs. +/* .IP bytes +/* A null-terminated string that is to be converted into a bit +/* mask. +/* .IP mask +/* A bit mask that is to be converted into null-terminated +/* string. +/* .IP flags +/* Bit-wise OR of one or more of the following. Where features +/* would have conflicting results (e.g., FATAL versus IGNORE), +/* the feature that takes precedence is described first. +/* +/* When converting from string to mask, at least one of the +/* following must be specified: BYTE_MASK_FATAL, BYTE_MASK_RETURN, +/* BYTE_MASK_WARN or BYTE_MASK_IGNORE. +/* +/* When converting from mask to string, at least one of the +/* following must be specified: BYTE_MASK_FATAL, BYTE_MASK_RETURN, +/* BYTE_MASK_WARN or BYTE_MASK_IGNORE. +/* .RS +/* .IP BYTE_MASK_FATAL +/* Require that all values in \fIbytes\fR exist in \fItable\fR, +/* and require that all bits listed in \fImask\fR exist in +/* \fItable\fR. Terminate with a fatal run-time error if this +/* condition is not met. This feature is enabled by default +/* when calling byte_mask() or str_name_mask(). +/* .IP BYTE_MASK_RETURN +/* Require that all values in \fIbytes\fR exist in \fItable\fR, +/* and require that all bits listed in \fImask\fR exist in +/* \fItable\fR. Log a warning, and return 0 (byte_mask()) or +/* a null pointer (str_byte_mask()) if this condition is not +/* met. This feature is not enabled by default when calling +/* byte_mask() or str_name_mask(). +/* .IP BYTE_MASK_WARN +/* Require that all values in \fIbytes\fR exist in \fItable\fR, +/* and require that all bits listed in \fImask\fR exist in +/* \fItable\fR. Log a warning if this condition is not met, +/* continue processing, and return all valid bits or bytes. +/* This feature is not enabled by default when calling byte_mask() +/* or str_byte_mask(). +/* .IP BYTE_MASK_IGNORE +/* Silently ignore values in \fIbytes\fR that don't exist in +/* \fItable\fR, and silently ignore bits listed in \fImask\fR +/* that don't exist in \fItable\fR. This feature is not enabled +/* by default when calling byte_mask() or str_byte_mask(). +/* .IP BYTE_MASK_ANY_CASE +/* Enable case-insensitive matching. This feature is not +/* enabled by default when calling byte_mask(); it has no +/* effect with str_byte_mask(). +/* .RE +/* The value BYTE_MASK_NONE explicitly requests no features, +/* and BYTE_MASK_DEFAULT enables the default options. +/* DIAGNOSTICS +/* Fatal: the \fIbytes\fR argument specifies a name not found in +/* \fItable\fR, or the \fImask\fR specifies a bit not found in +/* \fItable\fR. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* HISTORY +/* This code is a clone of Postfix name_mask(3). +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> +#include <ctype.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <stringops.h> +#include <byte_mask.h> +#include <vstring.h> + +#define STR(x) vstring_str(x) + +/* byte_mask_opt - compute mask corresponding to byte string */ + +int byte_mask_opt(const char *context, const BYTE_MASK *table, + const char *bytes, int flags) +{ + const char myname[] = "byte_mask"; + const char *bp; + int result = 0; + const BYTE_MASK *np; + + if ((flags & BYTE_MASK_REQUIRED) == 0) + msg_panic("%s: missing BYTE_MASK_FATAL/RETURN/WARN/IGNORE flag", + myname); + + /* + * Iterate over bytes string, and look up each byte in the table. If the + * byte is found, merge its mask with the result. + */ + for (bp = bytes; *bp; bp++) { + int byte_val = *(const unsigned char *) bp; + + for (np = table; /* void */ ; np++) { + if (np->byte_val == 0) { + if (flags & BYTE_MASK_FATAL) { + msg_fatal("unknown %s value \"%c\" in \"%s\"", + context, byte_val, bytes); + } else if (flags & BYTE_MASK_RETURN) { + msg_warn("unknown %s value \"%c\" in \"%s\"", + context, byte_val, bytes); + return (0); + } else if (flags & BYTE_MASK_WARN) { + msg_warn("unknown %s value \"%c\" in \"%s\"", + context, byte_val, bytes); + } + break; + } + if ((flags & BYTE_MASK_ANY_CASE) ? + (TOLOWER(byte_val) == TOLOWER(np->byte_val)) : + (byte_val == np->byte_val)) { + if (msg_verbose) + msg_info("%s: %c", myname, byte_val); + result |= np->mask; + break; + } + } + } + return (result); +} + +/* str_byte_mask_opt - mask to string */ + +const char *str_byte_mask_opt(VSTRING *buf, const char *context, + const BYTE_MASK *table, + int mask, int flags) +{ + const char myname[] = "byte_mask"; + const BYTE_MASK *np; + static VSTRING *my_buf = 0; + + if ((flags & STR_BYTE_MASK_REQUIRED) == 0) + msg_panic("%s: missing BYTE_MASK_FATAL/RETURN/WARN/IGNORE flag", + myname); + + if (buf == 0) { + if (my_buf == 0) + my_buf = vstring_alloc(1); + buf = my_buf; + } + VSTRING_RESET(buf); + + for (np = table; mask != 0; np++) { + if (np->byte_val == 0) { + if (flags & BYTE_MASK_FATAL) { + msg_fatal("%s: unknown %s bit in mask: 0x%x", + myname, context, mask); + } else if (flags & BYTE_MASK_RETURN) { + msg_warn("%s: unknown %s bit in mask: 0x%x", + myname, context, mask); + return (0); + } else if (flags & BYTE_MASK_WARN) { + msg_warn("%s: unknown %s bit in mask: 0x%x", + myname, context, mask); + } + break; + } + if (mask & np->mask) { + mask &= ~np->mask; + vstring_sprintf_append(buf, "%c", np->byte_val); + } + } + VSTRING_TERMINATE(buf); + + return (STR(buf)); +} + +#ifdef TEST + + /* + * Stand-alone test program. + */ +#include <stdlib.h> +#include <vstream.h> +#include <vstring_vstream.h> +#include <name_mask.h> + +int main(int argc, char **argv) +{ + static const BYTE_MASK demo_table[] = { + '0', 1 << 0, + '1', 1 << 1, + '2', 1 << 2, + '3', 1 << 3, + 0, 0, + }; + static const NAME_MASK feature_table[] = { + "DEFAULT", BYTE_MASK_DEFAULT, + "FATAL", BYTE_MASK_FATAL, + "ANY_CASE", BYTE_MASK_ANY_CASE, + "RETURN", BYTE_MASK_RETURN, + "WARN", BYTE_MASK_WARN, + "IGNORE", BYTE_MASK_IGNORE, + 0, + }; + int in_feature_mask; + int out_feature_mask; + int demo_mask; + const char *demo_str; + VSTRING *out_buf = vstring_alloc(1); + VSTRING *in_buf = vstring_alloc(1); + + if (argc != 3) + msg_fatal("usage: %s in-feature-mask out-feature-mask", argv[0]); + in_feature_mask = name_mask(argv[1], feature_table, argv[1]); + out_feature_mask = name_mask(argv[2], feature_table, argv[2]); + while (vstring_get_nonl(in_buf, VSTREAM_IN) != VSTREAM_EOF) { + demo_mask = byte_mask_opt("name", demo_table, + STR(in_buf), in_feature_mask); + demo_str = str_byte_mask_opt(out_buf, "mask", demo_table, + demo_mask, out_feature_mask); + vstream_printf("%s -> 0x%x -> %s\n", + STR(in_buf), demo_mask, + demo_str ? demo_str : "(null)"); + demo_mask <<=1; + demo_str = str_byte_mask_opt(out_buf, "mask", demo_table, + demo_mask, out_feature_mask); + vstream_printf("0x%x -> %s\n", + demo_mask, demo_str ? demo_str : "(null)"); + vstream_fflush(VSTREAM_OUT); + } + vstring_free(in_buf); + vstring_free(out_buf); + exit(0); +} + +#endif diff --git a/src/util/byte_mask.h b/src/util/byte_mask.h new file mode 100644 index 0000000..6cdea1d --- /dev/null +++ b/src/util/byte_mask.h @@ -0,0 +1,64 @@ +#ifndef _BYTE_MASK_H_INCLUDED_ +#define _BYTE_MASK_H_INCLUDED_ + +/*++ +/* NAME +/* byte_mask 3h +/* SUMMARY +/* map names to bit mask +/* SYNOPSIS +/* #include <byte_mask.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <vstring.h> + + /* + * External interface. + */ +typedef struct { + int byte_val; + int mask; +} BYTE_MASK; + +#define BYTE_MASK_FATAL (1<<0) +#define BYTE_MASK_ANY_CASE (1<<1) +#define BYTE_MASK_RETURN (1<<2) +#define BYTE_MASK_WARN (1<<6) +#define BYTE_MASK_IGNORE (1<<7) + +#define BYTE_MASK_REQUIRED \ + (BYTE_MASK_FATAL | BYTE_MASK_RETURN | BYTE_MASK_WARN | BYTE_MASK_IGNORE) +#define STR_BYTE_MASK_REQUIRED (BYTE_MASK_REQUIRED) + +#define BYTE_MASK_NONE 0 +#define BYTE_MASK_DEFAULT (BYTE_MASK_FATAL) + +#define byte_mask(tag, table, str) \ + byte_mask_opt((tag), (table), (str), BYTE_MASK_DEFAULT) +#define str_byte_mask(tag, table, mask) \ + str_byte_mask_opt(((VSTRING *) 0), (tag), (table), (mask), BYTE_MASK_DEFAULT) + +extern int byte_mask_opt(const char *, const BYTE_MASK *, const char *, int); +extern const char *str_byte_mask_opt(VSTRING *, const char *, const BYTE_MASK *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/byte_mask.in b/src/util/byte_mask.in new file mode 100644 index 0000000..6d89c5a --- /dev/null +++ b/src/util/byte_mask.in @@ -0,0 +1,7 @@ +0123 +0 +1 +2 +3 +1234 +abcd diff --git a/src/util/byte_mask.ref0 b/src/util/byte_mask.ref0 new file mode 100644 index 0000000..a6ab11b --- /dev/null +++ b/src/util/byte_mask.ref0 @@ -0,0 +1,14 @@ +0123 -> 0xf -> 0123 +0x1e -> 123 +0 -> 0x1 -> 0 +0x2 -> 1 +1 -> 0x2 -> 1 +0x4 -> 2 +2 -> 0x4 -> 2 +0x8 -> 3 +3 -> 0x8 -> 3 +0x10 -> +1234 -> 0xe -> 123 +0x1c -> 23 +abcd -> 0x0 -> +0x0 -> diff --git a/src/util/byte_mask.ref1 b/src/util/byte_mask.ref1 new file mode 100644 index 0000000..92e3f4f --- /dev/null +++ b/src/util/byte_mask.ref1 @@ -0,0 +1,22 @@ +unknown: warning: byte_mask: unknown mask bit in mask: 0x10 +0123 -> 0xf -> 0123 +0x1e -> 123 +0 -> 0x1 -> 0 +0x2 -> 1 +1 -> 0x2 -> 1 +0x4 -> 2 +2 -> 0x4 -> 2 +0x8 -> 3 +unknown: warning: byte_mask: unknown mask bit in mask: 0x10 +3 -> 0x8 -> 3 +0x10 -> +unknown: warning: unknown name value "4" in "1234" +unknown: warning: byte_mask: unknown mask bit in mask: 0x10 +1234 -> 0xe -> 123 +0x1c -> 23 +unknown: warning: unknown name value "a" in "abcd" +unknown: warning: unknown name value "b" in "abcd" +unknown: warning: unknown name value "c" in "abcd" +unknown: warning: unknown name value "d" in "abcd" +abcd -> 0x0 -> +0x0 -> diff --git a/src/util/byte_mask.ref2 b/src/util/byte_mask.ref2 new file mode 100644 index 0000000..631ec53 --- /dev/null +++ b/src/util/byte_mask.ref2 @@ -0,0 +1,10 @@ +unknown: fatal: byte_mask: unknown mask bit in mask: 0x10 +0 -> 0x1 -> 0 +0x2 -> 1 +1 -> 0x2 -> 1 +0x4 -> 2 +2 -> 0x4 -> 2 +0x8 -> 3 +unknown: fatal: byte_mask: unknown mask bit in mask: 0x10 +unknown: fatal: unknown name value "4" in "1234" +unknown: fatal: unknown name value "a" in "abcd" diff --git a/src/util/cache.in b/src/util/cache.in new file mode 100644 index 0000000..89974bd --- /dev/null +++ b/src/util/cache.in @@ -0,0 +1,26 @@ +a +1 +b +2 +c +3 +d +4 +e +5 +f +6 +f +e +d +c +b +a +1 +b +c +d +e +f +6 +f diff --git a/src/util/casefold.c b/src/util/casefold.c new file mode 100644 index 0000000..d3ebd4b --- /dev/null +++ b/src/util/casefold.c @@ -0,0 +1,359 @@ +/*++ +/* NAME +/* casefold 3 +/* SUMMARY +/* casefold text for caseless comparison +/* SYNOPSIS +/* #include <stringops.h> +/* +/* char *casefold( +/* VSTRING *dst, +/* const char *src) +/* +/* char *casefold_append( +/* VSTRING *dst, +/* const char *src) +/* +/* char *casefold_len( +/* VSTRING *dst, +/* const char *src, +/* ssize_t src_len) +/* AUXILIARY FUNCTIONS +/* char *casefoldx( +/* int flags, +/* VSTRING *dst, +/* const char *src, +/* ssize_t src_len) +/* DESCRIPTION +/* casefold() converts text to a form that is suitable for +/* caseless comparison, rather than presentation to humans. +/* +/* When compiled without EAI support or util_utf8_enable is +/* zero, casefold() implements ASCII case folding, leaving +/* non-ASCII byte values unchanged. +/* +/* When compiled with EAI support and util_utf8_enable is +/* non-zero, casefold() implements UTF-8 case folding using +/* the en_US locale, as recommended when the conversion result +/* is not meant to be presented to humans. +/* +/* casefold_len() implements casefold() with a source length +/* argument. +/* +/* casefold_append() implements casefold() without overwriting +/* the result. +/* +/* casefoldx() implements a more complex API that implements +/* all of the above and more. +/* +/* Arguments: +/* .IP src +/* Null-terminated input string. +/* .IP dest +/* Output buffer, null-terminated. Specify a null pointer to +/* use an internal buffer that is overwritten upon each call. +/* .IP src_len +/* The string length, -1 to determine the length dynamically. +/* .IP flags +/* Bitwise OR of zero or more of the following: +/* .RS +/* .IP CASEF_FLAG_UTF8 +/* Enable UTF-8 support. This flag has no effect when compiled +/* without EAI support. +/* .IP CASEF_FLAG_APPEND +/* Append the result to the buffer, instead of overwriting it. +/* DIAGNOSTICS +/* All errors are fatal. There appear to be no input-dependent +/* errors. +/* +/* With the ICU 4.8 library, there is no casefold error for +/* UTF-8 code points U+0000..U+10FFFF (including surrogate +/* range), not even when running inside an empty chroot jail. +/* Nor does malformed UTF-8 trigger errors; non-UTF-8 bytes +/* are copied verbatim. Based on ICU 4.8 source-code review +/* and experimentation(!) we conclude that UTF-8 casefolding +/* has no data-dependent error cases, and that it is safe to +/* treat all casefolding errors as fatal runtime errors. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> +#include <ctype.h> +#ifndef NO_EAI +#include <unicode/ucasemap.h> +#include <unicode/ustring.h> +#include <unicode/uchar.h> +#endif + +/* Utility library. */ + +#include <msg.h> +#include <stringops.h> + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* casefoldx - casefold an UTF-8 string */ + +char *casefoldx(int flags, VSTRING *dest, const char *src, ssize_t len) +{ + size_t old_len; + +#ifdef NO_EAI + + /* + * ASCII mode only. + */ + if (len < 0) + len = strlen(src); + if ((flags & CASEF_FLAG_APPEND) == 0) + VSTRING_RESET(dest); + old_len = VSTRING_LEN(dest); + vstring_strncat(dest, src, len); + lowercase(STR(dest) + old_len); + return (STR(dest)); +#else + + /* + * Unicode mode. + */ + const char myname[] = "casefold"; + static VSTRING *fold_buf = 0; + static UCaseMap *csm = 0; + UErrorCode error; + ssize_t space_needed; + int n; + + /* + * Handle special cases. + */ + if (len < 0) + len = strlen(src); + if (dest == 0) + dest = (fold_buf != 0 ? fold_buf : (fold_buf = vstring_alloc(100))); + if ((flags & CASEF_FLAG_APPEND) == 0) + VSTRING_RESET(dest); + old_len = VSTRING_LEN(dest); + + /* + * All-ASCII input, or ASCII mode only. + */ + if ((flags & CASEF_FLAG_UTF8) == 0 || allascii(src)) { + vstring_strncat(dest, src, len); + lowercase(STR(dest) + old_len); + return (STR(dest)); + } + + /* + * ICU 4.8 ucasemap_utf8FoldCase() does not complain about UTF-8 syntax + * errors. XXX Based on source-code review we conclude that non-UTF-8 + * bytes are copied verbatim, and experiments confirm this. Given that + * this behavior is intentional, we assume that it will stay that way. + */ +#if 0 + if (valid_utf8_string(src, len) == 0) { + if (err) + *err = "malformed UTF-8 or invalid codepoint"; + return (0); + } +#endif + + /* + * One-time initialization. With ICU 4.8 this works while chrooted. + */ + if (csm == 0) { + error = U_ZERO_ERROR; + csm = ucasemap_open("en_US", U_FOLD_CASE_DEFAULT, &error); + if (U_SUCCESS(error) == 0) + msg_fatal("ucasemap_open error: %s", u_errorName(error)); + } + + /* + * Fold the input, adjusting the buffer size if needed. Safety: don't + * loop forever. + * + * Note: the requested amount of space for casemapped output (as reported + * with space_needed below) does not include storage for the null + * terminator. The terminator is written only when the output buffer is + * large enough. This is why we overallocate space when the output does + * not fit. But if the output fits exactly, then the output will be + * unterminated, and we have to terminate the output ourselves. + */ + for (n = 0; n < 3; n++) { + error = U_ZERO_ERROR; + space_needed = ucasemap_utf8FoldCase(csm, STR(dest) + old_len, + vstring_avail(dest), src, len, &error); + if (U_SUCCESS(error)) { + vstring_set_payload_size(dest, old_len + space_needed); + if (vstring_avail(dest) == 0) /* exact fit, no terminator */ + VSTRING_TERMINATE(dest); /* add terminator */ + break; + } else if (error == U_BUFFER_OVERFLOW_ERROR) { + VSTRING_SPACE(dest, space_needed + 1); /* for terminator */ + } else { + msg_fatal("%s: conversion error for \"%s\": %s", + myname, src, u_errorName(error)); + } + } + return (STR(dest)); +#endif /* NO_EAI */ +} + +#ifdef TEST + +static void encode_utf8(VSTRING *buffer, int codepoint) +{ + const char myname[] = "encode_utf8"; + + VSTRING_RESET(buffer); + if (codepoint < 0x80) { + VSTRING_ADDCH(buffer, codepoint); + } else if (codepoint < 0x800) { + VSTRING_ADDCH(buffer, 0xc0 | (codepoint >> 6)); + VSTRING_ADDCH(buffer, 0x80 | (codepoint & 0x3f)); + } else if (codepoint < 0x10000) { + VSTRING_ADDCH(buffer, 0xe0 | (codepoint >> 12)); + VSTRING_ADDCH(buffer, 0x80 | ((codepoint >> 6) & 0x3f)); + VSTRING_ADDCH(buffer, 0x80 | (codepoint & 0x3f)); + } else if (codepoint <= 0x10FFFF) { + VSTRING_ADDCH(buffer, 0xf0 | (codepoint >> 18)); + VSTRING_ADDCH(buffer, 0x80 | ((codepoint >> 12) & 0x3f)); + VSTRING_ADDCH(buffer, 0x80 | ((codepoint >> 6) & 0x3f)); + VSTRING_ADDCH(buffer, 0x80 | (codepoint & 0x3f)); + } else { + msg_panic("%s: out-of-range codepoint U+%X", myname, codepoint); + } + VSTRING_TERMINATE(buffer); +} + +#include <stdlib.h> +#include <stdio.h> +#include <locale.h> + +#include <vstream.h> +#include <vstring_vstream.h> +#include <msg_vstream.h> + +int main(int argc, char **argv) +{ + VSTRING *buffer = vstring_alloc(1); + VSTRING *dest = vstring_alloc(1); + char *bp; + char *conv_res; + char *cmd; + int codepoint, first, last; + VSTREAM *fp; + + if (setlocale(LC_ALL, "C") == 0) + msg_fatal("setlocale(LC_ALL, C) failed: %m"); + + msg_vstream_init(argv[0], VSTREAM_ERR); + + util_utf8_enable = 1; + + VSTRING_SPACE(buffer, 256); /* chroot/file pathname */ + + while (vstring_fgets_nonl(buffer, VSTREAM_IN)) { + bp = STR(buffer); + vstream_printf("> %s\n", bp); + cmd = mystrtok(&bp, CHARS_SPACE); + if (cmd == 0 || *cmd == '#') + continue; + while (ISSPACE(*bp)) + bp++; + + /* + * Null-terminated string. + */ + if (strcmp(cmd, "fold") == 0) { + conv_res = casefold(dest, bp); + vstream_printf("\"%s\" ->fold \"%s\"\n", bp, conv_res); + } + + /* + * Codepoint range. + */ + else if (strcmp(cmd, "range") == 0 + && sscanf(bp, "%i %i", &first, &last) == 2 + && first <= last) { + for (codepoint = first; codepoint <= last; codepoint++) { + if (codepoint >= 0xD800 && codepoint <= 0xDFFF) { + vstream_printf("skipping surrogate range\n"); + codepoint = 0xDFFF; + } else { + encode_utf8(buffer, codepoint); + if (msg_verbose) + vstream_printf("U+%X -> %s\n", codepoint, STR(buffer)); + if (valid_utf8_string(STR(buffer), LEN(buffer)) == 0) + msg_fatal("bad utf-8 encoding for U+%X\n", codepoint); + casefold(dest, STR(buffer)); + } + } + vstream_printf("range completed: 0x%x..0x%x\n", first, last); + } + + /* + * Chroot directory. + */ + else if (strcmp(cmd, "chroot") == 0 + && sscanf(bp, "%255s", STR(buffer)) == 1) { + if (geteuid() == 0) { + if (chdir(STR(buffer)) < 0) + msg_fatal("chdir(%s): %m", STR(buffer)); + if (chroot(STR(buffer)) < 0) + msg_fatal("chroot(%s): %m", STR(buffer)); + vstream_printf("chroot %s completed\n", STR(buffer)); + } + } + + /* + * File. + */ + else if (strcmp(cmd, "file") == 0 + && sscanf(bp, "%255s", STR(buffer)) == 1) { + if ((fp = vstream_fopen(STR(buffer), O_RDONLY, 0)) == 0) + msg_fatal("open(%s): %m", STR(buffer)); + while (vstring_fgets_nonl(buffer, fp)) + vstream_printf("%s\n", casefold(dest, STR(buffer))); + vstream_fclose(fp); + } + + /* + * Verbose. + */ + else if (strcmp(cmd, "verbose") == 0 + && sscanf(bp, "%i", &msg_verbose) == 1) { + /* void */ ; + } + + /* + * Usage + */ + else { + vstream_printf("Usage: %s chroot <path> | file <path> | fold <text> | range <first> <last> | verbose <int>\n", + argv[0]); + } + vstream_fflush(VSTREAM_OUT); + } + vstring_free(buffer); + vstring_free(dest); + exit(0); +} + +#endif /* TEST */ diff --git a/src/util/casefold_test.in b/src/util/casefold_test.in new file mode 100644 index 0000000..c1a530e --- /dev/null +++ b/src/util/casefold_test.in @@ -0,0 +1,24 @@ +# Ignored when not running as root. +chroot /tmp +# Casefold U+0000 .. U+10FFFF excluding surrogates. +range 0x0 0xD7FF +range 0xD800 0xD800 +range 0xDFFF 0xDFFF +range 0xE000 0x10FFFF +# Demonstrate that range is not a noop. +verbose 1 +range 0xE000 0xE007 +verbose 0 +# Upper-case greek -> lower-case greek. +fold Δημοσθένους.example.com +# Exact-fit null termination test. +fold Δημοσθένους.exxample.com +# Upper-case ASCII -> lower-case ASCII. +fold HeLlO.ExAmPlE.CoM +# Folding does not change aliases for '.'. +fold x。example.com +fold x.example.com +fold x。example.com +# Bad UTF-8 +fold YYY€€€ +fold €€€XXX diff --git a/src/util/casefold_test.ref b/src/util/casefold_test.ref new file mode 100644 index 0000000..639fe06 --- /dev/null +++ b/src/util/casefold_test.ref @@ -0,0 +1,47 @@ +> # Ignored when not running as root. +> chroot /tmp +> # Casefold U+0000 .. U+10FFFF excluding surrogates. +> range 0x0 0xD7FF +range completed: 0x0..0xd7ff +> range 0xD800 0xD800 +skipping surrogate range +range completed: 0xd800..0xd800 +> range 0xDFFF 0xDFFF +skipping surrogate range +range completed: 0xdfff..0xdfff +> range 0xE000 0x10FFFF +range completed: 0xe000..0x10ffff +> # Demonstrate that range is not a noop. +> verbose 1 +> range 0xE000 0xE007 +U+E000 ->  +U+E001 -> î€ +U+E002 ->  +U+E003 ->  +U+E004 ->  +U+E005 ->  +U+E006 ->  +U+E007 ->  +range completed: 0xe000..0xe007 +> verbose 0 +> # Upper-case greek -> lower-case greek. +> fold Δημοσθένους.example.com +"Δημοσθένους.example.com" ->fold "δημοσθένουσ.example.com" +> # Exact-fit null termination test. +> fold Δημοσθένους.exxample.com +"Δημοσθένους.exxample.com" ->fold "δημοσθένουσ.exxample.com" +> # Upper-case ASCII -> lower-case ASCII. +> fold HeLlO.ExAmPlE.CoM +"HeLlO.ExAmPlE.CoM" ->fold "hello.example.com" +> # Folding does not change aliases for '.'. +> fold x。example.com +"x。example.com" ->fold "x。example.com" +> fold x.example.com +"x.example.com" ->fold "x.example.com" +> fold x。example.com +"x。example.com" ->fold "x。example.com" +> # Bad UTF-8 +> fold YYY€€€ +"YYY€€€" ->fold "yyy€€€" +> fold €€€XXX +"€€€XXX" ->fold "€€€xxx" diff --git a/src/util/check_arg.h b/src/util/check_arg.h new file mode 100644 index 0000000..09f0932 --- /dev/null +++ b/src/util/check_arg.h @@ -0,0 +1,157 @@ +#ifndef _CHECK_ARG_INCLUDED_ +#define _CHECK_ARG_INCLUDED_ + +/*++ +/* NAME +/* check_arg 3h +/* SUMMARY +/* type checking/narrowing/widening for unprototyped arguments +/* SYNOPSIS +/* #include <check_arg.h> +/* +/* /* Example checking infrastructure for int, int *, const int *. */ +/* CHECK_VAL_HELPER_DCL(tag, int); +/* CHECK_PTR_HELPER_DCL(tag, int); +/* CHECK_CPTR_HELPER_DCL(tag, int); +/* +/* /* Example variables with type int, int *, const int *. */ +/* int int_val; +/* int *int_ptr; +/* const int *int_cptr; +/* +/* /* Example variadic function with type-flag arguments. */ +/* func(FLAG_INT_VAL, CHECK_VAL(tag, int, int_val), +/* FLAG_INT_PTR, CHECK_PTR(tag, int, int_ptr), +/* FLAG_INT_CPTR, CHECK_CPTR(tag, int, int_cptr) +/* FLAG_END); +/* DESCRIPTION +/* This module implements wrappers for unprototyped function +/* arguments, to enable the same type checking, type narrowing, +/* and type widening as for prototyped function arguments. The +/* wrappers may also be useful in other contexts. +/* +/* Typically, these wrappers are hidden away in a per-module +/* header file that is read by the consumers of that module. +/* To protect consumers against name collisions between wrappers +/* in different header files, wrappers should be called with +/* a distinct per-module tag value. The tag syntax is that +/* of a C identifier. +/* +/* Use CHECK_VAL(tag, type, argument) for arguments with a +/* basic type: int, long, etc., and types defined with "typedef" +/* where indirection is built into the type itself (for example, +/* the result of "typedef int *foo" or function pointer +/* typedefs). +/* +/* Use CHECK_PTR(tag, type, argument) for non-const pointer +/* arguments, CHECK_CPTR(tag, type, argument) for const pointer +/* arguments, and CHECK_PPTR(tag, type, argument) for pointer- +/* to-pointer arguments. +/* +/* Use CHECK_*_HELPER_DCL(tag, type) to provide the +/* checking infrastructure for all CHECK_*(tag, type, ...) +/* instances with the same *, tag and type. Depending on +/* the compilation environment, the infrastructure consists +/* of an inline function definition or a dummy assignment +/* target declaration. +/* +/* The compiler should report the following problems: +/* .IP \(bu +/* Const pointer argument where a non-const pointer is expected. +/* .IP \(bu +/* Pointer argument where a non-pointer is expected and +/* vice-versa. +/* .IP \(bu +/* Pointer/pointer type mismatches except void/non-void pointers. +/* Just like function prototypes, all CHECK_*PTR() wrappers +/* cast their result to the desired type. +/* .IP \(bu +/* Non-constant non-pointer argument where a pointer is expected. +/*. PP +/* Just like function prototypes, the CHECK_*PTR() wrappers +/* handle "bare" numerical constants by casting their argument +/* to the desired pointer type. +/* +/* Just like function prototypes, the CHECK_VAL() wrapper +/* cannot force the caller to specify a particular non-pointer +/* type and casts its argument value to the desired type which +/* may wider or narrower than the argument value. +/* IMPLEMENTATION + + /* + * Choose between an implementation based on inline functions (standardized + * with C99) or conditional assignment (portable to older compilers, with + * some caveats as discussed below). + */ +#ifndef NO_INLINE + + /* + * Parameter checks expand into inline helper function calls. + */ +#define CHECK_VAL(tag, type, v) check_val_##tag##type(v) +#define CHECK_PTR(tag, type, v) check_ptr_##tag##type(v) +#define CHECK_CPTR(tag, type, v) check_cptr_##tag##type(v) +#define CHECK_PPTR(tag, type, v) check_pptr_##tag##type(v) + + /* + * Macros to instantiate the inline helper functions. + */ +#define CHECK_VAL_HELPER_DCL(tag, type) \ + static inline type check_val_##tag##type(type v) { return v; } +#define CHECK_PTR_HELPER_DCL(tag, type) \ + static inline type *check_ptr_##tag##type(type *v) { return v; } +#define CHECK_CPTR_HELPER_DCL(tag, type) \ + static inline const type *check_cptr_##tag##type(const type *v) \ + { return v; } +#define CHECK_PPTR_HELPER_DCL(tag, type) \ + static inline type **check_pptr_##tag##type(type **v) { return v; } + +#else /* NO_INLINE */ + + /* + * Parameter checks expand into unreachable conditional assignments. + * Inspired by OpenSSL's verified pointer check, our implementation also + * detects const/non-const pointer conflicts, and it also supports + * non-pointer expressions. + */ +#define CHECK_VAL(tag, type, v) ((type) (1 ? (v) : (CHECK_VAL_DUMMY(type) = (v)))) +#define CHECK_PTR(tag, type, v) ((type *) (1 ? (v) : (CHECK_PTR_DUMMY(type) = (v)))) +#define CHECK_CPTR(tag, type, v) \ + ((const type *) (1 ? (v) : (CHECK_CPTR_DUMMY(type) = (v)))) +#define CHECK_PPTR(tag, type, v) ((type **) (1 ? (v) : (CHECK_PPTR_DUMMY(type) = (v)))) + + /* + * These macros instantiate assignment target declarations. Since the + * assignment is made in unreachable code, the compiler "should" not emit + * any references to those assignment targets. We use the "extern" class so + * that gcc will not complain about unused variables. Using "extern" breaks + * when a compiler does emit references to unreachable assignment targets. + * Hopefully, those cases will be rare. + */ +#define CHECK_VAL_HELPER_DCL(tag, type) extern type CHECK_VAL_DUMMY(type) +#define CHECK_PTR_HELPER_DCL(tag, type) extern type *CHECK_PTR_DUMMY(type) +#define CHECK_CPTR_HELPER_DCL(tag, type) extern const type *CHECK_CPTR_DUMMY(type) +#define CHECK_PPTR_HELPER_DCL(tag, type) extern type **CHECK_PPTR_DUMMY(type) + + /* + * The actual dummy assignment target names. + */ +#define CHECK_VAL_DUMMY(type) check_val_dummy_##type +#define CHECK_PTR_DUMMY(type) check_ptr_dummy_##type +#define CHECK_CPTR_DUMMY(type) check_cptr_dummy_##type +#define CHECK_PPTR_DUMMY(type) check_pptr_dummy_##type + +#endif /* NO_INLINE */ + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/chroot_uid.c b/src/util/chroot_uid.c new file mode 100644 index 0000000..4a7660f --- /dev/null +++ b/src/util/chroot_uid.c @@ -0,0 +1,88 @@ +/*++ +/* NAME +/* chroot_uid 3 +/* SUMMARY +/* limit possible damage a process can do +/* SYNOPSIS +/* #include <chroot_uid.h> +/* +/* void chroot_uid(root_dir, user_name) +/* const char *root_dir; +/* const char *user_name; +/* DESCRIPTION +/* \fBchroot_uid\fR changes the process root to \fIroot_dir\fR and +/* changes process privileges to those of \fIuser_name\fR. +/* DIAGNOSTICS +/* System call errors are reported via the msg(3) interface. +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <pwd.h> +#include <unistd.h> +#include <grp.h> + +/* Utility library. */ + +#include "msg.h" +#include "chroot_uid.h" + +/* chroot_uid - restrict the damage that this program can do */ + +void chroot_uid(const char *root_dir, const char *user_name) +{ + struct passwd *pwd; + uid_t uid; + gid_t gid; + + /* + * Look up the uid/gid before entering the jail, and save them so they + * can't be clobbered. Set up the primary and secondary groups. + */ + if (user_name != 0) { + if ((pwd = getpwnam(user_name)) == 0) + msg_fatal("unknown user: %s", user_name); + uid = pwd->pw_uid; + gid = pwd->pw_gid; + if (setgid(gid) < 0) + msg_fatal("setgid(%ld): %m", (long) gid); + if (initgroups(user_name, gid) < 0) + msg_fatal("initgroups: %m"); + } + + /* + * Enter the jail. + */ + if (root_dir) { + if (chroot(root_dir)) + msg_fatal("chroot(%s): %m", root_dir); + if (chdir("/")) + msg_fatal("chdir(/): %m"); + } + + /* + * Drop the user privileges. + */ + if (user_name != 0) + if (setuid(uid) < 0) + msg_fatal("setuid(%ld): %m", (long) uid); + + /* + * Give the desperate developer a clue of what is happening. + */ + if (msg_verbose > 1) + msg_info("chroot %s user %s", + root_dir ? root_dir : "(none)", + user_name ? user_name : "(none)"); +} diff --git a/src/util/chroot_uid.h b/src/util/chroot_uid.h new file mode 100644 index 0000000..f2a8399 --- /dev/null +++ b/src/util/chroot_uid.h @@ -0,0 +1,29 @@ +#ifndef _CHROOT_UID_H_INCLUDED_ +#define _CHROOT_UID_H_INCLUDED_ + +/*++ +/* NAME +/* chroot_uid 3h +/* SUMMARY +/* limit possible damage a process can do +/* SYNOPSIS +/* #include <chroot_uid.h> +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern void chroot_uid(const char *, const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/cidr_match.c b/src/util/cidr_match.c new file mode 100644 index 0000000..0ae7c56 --- /dev/null +++ b/src/util/cidr_match.c @@ -0,0 +1,316 @@ +/*++ +/* NAME +/* cidr_match 3 +/* SUMMARY +/* CIDR-style pattern matching +/* SYNOPSIS +/* #include <cidr_match.h> +/* +/* VSTRING *cidr_match_parse(info, pattern, match, why) +/* CIDR_MATCH *info; +/* char *pattern; +/* VSTRING *why; +/* +/* int cidr_match_execute(info, address) +/* CIDR_MATCH *info; +/* const char *address; +/* AUXILIARY FUNCTIONS +/* VSTRING *cidr_match_parse_if(info, pattern, match, why) +/* CIDR_MATCH *info; +/* char *pattern; +/* VSTRING *why; +/* +/* void cidr_match_endif(info) +/* CIDR_MATCH *info; +/* DESCRIPTION +/* This module parses address or address/length patterns and +/* provides simple address matching. The implementation is +/* such that parsing and execution can be done without dynamic +/* memory allocation. The purpose is to minimize overhead when +/* called by functions that parse and execute on the fly, such +/* as match_hostaddr(). +/* +/* cidr_match_parse() parses an address or address/mask +/* expression and stores the result into the info argument. +/* A non-zero (or zero) match argument requests a positive (or +/* negative) match. The symbolic constants CIDR_MATCH_TRUE and +/* CIDR_MATCH_FALSE may help to improve code readability. +/* The result is non-zero in case of problems: either the +/* value of the why argument, or a newly allocated VSTRING +/* (the caller should give the latter to vstring_free()). +/* The pattern argument is destroyed. +/* +/* cidr_match_parse_if() parses the address that follows an IF +/* token, and stores the result into the info argument. +/* The arguments are the same as for cidr_match_parse(). +/* +/* cidr_match_endif() handles the occurrence of an ENDIF token, +/* and updates the info argument. +/* +/* cidr_match_execute() matches the specified address against +/* a list of parsed expressions, and returns the matching +/* expression's data structure. +/* SEE ALSO +/* dict_cidr(3) CIDR-style lookup table +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstring.h> +#include <stringops.h> +#include <split_at.h> +#include <myaddrinfo.h> +#include <mask_addr.h> +#include <cidr_match.h> + +/* Application-specific. */ + + /* + * This is how we figure out the address family, address bit count and + * address byte count for a CIDR_MATCH entry. + */ +#ifdef HAS_IPV6 +#define CIDR_MATCH_ADDR_FAMILY(a) (strchr((a), ':') ? AF_INET6 : AF_INET) +#define CIDR_MATCH_ADDR_BIT_COUNT(f) \ + ((f) == AF_INET6 ? MAI_V6ADDR_BITS : \ + (f) == AF_INET ? MAI_V4ADDR_BITS : \ + (msg_panic("%s: bad address family %d", myname, (f)), 0)) +#define CIDR_MATCH_ADDR_BYTE_COUNT(f) \ + ((f) == AF_INET6 ? MAI_V6ADDR_BYTES : \ + (f) == AF_INET ? MAI_V4ADDR_BYTES : \ + (msg_panic("%s: bad address family %d", myname, (f)), 0)) +#else +#define CIDR_MATCH_ADDR_FAMILY(a) (AF_INET) +#define CIDR_MATCH_ADDR_BIT_COUNT(f) \ + ((f) == AF_INET ? MAI_V4ADDR_BITS : \ + (msg_panic("%s: bad address family %d", myname, (f)), 0)) +#define CIDR_MATCH_ADDR_BYTE_COUNT(f) \ + ((f) == AF_INET ? MAI_V4ADDR_BYTES : \ + (msg_panic("%s: bad address family %d", myname, (f)), 0)) +#endif + +/* cidr_match_entry - match one entry */ + +static inline int cidr_match_entry(CIDR_MATCH *entry, + unsigned char *addr_bytes) +{ + unsigned char *mp; + unsigned char *np; + unsigned char *ap; + + /* Unoptimized case: netmask with some or all bits zero. */ + if (entry->mask_shift < entry->addr_bit_count) { + for (np = entry->net_bytes, mp = entry->mask_bytes, + ap = addr_bytes; /* void */ ; np++, mp++, ap++) { + if (ap >= addr_bytes + entry->addr_byte_count) + return (entry->match); + if ((*ap & *mp) != *np) + break; + } + } + /* Optimized case: all 1 netmask (i.e. no netmask specified). */ + else { + for (np = entry->net_bytes, + ap = addr_bytes; /* void */ ; np++, ap++) { + if (ap >= addr_bytes + entry->addr_byte_count) + return (entry->match); + if (*ap != *np) + break; + } + } + return (!entry->match); +} + +/* cidr_match_execute - match address against compiled CIDR pattern list */ + +CIDR_MATCH *cidr_match_execute(CIDR_MATCH *list, const char *addr) +{ + unsigned char addr_bytes[CIDR_MATCH_ABYTES]; + unsigned addr_family; + CIDR_MATCH *entry; + + addr_family = CIDR_MATCH_ADDR_FAMILY(addr); + if (inet_pton(addr_family, addr, addr_bytes) != 1) + return (0); + + for (entry = list; entry; entry = entry->next) { + + switch (entry->op) { + + case CIDR_MATCH_OP_MATCH: + if (entry->addr_family == addr_family) + if (cidr_match_entry(entry, addr_bytes)) + return (entry); + break; + + case CIDR_MATCH_OP_IF: + if (entry->addr_family == addr_family) + if (cidr_match_entry(entry, addr_bytes)) + continue; + /* An IF without matching ENDIF has no end-of block entry. */ + if ((entry = entry->block_end) == 0) + return (0); + /* FALLTHROUGH */ + + case CIDR_MATCH_OP_ENDIF: + continue; + } + } + return (0); +} + +/* cidr_match_parse - parse CIDR pattern */ + +VSTRING *cidr_match_parse(CIDR_MATCH *ip, char *pattern, int match, + VSTRING *why) +{ + const char *myname = "cidr_match_parse"; + char *mask_search; + char *mask; + MAI_HOSTADDR_STR hostaddr; + unsigned char *np; + unsigned char *mp; + + /* + * Strip [] from [addr/len] or [addr]/len, destroying the pattern. CIDR + * maps don't need [] to eliminate syntax ambiguity, but matchlists need + * it. While stripping [], figure out where we should start looking for + * /mask information. + */ + if (*pattern == '[') { + pattern++; + if ((mask_search = split_at(pattern, ']')) == 0) { + vstring_sprintf(why ? why : (why = vstring_alloc(20)), + "missing ']' character after \"[%s\"", pattern); + return (why); + } else if (*mask_search != '/') { + if (*mask_search != 0) { + vstring_sprintf(why ? why : (why = vstring_alloc(20)), + "garbage after \"[%s]\"", pattern); + return (why); + } + mask_search = pattern; + } + } else + mask_search = pattern; + + /* + * Parse the pattern into network and mask, destroying the pattern. + */ + if ((mask = split_at(mask_search, '/')) != 0) { + const char *parse_error; + + ip->addr_family = CIDR_MATCH_ADDR_FAMILY(pattern); + ip->addr_bit_count = CIDR_MATCH_ADDR_BIT_COUNT(ip->addr_family); + ip->addr_byte_count = CIDR_MATCH_ADDR_BYTE_COUNT(ip->addr_family); + if (!alldig(mask)) { + parse_error = "bad mask value"; + } else if ((ip->mask_shift = atoi(mask)) > ip->addr_bit_count) { + parse_error = "bad mask length"; + } else if (inet_pton(ip->addr_family, pattern, ip->net_bytes) != 1) { + parse_error = "bad network value"; + } else { + parse_error = 0; + } + if (parse_error != 0) { + vstring_sprintf(why ? why : (why = vstring_alloc(20)), + "%s in \"%s/%s\"", parse_error, pattern, mask); + return (why); + } + if (ip->mask_shift > 0) { + /* Allow for bytes > 8. */ + memset(ip->mask_bytes, ~0U, ip->addr_byte_count); + mask_addr(ip->mask_bytes, ip->addr_byte_count, ip->mask_shift); + } else + memset(ip->mask_bytes, 0, ip->addr_byte_count); + + /* + * Sanity check: all host address bits must be zero. + */ + for (np = ip->net_bytes, mp = ip->mask_bytes; + np < ip->net_bytes + ip->addr_byte_count; np++, mp++) { + if (*np & ~(*mp)) { + mask_addr(ip->net_bytes, ip->addr_byte_count, ip->mask_shift); + if (inet_ntop(ip->addr_family, ip->net_bytes, hostaddr.buf, + sizeof(hostaddr.buf)) == 0) + msg_fatal("inet_ntop: %m"); + vstring_sprintf(why ? why : (why = vstring_alloc(20)), + "non-null host address bits in \"%s/%s\", " + "perhaps you should use \"%s/%d\" instead", + pattern, mask, hostaddr.buf, ip->mask_shift); + return (why); + } + } + } + + /* + * No /mask specified. Treat a bare network address as /allbits. + */ + else { + ip->addr_family = CIDR_MATCH_ADDR_FAMILY(pattern); + ip->addr_bit_count = CIDR_MATCH_ADDR_BIT_COUNT(ip->addr_family); + ip->addr_byte_count = CIDR_MATCH_ADDR_BYTE_COUNT(ip->addr_family); + if (inet_pton(ip->addr_family, pattern, ip->net_bytes) != 1) { + vstring_sprintf(why ? why : (why = vstring_alloc(20)), + "bad address pattern: \"%s\"", pattern); + return (why); + } + ip->mask_shift = ip->addr_bit_count; + /* Allow for bytes > 8. */ + memset(ip->mask_bytes, ~0U, ip->addr_byte_count); + } + + /* + * Wrap up the result. + */ + ip->op = CIDR_MATCH_OP_MATCH; + ip->match = match; + ip->next = 0; + ip->block_end = 0; + + return (0); +} + +/* cidr_match_parse_if - parse CIDR pattern after IF */ + +VSTRING *cidr_match_parse_if(CIDR_MATCH *ip, char *pattern, int match, + VSTRING *why) +{ + VSTRING *ret; + + if ((ret = cidr_match_parse(ip, pattern, match, why)) == 0) + ip->op = CIDR_MATCH_OP_IF; + return (ret); +} + +/* cidr_match_endif - handle ENDIF pattern */ + +void cidr_match_endif(CIDR_MATCH *ip) +{ + memset(ip, 0, sizeof(*ip)); + ip->op = CIDR_MATCH_OP_ENDIF; + ip->next = 0; /* maybe not all bits 0 */ + ip->block_end = 0; +} diff --git a/src/util/cidr_match.h b/src/util/cidr_match.h new file mode 100644 index 0000000..22f16a0 --- /dev/null +++ b/src/util/cidr_match.h @@ -0,0 +1,82 @@ +#ifndef _CIDR_MATCH_H_INCLUDED_ +#define _CIDR_MATCH_H_INCLUDED_ + +/*++ +/* NAME +/* dict_cidr 3h +/* SUMMARY +/* CIDR-style pattern matching +/* SYNOPSIS +/* #include <cidr_match.h> +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include <limits.h> /* CHAR_BIT */ + + /* + * Utility library. + */ +#include <myaddrinfo.h> /* MAI_V6ADDR_BYTES etc. */ +#include <vstring.h> + + /* + * External interface. + * + * Address length is protocol dependent. Find out how large our address byte + * strings should be. + */ +#ifdef HAS_IPV6 +# define CIDR_MATCH_ABYTES MAI_V6ADDR_BYTES +#else +# define CIDR_MATCH_ABYTES MAI_V4ADDR_BYTES +#endif + + /* + * Each parsed CIDR pattern can be member of a linked list. + */ +typedef struct CIDR_MATCH { + int op; /* operation, match or control flow */ + int match; /* positive or negative match */ + unsigned char net_bytes[CIDR_MATCH_ABYTES]; /* network portion */ + unsigned char mask_bytes[CIDR_MATCH_ABYTES]; /* network mask */ + unsigned char addr_family; /* AF_XXX */ + unsigned char addr_byte_count; /* typically, 4 or 16 */ + unsigned char addr_bit_count; /* optimization */ + unsigned char mask_shift; /* optimization */ + struct CIDR_MATCH *next; /* next entry */ + struct CIDR_MATCH *block_end; /* block terminator */ +} CIDR_MATCH; + +#define CIDR_MATCH_OP_MATCH 1 /* Match this pattern */ +#define CIDR_MATCH_OP_IF 2 /* Increase if/endif nesting on match */ +#define CIDR_MATCH_OP_ENDIF 3 /* Decrease if/endif nesting on match */ + +#define CIDR_MATCH_TRUE 1 /* Request positive match */ +#define CIDR_MATCH_FALSE 0 /* Request negative match */ + +extern VSTRING *cidr_match_parse(CIDR_MATCH *, char *, int, VSTRING *); +extern VSTRING *cidr_match_parse_if(CIDR_MATCH *, char *, int, VSTRING *); +extern void cidr_match_endif(CIDR_MATCH *); + +extern CIDR_MATCH *cidr_match_execute(CIDR_MATCH *, const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/clean_env.c b/src/util/clean_env.c new file mode 100644 index 0000000..5ae5528 --- /dev/null +++ b/src/util/clean_env.c @@ -0,0 +1,146 @@ +/*++ +/* NAME +/* clean_env 3 +/* SUMMARY +/* clean up the environment +/* SYNOPSIS +/* #include <clean_env.h> +/* +/* void clean_env(preserve_list) +/* const char **preserve_list; +/* +/* void update_env(preserve_list) +/* const char **preserve_list; +/* DESCRIPTION +/* clean_env() reduces the process environment to the bare minimum. +/* The function takes a null-terminated list of arguments. +/* Each argument specifies the name of an environment variable +/* that should be preserved, or specifies a name=value that should +/* be entered into the new environment. +/* +/* update_env() applies name=value settings, but otherwise does not +/* change the process environment. +/* DIAGNOSTICS +/* Fatal error: out of memory. +/* SEE ALSO +/* safe_getenv(3), guarded getenv() +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <argv.h> +#include <safe.h> +#include <clean_env.h> +#include <stringops.h> + +/* clean_env - clean up the environment */ + +void clean_env(char **preserve_list) +{ + extern char **environ; + ARGV *save_list; + char *value; + char **cpp; + char *copy; + char *key; + char *val; + const char *err; + + /* + * Preserve or specify selected environment variables. + */ + save_list = argv_alloc(10); + for (cpp = preserve_list; *cpp; cpp++) { + if (strchr(*cpp, '=') != 0) { + copy = mystrdup(*cpp); + err = split_nameval(copy, &key, &val); + if (err != 0) + msg_fatal("clean_env: %s in: %s", err, *cpp); + argv_add(save_list, key, val, (char *) 0); + myfree(copy); + } else if ((value = safe_getenv(*cpp)) != 0) { + argv_add(save_list, *cpp, value, (char *) 0); + } + } + + /* + * Truncate the process environment, if available. On some systems + * (Ultrix!), environ can be a null pointer. + */ + if (environ) + environ[0] = 0; + + /* + * Restore preserved environment variables. + */ + for (cpp = save_list->argv; *cpp; cpp += 2) + if (setenv(cpp[0], cpp[1], 1)) + msg_fatal("setenv(%s, %s): %m", cpp[0], cpp[1]); + + /* + * Cleanup. + */ + argv_free(save_list); +} + +/* update_env - apply name=value settings only */ + +void update_env(char **preserve_list) +{ + char **cpp; + ARGV *save_list; + char *copy; + char *key; + char *val; + const char *err; + + /* + * Extract name=value settings. + */ + save_list = argv_alloc(10); + for (cpp = preserve_list; *cpp; cpp++) { + if (strchr(*cpp, '=') != 0) { + copy = mystrdup(*cpp); + err = split_nameval(copy, &key, &val); + if (err != 0) + msg_fatal("update_env: %s in: %s", err, *cpp); + argv_add(save_list, key, val, (char *) 0); + myfree(copy); + } + } + + /* + * Apply name=value settings. + */ + for (cpp = save_list->argv; *cpp; cpp += 2) + if (setenv(cpp[0], cpp[1], 1)) + msg_fatal("setenv(%s, %s): %m", cpp[0], cpp[1]); + + /* + * Cleanup. + */ + argv_free(save_list); +} diff --git a/src/util/clean_env.h b/src/util/clean_env.h new file mode 100644 index 0000000..5c89723 --- /dev/null +++ b/src/util/clean_env.h @@ -0,0 +1,36 @@ +#ifndef _CLEAN_ENV_H_INCLUDED_ +#define _CLEAN_ENV_H_INCLUDED_ + +/*++ +/* NAME +/* clean_env 3h +/* SUMMARY +/* clean up the environment +/* SYNOPSIS +/* #include <clean_env.h> +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern void clean_env(char **); +extern void update_env(char **); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/close_on_exec.c b/src/util/close_on_exec.c new file mode 100644 index 0000000..efa3415 --- /dev/null +++ b/src/util/close_on_exec.c @@ -0,0 +1,60 @@ +/*++ +/* NAME +/* close_on_exec 3 +/* SUMMARY +/* set/clear close-on-exec flag +/* SYNOPSIS +/* #include <iostuff.h> +/* +/* int close_on_exec(int fd, int on) +/* DESCRIPTION +/* the \fIclose_on_exec\fR() function manipulates the close-on-exec +/* flag for the specified open file, and returns the old setting. +/* +/* Arguments: +/* .IP fd +/* A file descriptor. +/* .IP on +/* Use CLOSE_ON_EXEC or PASS_ON_EXEC. +/* DIAGNOSTICS +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System interfaces. */ + +#include <sys_defs.h> +#include <fcntl.h> + +/* Utility library. */ + +#include "msg.h" + +/* Application-specific. */ + +#include "iostuff.h" + +#define PATTERN FD_CLOEXEC + +/* close_on_exec - set/clear close-on-exec flag */ + +int close_on_exec(fd, on) +int fd; +int on; +{ + int flags; + + if ((flags = fcntl(fd, F_GETFD, 0)) < 0) + msg_fatal("fcntl: get flags: %m"); + if (fcntl(fd, F_SETFD, on ? flags | PATTERN : flags & ~PATTERN) < 0) + msg_fatal("fcntl: set close-on-exec flag %s: %m", on ? "on" : "off"); + return ((flags & PATTERN) != 0); +} diff --git a/src/util/compat_va_copy.h b/src/util/compat_va_copy.h new file mode 100644 index 0000000..6a2042b --- /dev/null +++ b/src/util/compat_va_copy.h @@ -0,0 +1,44 @@ +#ifndef _COMPAT_VA_COPY_H_INCLUDED_ +#define _COMPAT_VA_COPY_H_INCLUDED_ + +/*++ +/* NAME +/* compat_va_copy 3h +/* SUMMARY +/* compatibility +/* SYNOPSIS +/* #include <compat_va_copy.h> +/* DESCRIPTION +/* .nf + + /* + * C99 defines va_start and va_copy as macros, so we can probe the + * compilation environment with #ifdef etc. Some environments define + * __va_copy so we probe for that, too. + */ +#if !defined(va_start) +#error "include <stdarg.h> first" +#endif + +#if !defined(VA_COPY) +#if defined(va_copy) +#define VA_COPY(dest, src) va_copy(dest, src) +#elif defined(__va_copy) +#define VA_COPY(dest, src) __va_copy(dest, src) +#else +#define VA_COPY(dest, src) (dest) = (src) +#endif +#endif /* VA_COPY */ + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/concatenate.c b/src/util/concatenate.c new file mode 100644 index 0000000..7b6a3eb --- /dev/null +++ b/src/util/concatenate.c @@ -0,0 +1,73 @@ +/*++ +/* NAME +/* concatenate 3 +/* SUMMARY +/* concatenate strings +/* SYNOPSIS +/* #include <stringops.h> +/* +/* char *concatenate(str, ...) +/* const char *str; +/* DESCRIPTION +/* The \fBconcatenate\fR routine concatenates a null-terminated +/* list of pointers to null-terminated character strings. +/* The result is dynamically allocated and should be passed to myfree() +/* when no longer needed. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdlib.h> /* 44BSD stdarg.h uses abort() */ +#include <stdarg.h> +#include <string.h> + +/* Utility library. */ + +#include "mymalloc.h" +#include "stringops.h" +#include "compat_va_copy.h" + +/* concatenate - concatenate null-terminated list of strings */ + +char *concatenate(const char *arg0,...) +{ + char *result; + va_list ap; + va_list ap2; + ssize_t len; + char *arg; + + /* + * Initialize argument lists. + */ + va_start(ap, arg0); + VA_COPY(ap2, ap); + + /* + * Compute the length of the resulting string. + */ + len = strlen(arg0); + while ((arg = va_arg(ap, char *)) != 0) + len += strlen(arg); + va_end(ap); + + /* + * Build the resulting string. Don't care about wasting a CPU cycle. + */ + result = mymalloc(len + 1); + strcpy(result, arg0); + while ((arg = va_arg(ap2, char *)) != 0) + strcat(result, arg); + va_end(ap2); + return (result); +} diff --git a/src/util/connect.h b/src/util/connect.h new file mode 100644 index 0000000..1e8de11 --- /dev/null +++ b/src/util/connect.h @@ -0,0 +1,43 @@ +#ifndef _CONNECT_H_INCLUDED_ +#define _CONNECT_H_INCLUDED_ + +/*++ +/* NAME +/* connect 3h +/* SUMMARY +/* client interface file +/* SYNOPSIS +/* #include <connect.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <iostuff.h> + + /* + * Client external interface. + */ +extern int unix_connect(const char *, int, int); +extern int inet_connect(const char *, int, int); +extern int stream_connect(const char *, int, int); +extern int unix_dgram_connect(const char *, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/ctable.c b/src/util/ctable.c new file mode 100644 index 0000000..4993671 --- /dev/null +++ b/src/util/ctable.c @@ -0,0 +1,321 @@ +/*++ +/* NAME +/* ctable 3 +/* SUMMARY +/* cache manager +/* SYNOPSIS +/* #include <ctable.h> +/* +/* CTABLE *ctable_create(limit, create, delete, context) +/* ssize_t limit; +/* void *(*create)(const char *key, void *context); +/* void (*delete)(void *value, void *context); +/* void *context; +/* +/* const void *ctable_locate(cache, key) +/* CTABLE *cache; +/* const char *key; +/* +/* const void *ctable_refresh(cache, key) +/* CTABLE *cache; +/* const char *key; +/* +/* const void *ctable_newcontext(cache, context) +/* CTABLE *cache; +/* void *context; +/* +/* void ctable_free(cache) +/* CTABLE *cache; +/* +/* void ctable_walk(cache, action) +/* CTABLE *cache; +/* void (*action)(const char *key, const void *value); +/* DESCRIPTION +/* This module maintains multiple caches. Cache items are purged +/* automatically when the number of items exceeds a configurable +/* limit. Caches never shrink. Each cache entry consists of a +/* string-valued lookup key and a generic data pointer value. +/* +/* ctable_create() creates a cache with the specified size limit, and +/* returns a pointer to the result. The create and delete arguments +/* specify pointers to call-back functions that create a value, given +/* a key, and delete a given value, respectively. The context argument +/* is passed on to the call-back routines. +/* The create() and delete() functions must not modify the cache. +/* +/* ctable_locate() looks up or generates the value that corresponds to +/* the specified key, and returns that value. +/* +/* ctable_refresh() flushes the value (if any) associated with +/* the specified key, and returns the same result as ctable_locate(). +/* +/* ctable_newcontext() updates the context that is passed on +/* to call-back routines. +/* +/* ctable_free() destroys the specified cache, including its contents. +/* +/* ctable_walk() iterates over all elements in the cache, and invokes +/* the action function for each cache element with the corresponding +/* key and value as arguments. This function is useful mainly for +/* cache performance debugging. +/* Note: the action() function must not modify the cache. +/* DIAGNOSTICS +/* Fatal errors: out of memory. Panic: interface violation. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdlib.h> +#include <stddef.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <ring.h> +#include <htable.h> +#include <ctable.h> + + /* + * Cache entries are kept in most-recently used order. We use a hash table + * to quickly locate cache entries. + */ +#define CTABLE_ENTRY struct ctable_entry + +struct ctable_entry { + RING ring; /* MRU linkage */ + const char *key; /* lookup key */ + void *value; /* corresponding value */ +}; + +#define RING_TO_CTABLE_ENTRY(ring_ptr) \ + RING_TO_APPL(ring_ptr, CTABLE_ENTRY, ring) +#define RING_PTR_OF(x) (&((x)->ring)) + +struct ctable { + HTABLE *table; /* table with key, ctable_entry pairs */ + size_t limit; /* max nr of entries */ + size_t used; /* current nr of entries */ + CTABLE_CREATE_FN create; /* constructor */ + CTABLE_DELETE_FN delete; /* destructor */ + RING ring; /* MRU linkage */ + void *context; /* application context */ +}; + +#define CTABLE_MIN_SIZE 5 + +/* ctable_create - create empty cache */ + +CTABLE *ctable_create(ssize_t limit, CTABLE_CREATE_FN create, + CTABLE_DELETE_FN delete, void *context) +{ + CTABLE *cache = (CTABLE *) mymalloc(sizeof(CTABLE)); + const char *myname = "ctable_create"; + + if (limit < 1) + msg_panic("%s: bad cache limit: %ld", myname, (long) limit); + + cache->table = htable_create(limit); + cache->limit = (limit < CTABLE_MIN_SIZE ? CTABLE_MIN_SIZE : limit); + cache->used = 0; + cache->create = create; + cache->delete = delete; + ring_init(RING_PTR_OF(cache)); + cache->context = context; + return (cache); +} + +/* ctable_locate - look up or create cache item */ + +const void *ctable_locate(CTABLE *cache, const char *key) +{ + const char *myname = "ctable_locate"; + CTABLE_ENTRY *entry; + + /* + * If the entry is not in the cache, make sure there is room for a new + * entry and install it at the front of the MRU chain. Otherwise, move + * the entry to the front of the MRU chain if it is not already there. + * All this means that the cache never shrinks. + */ + if ((entry = (CTABLE_ENTRY *) htable_find(cache->table, key)) == 0) { + if (cache->used >= cache->limit) { + entry = RING_TO_CTABLE_ENTRY(ring_pred(RING_PTR_OF(cache))); + if (msg_verbose) + msg_info("%s: purge entry key %s", myname, entry->key); + ring_detach(RING_PTR_OF(entry)); + cache->delete(entry->value, cache->context); + htable_delete(cache->table, entry->key, (void (*) (void *)) 0); + } else { + entry = (CTABLE_ENTRY *) mymalloc(sizeof(CTABLE_ENTRY)); + cache->used++; + } + entry->value = cache->create(key, cache->context); + entry->key = htable_enter(cache->table, key, (void *) entry)->key; + ring_append(RING_PTR_OF(cache), RING_PTR_OF(entry)); + if (msg_verbose) + msg_info("%s: install entry key %s", myname, entry->key); + } else if (entry == RING_TO_CTABLE_ENTRY(ring_succ(RING_PTR_OF(cache)))) { + if (msg_verbose) + msg_info("%s: leave existing entry key %s", myname, entry->key); + } else { + ring_detach(RING_PTR_OF(entry)); + ring_append(RING_PTR_OF(cache), RING_PTR_OF(entry)); + if (msg_verbose) + msg_info("%s: move existing entry key %s", myname, entry->key); + } + return (entry->value); +} + +/* ctable_refresh - page-in fresh data for given key */ + +const void *ctable_refresh(CTABLE *cache, const char *key) +{ + const char *myname = "ctable_refresh"; + CTABLE_ENTRY *entry; + + /* Materialize entry if missing. */ + if ((entry = (CTABLE_ENTRY *) htable_find(cache->table, key)) == 0) + return ctable_locate(cache, key); + + /* Otherwise, refresh its content. */ + cache->delete(entry->value, cache->context); + entry->value = cache->create(key, cache->context); + + /* Update its MRU linkage. */ + if (entry != RING_TO_CTABLE_ENTRY(ring_succ(RING_PTR_OF(cache)))) { + ring_detach(RING_PTR_OF(entry)); + ring_append(RING_PTR_OF(cache), RING_PTR_OF(entry)); + } + if (msg_verbose) + msg_info("%s: refresh entry key %s", myname, entry->key); + return (entry->value); +} + +/* ctable_newcontext - update call-back context */ + +void ctable_newcontext(CTABLE *cache, void *context) +{ + cache->context = context; +} + +static CTABLE *ctable_free_cache; + +/* ctable_free_callback - callback function */ + +static void ctable_free_callback(void *ptr) +{ + CTABLE_ENTRY *entry = (CTABLE_ENTRY *) ptr; + + ctable_free_cache->delete(entry->value, ctable_free_cache->context); + myfree((void *) entry); +} + +/* ctable_free - destroy cache and contents */ + +void ctable_free(CTABLE *cache) +{ + CTABLE *saved_cache = ctable_free_cache; + + /* + * XXX the hash table does not pass application context so we have to + * store it in a global variable. + */ + ctable_free_cache = cache; + htable_free(cache->table, ctable_free_callback); + myfree((void *) cache); + ctable_free_cache = saved_cache; +} + +/* ctable_walk - iterate over all cache entries */ + +void ctable_walk(CTABLE *cache, void (*action) (const char *, const void *)) +{ + RING *entry = RING_PTR_OF(cache); + + /* Walking down the MRU chain is less work than using ht_walk(). */ + + while ((entry = ring_succ(entry)) != RING_PTR_OF(cache)) + action((RING_TO_CTABLE_ENTRY(entry)->key), + (RING_TO_CTABLE_ENTRY(entry)->value)); +} + +#ifdef TEST + + /* + * Proof-of-concept test program. Read keys from stdin, ask for values not + * in cache. + */ +#include <unistd.h> +#include <vstream.h> +#include <vstring.h> +#include <vstring_vstream.h> +#include <msg_vstream.h> + +#define STR(x) vstring_str(x) + +static void *ask(const char *key, void *context) +{ + VSTRING *data_buf = (VSTRING *) context; + + vstream_printf("ask: %s = ", key); + vstream_fflush(VSTREAM_OUT); + if (vstring_get_nonl(data_buf, VSTREAM_IN) == VSTREAM_EOF) + vstream_longjmp(VSTREAM_IN, 1); + if (!isatty(0)) { + vstream_printf("%s\n", STR(data_buf)); + vstream_fflush(VSTREAM_OUT); + } + return (mystrdup(STR(data_buf))); +} + +static void drop(void *data, void *unused_context) +{ + myfree(data); +} + +int main(int unused_argc, char **argv) +{ + VSTRING *key_buf; + VSTRING *data_buf; + CTABLE *cache; + const char *value; + + msg_vstream_init(argv[0], VSTREAM_ERR); + key_buf = vstring_alloc(100); + data_buf = vstring_alloc(100); + cache = ctable_create(1, ask, drop, (void *) data_buf); + msg_verbose = 1; + vstream_control(VSTREAM_IN, CA_VSTREAM_CTL_EXCEPT, CA_VSTREAM_CTL_END); + + if (vstream_setjmp(VSTREAM_IN) == 0) { + for (;;) { + vstream_printf("key = "); + vstream_fflush(VSTREAM_OUT); + if (vstring_get_nonl(key_buf, VSTREAM_IN) == VSTREAM_EOF) + vstream_longjmp(VSTREAM_IN, 1); + if (!isatty(0)) { + vstream_printf("%s\n", STR(key_buf)); + vstream_fflush(VSTREAM_OUT); + } + value = ctable_locate(cache, STR(key_buf)); + vstream_printf("result: %s\n", value); + } + } + ctable_free(cache); + vstring_free(key_buf); + vstring_free(data_buf); + return (0); +} + +#endif diff --git a/src/util/ctable.h b/src/util/ctable.h new file mode 100644 index 0000000..ea16914 --- /dev/null +++ b/src/util/ctable.h @@ -0,0 +1,41 @@ +#ifndef _CTABLE_H_INCLUDED_ +#define _CTABLE_H_INCLUDED_ + +/*++ +/* NAME +/* ctable 5 +/* SUMMARY +/* cache manager +/* SYNOPSIS +/* #include <ctable.h> +/* DESCRIPTION +/* .nf + + /* + * Interface of the cache manager. The structure of a cache is not visible + * to the caller. + */ + +#define CTABLE struct ctable +typedef void *(*CTABLE_CREATE_FN) (const char *, void *); +typedef void (*CTABLE_DELETE_FN) (void *, void *); + +extern CTABLE *ctable_create(ssize_t, CTABLE_CREATE_FN, CTABLE_DELETE_FN, void *); +extern void ctable_free(CTABLE *); +extern void ctable_walk(CTABLE *, void (*) (const char *, const void *)); +extern const void *ctable_locate(CTABLE *, const char *); +extern const void *ctable_refresh(CTABLE *, const char *); +extern void ctable_newcontext(CTABLE *, void *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/ctable.in b/src/util/ctable.in new file mode 100644 index 0000000..78763cf --- /dev/null +++ b/src/util/ctable.in @@ -0,0 +1,39 @@ +a +1 +b +2 +c +3 +d +4 +e +5 +f +6 +f +a +1 +b +2 +c +3 +d +4 +e +5 +f +6 +f +e +d +c +b +a +1 +b +c +d +e +f +6 +f diff --git a/src/util/ctable.ref b/src/util/ctable.ref new file mode 100644 index 0000000..34e8a95 --- /dev/null +++ b/src/util/ctable.ref @@ -0,0 +1,99 @@ +key = a +ask: a = 1 +./ctable: ctable_locate: install entry key a +result: 1 +key = b +ask: b = 2 +./ctable: ctable_locate: install entry key b +result: 2 +key = c +ask: c = 3 +./ctable: ctable_locate: install entry key c +result: 3 +key = d +ask: d = 4 +./ctable: ctable_locate: install entry key d +result: 4 +key = e +ask: e = 5 +./ctable: ctable_locate: install entry key e +result: 5 +key = f +./ctable: ctable_locate: purge entry key a +ask: f = 6 +./ctable: ctable_locate: install entry key f +result: 6 +key = f +./ctable: ctable_locate: leave existing entry key f +result: 6 +key = a +./ctable: ctable_locate: purge entry key b +ask: a = 1 +./ctable: ctable_locate: install entry key a +result: 1 +key = b +./ctable: ctable_locate: purge entry key c +ask: b = 2 +./ctable: ctable_locate: install entry key b +result: 2 +key = c +./ctable: ctable_locate: purge entry key d +ask: c = 3 +./ctable: ctable_locate: install entry key c +result: 3 +key = d +./ctable: ctable_locate: purge entry key e +ask: d = 4 +./ctable: ctable_locate: install entry key d +result: 4 +key = e +./ctable: ctable_locate: purge entry key f +ask: e = 5 +./ctable: ctable_locate: install entry key e +result: 5 +key = f +./ctable: ctable_locate: purge entry key a +ask: f = 6 +./ctable: ctable_locate: install entry key f +result: 6 +key = f +./ctable: ctable_locate: leave existing entry key f +result: 6 +key = e +./ctable: ctable_locate: move existing entry key e +result: 5 +key = d +./ctable: ctable_locate: move existing entry key d +result: 4 +key = c +./ctable: ctable_locate: move existing entry key c +result: 3 +key = b +./ctable: ctable_locate: move existing entry key b +result: 2 +key = a +./ctable: ctable_locate: purge entry key f +ask: a = 1 +./ctable: ctable_locate: install entry key a +result: 1 +key = b +./ctable: ctable_locate: move existing entry key b +result: 2 +key = c +./ctable: ctable_locate: move existing entry key c +result: 3 +key = d +./ctable: ctable_locate: move existing entry key d +result: 4 +key = e +./ctable: ctable_locate: move existing entry key e +result: 5 +key = f +./ctable: ctable_locate: purge entry key a +ask: f = 6 +./ctable: ctable_locate: install entry key f +result: 6 +key = f +./ctable: ctable_locate: leave existing entry key f +result: 6 +key =
\ No newline at end of file diff --git a/src/util/dict.c b/src/util/dict.c new file mode 100644 index 0000000..5d53860 --- /dev/null +++ b/src/util/dict.c @@ -0,0 +1,664 @@ +/*++ +/* NAME +/* dict 3 +/* SUMMARY +/* dictionary manager +/* SYNOPSIS +/* #include <dict.h> +/* +/* void dict_register(dict_name, dict_info) +/* const char *dict_name; +/* DICT *dict_info; +/* +/* DICT *dict_handle(dict_name) +/* const char *dict_name; +/* +/* void dict_unregister(dict_name) +/* const char *dict_name; +/* +/* int dict_update(dict_name, member, value) +/* const char *dict_name; +/* const char *member; +/* const char *value; +/* +/* const char *dict_lookup(dict_name, member) +/* const char *dict_name; +/* const char *member; +/* +/* int dict_delete(dict_name, member) +/* const char *dict_name; +/* const char *member; +/* +/* int dict_sequence(dict_name, func, member, value) +/* const char *dict_name; +/* int func; +/* const char **member; +/* const char **value; +/* +/* const char *dict_eval(dict_name, string, int recursive) +/* const char *dict_name; +/* const char *string; +/* int recursive; +/* +/* int dict_walk(action, context) +/* void (*action)(dict_name, dict_handle, context) +/* void *context; +/* +/* int dict_error(dict_name) +/* const char *dict_name; +/* +/* const char *dict_changed_name() +/* +/* void DICT_OWNER_AGGREGATE_INIT(aggregate) +/* DICT_OWNER aggregate; +/* +/* void DICT_OWNER_AGGREGATE_UPDATE(aggregate, source) +/* DICT_OWNER aggregate; +/* DICT_OWNER source; +/* AUXILIARY FUNCTIONS +/* int dict_load_file_xt(dict_name, path) +/* const char *dict_name; +/* const char *path; +/* +/* void dict_load_fp(dict_name, fp) +/* const char *dict_name; +/* VSTREAM *fp; +/* +/* const char *dict_flags_str(dict_flags) +/* int dict_flags; +/* +/* int dict_flags_mask(names) +/* const char *names; +/* DESCRIPTION +/* This module maintains a collection of name-value dictionaries. +/* Each dictionary has its own name and has its own methods to read +/* or update members. Examples of dictionaries that can be accessed +/* in this manner are the global UNIX-style process environment, +/* hash tables, NIS maps, DBM files, and so on. Dictionary values +/* are not limited to strings but can be arbitrary objects as long +/* as they can be represented by character pointers. +/* FEATURES +/* .fi +/* .ad +/* Notable features of this module are: +/* .IP "macro expansion (string-valued dictionaries only)" +/* Macros of the form $\fIname\fR can be expanded to the current +/* value of \fIname\fR. The forms $(\fIname\fR) and ${\fIname\fR} are +/* also supported. +/* .IP "unknown names" +/* An update request for an unknown dictionary name will trigger +/* the instantiation of an in-memory dictionary with that name. +/* A lookup request (including delete and sequence) for an +/* unknown dictionary will result in a "not found" and "no +/* error" result. +/* .PP +/* dict_register() adds a new dictionary, including access methods, +/* to the list of known dictionaries, or increments the reference +/* count for an existing (name, dictionary) pair. Otherwise, it is +/* an error to pass an existing name (this would cause a memory leak). +/* +/* dict_handle() returns the generic dictionary handle of the +/* named dictionary, or a null pointer when the named dictionary +/* is not found. +/* +/* dict_unregister() decrements the reference count of the named +/* dictionary. When the reference count reaches zero, dict_unregister() +/* breaks the (name, dictionary) association and executes the +/* dictionary's optional \fIremove\fR method. +/* +/* dict_update() updates the value of the named dictionary member. +/* The dictionary member and the named dictionary are instantiated +/* on the fly. The result value is zero (DICT_STAT_SUCCESS) +/* when the update was made. +/* +/* dict_lookup() returns the value of the named member (i.e. without +/* expanding macros in the member value). The \fIdict_name\fR argument +/* specifies the dictionary to search. The result is a null pointer +/* when no value is found, otherwise the result is owned by the +/* underlying dictionary method. Make a copy if the result is to be +/* modified, or if the result is to survive multiple dict_lookup() calls. +/* +/* dict_delete() removes the named member from the named dictionary. +/* The result value is zero (DICT_STAT_SUCCESS) when the member +/* was found. +/* +/* dict_sequence() steps through the named dictionary and returns +/* keys and values in some implementation-defined order. The func +/* argument is DICT_SEQ_FUN_FIRST to set the cursor to the first +/* entry or DICT_SEQ_FUN_NEXT to select the next entry. The result +/* is owned by the underlying dictionary method. Make a copy if the +/* result is to be modified, or if the result is to survive multiple +/* dict_sequence() calls. The result value is zero (DICT_STAT_SUCCESS) +/* when a member was found. +/* +/* dict_eval() expands macro references in the specified string. +/* The result is owned by the dictionary manager. Make a copy if the +/* result is to survive multiple dict_eval() calls. When the +/* \fIrecursive\fR argument is non-zero, macro references in macro +/* lookup results are expanded recursively. +/* +/* dict_walk() iterates over all registered dictionaries in some +/* arbitrary order, and invokes the specified action routine with +/* as arguments: +/* .IP "const char *dict_name" +/* Dictionary name. +/* .IP "DICT *dict_handle" +/* Generic dictionary handle. +/* .IP "char *context" +/* Application context from the caller. +/* .PP +/* dict_changed_name() returns non-zero when any dictionary needs to +/* be re-opened because it has changed or because it was unlinked. +/* A non-zero result is the name of a changed dictionary. +/* +/* dict_load_file_xt() reads name-value entries from the named file. +/* Lines that begin with whitespace are concatenated to the preceding +/* line (the newline is deleted). +/* Each entry is stored in the dictionary named by \fIdict_name\fR. +/* The result is zero if the file could not be opened. +/* +/* dict_load_fp() reads name-value entries from an open stream. +/* It has the same semantics as the dict_load_file_xt() function. +/* +/* dict_flags_str() returns a printable representation of the +/* specified dictionary flags. The result is overwritten upon +/* each call. +/* +/* dict_flags_mask() returns the bitmask for the specified +/* comma/space-separated dictionary flag names. +/* TRUST AND PROVENANCE +/* .ad +/* .fi +/* Each dictionary has an owner attribute that contains (status, +/* uid) information about the owner of a dictionary. The +/* status is one of the following: +/* .IP DICT_OWNER_TRUSTED +/* The dictionary is owned by a trusted user. The uid is zero, +/* and specifies a UNIX user ID. +/* .IP DICT_OWNER_UNTRUSTED +/* The dictionary is owned by an untrusted user. The uid is +/* non-zero, and specifies a UNIX user ID. +/* .IP DICT_OWNER_UNKNOWN +/* The dictionary is owned by an unspecified user. For example, +/* the origin is unauthenticated, or different parts of a +/* dictionary aggregate (see below) are owned by different +/* untrusted users. The uid is non-zero and does not specify +/* a UNIX user ID. +/* .PP +/* Note that dictionary ownership does not necessarily imply +/* ownership of lookup results. For example, a PCRE table may +/* be owned by the trusted root user, but the result of $number +/* expansion can contain data from an arbitrary remote SMTP +/* client. See dict_open(3) for how to disallow $number +/* expansions with security-sensitive operations. +/* +/* Two macros are available to help determine the provenance +/* and trustworthiness of a dictionary aggregate. The macros +/* are unsafe because they may evaluate arguments more than +/* once. +/* +/* DICT_OWNER_AGGREGATE_INIT() initialize aggregate owner +/* attributes to the highest trust level. +/* +/* DICT_OWNER_AGGREGATE_UPDATE() updates the aggregate owner +/* attributes with the attributes of the specified source, and +/* reduces the aggregate trust level as appropriate. +/* SEE ALSO +/* htable(3) +/* BUGS +/* DIAGNOSTICS +/* Fatal errors: out of memory, malformed macro name. +/* +/* The lookup routine returns non-null when the request is +/* satisfied. The update, delete and sequence routines return +/* zero (DICT_STAT_SUCCESS) when the request is satisfied. +/* The dict_error() function returns non-zero only when the +/* last operation was not satisfied due to a dictionary access +/* error. The result can have the following values: +/* .IP DICT_ERR_NONE(zero) +/* There was no dictionary access error. For example, the +/* request was satisfied, the requested information did not +/* exist in the dictionary, or the information already existed +/* when it should not exist (collision). +/* .IP DICT_ERR_RETRY(<0) +/* The dictionary was temporarily unavailable. This can happen +/* with network-based services. +/* .IP DICT_ERR_CONFIG(<0) +/* The dictionary was unavailable due to a configuration error. +/* .PP +/* Generally, a program is expected to test the function result +/* value for "success" first. If the operation was not successful, +/* a program is expected to test for a non-zero dict->error +/* status to distinguish between a data notfound/collision +/* condition or a dictionary access error. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include "sys_defs.h" +#include <sys/stat.h> +#include <fcntl.h> +#include <ctype.h> +#include <string.h> +#include <time.h> + +/* Utility library. */ + +#include "msg.h" +#include "htable.h" +#include "mymalloc.h" +#include "vstream.h" +#include "vstring.h" +#include "readlline.h" +#include "mac_expand.h" +#include "stringops.h" +#include "iostuff.h" +#include "name_mask.h" +#include "dict.h" +#include "dict_ht.h" +#include "warn_stat.h" +#include "line_number.h" + +static HTABLE *dict_table; + + /* + * Each (name, dictionary) instance has a reference count. The count is part + * of the name, not the dictionary. The same dictionary may be registered + * under multiple names. The structure below keeps track of instances and + * reference counts. + */ +typedef struct { + DICT *dict; + int refcount; +} DICT_NODE; + +#define dict_node(dict) \ + (dict_table ? (DICT_NODE *) htable_find(dict_table, dict) : 0) + +/* Find a dictionary handle by name for lookup purposes. */ + +#define DICT_FIND_FOR_LOOKUP(dict, dict_name) do { \ + DICT_NODE *node; \ + if ((node = dict_node(dict_name)) != 0) \ + dict = node->dict; \ + else \ + dict = 0; \ +} while (0) + +/* Find a dictionary handle by name for update purposes. */ + +#define DICT_FIND_FOR_UPDATE(dict, dict_name) do { \ + DICT_NODE *node; \ + if ((node = dict_node(dict_name)) == 0) { \ + dict = dict_ht_open(dict_name, O_CREAT | O_RDWR, 0); \ + dict_register(dict_name, dict); \ + } else \ + dict = node->dict; \ +} while (0) + +#define STR(x) vstring_str(x) + +/* dict_register - make association with dictionary */ + +void dict_register(const char *dict_name, DICT *dict_info) +{ + const char *myname = "dict_register"; + DICT_NODE *node; + + if (dict_table == 0) + dict_table = htable_create(0); + if ((node = dict_node(dict_name)) == 0) { + node = (DICT_NODE *) mymalloc(sizeof(*node)); + node->dict = dict_info; + node->refcount = 0; + htable_enter(dict_table, dict_name, (void *) node); + } else if (dict_info != node->dict) + msg_fatal("%s: dictionary name exists: %s", myname, dict_name); + node->refcount++; + if (msg_verbose > 1) + msg_info("%s: %s %d", myname, dict_name, node->refcount); +} + +/* dict_handle - locate generic dictionary handle */ + +DICT *dict_handle(const char *dict_name) +{ + DICT_NODE *node; + + return ((node = dict_node(dict_name)) != 0 ? node->dict : 0); +} + +/* dict_node_free - dict_unregister() callback */ + +static void dict_node_free(void *ptr) +{ + DICT_NODE *node = (DICT_NODE *) ptr; + DICT *dict = node->dict; + + if (dict->close) + dict->close(dict); + myfree((void *) node); +} + +/* dict_unregister - break association with named dictionary */ + +void dict_unregister(const char *dict_name) +{ + const char *myname = "dict_unregister"; + DICT_NODE *node; + + if ((node = dict_node(dict_name)) == 0) + msg_panic("non-existing dictionary: %s", dict_name); + if (msg_verbose > 1) + msg_info("%s: %s %d", myname, dict_name, node->refcount); + if (--(node->refcount) == 0) + htable_delete(dict_table, dict_name, dict_node_free); +} + +/* dict_update - replace or add dictionary entry */ + +int dict_update(const char *dict_name, const char *member, const char *value) +{ + const char *myname = "dict_update"; + DICT *dict; + + DICT_FIND_FOR_UPDATE(dict, dict_name); + if (msg_verbose > 1) + msg_info("%s: %s = %s", myname, member, value); + return (dict->update(dict, member, value)); +} + +/* dict_lookup - look up dictionary entry */ + +const char *dict_lookup(const char *dict_name, const char *member) +{ + const char *myname = "dict_lookup"; + DICT *dict; + const char *ret; + + DICT_FIND_FOR_LOOKUP(dict, dict_name); + if (dict != 0) { + ret = dict->lookup(dict, member); + if (msg_verbose > 1) + msg_info("%s: %s = %s", myname, member, ret ? ret : + dict->error ? "(error)" : "(notfound)"); + return (ret); + } else { + if (msg_verbose > 1) + msg_info("%s: %s = %s", myname, member, "(notfound)"); + return (0); + } +} + +/* dict_delete - delete dictionary entry */ + +int dict_delete(const char *dict_name, const char *member) +{ + const char *myname = "dict_delete"; + DICT *dict; + + DICT_FIND_FOR_LOOKUP(dict, dict_name); + if (msg_verbose > 1) + msg_info("%s: delete %s", myname, member); + return (dict ? dict->delete(dict, member) : DICT_STAT_FAIL); +} + +/* dict_sequence - traverse dictionary */ + +int dict_sequence(const char *dict_name, const int func, + const char **member, const char **value) +{ + const char *myname = "dict_sequence"; + DICT *dict; + + DICT_FIND_FOR_LOOKUP(dict, dict_name); + if (msg_verbose > 1) + msg_info("%s: sequence func %d", myname, func); + return (dict ? dict->sequence(dict, func, member, value) : DICT_STAT_FAIL); +} + +/* dict_error - return last error */ + +int dict_error(const char *dict_name) +{ + DICT *dict; + + DICT_FIND_FOR_LOOKUP(dict, dict_name); + return (dict ? dict->error : DICT_ERR_NONE); +} + +/* dict_load_file_xt - read entries from text file */ + +int dict_load_file_xt(const char *dict_name, const char *path) +{ + VSTREAM *fp; + struct stat st; + time_t before; + time_t after; + + /* + * Read the file again if it is hot. This may result in reading a partial + * parameter name when a file changes in the middle of a read. + */ + for (before = time((time_t *) 0); /* see below */ ; before = after) { + if ((fp = vstream_fopen(path, O_RDONLY, 0)) == 0) + return (0); + dict_load_fp(dict_name, fp); + if (fstat(vstream_fileno(fp), &st) < 0) + msg_fatal("fstat %s: %m", path); + if (vstream_ferror(fp) || vstream_fclose(fp)) + msg_fatal("read %s: %m", path); + after = time((time_t *) 0); + if (st.st_mtime < before - 1 || st.st_mtime > after) + break; + if (msg_verbose > 1) + msg_info("pausing to let %s cool down", path); + doze(300000); + } + return (1); +} + +/* dict_load_fp - read entries from open stream */ + +void dict_load_fp(const char *dict_name, VSTREAM *fp) +{ + const char *myname = "dict_load_fp"; + VSTRING *buf; + char *member; + char *val; + const char *old; + int last_line; + int lineno; + const char *err; + struct stat st; + DICT *dict; + + /* + * Instantiate the dictionary even if the file is empty. + */ + DICT_FIND_FOR_UPDATE(dict, dict_name); + buf = vstring_alloc(100); + last_line = 0; + + if (fstat(vstream_fileno(fp), &st) < 0) + msg_fatal("fstat %s: %m", VSTREAM_PATH(fp)); + while (readllines(buf, fp, &last_line, &lineno)) { + if ((err = split_nameval(STR(buf), &member, &val)) != 0) + msg_fatal("%s, line %d: %s: \"%s\"", + VSTREAM_PATH(fp), + lineno, + err, STR(buf)); + if (msg_verbose > 1) + msg_info("%s: %s = %s", myname, member, val); + if ((old = dict->lookup(dict, member)) != 0 + && strcmp(old, val) != 0) + msg_warn("%s, line %d: overriding earlier entry: %s=%s", + VSTREAM_PATH(fp), lineno, member, old); + if (dict->update(dict, member, val) != 0) + msg_fatal("%s, line %d: unable to update %s:%s", + VSTREAM_PATH(fp), lineno, dict->type, dict->name); + } + vstring_free(buf); + dict->owner.uid = st.st_uid; + dict->owner.status = (st.st_uid != 0); +} + +/* dict_eval_lookup - macro parser call-back routine */ + +static const char *dict_eval_lookup(const char *key, int unused_type, + void *context) +{ + char *dict_name = (char *) context; + const char *pp = 0; + DICT *dict; + + /* + * XXX how would one recover? + */ + DICT_FIND_FOR_LOOKUP(dict, dict_name); + if (dict != 0 + && (pp = dict->lookup(dict, key)) == 0 && dict->error != 0) + msg_fatal("dictionary %s: lookup %s: operation failed", dict_name, key); + return (pp); +} + +/* dict_eval - expand embedded dictionary references */ + +const char *dict_eval(const char *dict_name, const char *value, int recursive) +{ + const char *myname = "dict_eval"; + static VSTRING *buf; + int status; + + /* + * Initialize. + */ + if (buf == 0) + buf = vstring_alloc(10); + + /* + * Expand macros, possibly recursively. + */ +#define DONT_FILTER (char *) 0 + + status = mac_expand(buf, value, + recursive ? MAC_EXP_FLAG_RECURSE : MAC_EXP_FLAG_NONE, + DONT_FILTER, dict_eval_lookup, (void *) dict_name); + if (status & MAC_PARSE_ERROR) + msg_fatal("dictionary %s: macro processing error", dict_name); + if (msg_verbose > 1) { + if (strcmp(value, STR(buf)) != 0) + msg_info("%s: expand %s -> %s", myname, value, STR(buf)); + else + msg_info("%s: const %s", myname, value); + } + return (STR(buf)); +} + +/* dict_walk - iterate over all dictionaries in arbitrary order */ + +void dict_walk(DICT_WALK_ACTION action, void *ptr) +{ + HTABLE_INFO **ht_info_list; + HTABLE_INFO **ht; + HTABLE_INFO *h; + + ht_info_list = htable_list(dict_table); + for (ht = ht_info_list; (h = *ht) != 0; ht++) + action(h->key, (DICT *) h->value, ptr); + myfree((void *) ht_info_list); +} + +/* dict_changed_name - see if any dictionary has changed */ + +const char *dict_changed_name(void) +{ + const char *myname = "dict_changed_name"; + struct stat st; + HTABLE_INFO **ht_info_list; + HTABLE_INFO **ht; + HTABLE_INFO *h; + const char *status; + DICT *dict; + + ht_info_list = htable_list(dict_table); + for (status = 0, ht = ht_info_list; status == 0 && (h = *ht) != 0; ht++) { + dict = ((DICT_NODE *) h->value)->dict; + if (dict->stat_fd < 0) /* not file-based */ + continue; + if (dict->mtime == 0) /* not bloody likely */ + msg_warn("%s: table %s: null time stamp", myname, h->key); + if (fstat(dict->stat_fd, &st) < 0) + msg_fatal("%s: fstat: %m", myname); + if (((dict->flags & DICT_FLAG_MULTI_WRITER) == 0 + && st.st_mtime != dict->mtime) + || st.st_nlink == 0) + status = h->key; + } + myfree((void *) ht_info_list); + return (status); +} + +/* dict_changed - backwards compatibility */ + +int dict_changed(void) +{ + return (dict_changed_name() != 0); +} + + /* + * Mapping between flag names and flag values. + */ +static const NAME_MASK dict_mask[] = { + "warn_dup", DICT_FLAG_DUP_WARN, /* if file, warn about dups */ + "ignore_dup", DICT_FLAG_DUP_IGNORE, /* if file, ignore dups */ + "try0null", DICT_FLAG_TRY0NULL, /* do not append 0 to key/value */ + "try1null", DICT_FLAG_TRY1NULL, /* append 0 to key/value */ + "fixed", DICT_FLAG_FIXED, /* fixed key map */ + "pattern", DICT_FLAG_PATTERN, /* keys are patterns */ + "lock", DICT_FLAG_LOCK, /* lock before access */ + "replace", DICT_FLAG_DUP_REPLACE, /* if file, replace dups */ + "sync_update", DICT_FLAG_SYNC_UPDATE, /* if file, sync updates */ + "debug", DICT_FLAG_DEBUG, /* log access */ + "no_regsub", DICT_FLAG_NO_REGSUB, /* disallow regexp substitution */ + "no_proxy", DICT_FLAG_NO_PROXY, /* disallow proxy mapping */ + "no_unauth", DICT_FLAG_NO_UNAUTH, /* disallow unauthenticated data */ + "fold_fix", DICT_FLAG_FOLD_FIX, /* case-fold with fixed-case key map */ + "fold_mul", DICT_FLAG_FOLD_MUL, /* case-fold with multi-case key map */ + "open_lock", DICT_FLAG_OPEN_LOCK, /* permanent lock upon open */ + "bulk_update", DICT_FLAG_BULK_UPDATE, /* bulk update if supported */ + "multi_writer", DICT_FLAG_MULTI_WRITER, /* multi-writer safe */ + "utf8_request", DICT_FLAG_UTF8_REQUEST, /* request UTF-8 activation */ + "utf8_active", DICT_FLAG_UTF8_ACTIVE, /* UTF-8 is activated */ + "src_rhs_is_file", DICT_FLAG_SRC_RHS_IS_FILE, /* value from file */ + 0, +}; + +/* dict_flags_str - convert bitmask to symbolic flag names */ + +const char *dict_flags_str(int dict_flags) +{ + static VSTRING *buf = 0; + + if (buf == 0) + buf = vstring_alloc(1); + + return (str_name_mask_opt(buf, "dictionary flags", dict_mask, dict_flags, + NAME_MASK_NUMBER | NAME_MASK_PIPE)); +} + +/* dict_flags_mask - convert symbolic flag names to bitmask */ + +int dict_flags_mask(const char *names) +{ + return (name_mask("dictionary flags", dict_mask, names)); +} diff --git a/src/util/dict.h b/src/util/dict.h new file mode 100644 index 0000000..4f0cab8 --- /dev/null +++ b/src/util/dict.h @@ -0,0 +1,340 @@ +#ifndef _DICT_H_INCLUDED_ +#define _DICT_H_INCLUDED_ + +/*++ +/* NAME +/* dict 3h +/* SUMMARY +/* dictionary manager +/* SYNOPSIS +/* #include <dict.h> +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include <sys/stat.h> +#include <fcntl.h> +#include <setjmp.h> + +#ifdef NO_SIGSETJMP +#define DICT_JMP_BUF jmp_buf +#else +#define DICT_JMP_BUF sigjmp_buf +#endif + + /* + * Utility library. + */ +#include <vstream.h> +#include <argv.h> +#include <vstring.h> +#include <myflock.h> + + /* + * Provenance information. + */ +typedef struct DICT_OWNER { + int status; /* see below */ + uid_t uid; /* use only if status == UNTRUSTED */ +} DICT_OWNER; + + /* + * Note that trust levels are not in numerical order. + */ +#define DICT_OWNER_UNKNOWN (-1) /* ex: unauthenticated tcp, proxy */ +#define DICT_OWNER_TRUSTED (!1) /* ex: root-owned config file */ +#define DICT_OWNER_UNTRUSTED (!0) /* ex: non-root config file */ + + /* + * When combining tables with different provenance, we initialize to the + * highest trust level, and remember the lowest trust level that we find + * during aggregation. If we combine tables that are owned by different + * untrusted users, the resulting provenance is "unknown". + */ +#define DICT_OWNER_AGGREGATE_INIT(dst) { \ + (dst).status = DICT_OWNER_TRUSTED; \ + (dst).uid = 0; \ + } while (0) + + /* + * The following is derived from the 3x3 transition matrix. + */ +#define DICT_OWNER_AGGREGATE_UPDATE(dst, src) do { \ + if ((dst).status == DICT_OWNER_TRUSTED \ + || (src).status == DICT_OWNER_UNKNOWN) { \ + (dst) = (src); \ + } else if ((dst).status == (src).status \ + && (dst).uid != (src).uid) { \ + (dst).status = DICT_OWNER_UNKNOWN; \ + (dst).uid = ~0; \ + } \ + } while (0) + + /* + * Generic dictionary interface - in reality, a dictionary extends this + * structure with private members to maintain internal state. + */ +typedef struct DICT { + char *type; /* for diagnostics */ + char *name; /* for diagnostics */ + int flags; /* see below */ + const char *(*lookup) (struct DICT *, const char *); + int (*update) (struct DICT *, const char *, const char *); + int (*delete) (struct DICT *, const char *); + int (*sequence) (struct DICT *, int, const char **, const char **); + int (*lock) (struct DICT *, int); + void (*close) (struct DICT *); + int lock_type; /* for read/write lock */ + int lock_fd; /* for read/write lock */ + int stat_fd; /* change detection */ + time_t mtime; /* mod time at open */ + VSTRING *fold_buf; /* key folding buffer */ + DICT_OWNER owner; /* provenance */ + int error; /* last operation only */ + DICT_JMP_BUF *jbuf; /* exception handling */ + struct DICT_UTF8_BACKUP *utf8_backup; /* see below */ + struct VSTRING *file_buf; /* dict_file_to_buf() */ + struct VSTRING *file_b64; /* dict_file_to_b64() */ +} DICT; + +extern DICT *dict_alloc(const char *, const char *, ssize_t); +extern void dict_free(DICT *); + +extern DICT *dict_debug(DICT *); + +#define DICT_DEBUG(d) ((d)->flags & DICT_FLAG_DEBUG ? dict_debug(d) : (d)) + + /* + * See dict_open.c embedded manpage for flag definitions. + */ +#define DICT_FLAG_NONE (0) +#define DICT_FLAG_DUP_WARN (1<<0) /* warn about dups if not supported */ +#define DICT_FLAG_DUP_IGNORE (1<<1) /* ignore dups if not supported */ +#define DICT_FLAG_TRY0NULL (1<<2) /* do not append 0 to key/value */ +#define DICT_FLAG_TRY1NULL (1<<3) /* append 0 to key/value */ +#define DICT_FLAG_FIXED (1<<4) /* fixed key map */ +#define DICT_FLAG_PATTERN (1<<5) /* keys are patterns */ +#define DICT_FLAG_LOCK (1<<6) /* use temp lock before access */ +#define DICT_FLAG_DUP_REPLACE (1<<7) /* replace dups if supported */ +#define DICT_FLAG_SYNC_UPDATE (1<<8) /* sync updates if supported */ +#define DICT_FLAG_DEBUG (1<<9) /* log access */ +/*#define DICT_FLAG_FOLD_KEY (1<<10) /* lowercase the lookup key */ +#define DICT_FLAG_NO_REGSUB (1<<11) /* disallow regexp substitution */ +#define DICT_FLAG_NO_PROXY (1<<12) /* disallow proxy mapping */ +#define DICT_FLAG_NO_UNAUTH (1<<13) /* disallow unauthenticated data */ +#define DICT_FLAG_FOLD_FIX (1<<14) /* case-fold key with fixed-case map */ +#define DICT_FLAG_FOLD_MUL (1<<15) /* case-fold key with multi-case map */ +#define DICT_FLAG_FOLD_ANY (DICT_FLAG_FOLD_FIX | DICT_FLAG_FOLD_MUL) +#define DICT_FLAG_OPEN_LOCK (1<<16) /* perm lock if not multi-writer safe */ +#define DICT_FLAG_BULK_UPDATE (1<<17) /* optimize for bulk updates */ +#define DICT_FLAG_MULTI_WRITER (1<<18) /* multi-writer safe map */ +#define DICT_FLAG_UTF8_REQUEST (1<<19) /* activate UTF-8 if possible */ +#define DICT_FLAG_UTF8_ACTIVE (1<<20) /* UTF-8 proxy layer is present */ +#define DICT_FLAG_SRC_RHS_IS_FILE \ + (1<<21) /* Map source RHS is a file */ + +#define DICT_FLAG_UTF8_MASK (DICT_FLAG_UTF8_REQUEST) + + /* IMPORTANT: Update the dict_mask[] table when the above changes */ + + /* + * The subsets of flags that control how a map is used. These are relevant + * mainly for proxymap support. Note: some categories overlap. + * + * DICT_FLAG_IMPL_MASK - flags that are set by the map implementation itself. + * + * DICT_FLAG_PARANOID - requestor flags that forbid the use of insecure map + * types for security-sensitive operations. These flags are checked by the + * map implementation itself upon open, lookup etc. requests. + * + * DICT_FLAG_RQST_MASK - all requestor flags, including paranoid flags, that + * the requestor may change between open, lookup etc. requests. These + * specify requestor properties, not map properties. + * + * DICT_FLAG_INST_MASK - none of the above flags. The requestor may not change + * these flags between open, lookup, etc. requests (although a map may make + * changes to its copy of some of these flags). The proxymap server opens + * only one map instance for all client requests with the same values of + * these flags, and the proxymap client uses its own saved copy of these + * flags. DICT_FLAG_SRC_RHS_IS_FILE is an example of such a flag. + */ +#define DICT_FLAG_PARANOID \ + (DICT_FLAG_NO_REGSUB | DICT_FLAG_NO_PROXY | DICT_FLAG_NO_UNAUTH) +#define DICT_FLAG_IMPL_MASK (DICT_FLAG_FIXED | DICT_FLAG_PATTERN | \ + DICT_FLAG_MULTI_WRITER) +#define DICT_FLAG_RQST_MASK (DICT_FLAG_FOLD_ANY | DICT_FLAG_LOCK | \ + DICT_FLAG_DUP_REPLACE | DICT_FLAG_DUP_WARN | \ + DICT_FLAG_DUP_IGNORE | DICT_FLAG_SYNC_UPDATE | \ + DICT_FLAG_PARANOID | DICT_FLAG_UTF8_MASK) +#define DICT_FLAG_INST_MASK ~(DICT_FLAG_IMPL_MASK | DICT_FLAG_RQST_MASK) + + /* + * Feature tests. + */ +#define DICT_NEED_UTF8_ACTIVATION(enable, flags) \ + ((enable) && ((flags) & DICT_FLAG_UTF8_MASK)) + + /* + * dict->error values. Errors must be negative; smtpd_check depends on this. + */ +#define DICT_ERR_NONE 0 /* no error */ +#define DICT_ERR_RETRY (-1) /* soft error */ +#define DICT_ERR_CONFIG (-2) /* configuration error */ + + /* + * Result values for exposed functions except lookup. FAIL/ERROR are + * suggested values, not for use in comparisons for equality. + */ +#define DICT_STAT_FAIL 1 /* any value > 0: notfound, conflict */ +#define DICT_STAT_SUCCESS 0 /* request satisfied */ +#define DICT_STAT_ERROR (-1) /* any value < 0: database error */ + + /* + * Set an error code and return a result value. + */ +#define DICT_ERR_VAL_RETURN(dict, err, val) do { \ + (dict)->error = (err); \ + return (val); \ + } while (0) + + /* + * Sequence function types. + */ +#define DICT_SEQ_FUN_FIRST 0 /* set cursor to first record */ +#define DICT_SEQ_FUN_NEXT 1 /* set cursor to next record */ + + /* + * Interface for dictionary types. + */ +extern ARGV *dict_mapnames(void); +typedef void (*DICT_MAPNAMES_EXTEND_FN) (ARGV *); +extern DICT_MAPNAMES_EXTEND_FN dict_mapnames_extend(DICT_MAPNAMES_EXTEND_FN); + + + /* + * High-level interface, with logical dictionary names. + */ +extern void dict_register(const char *, DICT *); +extern DICT *dict_handle(const char *); +extern void dict_unregister(const char *); +extern int dict_update(const char *, const char *, const char *); +extern const char *dict_lookup(const char *, const char *); +extern int dict_delete(const char *, const char *); +extern int dict_sequence(const char *, const int, const char **, const char **); +extern int dict_load_file_xt(const char *, const char *); +extern void dict_load_fp(const char *, VSTREAM *); +extern const char *dict_eval(const char *, const char *, int); +extern int dict_error(const char *); + + /* + * Low-level interface, with physical dictionary handles. + */ +typedef DICT *(*DICT_OPEN_FN) (const char *, int, int); +typedef DICT_OPEN_FN (*DICT_OPEN_EXTEND_FN) (const char *); +extern DICT *dict_open(const char *, int, int); +extern DICT *dict_open3(const char *, const char *, int, int); +extern void dict_open_register(const char *, DICT_OPEN_FN); +extern DICT_OPEN_EXTEND_FN dict_open_extend(DICT_OPEN_EXTEND_FN); + +#define dict_get(dp, key) ((const char *) (dp)->lookup((dp), (key))) +#define dict_put(dp, key, val) (dp)->update((dp), (key), (val)) +#define dict_del(dp, key) (dp)->delete((dp), (key)) +#define dict_seq(dp, f, key, val) (dp)->sequence((dp), (f), (key), (val)) +#define dict_close(dp) (dp)->close(dp) +typedef void (*DICT_WALK_ACTION) (const char *, DICT *, void *); +extern void dict_walk(DICT_WALK_ACTION, void *); +extern int dict_changed(void); +extern const char *dict_changed_name(void); +extern const char *dict_flags_str(int); +extern int dict_flags_mask(const char *); +extern void dict_type_override(DICT *, const char *); + + /* + * Check and convert UTF-8 keys and values. + */ +typedef struct DICT_UTF8_BACKUP { + const char *(*lookup) (struct DICT *, const char *); + int (*update) (struct DICT *, const char *, const char *); + int (*delete) (struct DICT *, const char *); +} DICT_UTF8_BACKUP; + +extern DICT *dict_utf8_activate(DICT *); + + /* + * Driver for interactive or scripted tests. + */ +void dict_test(int, char **); + + /* + * Behind-the-scenes support to continue execution with reduced + * functionality. + */ +extern int dict_allow_surrogate; +extern DICT *PRINTFLIKE(5, 6) dict_surrogate(const char *, const char *, int, int, const char *,...); + + /* + * This name is reserved for matchlist error handling. + */ +#define DICT_TYPE_NOFILE "non-existent" +#define DICT_TYPE_NOUTF8 "non-UTF-8" + + /* + * Duplicated from vstream(3). This should probably be abstracted out. + * + * Exception handling. We use pointer to jmp_buf to avoid a lot of unused + * baggage for streams that don't need this functionality. + * + * XXX sigsetjmp()/siglongjmp() save and restore the signal mask which can + * avoid surprises in code that manipulates signals, but unfortunately some + * systems have bugs in their implementation. + */ +#ifdef NO_SIGSETJMP +#define dict_setjmp(dict) setjmp((dict)->jbuf[0]) +#define dict_longjmp(dict, val) longjmp((dict)->jbuf[0], (val)) +#else +#define dict_setjmp(dict) sigsetjmp((dict)->jbuf[0], 1) +#define dict_longjmp(dict, val) siglongjmp((dict)->jbuf[0], (val)) +#endif +#define dict_isjmp(dict) ((dict)->jbuf != 0) + + /* + * Temporary API. If exception handling proves to be useful, + * dict_jmp_alloc() should be integrated into dict_alloc(). + */ +extern void dict_jmp_alloc(DICT *); + + /* + * dict_file(3). + */ +extern struct VSTRING *dict_file_to_buf(DICT *, const char *); +extern struct VSTRING *dict_file_to_b64(DICT *, const char *); +extern struct VSTRING *dict_file_from_b64(DICT *, const char *); +extern char *dict_file_get_error(DICT *); +extern void dict_file_purge_buffers(DICT *); +extern const char *dict_file_lookup(DICT *dict, const char *); + + /* + * dict_stream(3) + */ +extern VSTREAM *dict_stream_open(const char *dict_type, const char *mapname, + int open_flags, int dict_flags, struct stat * st, VSTRING **why); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/dict_alloc.c b/src/util/dict_alloc.c new file mode 100644 index 0000000..3285a38 --- /dev/null +++ b/src/util/dict_alloc.c @@ -0,0 +1,196 @@ +/*++ +/* NAME +/* dict_alloc 3 +/* SUMMARY +/* dictionary memory manager +/* SYNOPSIS +/* #include <dict.h> +/* +/* DICT *dict_alloc(dict_type, dict_name, size) +/* const char *dict_type; +/* const char *dict_name; +/* ssize_t size; +/* +/* void dict_free(dict) +/* DICT *ptr; +/* +/* void dict_jmp_alloc(dict) +/* DICT *ptr; +/* DESCRIPTION +/* dict_alloc() allocates memory for a dictionary structure of +/* \fIsize\fR bytes, initializes all generic dictionary +/* properties to default settings, +/* and installs default methods that do not support any operation. +/* The caller is supposed to override the default methods with +/* ones that it supports. +/* The purpose of the default methods is to trap an attempt to +/* invoke an unsupported method. +/* +/* One exception is the default lock function. When the +/* dictionary provides a file handle for locking, the default +/* lock function returns the result from myflock with the +/* locking method specified in the lock_type member, otherwise +/* it returns 0. Presently, the lock function is used only to +/* implement the DICT_FLAG_OPEN_LOCK feature (lock the database +/* exclusively after it is opened) for databases that are not +/* multi-writer safe. +/* +/* dict_free() releases memory and cleans up after dict_alloc(). +/* It is up to the caller to dispose of any memory that was allocated +/* by the caller. +/* +/* dict_jmp_alloc() implements preliminary support for exception +/* handling. This will eventually be built into dict_alloc(). +/* +/* Arguments: +/* .IP dict_type +/* The official name for this type of dictionary, as used by +/* dict_open(3) etc. This is stored under the \fBtype\fR +/* member. +/* .IP dict_name +/* Dictionary name. This is stored as the \fBname\fR member. +/* .IP size +/* The size in bytes of the dictionary subclass structure instance. +/* SEE ALSO +/* dict(3) +/* DIAGNOSTICS +/* Fatal errors: the process invokes a default method. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System libraries. */ + +#include "sys_defs.h" + +/* Utility library. */ + +#include "msg.h" +#include "mymalloc.h" +#include "myflock.h" +#include "dict.h" + +/* dict_default_lookup - trap unimplemented operation */ + +static const char *dict_default_lookup(DICT *dict, const char *unused_key) +{ + msg_fatal("table %s:%s: lookup operation is not supported", + dict->type, dict->name); +} + +/* dict_default_update - trap unimplemented operation */ + +static int dict_default_update(DICT *dict, const char *unused_key, + const char *unused_value) +{ + msg_fatal("table %s:%s: update operation is not supported", + dict->type, dict->name); +} + +/* dict_default_delete - trap unimplemented operation */ + +static int dict_default_delete(DICT *dict, const char *unused_key) +{ + msg_fatal("table %s:%s: delete operation is not supported", + dict->type, dict->name); +} + +/* dict_default_sequence - trap unimplemented operation */ + +static int dict_default_sequence(DICT *dict, int unused_function, + const char **unused_key, const char **unused_value) +{ + msg_fatal("table %s:%s: sequence operation is not supported", + dict->type, dict->name); +} + +/* dict_default_lock - default lock handler */ + +static int dict_default_lock(DICT *dict, int operation) +{ + if (dict->lock_fd >= 0) { + return (myflock(dict->lock_fd, dict->lock_type, operation)); + } else { + return (0); + } +} + +/* dict_default_close - trap unimplemented operation */ + +static void dict_default_close(DICT *dict) +{ + msg_fatal("table %s:%s: close operation is not supported", + dict->type, dict->name); +} + +/* dict_alloc - allocate dictionary object, initialize super-class */ + +DICT *dict_alloc(const char *dict_type, const char *dict_name, ssize_t size) +{ + DICT *dict = (DICT *) mymalloc(size); + + dict->type = mystrdup(dict_type); + dict->name = mystrdup(dict_name); + dict->flags = DICT_FLAG_FIXED; + dict->lookup = dict_default_lookup; + dict->update = dict_default_update; + dict->delete = dict_default_delete; + dict->sequence = dict_default_sequence; + dict->close = dict_default_close; + dict->lock = dict_default_lock; + dict->lock_type = INTERNAL_LOCK; + dict->lock_fd = -1; + dict->stat_fd = -1; + dict->mtime = 0; + dict->fold_buf = 0; + dict->owner.status = DICT_OWNER_UNKNOWN; + dict->owner.uid = INT_MAX; + dict->error = DICT_ERR_NONE; + dict->jbuf = 0; + dict->utf8_backup = 0; + dict->file_buf = 0; + dict->file_b64 = 0; + return dict; +} + +/* dict_free - super-class destructor */ + +void dict_free(DICT *dict) +{ + myfree(dict->type); + myfree(dict->name); + if (dict->jbuf) + myfree((void *) dict->jbuf); + if (dict->utf8_backup) + myfree((void *) dict->utf8_backup); + if (dict->file_buf) + vstring_free(dict->file_buf); + if (dict->file_b64) + vstring_free(dict->file_b64); + myfree((void *) dict); +} + + /* + * TODO: add a dict_flags() argument to dict_alloc() and handle jump buffer + * allocation there. + */ + +/* dict_jmp_alloc - enable exception handling */ + +void dict_jmp_alloc(DICT *dict) +{ + if (dict->jbuf == 0) + dict->jbuf = (DICT_JMP_BUF *) mymalloc(sizeof(DICT_JMP_BUF)); +} diff --git a/src/util/dict_cache.c b/src/util/dict_cache.c new file mode 100644 index 0000000..d8e874d --- /dev/null +++ b/src/util/dict_cache.c @@ -0,0 +1,1121 @@ +/*++ +/* NAME +/* dict_cache 3 +/* SUMMARY +/* External cache manager +/* SYNOPSIS +/* #include <dict_cache.h> +/* +/* DICT_CACHE *dict_cache_open(dbname, open_flags, dict_flags) +/* const char *dbname; +/* int open_flags; +/* int dict_flags; +/* +/* void dict_cache_close(cache) +/* DICT_CACHE *cache; +/* +/* const char *dict_cache_lookup(cache, cache_key) +/* DICT_CACHE *cache; +/* const char *cache_key; +/* +/* int dict_cache_update(cache, cache_key, cache_val) +/* DICT_CACHE *cache; +/* const char *cache_key; +/* const char *cache_val; +/* +/* int dict_cache_delete(cache, cache_key) +/* DICT_CACHE *cache; +/* const char *cache_key; +/* +/* int dict_cache_sequence(cache, first_next, cache_key, cache_val) +/* DICT_CACHE *cache; +/* int first_next; +/* const char **cache_key; +/* const char **cache_val; +/* AUXILIARY FUNCTIONS +/* void dict_cache_control(cache, name, value, ...) +/* DICT_CACHE *cache; +/* int name; +/* +/* typedef int (*DICT_CACHE_VALIDATOR_FN) (const char *cache_key, +/* const char *cache_val, void *context); +/* +/* const char *dict_cache_name(cache) +/* DICT_CACHE *cache; +/* DESCRIPTION +/* This module maintains external cache files with support +/* for expiration. The underlying table must implement the +/* "lookup", "update", "delete" and "sequence" operations. +/* +/* Although this API is similar to the one documented in +/* dict_open(3), there are subtle differences in the interaction +/* between the iterators that access all cache elements, and +/* other operations that access individual cache elements. +/* +/* In particular, when a "sequence" or "cleanup" operation is +/* in progress the cache intercepts requests to delete the +/* "current" entry, as this would cause some databases to +/* mis-behave. Instead, the cache implements a "delete behind" +/* strategy, and deletes such an entry after the "sequence" +/* or "cleanup" operation moves on to the next cache element. +/* The "delete behind" strategy also affects the cache lookup +/* and update operations as detailed below. +/* +/* dict_cache_open() is a wrapper around the dict_open() +/* function. It opens the specified cache and returns a handle +/* that must be used for subsequent access. This function does +/* not return in case of error. +/* +/* dict_cache_close() closes the specified cache and releases +/* memory that was allocated by dict_cache_open(), and terminates +/* any thread that was started with dict_cache_control(). +/* +/* dict_cache_lookup() looks up the specified cache entry. +/* The result value is a null pointer when the cache entry was +/* not found, or when the entry is scheduled for "delete +/* behind". +/* +/* dict_cache_update() updates the specified cache entry. If +/* the entry is scheduled for "delete behind", the delete +/* operation is canceled (because of this, the cache must be +/* opened with DICT_FLAG_DUP_REPLACE). This function does not +/* return in case of error. +/* +/* dict_cache_delete() removes the specified cache entry. If +/* this is the "current" entry of a "sequence" operation, the +/* entry is scheduled for "delete behind". The result value +/* is zero when the entry was found. +/* +/* dict_cache_sequence() iterates over the specified cache and +/* returns each entry in an implementation-defined order. The +/* result value is zero when a cache entry was found. +/* +/* Important: programs must not use both dict_cache_sequence() +/* and the built-in cache cleanup feature. +/* +/* dict_cache_control() provides control over the built-in +/* cache cleanup feature and logging. The arguments are a list +/* of macros with zero or more arguments, terminated with +/* CA_DICT_CACHE_CTL_END which has none. The following lists +/* the macros and corresponding argument types. +/* .IP "CA_DICT_CACHE_CTL_FLAGS(int flags)" +/* The arguments to this command are the bit-wise OR of zero +/* or more of the following: +/* .RS +/* .IP CA_DICT_CACHE_CTL_FLAG_VERBOSE +/* Enable verbose logging of cache activity. +/* .IP CA_DICT_CACHE_CTL_FLAG_EXP_SUMMARY +/* Log cache statistics after each cache cleanup run. +/* .RE +/* .IP "CA_DICT_CACHE_CTL_INTERVAL(int interval)" +/* The interval between cache cleanup runs. Specify a null +/* validator or interval to stop cache cleanup. +/* .IP "CA_DICT_CACHE_CTL_VALIDATOR(DICT_CACHE_VALIDATOR_FN validator)" +/* An application call-back routine that returns non-zero when +/* a cache entry should be kept. The call-back function should +/* not make changes to the cache. Specify a null validator or +/* interval to stop cache cleanup. +/* .IP "CA_DICT_CACHE_CTL_CONTEXT(void *context)" +/* Application context that is passed to the validator function. +/* .RE +/* .PP +/* dict_cache_name() returns the name of the specified cache. +/* +/* Arguments: +/* .IP "dbname, open_flags, dict_flags" +/* These are passed unchanged to dict_open(). The cache must +/* be opened with DICT_FLAG_DUP_REPLACE. +/* .IP cache +/* Cache handle created with dict_cache_open(). +/* .IP cache_key +/* Cache lookup key. +/* .IP cache_val +/* Information that is stored under a cache lookup key. +/* .IP first_next +/* One of DICT_SEQ_FUN_FIRST (first cache element) or +/* DICT_SEQ_FUN_NEXT (next cache element). +/* .sp +/* Note: there is no "stop" request. To ensure that the "delete +/* behind" strategy does not interfere with database access, +/* allow dict_cache_sequence() to run to completion. +/* .IP table +/* A bare dictionary handle. +/* DIAGNOSTICS +/* When a request is satisfied, the lookup routine returns +/* non-null, and the update, delete and sequence routines +/* return zero. The cache->error value is zero when a request +/* could not be satisfied because an item did not exist (delete, +/* sequence) or if it could not be updated. The cache->error +/* value is non-zero only when a request could not be satisfied, +/* and the cause was a database error. +/* +/* Cache access errors are logged with a warning message. To +/* avoid spamming the log, each type of operation logs no more +/* than one cache access error per second, per cache. Specify +/* the DICT_CACHE_FLAG_VERBOSE flag (see above) to log all +/* warnings. +/* BUGS +/* There should be a way to suspend automatic program suicide +/* until a cache cleanup run is completed. Some entries may +/* never be removed when the process max_idle time is less +/* than the time needed to make a full pass over the cache. +/* +/* The delete-behind strategy assumes that all updates are +/* made by a single process. Otherwise, delete-behind may +/* remove an entry that was updated after it was scheduled for +/* deletion. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* HISTORY +/* .ad +/* .fi +/* A predecessor of this code was written first for the Postfix +/* tlsmgr(8) daemon. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> +#include <stdlib.h> + +/* Utility library. */ + +#include <msg.h> +#include <dict.h> +#include <mymalloc.h> +#include <events.h> +#include <dict_cache.h> + +/* Application-specific. */ + + /* + * XXX Deleting entries while enumerating a map can he tricky. Some map + * types have a concept of cursor and support a "delete the current element" + * operation. Some map types without cursors don't behave well when the + * current first/next entry is deleted (example: with Berkeley DB < 2, the + * "next" operation produces garbage). To avoid trouble, we delete an entry + * after advancing the current first/next position beyond it; we use the + * same strategy with application requests to delete the current entry. + */ + + /* + * Opaque data structure. Use dict_cache_name() to access the name of the + * underlying database. + */ +struct DICT_CACHE { + char *name; /* full name including proxy: */ + int cache_flags; /* see below */ + int user_flags; /* logging */ + DICT *db; /* database handle */ + int error; /* last operation only */ + + /* Delete-behind support. */ + char *saved_curr_key; /* "current" cache lookup key */ + char *saved_curr_val; /* "current" cache lookup result */ + + /* Cleanup support. */ + int exp_interval; /* time between cleanup runs */ + DICT_CACHE_VALIDATOR_FN exp_validator; /* expiration call-back */ + void *exp_context; /* call-back context */ + int retained; /* entries retained in cleanup run */ + int dropped; /* entries removed in cleanup run */ + + /* Rate-limited logging support. */ + int log_delay; + time_t upd_log_stamp; /* last update warning */ + time_t get_log_stamp; /* last lookup warning */ + time_t del_log_stamp; /* last delete warning */ + time_t seq_log_stamp; /* last sequence warning */ +}; + +#define DC_FLAG_DEL_SAVED_CURRENT_KEY (1<<0) /* delete-behind is scheduled */ + + /* + * Don't log cache access errors more than once per second. + */ +#define DC_DEF_LOG_DELAY 1 + + /* + * Macros to make obscure code more readable. + */ +#define DC_SCHEDULE_FOR_DELETE_BEHIND(cp) \ + ((cp)->cache_flags |= DC_FLAG_DEL_SAVED_CURRENT_KEY) + +#define DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key) \ + ((cp)->saved_curr_key && strcmp((cp)->saved_curr_key, (cache_key)) == 0) + +#define DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp) \ + (/* NOT: (cp)->saved_curr_key && */ \ + ((cp)->cache_flags & DC_FLAG_DEL_SAVED_CURRENT_KEY) != 0) + +#define DC_CANCEL_DELETE_BEHIND(cp) \ + ((cp)->cache_flags &= ~DC_FLAG_DEL_SAVED_CURRENT_KEY) + + /* + * Special key to store the time of the last cache cleanup run completion. + */ +#define DC_LAST_CACHE_CLEANUP_COMPLETED "_LAST_CACHE_CLEANUP_COMPLETED_" + +/* dict_cache_lookup - load entry from cache */ + +const char *dict_cache_lookup(DICT_CACHE *cp, const char *cache_key) +{ + const char *myname = "dict_cache_lookup"; + const char *cache_val; + DICT *db = cp->db; + + /* + * Search for the cache entry. Don't return an entry that is scheduled + * for delete-behind. + */ + if (DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp) + && DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key)) { + if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) + msg_info("%s: key=%s (pretend not found - scheduled for deletion)", + myname, cache_key); + DICT_ERR_VAL_RETURN(cp, DICT_ERR_NONE, (char *) 0); + } else { + cache_val = dict_get(db, cache_key); + if (cache_val == 0 && db->error != 0) + msg_rate_delay(&cp->get_log_stamp, cp->log_delay, msg_warn, + "%s: cache lookup for '%s' failed due to error", + cp->name, cache_key); + if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) + msg_info("%s: key=%s value=%s", myname, cache_key, + cache_val ? cache_val : db->error ? + "error" : "(not found)"); + DICT_ERR_VAL_RETURN(cp, db->error, cache_val); + } +} + +/* dict_cache_update - save entry to cache */ + +int dict_cache_update(DICT_CACHE *cp, const char *cache_key, + const char *cache_val) +{ + const char *myname = "dict_cache_update"; + DICT *db = cp->db; + int put_res; + + /* + * Store the cache entry and cancel the delete-behind operation. + */ + if (DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp) + && DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key)) { + if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) + msg_info("%s: cancel delete-behind for key=%s", myname, cache_key); + DC_CANCEL_DELETE_BEHIND(cp); + } + if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) + msg_info("%s: key=%s value=%s", myname, cache_key, cache_val); + put_res = dict_put(db, cache_key, cache_val); + if (put_res != 0) + msg_rate_delay(&cp->upd_log_stamp, cp->log_delay, msg_warn, + "%s: could not update entry for %s", cp->name, cache_key); + DICT_ERR_VAL_RETURN(cp, db->error, put_res); +} + +/* dict_cache_delete - delete entry from cache */ + +int dict_cache_delete(DICT_CACHE *cp, const char *cache_key) +{ + const char *myname = "dict_cache_delete"; + int del_res; + DICT *db = cp->db; + + /* + * Delete the entry, unless we would delete the current first/next entry. + * In that case, schedule the "current" entry for delete-behind to avoid + * mis-behavior by some databases. + */ + if (DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key)) { + DC_SCHEDULE_FOR_DELETE_BEHIND(cp); + if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) + msg_info("%s: key=%s (current entry - schedule for delete-behind)", + myname, cache_key); + DICT_ERR_VAL_RETURN(cp, DICT_ERR_NONE, DICT_STAT_SUCCESS); + } else { + del_res = dict_del(db, cache_key); + if (del_res != 0) + msg_rate_delay(&cp->del_log_stamp, cp->log_delay, msg_warn, + "%s: could not delete entry for %s", cp->name, cache_key); + if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) + msg_info("%s: key=%s (%s)", myname, cache_key, + del_res == 0 ? "found" : + db->error ? "error" : "not found"); + DICT_ERR_VAL_RETURN(cp, db->error, del_res); + } +} + +/* dict_cache_sequence - look up the first/next cache entry */ + +int dict_cache_sequence(DICT_CACHE *cp, int first_next, + const char **cache_key, + const char **cache_val) +{ + const char *myname = "dict_cache_sequence"; + int seq_res; + const char *raw_cache_key; + const char *raw_cache_val; + char *previous_curr_key; + char *previous_curr_val; + DICT *db = cp->db; + + /* + * Find the first or next database entry. Hide the record with the cache + * cleanup completion time stamp. + */ + seq_res = dict_seq(db, first_next, &raw_cache_key, &raw_cache_val); + if (seq_res == 0 + && strcmp(raw_cache_key, DC_LAST_CACHE_CLEANUP_COMPLETED) == 0) + seq_res = + dict_seq(db, DICT_SEQ_FUN_NEXT, &raw_cache_key, &raw_cache_val); + if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) + msg_info("%s: key=%s value=%s", myname, + seq_res == 0 ? raw_cache_key : db->error ? + "(error)" : "(not found)", + seq_res == 0 ? raw_cache_val : db->error ? + "(error)" : "(not found)"); + if (db->error) + msg_rate_delay(&cp->seq_log_stamp, cp->log_delay, msg_warn, + "%s: sequence error", cp->name); + + /* + * Save the current cache_key and cache_val before they are clobbered by + * our own delete operation below. This also prevents surprises when the + * application accesses the database after this function returns. + * + * We also use the saved cache_key to protect the current entry against + * application delete requests. + */ + previous_curr_key = cp->saved_curr_key; + previous_curr_val = cp->saved_curr_val; + if (seq_res == 0) { + cp->saved_curr_key = mystrdup(raw_cache_key); + cp->saved_curr_val = mystrdup(raw_cache_val); + } else { + cp->saved_curr_key = 0; + cp->saved_curr_val = 0; + } + + /* + * Delete behind. + */ + if (db->error == 0 && DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp)) { + DC_CANCEL_DELETE_BEHIND(cp); + if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) + msg_info("%s: delete-behind key=%s value=%s", + myname, previous_curr_key, previous_curr_val); + if (dict_del(db, previous_curr_key) != 0) + msg_rate_delay(&cp->del_log_stamp, cp->log_delay, msg_warn, + "%s: could not delete entry for %s", + cp->name, previous_curr_key); + } + + /* + * Clean up previous iteration key and value. + */ + if (previous_curr_key) + myfree(previous_curr_key); + if (previous_curr_val) + myfree(previous_curr_val); + + /* + * Return the result. + */ + *cache_key = (cp)->saved_curr_key; + *cache_val = (cp)->saved_curr_val; + DICT_ERR_VAL_RETURN(cp, db->error, seq_res); +} + +/* dict_cache_delete_behind_reset - reset "delete behind" state */ + +static void dict_cache_delete_behind_reset(DICT_CACHE *cp) +{ +#define FREE_AND_WIPE(s) do { if (s) { myfree(s); (s) = 0; } } while (0) + + DC_CANCEL_DELETE_BEHIND(cp); + FREE_AND_WIPE(cp->saved_curr_key); + FREE_AND_WIPE(cp->saved_curr_val); +} + +/* dict_cache_clean_stat_log_reset - log and reset cache cleanup statistics */ + +static void dict_cache_clean_stat_log_reset(DICT_CACHE *cp, + const char *full_partial) +{ + if (cp->user_flags & DICT_CACHE_FLAG_STATISTICS) + msg_info("cache %s %s cleanup: retained=%d dropped=%d entries", + cp->name, full_partial, cp->retained, cp->dropped); + cp->retained = cp->dropped = 0; +} + +/* dict_cache_clean_event - examine one cache entry */ + +static void dict_cache_clean_event(int unused_event, void *cache_context) +{ + const char *myname = "dict_cache_clean_event"; + DICT_CACHE *cp = (DICT_CACHE *) cache_context; + const char *cache_key; + const char *cache_val; + int next_interval; + VSTRING *stamp_buf; + int first_next; + + /* + * We interleave cache cleanup with other processing, so that the + * application's service remains available, with perhaps increased + * latency. + */ + + /* + * Start a new cache cleanup run. + */ + if (cp->saved_curr_key == 0) { + cp->retained = cp->dropped = 0; + first_next = DICT_SEQ_FUN_FIRST; + if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) + msg_info("%s: start %s cache cleanup", myname, cp->name); + } + + /* + * Continue a cache cleanup run in progress. + */ + else { + first_next = DICT_SEQ_FUN_NEXT; + } + + /* + * Examine one cache entry. + */ + if (dict_cache_sequence(cp, first_next, &cache_key, &cache_val) == 0) { + if (cp->exp_validator(cache_key, cache_val, cp->exp_context) == 0) { + DC_SCHEDULE_FOR_DELETE_BEHIND(cp); + cp->dropped++; + if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) + msg_info("%s: drop %s cache entry for %s", + myname, cp->name, cache_key); + } else { + cp->retained++; + if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) + msg_info("%s: keep %s cache entry for %s", + myname, cp->name, cache_key); + } + next_interval = 0; + } + + /* + * Cache cleanup completed. Report vital statistics. + */ + else if (cp->error != 0) { + msg_warn("%s: cache cleanup scan terminated due to error", cp->name); + dict_cache_clean_stat_log_reset(cp, "partial"); + next_interval = cp->exp_interval; + } else { + if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) + msg_info("%s: done %s cache cleanup scan", myname, cp->name); + dict_cache_clean_stat_log_reset(cp, "full"); + stamp_buf = vstring_alloc(100); + vstring_sprintf(stamp_buf, "%ld", (long) event_time()); + dict_put(cp->db, DC_LAST_CACHE_CLEANUP_COMPLETED, + vstring_str(stamp_buf)); + vstring_free(stamp_buf); + next_interval = cp->exp_interval; + } + event_request_timer(dict_cache_clean_event, cache_context, next_interval); +} + +/* dict_cache_control - schedule or stop the cache cleanup thread */ + +void dict_cache_control(DICT_CACHE *cp,...) +{ + const char *myname = "dict_cache_control"; + const char *last_done; + time_t next_interval; + int cache_cleanup_is_active = (cp->exp_validator && cp->exp_interval); + va_list ap; + int name; + + /* + * Update the control settings. + */ + va_start(ap, cp); + while ((name = va_arg(ap, int)) > 0) { + switch (name) { + case DICT_CACHE_CTL_END: + break; + case DICT_CACHE_CTL_FLAGS: + cp->user_flags = va_arg(ap, int); + cp->log_delay = (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) ? + 0 : DC_DEF_LOG_DELAY; + break; + case DICT_CACHE_CTL_INTERVAL: + cp->exp_interval = va_arg(ap, int); + if (cp->exp_interval < 0) + msg_panic("%s: bad %s cache cleanup interval %d", + myname, cp->name, cp->exp_interval); + break; + case DICT_CACHE_CTL_VALIDATOR: + cp->exp_validator = va_arg(ap, DICT_CACHE_VALIDATOR_FN); + break; + case DICT_CACHE_CTL_CONTEXT: + cp->exp_context = va_arg(ap, void *); + break; + default: + msg_panic("%s: bad command: %d", myname, name); + } + } + va_end(ap); + + /* + * Schedule the cache cleanup thread. + */ + if (cp->exp_interval && cp->exp_validator) { + + /* + * Sanity checks. + */ + if (cache_cleanup_is_active) + msg_panic("%s: %s cache cleanup is already scheduled", + myname, cp->name); + + /* + * The next start time depends on the last completion time. + */ +#define NEXT_START(last, delta) ((delta) + (unsigned long) atol(last)) +#define NOW (time((time_t *) 0)) /* NOT: event_time() */ + + if ((last_done = dict_get(cp->db, DC_LAST_CACHE_CLEANUP_COMPLETED)) == 0 + || (next_interval = (NEXT_START(last_done, cp->exp_interval) - NOW)) < 0) + next_interval = 0; + if (next_interval > cp->exp_interval) + next_interval = cp->exp_interval; + if ((cp->user_flags & DICT_CACHE_FLAG_VERBOSE) && next_interval > 0) + msg_info("%s cache cleanup will start after %ds", + cp->name, (int) next_interval); + event_request_timer(dict_cache_clean_event, (void *) cp, + (int) next_interval); + } + + /* + * Cancel the cache cleanup thread. + */ + else if (cache_cleanup_is_active) { + if (cp->retained || cp->dropped) + dict_cache_clean_stat_log_reset(cp, "partial"); + dict_cache_delete_behind_reset(cp); + event_cancel_timer(dict_cache_clean_event, (void *) cp); + } +} + +/* dict_cache_open - open cache file */ + +DICT_CACHE *dict_cache_open(const char *dbname, int open_flags, int dict_flags) +{ + DICT_CACHE *cp; + DICT *dict; + + /* + * Open the database as requested. Don't attempt to second-guess the + * application. + */ + dict = dict_open(dbname, open_flags, dict_flags); + + /* + * Create the DICT_CACHE object. + */ + cp = (DICT_CACHE *) mymalloc(sizeof(*cp)); + cp->name = mystrdup(dbname); + cp->cache_flags = 0; + cp->user_flags = 0; + cp->db = dict; + cp->saved_curr_key = 0; + cp->saved_curr_val = 0; + cp->exp_interval = 0; + cp->exp_validator = 0; + cp->exp_context = 0; + cp->retained = 0; + cp->dropped = 0; + cp->log_delay = DC_DEF_LOG_DELAY; + cp->upd_log_stamp = cp->get_log_stamp = + cp->del_log_stamp = cp->seq_log_stamp = 0; + + return (cp); +} + +/* dict_cache_close - close cache file */ + +void dict_cache_close(DICT_CACHE *cp) +{ + + /* + * Destroy the DICT_CACHE object. + */ + dict_cache_control(cp, DICT_CACHE_CTL_INTERVAL, 0, DICT_CACHE_CTL_END); + myfree(cp->name); + dict_close(cp->db); + if (cp->saved_curr_key) + myfree(cp->saved_curr_key); + if (cp->saved_curr_val) + myfree(cp->saved_curr_val); + myfree((void *) cp); +} + +/* dict_cache_name - get the cache name */ + +const char *dict_cache_name(DICT_CACHE *cp) +{ + + /* + * This is used for verbose logging or warning messages, so the cost of + * call is only made where needed (well sort off - code that does not + * execute still presents overhead for the processor pipeline, processor + * cache, etc). + */ + return (cp->name); +} + + /* + * Test driver with support for interleaved access. First, enter a number of + * requests to look up, update or delete a sequence of cache entries, then + * interleave those sequences with the "run" command. + */ +#ifdef TEST +#include <msg_vstream.h> +#include <vstring_vstream.h> +#include <argv.h> +#include <stringops.h> + +#define DELIMS " " +#define USAGE "\n\tTo manage settings:" \ + "\n\tverbose <level> (verbosity level)" \ + "\n\telapsed <level> (0=don't show elapsed time)" \ + "\n\tlmdb_map_size <limit> (initial LMDB size limit)" \ + "\n\tcache <type>:<name> (switch to named database)" \ + "\n\tstatus (show map size, cache, pending requests)" \ + "\n\n\tTo manage pending requests:" \ + "\n\treset (discard pending requests)" \ + "\n\trun (execute pending requests in interleaved order)" \ + "\n\n\tTo add a pending request:" \ + "\n\tquery <key-suffix> <count> (negative to reverse order)" \ + "\n\tupdate <key-suffix> <count> (negative to reverse order)" \ + "\n\tdelete <key-suffix> <count> (negative to reverse order)" \ + "\n\tpurge <key-suffix>" \ + "\n\tcount <key-suffix>" + + /* + * For realism, open the cache with the same flags as postscreen(8) and + * verify(8). + */ +#define DICT_CACHE_OPEN_FLAGS (DICT_FLAG_DUP_REPLACE | DICT_FLAG_SYNC_UPDATE | \ + DICT_FLAG_OPEN_LOCK) + + /* + * Storage for one request to access a sequence of cache entries. + */ +typedef struct DICT_CACHE_SREQ { + int flags; /* per-request: reverse, purge */ + char *cmd; /* command for status report */ + void (*action) (struct DICT_CACHE_SREQ *, DICT_CACHE *, VSTRING *); + char *suffix; /* key suffix */ + int done; /* progress indicator */ + int todo; /* number of entries to process */ + int first_next; /* first/next */ +} DICT_CACHE_SREQ; + +#define DICT_CACHE_SREQ_FLAG_PURGE (1<<1) /* purge instead of count */ +#define DICT_CACHE_SREQ_FLAG_REVERSE (1<<2) /* reverse instead of forward */ + +#define DICT_CACHE_SREQ_LIMIT 10 + + /* + * All test requests combined. + */ +typedef struct DICT_CACHE_TEST { + int flags; /* exclusion flags */ + int size; /* allocated slots */ + int used; /* used slots */ + DICT_CACHE_SREQ job_list[1]; /* actually, a bunch */ +} DICT_CACHE_TEST; + +#define DICT_CACHE_TEST_FLAG_ITER (1<<0) /* count or purge */ + +#define STR(x) vstring_str(x) + +int show_elapsed = 1; /* show elapsed time */ + +#ifdef HAS_LMDB +extern size_t dict_lmdb_map_size; /* LMDB-specific */ + +#endif + +/* usage - command-line usage message */ + +static NORETURN usage(const char *progname) +{ + msg_fatal("usage: %s (no argument)", progname); +} + +/* make_tagged_key - make tagged search key */ + +static void make_tagged_key(VSTRING *bp, DICT_CACHE_SREQ *cp) +{ + if (cp->done < 0) + msg_panic("make_tagged_key: bad done count: %d", cp->done); + if (cp->todo < 1) + msg_panic("make_tagged_key: bad todo count: %d", cp->todo); + vstring_sprintf(bp, "%d-%s", + (cp->flags & DICT_CACHE_SREQ_FLAG_REVERSE) ? + cp->todo - cp->done - 1 : cp->done, cp->suffix); +} + +/* create_requests - create request list */ + +static DICT_CACHE_TEST *create_requests(int count) +{ + DICT_CACHE_TEST *tp; + DICT_CACHE_SREQ *cp; + + tp = (DICT_CACHE_TEST *) mymalloc(sizeof(DICT_CACHE_TEST) + + (count - 1) *sizeof(DICT_CACHE_SREQ)); + tp->flags = 0; + tp->size = count; + tp->used = 0; + for (cp = tp->job_list; cp < tp->job_list + count; cp++) { + cp->flags = 0; + cp->cmd = 0; + cp->action = 0; + cp->suffix = 0; + cp->todo = 0; + cp->first_next = DICT_SEQ_FUN_FIRST; + } + return (tp); +} + +/* reset_requests - reset request list */ + +static void reset_requests(DICT_CACHE_TEST *tp) +{ + DICT_CACHE_SREQ *cp; + + tp->flags = 0; + tp->used = 0; + for (cp = tp->job_list; cp < tp->job_list + tp->size; cp++) { + cp->flags = 0; + if (cp->cmd) { + myfree(cp->cmd); + cp->cmd = 0; + } + cp->action = 0; + if (cp->suffix) { + myfree(cp->suffix); + cp->suffix = 0; + } + cp->todo = 0; + cp->first_next = DICT_SEQ_FUN_FIRST; + } +} + +/* free_requests - destroy request list */ + +static void free_requests(DICT_CACHE_TEST *tp) +{ + reset_requests(tp); + myfree((void *) tp); +} + +/* run_requests - execute pending requests in interleaved order */ + +static void run_requests(DICT_CACHE_TEST *tp, DICT_CACHE *dp, VSTRING *bp) +{ + DICT_CACHE_SREQ *cp; + int todo; + struct timeval start; + struct timeval finish; + struct timeval elapsed; + + if (dp == 0) { + msg_warn("no cache"); + return; + } + GETTIMEOFDAY(&start); + do { + todo = 0; + for (cp = tp->job_list; cp < tp->job_list + tp->used; cp++) { + if (cp->done < cp->todo) { + todo = 1; + cp->action(cp, dp, bp); + } + } + } while (todo); + GETTIMEOFDAY(&finish); + timersub(&finish, &start, &elapsed); + if (show_elapsed) + vstream_printf("Elapsed: %g\n", + elapsed.tv_sec + elapsed.tv_usec / 1000000.0); + + reset_requests(tp); +} + +/* show_status - show settings and pending requests */ + +static void show_status(DICT_CACHE_TEST *tp, DICT_CACHE *dp) +{ + DICT_CACHE_SREQ *cp; + +#ifdef HAS_LMDB + vstream_printf("lmdb_map_size\t%ld\n", (long) dict_lmdb_map_size); +#endif + vstream_printf("cache\t%s\n", dp ? dp->name : "(none)"); + + if (tp->used == 0) + vstream_printf("No pending requests\n"); + else + vstream_printf("%s\t%s\t%s\t%s\t%s\t%s\n", + "cmd", "dir", "suffix", "count", "done", "first/next"); + + for (cp = tp->job_list; cp < tp->job_list + tp->used; cp++) + if (cp->todo > 0) + vstream_printf("%s\t%s\t%s\t%d\t%d\t%d\n", + cp->cmd, + (cp->flags & DICT_CACHE_SREQ_FLAG_REVERSE) ? + "reverse" : "forward", + cp->suffix ? cp->suffix : "(null)", cp->todo, + cp->done, cp->first_next); +} + +/* query_action - lookup cache entry */ + +static void query_action(DICT_CACHE_SREQ *cp, DICT_CACHE *dp, VSTRING *bp) +{ + const char *lookup; + + make_tagged_key(bp, cp); + if ((lookup = dict_cache_lookup(dp, STR(bp))) == 0) { + if (dp->error) + msg_warn("query_action: query failed: %s: %m", STR(bp)); + else + msg_warn("query_action: query failed: %s", STR(bp)); + } else if (strcmp(STR(bp), lookup) != 0) { + msg_warn("lookup result \"%s\" differs from key \"%s\"", + lookup, STR(bp)); + } + cp->done += 1; +} + +/* update_action - update cache entry */ + +static void update_action(DICT_CACHE_SREQ *cp, DICT_CACHE *dp, VSTRING *bp) +{ + make_tagged_key(bp, cp); + if (dict_cache_update(dp, STR(bp), STR(bp)) != 0) { + if (dp->error) + msg_warn("update_action: update failed: %s: %m", STR(bp)); + else + msg_warn("update_action: update failed: %s", STR(bp)); + } + cp->done += 1; +} + +/* delete_action - delete cache entry */ + +static void delete_action(DICT_CACHE_SREQ *cp, DICT_CACHE *dp, VSTRING *bp) +{ + make_tagged_key(bp, cp); + if (dict_cache_delete(dp, STR(bp)) != 0) { + if (dp->error) + msg_warn("delete_action: delete failed: %s: %m", STR(bp)); + else + msg_warn("delete_action: delete failed: %s", STR(bp)); + } + cp->done += 1; +} + +/* iter_action - iterate over cache and act on entries with given suffix */ + +static void iter_action(DICT_CACHE_SREQ *cp, DICT_CACHE *dp, VSTRING *bp) +{ + const char *cache_key; + const char *cache_val; + const char *what; + const char *suffix; + + if (dict_cache_sequence(dp, cp->first_next, &cache_key, &cache_val) == 0) { + if (strcmp(cache_key, cache_val) != 0) + msg_warn("value \"%s\" differs from key \"%s\"", + cache_val, cache_key); + suffix = cache_key + strspn(cache_key, "0123456789"); + if (suffix[0] == '-' && strcmp(suffix + 1, cp->suffix) == 0) { + cp->done += 1; + cp->todo = cp->done + 1; /* XXX */ + if ((cp->flags & DICT_CACHE_SREQ_FLAG_PURGE) + && dict_cache_delete(dp, cache_key) != 0) { + if (dp->error) + msg_warn("purge_action: delete failed: %s: %m", STR(bp)); + else + msg_warn("purge_action: delete failed: %s", STR(bp)); + } + } + cp->first_next = DICT_SEQ_FUN_NEXT; + } else { + what = (cp->flags & DICT_CACHE_SREQ_FLAG_PURGE) ? "purge" : "count"; + if (dp->error) + msg_warn("%s error after %d: %m", what, cp->done); + else + vstream_printf("suffix=%s %s=%d\n", cp->suffix, what, cp->done); + cp->todo = 0; + } +} + + /* + * Table-driven support. + */ +typedef struct DICT_CACHE_SREQ_INFO { + const char *name; + int argc; + void (*action) (DICT_CACHE_SREQ *, DICT_CACHE *, VSTRING *); + int test_flags; + int req_flags; +} DICT_CACHE_SREQ_INFO; + +static DICT_CACHE_SREQ_INFO req_info[] = { + {"query", 3, query_action}, + {"update", 3, update_action}, + {"delete", 3, delete_action}, + {"count", 2, iter_action, DICT_CACHE_TEST_FLAG_ITER}, + {"purge", 2, iter_action, DICT_CACHE_TEST_FLAG_ITER, DICT_CACHE_SREQ_FLAG_PURGE}, + 0, +}; + +/* add_request - add a request to the list */ + +static void add_request(DICT_CACHE_TEST *tp, ARGV *argv) +{ + DICT_CACHE_SREQ_INFO *rp; + DICT_CACHE_SREQ *cp; + int req_flags; + int count; + char *cmd = argv->argv[0]; + char *suffix = (argv->argc > 1 ? argv->argv[1] : 0); + char *todo = (argv->argc > 2 ? argv->argv[2] : "1"); /* XXX */ + + if (tp->used >= tp->size) { + msg_warn("%s: request list is full", cmd); + return; + } + for (rp = req_info; /* See below */ ; rp++) { + if (rp->name == 0) { + vstream_printf("usage: %s\n", USAGE); + return; + } + if (strcmp(rp->name, argv->argv[0]) == 0 + && rp->argc == argv->argc) + break; + } + req_flags = rp->req_flags; + if (todo[0] == '-') { + req_flags |= DICT_CACHE_SREQ_FLAG_REVERSE; + todo += 1; + } + if (!alldig(todo) || (count = atoi(todo)) == 0) { + msg_warn("%s: bad count: %s", cmd, todo); + return; + } + if (tp->flags & rp->test_flags) { + msg_warn("%s: command conflicts with other command", cmd); + return; + } + tp->flags |= rp->test_flags; + cp = tp->job_list + tp->used; + cp->cmd = mystrdup(cmd); + cp->action = rp->action; + if (suffix) + cp->suffix = mystrdup(suffix); + cp->done = 0; + cp->flags = req_flags; + cp->todo = count; + tp->used += 1; +} + +/* main - main program */ + +int main(int argc, char **argv) +{ + DICT_CACHE_TEST *test_job; + VSTRING *inbuf = vstring_alloc(100); + char *bufp; + ARGV *args; + DICT_CACHE *cache = 0; + int stdin_is_tty; + + msg_vstream_init(argv[0], VSTREAM_ERR); + if (argc != 1) + usage(argv[0]); + + + test_job = create_requests(DICT_CACHE_SREQ_LIMIT); + + stdin_is_tty = isatty(0); + + for (;;) { + if (stdin_is_tty) { + vstream_printf("> "); + vstream_fflush(VSTREAM_OUT); + } + if (vstring_fgets_nonl(inbuf, VSTREAM_IN) == 0) + break; + bufp = vstring_str(inbuf); + if (!stdin_is_tty) { + vstream_printf("> %s\n", bufp); + vstream_fflush(VSTREAM_OUT); + } + if (*bufp == '#') + continue; + args = argv_split(bufp, DELIMS); + if (argc == 0) { + vstream_printf("usage: %s\n", USAGE); + vstream_fflush(VSTREAM_OUT); + continue; + } + if (strcmp(args->argv[0], "verbose") == 0 && args->argc == 2) { + msg_verbose = atoi(args->argv[1]); + } else if (strcmp(args->argv[0], "elapsed") == 0 && args->argc == 2) { + show_elapsed = atoi(args->argv[1]); +#ifdef HAS_LMDB + } else if (strcmp(args->argv[0], "lmdb_map_size") == 0 && args->argc == 2) { + dict_lmdb_map_size = atol(args->argv[1]); +#endif + } else if (strcmp(args->argv[0], "cache") == 0 && args->argc == 2) { + if (cache) + dict_cache_close(cache); + cache = dict_cache_open(args->argv[1], O_CREAT | O_RDWR, + DICT_CACHE_OPEN_FLAGS); + } else if (strcmp(args->argv[0], "reset") == 0 && args->argc == 1) { + reset_requests(test_job); + } else if (strcmp(args->argv[0], "run") == 0 && args->argc == 1) { + run_requests(test_job, cache, inbuf); + } else if (strcmp(args->argv[0], "status") == 0 && args->argc == 1) { + show_status(test_job, cache); + } else { + add_request(test_job, args); + } + vstream_fflush(VSTREAM_OUT); + argv_free(args); + } + + vstring_free(inbuf); + free_requests(test_job); + if (cache) + dict_cache_close(cache); + return (0); +} + +#endif diff --git a/src/util/dict_cache.h b/src/util/dict_cache.h new file mode 100644 index 0000000..2d403b5 --- /dev/null +++ b/src/util/dict_cache.h @@ -0,0 +1,67 @@ +#ifndef _DICT_CACHE_H_INCLUDED_ +#define _DICT_CACHE_H_INCLUDED_ + +/*++ +/* NAME +/* dict_cache 3h +/* SUMMARY +/* External cache manager +/* SYNOPSIS +/* #include <dict_cache.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <dict.h> +#include <check_arg.h> + + /* + * External interface. + */ +typedef struct DICT_CACHE DICT_CACHE; +typedef int (*DICT_CACHE_VALIDATOR_FN) (const char *, const char *, void *); + +extern DICT_CACHE *dict_cache_open(const char *, int, int); +extern void dict_cache_close(DICT_CACHE *); +extern const char *dict_cache_lookup(DICT_CACHE *, const char *); +extern int dict_cache_update(DICT_CACHE *, const char *, const char *); +extern int dict_cache_delete(DICT_CACHE *, const char *); +extern int dict_cache_sequence(DICT_CACHE *, int, const char **, const char **); +extern void dict_cache_control(DICT_CACHE *,...); +extern const char *dict_cache_name(DICT_CACHE *); + +#define DICT_CACHE_FLAG_VERBOSE (1<<0) /* verbose operation */ +#define DICT_CACHE_FLAG_STATISTICS (1<<1) /* log cache statistics */ + +/* Legacy API: type-unchecked argument, internal use. */ +#define DICT_CACHE_CTL_END 0 /* list terminator */ +#define DICT_CACHE_CTL_FLAGS 1 /* see above */ +#define DICT_CACHE_CTL_INTERVAL 2 /* cleanup interval */ +#define DICT_CACHE_CTL_VALIDATOR 3 /* call-back validator */ +#define DICT_CACHE_CTL_CONTEXT 4 /* call-back context */ + +/* Safer API: type-checked arguments, external use. */ +#define CA_DICT_CACHE_CTL_END DICT_CACHE_CTL_END +#define CA_DICT_CACHE_CTL_FLAGS(v) DICT_CACHE_CTL_FLAGS, CHECK_VAL(DICT_CACHE, int, (v)) +#define CA_DICT_CACHE_CTL_INTERVAL(v) DICT_CACHE_CTL_INTERVAL, CHECK_VAL(DICT_CACHE, int, (v)) +#define CA_DICT_CACHE_CTL_VALIDATOR(v) DICT_CACHE_CTL_VALIDATOR, CHECK_VAL(DICT_CACHE, DICT_CACHE_VALIDATOR_FN, (v)) +#define CA_DICT_CACHE_CTL_CONTEXT(v) DICT_CACHE_CTL_CONTEXT, CHECK_PTR(DICT_CACHE, void, (v)) + +CHECK_VAL_HELPER_DCL(DICT_CACHE, int); +CHECK_VAL_HELPER_DCL(DICT_CACHE, DICT_CACHE_VALIDATOR_FN); +CHECK_PTR_HELPER_DCL(DICT_CACHE, void); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_cdb.c b/src/util/dict_cdb.c new file mode 100644 index 0000000..a9133cc --- /dev/null +++ b/src/util/dict_cdb.c @@ -0,0 +1,446 @@ +/*++ +/* NAME +/* dict_cdb 3 +/* SUMMARY +/* dictionary manager interface to CDB files +/* SYNOPSIS +/* #include <dict_cdb.h> +/* +/* DICT *dict_cdb_open(path, open_flags, dict_flags) +/* const char *path; +/* int open_flags; +/* int dict_flags; +/* +/* DESCRIPTION +/* dict_cdb_open() opens the specified CDB database. The result is +/* a pointer to a structure that can be used to access the dictionary +/* using the generic methods documented in dict_open(3). +/* +/* Arguments: +/* .IP path +/* The database pathname, not including the ".cdb" suffix. +/* .IP open_flags +/* Flags passed to open(). Specify O_RDONLY or O_WRONLY|O_CREAT|O_TRUNC. +/* .IP dict_flags +/* Flags used by the dictionary interface. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* DIAGNOSTICS +/* Fatal errors: cannot open file, write error, out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Michael Tokarev <mjt@tls.msk.ru> based on dict_db.c by +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#include "sys_defs.h" + +/* System library. */ + +#include <sys/stat.h> +#include <limits.h> +#include <string.h> +#include <unistd.h> +#include <stdio.h> + +/* Utility library. */ + +#include "msg.h" +#include "mymalloc.h" +#include "vstring.h" +#include "stringops.h" +#include "iostuff.h" +#include "myflock.h" +#include "stringops.h" +#include "dict.h" +#include "dict_cdb.h" +#include "warn_stat.h" + +#ifdef HAS_CDB + +#include <cdb.h> +#ifndef TINYCDB_VERSION +#include <cdb_make.h> +#endif +#ifndef cdb_fileno +#define cdb_fileno(c) ((c)->fd) +#endif + +#ifndef CDB_SUFFIX +#define CDB_SUFFIX ".cdb" +#endif +#ifndef CDB_TMP_SUFFIX +#define CDB_TMP_SUFFIX CDB_SUFFIX ".tmp" +#endif + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + struct cdb cdb; /* cdb structure */ +} DICT_CDBQ; /* query interface */ + +typedef struct { + DICT dict; /* generic members */ + struct cdb_make cdbm; /* cdb_make structure */ + char *cdb_path; /* cdb pathname (.cdb) */ + char *tmp_path; /* temporary pathname (.tmp) */ +} DICT_CDBM; /* rebuild interface */ + +/* dict_cdbq_lookup - find database entry, query mode */ + +static const char *dict_cdbq_lookup(DICT *dict, const char *name) +{ + DICT_CDBQ *dict_cdbq = (DICT_CDBQ *) dict; + unsigned vlen; + int status = 0; + static char *buf; + static unsigned len; + const char *result = 0; + + dict->error = 0; + + /* CDB is constant, so do not try to acquire a lock. */ + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + + /* + * See if this CDB file was written with one null byte appended to key + * and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + status = cdb_find(&dict_cdbq->cdb, name, strlen(name) + 1); + if (status > 0) + dict->flags &= ~DICT_FLAG_TRY0NULL; + } + + /* + * See if this CDB file was written with no null byte appended to key and + * value. + */ + if (status == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { + status = cdb_find(&dict_cdbq->cdb, name, strlen(name)); + if (status > 0) + dict->flags &= ~DICT_FLAG_TRY1NULL; + } + if (status < 0) + msg_fatal("error reading %s: %m", dict->name); + + if (status) { + vlen = cdb_datalen(&dict_cdbq->cdb); + if (len < vlen) { + if (buf == 0) + buf = mymalloc(vlen + 1); + else + buf = myrealloc(buf, vlen + 1); + len = vlen; + } + if (cdb_read(&dict_cdbq->cdb, buf, vlen, + cdb_datapos(&dict_cdbq->cdb)) < 0) + msg_fatal("error reading %s: %m", dict->name); + buf[vlen] = '\0'; + result = buf; + } + /* No locking so not release the lock. */ + + return (result); +} + +/* dict_cdbq_close - close data base, query mode */ + +static void dict_cdbq_close(DICT *dict) +{ + DICT_CDBQ *dict_cdbq = (DICT_CDBQ *) dict; + + cdb_free(&dict_cdbq->cdb); + close(dict->stat_fd); + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_cdbq_open - open data base, query mode */ + +static DICT *dict_cdbq_open(const char *path, int dict_flags) +{ + DICT_CDBQ *dict_cdbq; + struct stat st; + char *cdb_path; + int fd; + + /* + * Let the optimizer worry about eliminating redundant code. + */ +#define DICT_CDBQ_OPEN_RETURN(d) do { \ + DICT *__d = (d); \ + myfree(cdb_path); \ + return (__d); \ + } while (0) + + cdb_path = concatenate(path, CDB_SUFFIX, (char *) 0); + + if ((fd = open(cdb_path, O_RDONLY)) < 0) + DICT_CDBQ_OPEN_RETURN(dict_surrogate(DICT_TYPE_CDB, path, + O_RDONLY, dict_flags, + "open database %s: %m", cdb_path)); + + dict_cdbq = (DICT_CDBQ *) dict_alloc(DICT_TYPE_CDB, + cdb_path, sizeof(*dict_cdbq)); +#if defined(TINYCDB_VERSION) + if (cdb_init(&(dict_cdbq->cdb), fd) != 0) + msg_fatal("dict_cdbq_open: unable to init %s: %m", cdb_path); +#else + cdb_init(&(dict_cdbq->cdb), fd); +#endif + dict_cdbq->dict.lookup = dict_cdbq_lookup; + dict_cdbq->dict.close = dict_cdbq_close; + dict_cdbq->dict.stat_fd = fd; + if (fstat(fd, &st) < 0) + msg_fatal("dict_dbq_open: fstat: %m"); + dict_cdbq->dict.mtime = st.st_mtime; + dict_cdbq->dict.owner.uid = st.st_uid; + dict_cdbq->dict.owner.status = (st.st_uid != 0); + close_on_exec(fd, CLOSE_ON_EXEC); + + /* + * Warn if the source file is newer than the indexed file, except when + * the source file changed only seconds ago. + */ + if (stat(path, &st) == 0 + && st.st_mtime > dict_cdbq->dict.mtime + && st.st_mtime < time((time_t *) 0) - 100) + msg_warn("database %s is older than source file %s", cdb_path, path); + + /* + * If undecided about appending a null byte to key and value, choose to + * try both in query mode. + */ + if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + dict_flags |= DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL; + dict_cdbq->dict.flags = dict_flags | DICT_FLAG_FIXED; + if (dict_flags & DICT_FLAG_FOLD_FIX) + dict_cdbq->dict.fold_buf = vstring_alloc(10); + + DICT_CDBQ_OPEN_RETURN(DICT_DEBUG (&dict_cdbq->dict)); +} + +/* dict_cdbm_update - add database entry, create mode */ + +static int dict_cdbm_update(DICT *dict, const char *name, const char *value) +{ + DICT_CDBM *dict_cdbm = (DICT_CDBM *) dict; + unsigned ksize, vsize; + int r; + + dict->error = 0; + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + ksize = strlen(name); + vsize = strlen(value); + + /* + * Optionally append a null byte to key and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + ksize++; + vsize++; + } + + /* + * Do the add operation. No locking is done. + */ +#ifdef TINYCDB_VERSION +#ifndef CDB_PUT_ADD +#error please upgrate tinycdb to at least 0.5 version +#endif + if (dict->flags & DICT_FLAG_DUP_IGNORE) + r = CDB_PUT_ADD; + else if (dict->flags & DICT_FLAG_DUP_REPLACE) + r = CDB_PUT_REPLACE; + else + r = CDB_PUT_INSERT; + r = cdb_make_put(&dict_cdbm->cdbm, name, ksize, value, vsize, r); + if (r < 0) + msg_fatal("error writing %s: %m", dict_cdbm->tmp_path); + else if (r > 0) { + if (dict->flags & (DICT_FLAG_DUP_IGNORE | DICT_FLAG_DUP_REPLACE)) + /* void */ ; + else if (dict->flags & DICT_FLAG_DUP_WARN) + msg_warn("%s: duplicate entry: \"%s\"", + dict_cdbm->dict.name, name); + else + msg_fatal("%s: duplicate entry: \"%s\"", + dict_cdbm->dict.name, name); + } + return (r); +#else + if (cdb_make_add(&dict_cdbm->cdbm, name, ksize, value, vsize) < 0) + msg_fatal("error writing %s: %m", dict_cdbm->tmp_path); + return (0); +#endif +} + +/* dict_cdbm_close - close data base and rename file.tmp to file.cdb */ + +static void dict_cdbm_close(DICT *dict) +{ + DICT_CDBM *dict_cdbm = (DICT_CDBM *) dict; + int fd = cdb_fileno(&dict_cdbm->cdbm); + + /* + * Note: if FCNTL locking is used, closing any file descriptor on a + * locked file cancels all locks that the process may have on that file. + * CDB is FCNTL locking safe, because it uses the same file descriptor + * for database I/O and locking. + */ + if (cdb_make_finish(&dict_cdbm->cdbm) < 0) + msg_fatal("finish database %s: %m", dict_cdbm->tmp_path); + if (rename(dict_cdbm->tmp_path, dict_cdbm->cdb_path) < 0) + msg_fatal("rename database from %s to %s: %m", + dict_cdbm->tmp_path, dict_cdbm->cdb_path); + if (close(fd) < 0) /* releases a lock */ + msg_fatal("close database %s: %m", dict_cdbm->cdb_path); + myfree(dict_cdbm->cdb_path); + myfree(dict_cdbm->tmp_path); + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_cdbm_open - create database as file.tmp */ + +static DICT *dict_cdbm_open(const char *path, int dict_flags) +{ + DICT_CDBM *dict_cdbm; + char *cdb_path; + char *tmp_path; + int fd; + struct stat st0, st1; + + /* + * Let the optimizer worry about eliminating redundant code. + */ +#define DICT_CDBM_OPEN_RETURN(d) do { \ + DICT *__d = (d); \ + if (cdb_path) \ + myfree(cdb_path); \ + if (tmp_path) \ + myfree(tmp_path); \ + return (__d); \ + } while (0) + + cdb_path = concatenate(path, CDB_SUFFIX, (char *) 0); + tmp_path = concatenate(path, CDB_TMP_SUFFIX, (char *) 0); + + /* + * Repeat until we have opened *and* locked *existing* file. Since the + * new (tmp) file will be renamed to be .cdb file, locking here is + * somewhat funny to work around possible race conditions. Note that we + * can't open a file with O_TRUNC as we can't know if another process + * isn't creating it at the same time. + */ + for (;;) { + if ((fd = open(tmp_path, O_RDWR | O_CREAT, 0644)) < 0) + DICT_CDBM_OPEN_RETURN(dict_surrogate(DICT_TYPE_CDB, path, + O_RDWR, dict_flags, + "open database %s: %m", + tmp_path)); + if (fstat(fd, &st0) < 0) + msg_fatal("fstat(%s): %m", tmp_path); + + /* + * Get an exclusive lock - we're going to change the database so we + * can't have any spectators. + */ + if (myflock(fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("lock %s: %m", tmp_path); + + if (stat(tmp_path, &st1) < 0) + msg_fatal("stat(%s): %m", tmp_path); + + /* + * Compare file's state before and after lock: should be the same, + * and nlinks should be >0, or else we opened non-existing file... + */ + if (st0.st_ino == st1.st_ino && st0.st_dev == st1.st_dev + && st0.st_rdev == st1.st_rdev && st0.st_nlink == st1.st_nlink + && st0.st_nlink > 0) + break; /* successfully opened */ + + close(fd); + + } + +#ifndef NO_FTRUNCATE + if (st0.st_size) + ftruncate(fd, 0); +#endif + + dict_cdbm = (DICT_CDBM *) dict_alloc(DICT_TYPE_CDB, path, + sizeof(*dict_cdbm)); + if (cdb_make_start(&dict_cdbm->cdbm, fd) < 0) + msg_fatal("initialize database %s: %m", tmp_path); + dict_cdbm->dict.close = dict_cdbm_close; + dict_cdbm->dict.update = dict_cdbm_update; + dict_cdbm->cdb_path = cdb_path; + dict_cdbm->tmp_path = tmp_path; + cdb_path = tmp_path = 0; /* DICT_CDBM_OPEN_RETURN() */ + dict_cdbm->dict.owner.uid = st1.st_uid; + dict_cdbm->dict.owner.status = (st1.st_uid != 0); + close_on_exec(fd, CLOSE_ON_EXEC); + + /* + * If undecided about appending a null byte to key and value, choose a + * default to not append a null byte when creating a cdb. + */ + if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + dict_flags |= DICT_FLAG_TRY0NULL; + else if ((dict_flags & DICT_FLAG_TRY1NULL) + && (dict_flags & DICT_FLAG_TRY0NULL)) + dict_flags &= ~DICT_FLAG_TRY0NULL; + dict_cdbm->dict.flags = dict_flags | DICT_FLAG_FIXED; + if (dict_flags & DICT_FLAG_FOLD_FIX) + dict_cdbm->dict.fold_buf = vstring_alloc(10); + + DICT_CDBM_OPEN_RETURN(DICT_DEBUG (&dict_cdbm->dict)); +} + +/* dict_cdb_open - open data base for query mode or create mode */ + +DICT *dict_cdb_open(const char *path, int open_flags, int dict_flags) +{ + switch (open_flags & (O_RDONLY | O_RDWR | O_WRONLY | O_CREAT | O_TRUNC)) { + case O_RDONLY: /* query mode */ + return dict_cdbq_open(path, dict_flags); + case O_WRONLY | O_CREAT | O_TRUNC: /* create mode */ + case O_RDWR | O_CREAT | O_TRUNC: /* sloppiness */ + return dict_cdbm_open(path, dict_flags); + default: + msg_fatal("dict_cdb_open: inappropriate open flags for cdb database" + " - specify O_RDONLY or O_WRONLY|O_CREAT|O_TRUNC"); + } +} + +#endif /* HAS_CDB */ diff --git a/src/util/dict_cdb.h b/src/util/dict_cdb.h new file mode 100644 index 0000000..e2c13d4 --- /dev/null +++ b/src/util/dict_cdb.h @@ -0,0 +1,37 @@ +#ifndef _DICT_CDB_H_INCLUDED_ +#define _DICT_CDB_H_INCLUDED_ + +/*++ +/* NAME +/* dict_cdb 3h +/* SUMMARY +/* dictionary manager interface to CDB files +/* SYNOPSIS +/* #include <dict_cdb.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <dict.h> + + /* + * External interface. + */ +#define DICT_TYPE_CDB "cdb" + +extern DICT *dict_cdb_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif /* _DICT_CDB_H_INCLUDED_ */ diff --git a/src/util/dict_cidr.c b/src/util/dict_cidr.c new file mode 100644 index 0000000..d29a2ba --- /dev/null +++ b/src/util/dict_cidr.c @@ -0,0 +1,361 @@ +/*++ +/* NAME +/* dict_cidr 3 +/* SUMMARY +/* Dictionary interface for CIDR data +/* SYNOPSIS +/* #include <dict_cidr.h> +/* +/* DICT *dict_cidr_open(name, open_flags, dict_flags) +/* const char *name; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_cidr_open() opens the named file and stores +/* the key/value pairs where the key must be either a +/* "naked" IP address or a netblock in CIDR notation. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* AUTHOR(S) +/* Jozsef Kadlecsik +/* kadlec@blackhole.kfki.hu +/* KFKI Research Institute for Particle and Nuclear Physics +/* POB. 49 +/* 1525 Budapest, Hungary +/* +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +/* Utility library. */ + +#include <mymalloc.h> +#include <msg.h> +#include <vstream.h> +#include <vstring.h> +#include <stringops.h> +#include <readlline.h> +#include <dict.h> +#include <myaddrinfo.h> +#include <cidr_match.h> +#include <dict_cidr.h> +#include <warn_stat.h> +#include <mvect.h> + +/* Application-specific. */ + + /* + * Each rule in a CIDR table is parsed and stored in a linked list. + */ +typedef struct DICT_CIDR_ENTRY { + CIDR_MATCH cidr_info; /* must be first */ + char *value; /* lookup result */ + int lineno; +} DICT_CIDR_ENTRY; + +typedef struct { + DICT dict; /* generic members */ + DICT_CIDR_ENTRY *head; /* first entry */ +} DICT_CIDR; + +/* dict_cidr_lookup - CIDR table lookup */ + +static const char *dict_cidr_lookup(DICT *dict, const char *key) +{ + DICT_CIDR *dict_cidr = (DICT_CIDR *) dict; + DICT_CIDR_ENTRY *entry; + + if (msg_verbose) + msg_info("dict_cidr_lookup: %s: %s", dict->name, key); + + dict->error = 0; + + if ((entry = (DICT_CIDR_ENTRY *) + cidr_match_execute(&(dict_cidr->head->cidr_info), key)) != 0) + return (entry->value); + return (0); +} + +/* dict_cidr_close - close the CIDR table */ + +static void dict_cidr_close(DICT *dict) +{ + DICT_CIDR *dict_cidr = (DICT_CIDR *) dict; + DICT_CIDR_ENTRY *entry; + DICT_CIDR_ENTRY *next; + + for (entry = dict_cidr->head; entry; entry = next) { + next = (DICT_CIDR_ENTRY *) entry->cidr_info.next; + myfree(entry->value); + myfree((void *) entry); + } + dict_free(dict); +} + +/* dict_cidr_parse_rule - parse CIDR table rule into network, mask and value */ + +static DICT_CIDR_ENTRY *dict_cidr_parse_rule(DICT *dict, char *p, int lineno, + int nesting, VSTRING *why) +{ + DICT_CIDR_ENTRY *rule; + char *pattern; + char *value; + CIDR_MATCH cidr_info; + MAI_HOSTADDR_STR hostaddr; + int match = 1; + + /* + * IF must be followed by a pattern. + */ + if (strncasecmp(p, "IF", 2) == 0 && !ISALNUM(p[2])) { + p += 2; + for (;;) { + if (*p == '!') + match = !match; + else if (!ISSPACE(*p)) + break; + p++; + } + if (*p == 0) { + vstring_sprintf(why, "no address pattern"); + return (0); + } + trimblanks(p, 0)[0] = 0; /* Trim trailing blanks */ + if (cidr_match_parse_if(&cidr_info, p, match, why) != 0) + return (0); + value = ""; + } + + /* + * ENDIF must not be followed by other text. + */ + else if (strncasecmp(p, "ENDIF", 5) == 0 && !ISALNUM(p[5])) { + p += 5; + while (*p && ISSPACE(*p)) /* Skip whitespace */ + p++; + if (*p != 0) { + vstring_sprintf(why, "garbage after ENDIF"); + return (0); + } + if (nesting == 0) { + vstring_sprintf(why, "ENDIF without IF"); + return (0); + } + cidr_match_endif(&cidr_info); + value = ""; + } + + /* + * An address pattern. + */ + else { + + /* + * Process negation operators. + */ + for (;;) { + if (*p == '!') + match = !match; + else if (!ISSPACE(*p)) + break; + p++; + } + + /* + * Split the rule into key and value. We already eliminated leading + * whitespace, comments, empty lines or lines with whitespace only. + * This means a null key can't happen but we will handle this anyway. + */ + pattern = p; + while (*p && !ISSPACE(*p)) /* Skip over key */ + p++; + if (*p) /* Terminate key */ + *p++ = 0; + while (*p && ISSPACE(*p)) /* Skip whitespace */ + p++; + value = p; + trimblanks(value, 0)[0] = 0; /* Trim trailing blanks */ + if (*pattern == 0) { + vstring_sprintf(why, "no address pattern"); + return (0); + } + + /* + * Parse the pattern, destroying it in the process. + */ + if (cidr_match_parse(&cidr_info, pattern, match, why) != 0) + return (0); + + if (*value == 0) { + vstring_sprintf(why, "no lookup result"); + return (0); + } + } + + /* + * Optionally replace the value file the contents of a file. + */ + if (dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) { + VSTRING *base64_buf; + char *err; + + if ((base64_buf = dict_file_to_b64(dict, value)) == 0) { + err = dict_file_get_error(dict); + vstring_strcpy(why, err); + myfree(err); + return (0); + } + value = vstring_str(base64_buf); + } + + /* + * Bundle up the result. + */ + rule = (DICT_CIDR_ENTRY *) mymalloc(sizeof(DICT_CIDR_ENTRY)); + rule->cidr_info = cidr_info; + rule->value = mystrdup(value); + rule->lineno = lineno; + + if (msg_verbose) { + if (inet_ntop(cidr_info.addr_family, cidr_info.net_bytes, + hostaddr.buf, sizeof(hostaddr.buf)) == 0) + msg_fatal("inet_ntop: %m"); + msg_info("dict_cidr_open: add %s/%d %s", + hostaddr.buf, cidr_info.mask_shift, rule->value); + } + return (rule); +} + +/* dict_cidr_open - parse CIDR table */ + +DICT *dict_cidr_open(const char *mapname, int open_flags, int dict_flags) +{ + const char myname[] = "dict_cidr_open"; + DICT_CIDR *dict_cidr; + VSTREAM *map_fp = 0; + struct stat st; + VSTRING *line_buffer = 0; + VSTRING *why = 0; + DICT_CIDR_ENTRY *rule; + DICT_CIDR_ENTRY *last_rule = 0; + int last_line = 0; + int lineno; + int nesting = 0; + DICT_CIDR_ENTRY **rule_stack = 0; + MVECT mvect; + + /* + * Let the optimizer worry about eliminating redundant code. + */ +#define DICT_CIDR_OPEN_RETURN(d) do { \ + DICT *__d = (d); \ + if (map_fp != 0 && vstream_fclose(map_fp)) \ + msg_fatal("cidr map %s: read error: %m", mapname); \ + if (line_buffer != 0) \ + vstring_free(line_buffer); \ + if (why != 0) \ + vstring_free(why); \ + return (__d); \ + } while (0) + + /* + * Sanity checks. + */ + if (open_flags != O_RDONLY) + DICT_CIDR_OPEN_RETURN(dict_surrogate(DICT_TYPE_CIDR, mapname, + open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_CIDR, mapname)); + + /* + * Open the configuration file. + */ + if ((map_fp = dict_stream_open(DICT_TYPE_CIDR, mapname, O_RDONLY, + dict_flags, &st, &why)) == 0) + DICT_CIDR_OPEN_RETURN(dict_surrogate(DICT_TYPE_CIDR, mapname, + open_flags, dict_flags, + "%s", vstring_str(why))); + line_buffer = vstring_alloc(100); + why = vstring_alloc(100); + + /* + * XXX Eliminate unnecessary queries by setting a flag that says "this + * map matches network addresses only". + */ + dict_cidr = (DICT_CIDR *) dict_alloc(DICT_TYPE_CIDR, mapname, + sizeof(*dict_cidr)); + dict_cidr->dict.lookup = dict_cidr_lookup; + dict_cidr->dict.close = dict_cidr_close; + dict_cidr->dict.flags = dict_flags | DICT_FLAG_PATTERN; + dict_cidr->head = 0; + + dict_cidr->dict.owner.uid = st.st_uid; + dict_cidr->dict.owner.status = (st.st_uid != 0); + + while (readllines(line_buffer, map_fp, &last_line, &lineno)) { + rule = dict_cidr_parse_rule(&dict_cidr->dict, + vstring_str(line_buffer), lineno, + nesting, why); + if (rule == 0) { + msg_warn("cidr map %s, line %d: %s: skipping this rule", + mapname, lineno, vstring_str(why)); + continue; + } + if (rule->cidr_info.op == CIDR_MATCH_OP_IF) { + if (rule_stack == 0) + rule_stack = (DICT_CIDR_ENTRY **) mvect_alloc(&mvect, + sizeof(*rule_stack), nesting + 1, + (MVECT_FN) 0, (MVECT_FN) 0); + else + rule_stack = + (DICT_CIDR_ENTRY **) mvect_realloc(&mvect, nesting + 1); + rule_stack[nesting] = rule; + nesting++; + } else if (rule->cidr_info.op == CIDR_MATCH_OP_ENDIF) { + DICT_CIDR_ENTRY *if_rule; + + if (nesting-- <= 0) + /* Already handled in dict_cidr_parse_rule(). */ + msg_panic("%s: ENDIF without IF", myname); + if_rule = rule_stack[nesting]; + if (if_rule->cidr_info.op != CIDR_MATCH_OP_IF) + msg_panic("%s: unexpected rule stack element type %d", + myname, if_rule->cidr_info.op); + if_rule->cidr_info.block_end = &(rule->cidr_info); + } + if (last_rule == 0) + dict_cidr->head = rule; + else + last_rule->cidr_info.next = &(rule->cidr_info); + last_rule = rule; + } + + while (nesting-- > 0) + msg_warn("cidr map %s, line %d: IF has no matching ENDIF", + mapname, rule_stack[nesting]->lineno); + + if (rule_stack) + (void) mvect_free(&mvect); + + dict_file_purge_buffers(&dict_cidr->dict); + DICT_CIDR_OPEN_RETURN(DICT_DEBUG (&dict_cidr->dict)); +} diff --git a/src/util/dict_cidr.h b/src/util/dict_cidr.h new file mode 100644 index 0000000..308a765 --- /dev/null +++ b/src/util/dict_cidr.h @@ -0,0 +1,43 @@ +#ifndef _DICT_CIDR_H_INCLUDED_ +#define _DICT_CIDR_H_INCLUDED_ + +/*++ +/* NAME +/* dict_cidr 3h +/* SUMMARY +/* Dictionary manager interface to handle cidr data. +/* SYNOPSIS +/* #include <dict_cidr.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <dict.h> + + /* + * External interface. + */ +extern DICT *dict_cidr_open(const char *, int, int); + +#define DICT_TYPE_CIDR "cidr" + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Jozsef Kadlecsik +/* kadlec@blackhole.kfki.hu +/* KFKI Research Institute for Particle and Nuclear Physics +/* POB. 49 +/* 1525 Budapest 114, Hungary +/*--*/ + +#endif diff --git a/src/util/dict_cidr.in b/src/util/dict_cidr.in new file mode 100644 index 0000000..ee20c17 --- /dev/null +++ b/src/util/dict_cidr.in @@ -0,0 +1,23 @@ +get 172.16.0.0 +get 172.16.0.1 +get 172.16.7.255 +get 172.16.8.1 +get 172.16.17.1 +get 172.17.1.1 +get 172.17.1.2 +get 2001:240:5c7:0:2d0:b7ff:fe88:2ca7 +get 2001:240:5c7:0:2d0:b7ff:febe:ca9f +get 1.1.1.1 +get 1:1:1:1:1:1:1:1 +get 1.2.3.3 +get 1.2.3.4 +get 1.2.3.5 +get 1.2.3.6 +get 1.2.3.7 +get 1.2.3.8 +get ::f3 +get ::f4 +get ::f5 +get ::f6 +get ::f7 +get ::f8 diff --git a/src/util/dict_cidr.map b/src/util/dict_cidr.map new file mode 100644 index 0000000..ac0f9b5 --- /dev/null +++ b/src/util/dict_cidr.map @@ -0,0 +1,50 @@ +172.16.0.0/21 554 match bad netblock 172.16.0.0/21 +172.16.8.0/21 554 match bad netblock 172.16.8.0/21 +172.16.0.0/16 554 match bad netblock 172.16.0.0/16 +172.17.1.1 554 match bad naked address +172.16.1.3/21 whatever +172.16.1.3/33 whatever +172.999.0.0/21 whatever +172.16.1.999 whatever +172.16.1.4 +if 1.2.0.0/16 +if 1.2.3.4/30 +1.2.3.3 1.2.3.3 can't happen +1.2.3.4 1.2.3.4 can happen +1.2.3.5 1.2.3.5 can happen +1.2.3.6 1.2.3.6 can happen +1.2.3.7 1.2.3.7 can happen +1.2.3.8 1.2.3.8 can't happen +endif +endif +if !1.2.3.4/30 +1.2.3.3 1.2.3.3 can happen +1.2.3.8 1.2.3.8 can happen +endif +if ::f4/126 +::f3 ::f3 can't happen +::f4 ::f4 can happen +::f5 ::f5 can happen +::f6 ::f6 can happen +::f7 ::f7 can happen +::f8 ::f8 can't happen +endif +if !::f4/126 +::f3 ::f3 can happen +::f8 ::f8 can happen +endif +2001:240:5c7:0:2d0:b7ff:fe88:2ca7 match 2001:240:5c7:0:2d0:b7ff:fe88:2ca7 +2001:240:5c7::/64 match netblock 2001:240:5c7::/64 +1.0.0.0/0 match 0.0.0.0/0 +! ! 0.0.0.0/0 match 0.0.0.0/0 +1::/0 match ::/0 +::/0 match ::/0 +[1234 can't happen +[1234]junk can't happen +172.16.1.3/3x can't happen +endif +endif +if 1:2::3:4 +if 1:2::3:5 +if ! +! diff --git a/src/util/dict_cidr.ref b/src/util/dict_cidr.ref new file mode 100644 index 0000000..305e3fd --- /dev/null +++ b/src/util/dict_cidr.ref @@ -0,0 +1,63 @@ +./dict_open: warning: cidr map dict_cidr.map, line 5: non-null host address bits in "172.16.1.3/21", perhaps you should use "172.16.0.0/21" instead: skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 6: bad mask length in "172.16.1.3/33": skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 7: bad network value in "172.999.0.0/21": skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 8: bad address pattern: "172.16.1.999": skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 9: no lookup result: skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 38: non-null host address bits in "1.0.0.0/0", perhaps you should use "0.0.0.0/0" instead: skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 40: non-null host address bits in "1::/0", perhaps you should use "::/0" instead: skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 42: missing ']' character after "[1234": skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 43: garbage after "[1234]": skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 44: bad mask value in "172.16.1.3/3x": skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 45: ENDIF without IF: skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 46: ENDIF without IF: skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 49: no address pattern: skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 50: no address pattern: skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 48: IF has no matching ENDIF +./dict_open: warning: cidr map dict_cidr.map, line 47: IF has no matching ENDIF +owner=untrusted (uid=USER) +> get 172.16.0.0 +172.16.0.0=554 match bad netblock 172.16.0.0/21 +> get 172.16.0.1 +172.16.0.1=554 match bad netblock 172.16.0.0/21 +> get 172.16.7.255 +172.16.7.255=554 match bad netblock 172.16.0.0/21 +> get 172.16.8.1 +172.16.8.1=554 match bad netblock 172.16.8.0/21 +> get 172.16.17.1 +172.16.17.1=554 match bad netblock 172.16.0.0/16 +> get 172.17.1.1 +172.17.1.1=554 match bad naked address +> get 172.17.1.2 +172.17.1.2=match 0.0.0.0/0 +> get 2001:240:5c7:0:2d0:b7ff:fe88:2ca7 +2001:240:5c7:0:2d0:b7ff:fe88:2ca7=match 2001:240:5c7:0:2d0:b7ff:fe88:2ca7 +> get 2001:240:5c7:0:2d0:b7ff:febe:ca9f +2001:240:5c7:0:2d0:b7ff:febe:ca9f=match netblock 2001:240:5c7::/64 +> get 1.1.1.1 +1.1.1.1=match 0.0.0.0/0 +> get 1:1:1:1:1:1:1:1 +1:1:1:1:1:1:1:1=match ::/0 +> get 1.2.3.3 +1.2.3.3=1.2.3.3 can happen +> get 1.2.3.4 +1.2.3.4=1.2.3.4 can happen +> get 1.2.3.5 +1.2.3.5=1.2.3.5 can happen +> get 1.2.3.6 +1.2.3.6=1.2.3.6 can happen +> get 1.2.3.7 +1.2.3.7=1.2.3.7 can happen +> get 1.2.3.8 +1.2.3.8=1.2.3.8 can happen +> get ::f3 +::f3=::f3 can happen +> get ::f4 +::f4=::f4 can happen +> get ::f5 +::f5=::f5 can happen +> get ::f6 +::f6=::f6 can happen +> get ::f7 +::f7=::f7 can happen +> get ::f8 +::f8=::f8 can happen diff --git a/src/util/dict_cidr_file.in b/src/util/dict_cidr_file.in new file mode 100644 index 0000000..531d735 --- /dev/null +++ b/src/util/dict_cidr_file.in @@ -0,0 +1,3 @@ +get 1.1.1.1 +get 2.2.2.2 +get 3.3.3.3 diff --git a/src/util/dict_cidr_file.map b/src/util/dict_cidr_file.map new file mode 100644 index 0000000..e85e8ca --- /dev/null +++ b/src/util/dict_cidr_file.map @@ -0,0 +1,3 @@ +1.1.1.1 dict_cidr_file1 +2.2.2.2 dict_cidr_file2 +3.3.3.3 dict_cidr_file3 diff --git a/src/util/dict_cidr_file.ref b/src/util/dict_cidr_file.ref new file mode 100644 index 0000000..3e9e792 --- /dev/null +++ b/src/util/dict_cidr_file.ref @@ -0,0 +1,8 @@ +./dict_open: warning: cidr map dict_cidr_file.map, line 3: open dict_cidr_file3: No such file or directory: skipping this rule +owner=untrusted (uid=USER) +> get 1.1.1.1 +1.1.1.1=dGhpcy1pcy1maWxlMQo= +> get 2.2.2.2 +2.2.2.2=dGhpcy1pcy1maWxlMgo= +> get 3.3.3.3 +3.3.3.3: not found diff --git a/src/util/dict_db.c b/src/util/dict_db.c new file mode 100644 index 0000000..b2d0c33 --- /dev/null +++ b/src/util/dict_db.c @@ -0,0 +1,880 @@ +/*++ +/* NAME +/* dict_db 3 +/* SUMMARY +/* dictionary manager interface to DB files +/* SYNOPSIS +/* #include <dict_db.h> +/* +/* extern int dict_db_cache_size; +/* +/* DEFINE_DICT_DB_CACHE_SIZE; +/* +/* DICT *dict_hash_open(path, open_flags, dict_flags) +/* const char *path; +/* int open_flags; +/* int dict_flags; +/* +/* DICT *dict_btree_open(path, open_flags, dict_flags) +/* const char *path; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_XXX_open() opens the specified DB database. The result is +/* a pointer to a structure that can be used to access the dictionary +/* using the generic methods documented in dict_open(3). +/* +/* The dict_db_cache_size variable specifies a non-default per-table +/* I/O buffer size. The default buffer size is adequate for reading. +/* For better performance while creating a large table, specify a large +/* buffer size before opening the file. +/* +/* This variable cannot be exported via the dict(3) API and +/* must therefore be defined in the calling program by invoking +/* the DEFINE_DICT_DB_CACHE_SIZE macro at the global level. +/* +/* Arguments: +/* .IP path +/* The database pathname, not including the ".db" suffix. +/* .IP open_flags +/* Flags passed to dbopen(). +/* .IP dict_flags +/* Flags used by the dictionary interface. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* DIAGNOSTICS +/* Fatal errors: cannot open file, write error, out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#include "sys_defs.h" + +#ifdef HAS_DB + +/* System library. */ + +#include <sys/stat.h> +#include <limits.h> +#ifdef PATH_DB_H +#include PATH_DB_H +#else +#include <db.h> +#endif +#include <string.h> +#include <unistd.h> +#include <errno.h> + +#if defined(_DB_185_H_) && defined(USE_FCNTL_LOCK) +#error "Error: this system must not use the db 1.85 compatibility interface" +#endif + +#ifndef DB_VERSION_MAJOR +#define DB_VERSION_MAJOR 1 +#define DICT_DB_GET(db, key, val, flag) db->get(db, key, val, flag) +#define DICT_DB_PUT(db, key, val, flag) db->put(db, key, val, flag) +#define DICT_DB_DEL(db, key, flag) db->del(db, key, flag) +#define DICT_DB_SYNC(db, flag) db->sync(db, flag) +#define DICT_DB_CLOSE(db) db->close(db) +#define DONT_CLOBBER R_NOOVERWRITE +#endif + +#if DB_VERSION_MAJOR > 1 +#define DICT_DB_GET(db, key, val, flag) sanitize(db->get(db, 0, key, val, flag)) +#define DICT_DB_PUT(db, key, val, flag) sanitize(db->put(db, 0, key, val, flag)) +#define DICT_DB_DEL(db, key, flag) sanitize(db->del(db, 0, key, flag)) +#define DICT_DB_SYNC(db, flag) ((errno = db->sync(db, flag)) ? -1 : 0) +#define DICT_DB_CLOSE(db) ((errno = db->close(db, 0)) ? -1 : 0) +#define DONT_CLOBBER DB_NOOVERWRITE +#endif + +#if (DB_VERSION_MAJOR == 2 && DB_VERSION_MINOR < 6) +#define DICT_DB_CURSOR(db, curs) (db)->cursor((db), NULL, (curs)) +#else +#define DICT_DB_CURSOR(db, curs) (db)->cursor((db), NULL, (curs), 0) +#endif + +#ifndef DB_FCNTL_LOCKING +#define DB_FCNTL_LOCKING 0 +#endif + +/* Utility library. */ + +#include "msg.h" +#include "mymalloc.h" +#include "vstring.h" +#include "stringops.h" +#include "iostuff.h" +#include "myflock.h" +#include "dict.h" +#include "dict_db.h" +#include "warn_stat.h" + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + DB *db; /* open db file */ +#if DB_VERSION_MAJOR > 2 + DB_ENV *dbenv; +#endif +#if DB_VERSION_MAJOR > 1 + DBC *cursor; /* dict_db_sequence() */ +#endif + VSTRING *key_buf; /* key result */ + VSTRING *val_buf; /* value result */ +} DICT_DB; + +#define SCOPY(buf, data, size) \ + vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size)) + +#define DICT_DB_NELM 4096 + +#if DB_VERSION_MAJOR > 1 + +/* sanitize - sanitize db_get/put/del result */ + +static int sanitize(int status) +{ + + /* + * XXX This is unclean but avoids a lot of clutter elsewhere. Categorize + * results into non-fatal errors (i.e., errors that we can deal with), + * success, or fatal error (i.e., all other errors). + */ + switch (status) { + + case DB_NOTFOUND: /* get, del */ + case DB_KEYEXIST: /* put */ + return (1); /* non-fatal */ + + case 0: + return (0); /* success */ + + case DB_KEYEMPTY: /* get, others? */ + status = EINVAL; + /* FALLTHROUGH */ + default: + errno = status; + return (-1); /* fatal */ + } +} + +#endif + +/* dict_db_lookup - find database entry */ + +static const char *dict_db_lookup(DICT *dict, const char *name) +{ + DICT_DB *dict_db = (DICT_DB *) dict; + DB *db = dict_db->db; + DBT db_key; + DBT db_value; + int status; + const char *result = 0; + + dict->error = 0; + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_db_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + memset(&db_key, 0, sizeof(db_key)); + memset(&db_value, 0, sizeof(db_value)); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + + /* + * Acquire a shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) + msg_fatal("%s: lock dictionary: %m", dict_db->dict.name); + + /* + * See if this DB file was written with one null byte appended to key and + * value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + db_key.data = (void *) name; + db_key.size = strlen(name) + 1; + if ((status = DICT_DB_GET(db, &db_key, &db_value, 0)) < 0) + msg_fatal("error reading %s: %m", dict_db->dict.name); + if (status == 0) { + dict->flags &= ~DICT_FLAG_TRY0NULL; + result = SCOPY(dict_db->val_buf, db_value.data, db_value.size); + } + } + + /* + * See if this DB file was written with no null byte appended to key and + * value. + */ + if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { + db_key.data = (void *) name; + db_key.size = strlen(name); + if ((status = DICT_DB_GET(db, &db_key, &db_value, 0)) < 0) + msg_fatal("error reading %s: %m", dict_db->dict.name); + if (status == 0) { + dict->flags &= ~DICT_FLAG_TRY1NULL; + result = SCOPY(dict_db->val_buf, db_value.data, db_value.size); + } + } + + /* + * Release the shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name); + + return (result); +} + +/* dict_db_update - add or update database entry */ + +static int dict_db_update(DICT *dict, const char *name, const char *value) +{ + DICT_DB *dict_db = (DICT_DB *) dict; + DB *db = dict_db->db; + DBT db_key; + DBT db_value; + int status; + + dict->error = 0; + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_db_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + memset(&db_key, 0, sizeof(db_key)); + memset(&db_value, 0, sizeof(db_value)); + db_key.data = (void *) name; + db_value.data = (void *) value; + db_key.size = strlen(name); + db_value.size = strlen(value); + + /* + * If undecided about appending a null byte to key and value, choose a + * default depending on the platform. + */ + if ((dict->flags & DICT_FLAG_TRY1NULL) + && (dict->flags & DICT_FLAG_TRY0NULL)) { +#ifdef DB_NO_TRAILING_NULL + dict->flags &= ~DICT_FLAG_TRY1NULL; +#else + dict->flags &= ~DICT_FLAG_TRY0NULL; +#endif + } + + /* + * Optionally append a null byte to key and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + db_key.size++; + db_value.size++; + } + + /* + * Acquire an exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("%s: lock dictionary: %m", dict_db->dict.name); + + /* + * Do the update. + */ + if ((status = DICT_DB_PUT(db, &db_key, &db_value, + (dict->flags & DICT_FLAG_DUP_REPLACE) ? 0 : DONT_CLOBBER)) < 0) + msg_fatal("error writing %s: %m", dict_db->dict.name); + if (status) { + if (dict->flags & DICT_FLAG_DUP_IGNORE) + /* void */ ; + else if (dict->flags & DICT_FLAG_DUP_WARN) + msg_warn("%s: duplicate entry: \"%s\"", dict_db->dict.name, name); + else + msg_fatal("%s: duplicate entry: \"%s\"", dict_db->dict.name, name); + } + if (dict->flags & DICT_FLAG_SYNC_UPDATE) + if (DICT_DB_SYNC(db, 0) < 0) + msg_fatal("%s: flush dictionary: %m", dict_db->dict.name); + + /* + * Release the exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name); + + return (status); +} + +/* delete one entry from the dictionary */ + +static int dict_db_delete(DICT *dict, const char *name) +{ + DICT_DB *dict_db = (DICT_DB *) dict; + DB *db = dict_db->db; + DBT db_key; + int status = 1; + int flags = 0; + + dict->error = 0; + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_db_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + memset(&db_key, 0, sizeof(db_key)); + + /* + * Acquire an exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("%s: lock dictionary: %m", dict_db->dict.name); + + /* + * See if this DB file was written with one null byte appended to key and + * value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + db_key.data = (void *) name; + db_key.size = strlen(name) + 1; + if ((status = DICT_DB_DEL(db, &db_key, flags)) < 0) + msg_fatal("error deleting from %s: %m", dict_db->dict.name); + if (status == 0) + dict->flags &= ~DICT_FLAG_TRY0NULL; + } + + /* + * See if this DB file was written with no null byte appended to key and + * value. + */ + if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { + db_key.data = (void *) name; + db_key.size = strlen(name); + if ((status = DICT_DB_DEL(db, &db_key, flags)) < 0) + msg_fatal("error deleting from %s: %m", dict_db->dict.name); + if (status == 0) + dict->flags &= ~DICT_FLAG_TRY1NULL; + } + if (dict->flags & DICT_FLAG_SYNC_UPDATE) + if (DICT_DB_SYNC(db, 0) < 0) + msg_fatal("%s: flush dictionary: %m", dict_db->dict.name); + + /* + * Release the exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name); + + return status; +} + +/* dict_db_sequence - traverse the dictionary */ + +static int dict_db_sequence(DICT *dict, int function, + const char **key, const char **value) +{ + const char *myname = "dict_db_sequence"; + DICT_DB *dict_db = (DICT_DB *) dict; + DB *db = dict_db->db; + DBT db_key; + DBT db_value; + int status = 0; + int db_function; + + dict->error = 0; + +#if DB_VERSION_MAJOR > 1 + + /* + * Initialize. + */ + memset(&db_key, 0, sizeof(db_key)); + memset(&db_value, 0, sizeof(db_value)); + + /* + * Determine the function. + */ + switch (function) { + case DICT_SEQ_FUN_FIRST: + if (dict_db->cursor == 0) + DICT_DB_CURSOR(db, &(dict_db->cursor)); + db_function = DB_FIRST; + break; + case DICT_SEQ_FUN_NEXT: + if (dict_db->cursor == 0) + msg_panic("%s: no cursor", myname); + db_function = DB_NEXT; + break; + default: + msg_panic("%s: invalid function %d", myname, function); + } + + /* + * Acquire a shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) + msg_fatal("%s: lock dictionary: %m", dict_db->dict.name); + + /* + * Database lookup. + */ + status = + dict_db->cursor->c_get(dict_db->cursor, &db_key, &db_value, db_function); + if (status != 0 && status != DB_NOTFOUND) + msg_fatal("error [%d] seeking %s: %m", status, dict_db->dict.name); + + /* + * Release the shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name); + + if (status == 0) { + + /* + * Copy the result so it is guaranteed null terminated. + */ + *key = SCOPY(dict_db->key_buf, db_key.data, db_key.size); + *value = SCOPY(dict_db->val_buf, db_value.data, db_value.size); + } + return (status); +#else + + /* + * determine the function + */ + switch (function) { + case DICT_SEQ_FUN_FIRST: + db_function = R_FIRST; + break; + case DICT_SEQ_FUN_NEXT: + db_function = R_NEXT; + break; + default: + msg_panic("%s: invalid function %d", myname, function); + } + + /* + * Acquire a shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) + msg_fatal("%s: lock dictionary: %m", dict_db->dict.name); + + if ((status = db->seq(db, &db_key, &db_value, db_function)) < 0) + msg_fatal("error seeking %s: %m", dict_db->dict.name); + + /* + * Release the shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name); + + if (status == 0) { + + /* + * Copy the result so that it is guaranteed null terminated. + */ + *key = SCOPY(dict_db->key_buf, db_key.data, db_key.size); + *value = SCOPY(dict_db->val_buf, db_value.data, db_value.size); + } + return status; +#endif +} + +/* dict_db_close - close data base */ + +static void dict_db_close(DICT *dict) +{ + DICT_DB *dict_db = (DICT_DB *) dict; + +#if DB_VERSION_MAJOR > 1 + if (dict_db->cursor) + dict_db->cursor->c_close(dict_db->cursor); +#endif + if (DICT_DB_SYNC(dict_db->db, 0) < 0) + msg_fatal("flush database %s: %m", dict_db->dict.name); + + /* + * With some Berkeley DB implementations, close fails with a bogus ENOENT + * error, while it reports no errors with put+sync, no errors with + * del+sync, and no errors with the sync operation just before this + * comment. This happens in programs that never fork and that never share + * the database with other processes. The bogus close error has been + * reported for programs that use the first/next iterator. Instead of + * making Postfix look bad because it reports errors that other programs + * ignore, I'm going to report the bogus error as a non-error. + */ + if (DICT_DB_CLOSE(dict_db->db) < 0) + msg_info("close database %s: %m (possible Berkeley DB bug)", + dict_db->dict.name); +#if DB_VERSION_MAJOR > 2 + dict_db->dbenv->close(dict_db->dbenv, 0); +#endif + if (dict_db->key_buf) + vstring_free(dict_db->key_buf); + if (dict_db->val_buf) + vstring_free(dict_db->val_buf); + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +#if DB_VERSION_MAJOR > 2 + +/* dict_db_new_env - workaround for undocumented ./DB_CONFIG read */ + +static DB_ENV *dict_db_new_env(const char *db_path) +{ + VSTRING *db_home_buf; + DB_ENV *dbenv; + u_int32_t cache_size_gbytes; + u_int32_t cache_size_bytes; + int ncache; + + if ((errno = db_env_create(&dbenv, 0)) != 0) + msg_fatal("create DB environment: %m"); +#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 7) + if ((errno = dbenv->get_cachesize(dbenv, &cache_size_gbytes, + &cache_size_bytes, &ncache)) != 0) + msg_fatal("get DB cache size: %m"); + if (cache_size_gbytes == 0 && cache_size_bytes < dict_db_cache_size) { + if ((errno = dbenv->set_cache_max(dbenv, cache_size_gbytes, + dict_db_cache_size)) != 0) + msg_fatal("set DB max cache size %d: %m", dict_db_cache_size); + if ((errno = dbenv->set_cachesize(dbenv, cache_size_gbytes, + dict_db_cache_size, ncache)) != 0) + msg_fatal("set DB cache size %d: %m", dict_db_cache_size); + } +#endif + /* XXX db_home is also the default directory for the .db file. */ + db_home_buf = vstring_alloc(100); + if ((errno = dbenv->open(dbenv, sane_dirname(db_home_buf, db_path), + DB_INIT_MPOOL | DB_CREATE | DB_PRIVATE, 0)) != 0) + msg_fatal("open DB environment: %m"); + vstring_free(db_home_buf); + return (dbenv); +} + +#endif + +/* dict_db_open - open data base */ + +static DICT *dict_db_open(const char *class, const char *path, int open_flags, + int type, void *tweak, int dict_flags) +{ + DICT_DB *dict_db; + struct stat st; + DB *db = 0; + char *db_path = 0; + VSTRING *db_base_buf = 0; + int lock_fd = -1; + int dbfd; + +#if DB_VERSION_MAJOR > 1 + int db_flags; + +#endif +#if DB_VERSION_MAJOR > 2 + DB_ENV *dbenv = 0; + +#endif + + /* + * Mismatches between #include file and library are a common cause for + * trouble. + */ +#if DB_VERSION_MAJOR > 1 + int major_version; + int minor_version; + int patch_version; + + (void) db_version(&major_version, &minor_version, &patch_version); + if (major_version != DB_VERSION_MAJOR || minor_version != DB_VERSION_MINOR) + return (dict_surrogate(class, path, open_flags, dict_flags, + "incorrect version of Berkeley DB: " + "compiled against %d.%d.%d, run-time linked against %d.%d.%d", + DB_VERSION_MAJOR, DB_VERSION_MINOR, DB_VERSION_PATCH, + major_version, minor_version, patch_version)); + if (msg_verbose) { + msg_info("Compiled against Berkeley DB: %d.%d.%d\n", + DB_VERSION_MAJOR, DB_VERSION_MINOR, DB_VERSION_PATCH); + msg_info("Run-time linked against Berkeley DB: %d.%d.%d\n", + major_version, minor_version, patch_version); + } +#else + if (msg_verbose) + msg_info("Compiled against Berkeley DB version 1"); +#endif + + db_path = concatenate(path, ".db", (char *) 0); + + /* + * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in + * the time domain) locking while accessing individual database records. + * + * Programs such as postmap/postalias use their own large-grained (in the + * time domain) locks while rewriting the entire file. + * + * XXX DB version 4.1 will not open a zero-length file. This means we must + * open an existing file without O_CREAT|O_TRUNC, and that we must let + * db_open() create a non-existent file for us. + */ +#define LOCK_OPEN_FLAGS(f) ((f) & ~(O_CREAT|O_TRUNC)) +#if DB_VERSION_MAJOR <= 2 +#define FREE_RETURN(e) do { \ + DICT *_dict = (e); if (db) DICT_DB_CLOSE(db); \ + if (lock_fd >= 0) (void) close(lock_fd); \ + if (db_base_buf) vstring_free(db_base_buf); \ + if (db_path) myfree(db_path); return (_dict); \ + } while (0) +#else +#define FREE_RETURN(e) do { \ + DICT *_dict = (e); if (db) DICT_DB_CLOSE(db); \ + if (dbenv) dbenv->close(dbenv, 0); \ + if (lock_fd >= 0) (void) close(lock_fd); \ + if (db_base_buf) vstring_free(db_base_buf); \ + if (db_path) myfree(db_path); \ + return (_dict); \ + } while (0) +#endif + + if (dict_flags & DICT_FLAG_LOCK) { + if ((lock_fd = open(db_path, LOCK_OPEN_FLAGS(open_flags), 0644)) < 0) { + if (errno != ENOENT) + FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags, + "open database %s: %m", db_path)); + } else { + if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) + msg_fatal("shared-lock database %s for open: %m", db_path); + } + } + + /* + * Use the DB 1.x programming interface. This is the default interface + * with 4.4BSD systems. It is also available via the db_185 compatibility + * interface, but that interface does not have the undocumented feature + * that we need to make file locking safe with POSIX fcntl() locking. + */ +#if DB_VERSION_MAJOR < 2 + if ((db = dbopen(db_path, open_flags, 0644, type, tweak)) == 0) + FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags, + "open database %s: %m", db_path)); + dbfd = db->fd(db); +#endif + + /* + * Use the DB 2.x programming interface. Jump a couple extra hoops. + */ +#if DB_VERSION_MAJOR == 2 + db_flags = DB_FCNTL_LOCKING; + if (open_flags == O_RDONLY) + db_flags |= DB_RDONLY; + if (open_flags & O_CREAT) + db_flags |= DB_CREATE; + if (open_flags & O_TRUNC) + db_flags |= DB_TRUNCATE; + if ((errno = db_open(db_path, type, db_flags, 0644, 0, tweak, &db)) != 0) + FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags, + "open database %s: %m", db_path)); + if (db == 0) + msg_panic("db_open null result"); + if ((errno = db->fd(db, &dbfd)) != 0) + msg_fatal("get database file descriptor: %m"); +#endif + + /* + * Use the DB 3.x programming interface. Jump even more hoops. + */ +#if DB_VERSION_MAJOR > 2 + db_flags = DB_FCNTL_LOCKING; + if (open_flags == O_RDONLY) + db_flags |= DB_RDONLY; + if (open_flags & O_CREAT) + db_flags |= DB_CREATE; + if (open_flags & O_TRUNC) + db_flags |= DB_TRUNCATE; + if ((errno = db_create(&db, dbenv = dict_db_new_env(db_path), 0)) != 0) + msg_fatal("create DB database: %m"); + if (db == 0) + msg_panic("db_create null result"); + if (type == DB_HASH && db->set_h_nelem(db, DICT_DB_NELM) != 0) + msg_fatal("set DB hash element count %d: %m", DICT_DB_NELM); + db_base_buf = vstring_alloc(100); +#if DB_VERSION_MAJOR == 18 || DB_VERSION_MAJOR == 6 || DB_VERSION_MAJOR == 5 || \ + (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR > 0) + if ((errno = db->open(db, 0, sane_basename(db_base_buf, db_path), + 0, type, db_flags, 0644)) != 0) + FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags, + "open database %s: %m", db_path)); +#elif (DB_VERSION_MAJOR == 3 || DB_VERSION_MAJOR == 4) + if ((errno = db->open(db, sane_basename(db_base_buf, db_path), 0, + type, db_flags, 0644)) != 0) + FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags, + "open database %s: %m", db_path)); +#else +#error "Unsupported Berkeley DB version" +#endif + vstring_free(db_base_buf); + if ((errno = db->fd(db, &dbfd)) != 0) + msg_fatal("get database file descriptor: %m"); +#endif + if ((dict_flags & DICT_FLAG_LOCK) && lock_fd >= 0) { + if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("unlock database %s for open: %m", db_path); + if (close(lock_fd) < 0) + msg_fatal("close database %s: %m", db_path); + lock_fd = -1; + } + dict_db = (DICT_DB *) dict_alloc(class, db_path, sizeof(*dict_db)); + dict_db->dict.lookup = dict_db_lookup; + dict_db->dict.update = dict_db_update; + dict_db->dict.delete = dict_db_delete; + dict_db->dict.sequence = dict_db_sequence; + dict_db->dict.close = dict_db_close; + dict_db->dict.lock_fd = dbfd; + dict_db->dict.stat_fd = dbfd; + if (fstat(dict_db->dict.stat_fd, &st) < 0) + msg_fatal("dict_db_open: fstat: %m"); + dict_db->dict.mtime = st.st_mtime; + dict_db->dict.owner.uid = st.st_uid; + dict_db->dict.owner.status = (st.st_uid != 0); + + /* + * Warn if the source file is newer than the indexed file, except when + * the source file changed only seconds ago. + */ + if ((dict_flags & DICT_FLAG_LOCK) != 0 + && stat(path, &st) == 0 + && st.st_mtime > dict_db->dict.mtime + && st.st_mtime < time((time_t *) 0) - 100) + msg_warn("database %s is older than source file %s", db_path, path); + + close_on_exec(dict_db->dict.lock_fd, CLOSE_ON_EXEC); + close_on_exec(dict_db->dict.stat_fd, CLOSE_ON_EXEC); + dict_db->dict.flags = dict_flags | DICT_FLAG_FIXED; + if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + dict_db->dict.flags |= (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL); + if (dict_flags & DICT_FLAG_FOLD_FIX) + dict_db->dict.fold_buf = vstring_alloc(10); + dict_db->db = db; +#if DB_VERSION_MAJOR > 2 + dict_db->dbenv = dbenv; +#endif +#if DB_VERSION_MAJOR > 1 + dict_db->cursor = 0; +#endif + dict_db->key_buf = 0; + dict_db->val_buf = 0; + + myfree(db_path); + return (DICT_DEBUG (&dict_db->dict)); +} + +/* dict_hash_open - create association with data base */ + +DICT *dict_hash_open(const char *path, int open_flags, int dict_flags) +{ +#if DB_VERSION_MAJOR < 2 + HASHINFO tweak; + + memset((void *) &tweak, 0, sizeof(tweak)); + tweak.nelem = DICT_DB_NELM; + tweak.cachesize = dict_db_cache_size; +#endif +#if DB_VERSION_MAJOR == 2 + DB_INFO tweak; + + memset((void *) &tweak, 0, sizeof(tweak)); + tweak.h_nelem = DICT_DB_NELM; + tweak.db_cachesize = dict_db_cache_size; +#endif +#if DB_VERSION_MAJOR > 2 + void *tweak; + + tweak = 0; +#endif + return (dict_db_open(DICT_TYPE_HASH, path, open_flags, DB_HASH, + (void *) &tweak, dict_flags)); +} + +/* dict_btree_open - create association with data base */ + +DICT *dict_btree_open(const char *path, int open_flags, int dict_flags) +{ +#if DB_VERSION_MAJOR < 2 + BTREEINFO tweak; + + memset((void *) &tweak, 0, sizeof(tweak)); + tweak.cachesize = dict_db_cache_size; +#endif +#if DB_VERSION_MAJOR == 2 + DB_INFO tweak; + + memset((void *) &tweak, 0, sizeof(tweak)); + tweak.db_cachesize = dict_db_cache_size; +#endif +#if DB_VERSION_MAJOR > 2 + void *tweak; + + tweak = 0; +#endif + + return (dict_db_open(DICT_TYPE_BTREE, path, open_flags, DB_BTREE, + (void *) &tweak, dict_flags)); +} + +#endif diff --git a/src/util/dict_db.h b/src/util/dict_db.h new file mode 100644 index 0000000..cc14dc8 --- /dev/null +++ b/src/util/dict_db.h @@ -0,0 +1,55 @@ +#ifndef _DICT_DB_H_INCLUDED_ +#define _DICT_DB_H_INCLUDED_ + +/*++ +/* NAME +/* dict_db 3h +/* SUMMARY +/* dictionary manager interface to DB files +/* SYNOPSIS +/* #include <dict_db.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <dict.h> + + /* + * External interface. + */ +#define DICT_TYPE_HASH "hash" +#define DICT_TYPE_BTREE "btree" + +extern DICT *dict_hash_open(const char *, int, int); +extern DICT *dict_btree_open(const char *, int, int); + + /* + * XXX Should be part of the DICT interface. + * + * You can override the default dict_db_cache_size setting before calling + * dict_hash_open() or dict_btree_open(). This is done in mkmap_db_open() to + * set a larger memory pool for database (re)builds. + */ +extern int dict_db_cache_size; + +#define DEFINE_DICT_DB_CACHE_SIZE int dict_db_cache_size = (128 * 1024) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/dict_dbm.c b/src/util/dict_dbm.c new file mode 100644 index 0000000..e47b7ee --- /dev/null +++ b/src/util/dict_dbm.c @@ -0,0 +1,503 @@ +/*++ +/* NAME +/* dict_dbm 3 +/* SUMMARY +/* dictionary manager interface to DBM files +/* SYNOPSIS +/* #include <dict_dbm.h> +/* +/* DICT *dict_dbm_open(path, open_flags, dict_flags) +/* const char *name; +/* const char *path; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_dbm_open() opens the named DBM database and makes it available +/* via the generic interface described in dict_open(3). +/* DIAGNOSTICS +/* Fatal errors: cannot open file, file write error, out of memory. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* ndbm(3) data base subroutines +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#include "sys_defs.h" + +#ifdef HAS_DBM + +/* System library. */ + +#include <sys/stat.h> +#ifdef PATH_NDBM_H +#include PATH_NDBM_H +#else +#include <ndbm.h> +#endif +#ifdef R_FIRST +#error "Error: you are including the Berkeley DB version of ndbm.h" +#error "To build with Postfix NDBM support, delete the Berkeley DB ndbm.h file" +#endif +#include <string.h> +#include <unistd.h> + +/* Utility library. */ + +#include "msg.h" +#include "mymalloc.h" +#include "htable.h" +#include "iostuff.h" +#include "vstring.h" +#include "myflock.h" +#include "stringops.h" +#include "dict.h" +#include "dict_dbm.h" +#include "warn_stat.h" + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + DBM *dbm; /* open database */ + VSTRING *key_buf; /* key buffer */ + VSTRING *val_buf; /* result buffer */ +} DICT_DBM; + +#define SCOPY(buf, data, size) \ + vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size)) + +/* dict_dbm_lookup - find database entry */ + +static const char *dict_dbm_lookup(DICT *dict, const char *name) +{ + DICT_DBM *dict_dbm = (DICT_DBM *) dict; + datum dbm_key; + datum dbm_value; + const char *result = 0; + + dict->error = 0; + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_dbm_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + + /* + * Acquire an exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) + msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name); + + /* + * See if this DBM file was written with one null byte appended to key + * and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + dbm_key.dptr = (void *) name; + dbm_key.dsize = strlen(name) + 1; + dbm_value = dbm_fetch(dict_dbm->dbm, dbm_key); + if (dbm_value.dptr != 0) { + dict->flags &= ~DICT_FLAG_TRY0NULL; + result = SCOPY(dict_dbm->val_buf, dbm_value.dptr, dbm_value.dsize); + } + } + + /* + * See if this DBM file was written with no null byte appended to key and + * value. + */ + if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { + dbm_key.dptr = (void *) name; + dbm_key.dsize = strlen(name); + dbm_value = dbm_fetch(dict_dbm->dbm, dbm_key); + if (dbm_value.dptr != 0) { + dict->flags &= ~DICT_FLAG_TRY1NULL; + result = SCOPY(dict_dbm->val_buf, dbm_value.dptr, dbm_value.dsize); + } + } + + /* + * Release the exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name); + + return (result); +} + +/* dict_dbm_update - add or update database entry */ + +static int dict_dbm_update(DICT *dict, const char *name, const char *value) +{ + DICT_DBM *dict_dbm = (DICT_DBM *) dict; + datum dbm_key; + datum dbm_value; + int status; + + dict->error = 0; + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_dbm_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + dbm_key.dptr = (void *) name; + dbm_value.dptr = (void *) value; + dbm_key.dsize = strlen(name); + dbm_value.dsize = strlen(value); + + /* + * If undecided about appending a null byte to key and value, choose a + * default depending on the platform. + */ + if ((dict->flags & DICT_FLAG_TRY1NULL) + && (dict->flags & DICT_FLAG_TRY0NULL)) { +#ifdef DBM_NO_TRAILING_NULL + dict->flags &= ~DICT_FLAG_TRY1NULL; +#else + dict->flags &= ~DICT_FLAG_TRY0NULL; +#endif + } + + /* + * Optionally append a null byte to key and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + dbm_key.dsize++; + dbm_value.dsize++; + } + + /* + * Acquire an exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name); + + /* + * Do the update. + */ + if ((status = dbm_store(dict_dbm->dbm, dbm_key, dbm_value, + (dict->flags & DICT_FLAG_DUP_REPLACE) ? DBM_REPLACE : DBM_INSERT)) < 0) + msg_fatal("error writing DBM database %s: %m", dict_dbm->dict.name); + if (status) { + if (dict->flags & DICT_FLAG_DUP_IGNORE) + /* void */ ; + else if (dict->flags & DICT_FLAG_DUP_WARN) + msg_warn("%s: duplicate entry: \"%s\"", dict_dbm->dict.name, name); + else + msg_fatal("%s: duplicate entry: \"%s\"", dict_dbm->dict.name, name); + } + + /* + * Release the exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name); + + return (status); +} + +/* dict_dbm_delete - delete one entry from the dictionary */ + +static int dict_dbm_delete(DICT *dict, const char *name) +{ + DICT_DBM *dict_dbm = (DICT_DBM *) dict; + datum dbm_key; + int status = 1; + + dict->error = 0; + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_dbm_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + + /* + * Acquire an exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name); + + /* + * See if this DBM file was written with one null byte appended to key + * and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + dbm_key.dptr = (void *) name; + dbm_key.dsize = strlen(name) + 1; + dbm_clearerr(dict_dbm->dbm); + if ((status = dbm_delete(dict_dbm->dbm, dbm_key)) < 0) { + if (dbm_error(dict_dbm->dbm) != 0) /* fatal error */ + msg_fatal("error deleting from %s: %m", dict_dbm->dict.name); + status = 1; /* not found */ + } else { + dict->flags &= ~DICT_FLAG_TRY0NULL; /* found */ + } + } + + /* + * See if this DBM file was written with no null byte appended to key and + * value. + */ + if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { + dbm_key.dptr = (void *) name; + dbm_key.dsize = strlen(name); + dbm_clearerr(dict_dbm->dbm); + if ((status = dbm_delete(dict_dbm->dbm, dbm_key)) < 0) { + if (dbm_error(dict_dbm->dbm) != 0) /* fatal error */ + msg_fatal("error deleting from %s: %m", dict_dbm->dict.name); + status = 1; /* not found */ + } else { + dict->flags &= ~DICT_FLAG_TRY1NULL; /* found */ + } + } + + /* + * Release the exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name); + + return (status); +} + +/* traverse the dictionary */ + +static int dict_dbm_sequence(DICT *dict, int function, + const char **key, const char **value) +{ + const char *myname = "dict_dbm_sequence"; + DICT_DBM *dict_dbm = (DICT_DBM *) dict; + datum dbm_key; + datum dbm_value; + int status; + + dict->error = 0; + + /* + * Acquire a shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) + msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name); + + /* + * Determine and execute the seek function. It returns the key. + */ + switch (function) { + case DICT_SEQ_FUN_FIRST: + dbm_key = dbm_firstkey(dict_dbm->dbm); + break; + case DICT_SEQ_FUN_NEXT: + dbm_key = dbm_nextkey(dict_dbm->dbm); + break; + default: + msg_panic("%s: invalid function: %d", myname, function); + } + + if (dbm_key.dptr != 0 && dbm_key.dsize > 0) { + + /* + * Copy the key so that it is guaranteed null terminated. + */ + *key = SCOPY(dict_dbm->key_buf, dbm_key.dptr, dbm_key.dsize); + + /* + * Fetch the corresponding value. + */ + dbm_value = dbm_fetch(dict_dbm->dbm, dbm_key); + + if (dbm_value.dptr != 0 && dbm_value.dsize > 0) { + + /* + * Copy the value so that it is guaranteed null terminated. + */ + *value = SCOPY(dict_dbm->val_buf, dbm_value.dptr, dbm_value.dsize); + status = 0; + } else { + + /* + * Determine if we have hit the last record or an error + * condition. + */ + if (dbm_error(dict_dbm->dbm)) + msg_fatal("error seeking %s: %m", dict_dbm->dict.name); + status = 1; /* no error: eof/not found + * (should not happen!) */ + } + } else { + + /* + * Determine if we have hit the last record or an error condition. + */ + if (dbm_error(dict_dbm->dbm)) + msg_fatal("error seeking %s: %m", dict_dbm->dict.name); + status = 1; /* no error: eof/not found */ + } + + /* + * Release the shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name); + + return (status); +} + +/* dict_dbm_close - disassociate from data base */ + +static void dict_dbm_close(DICT *dict) +{ + DICT_DBM *dict_dbm = (DICT_DBM *) dict; + + dbm_close(dict_dbm->dbm); + if (dict_dbm->key_buf) + vstring_free(dict_dbm->key_buf); + if (dict_dbm->val_buf) + vstring_free(dict_dbm->val_buf); + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_dbm_open - open DBM data base */ + +DICT *dict_dbm_open(const char *path, int open_flags, int dict_flags) +{ + DICT_DBM *dict_dbm; + struct stat st; + DBM *dbm; + char *dbm_path = 0; + int lock_fd; + + /* + * Let the optimizer worry about eliminating redundant code. + */ +#define DICT_DBM_OPEN_RETURN(d) do { \ + DICT *__d = (d); \ + if (dbm_path != 0) \ + myfree(dbm_path); \ + return (__d); \ + } while (0) + + /* + * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in + * the time domain) locking while accessing individual database records. + * + * Programs such as postmap/postalias use their own large-grained (in the + * time domain) locks while rewriting the entire file. + */ + if (dict_flags & DICT_FLAG_LOCK) { + dbm_path = concatenate(path, ".dir", (char *) 0); + if ((lock_fd = open(dbm_path, open_flags, 0644)) < 0) + DICT_DBM_OPEN_RETURN(dict_surrogate(DICT_TYPE_DBM, path, + open_flags, dict_flags, + "open database %s: %m", + dbm_path)); + if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) + msg_fatal("shared-lock database %s for open: %m", dbm_path); + } + + /* + * XXX SunOS 5.x has no const in dbm_open() prototype. + */ + if ((dbm = dbm_open((char *) path, open_flags, 0644)) == 0) + DICT_DBM_OPEN_RETURN(dict_surrogate(DICT_TYPE_DBM, path, + open_flags, dict_flags, + "open database %s.{dir,pag}: %m", + path)); + + if (dict_flags & DICT_FLAG_LOCK) { + if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("unlock database %s for open: %m", dbm_path); + if (close(lock_fd) < 0) + msg_fatal("close database %s: %m", dbm_path); + } + dict_dbm = (DICT_DBM *) dict_alloc(DICT_TYPE_DBM, path, sizeof(*dict_dbm)); + dict_dbm->dict.lookup = dict_dbm_lookup; + dict_dbm->dict.update = dict_dbm_update; + dict_dbm->dict.delete = dict_dbm_delete; + dict_dbm->dict.sequence = dict_dbm_sequence; + dict_dbm->dict.close = dict_dbm_close; + dict_dbm->dict.lock_fd = dbm_dirfno(dbm); + dict_dbm->dict.stat_fd = dbm_pagfno(dbm); + if (dict_dbm->dict.lock_fd == dict_dbm->dict.stat_fd) + msg_fatal("open database %s: cannot support GDBM", path); + if (fstat(dict_dbm->dict.stat_fd, &st) < 0) + msg_fatal("dict_dbm_open: fstat: %m"); + dict_dbm->dict.mtime = st.st_mtime; + dict_dbm->dict.owner.uid = st.st_uid; + dict_dbm->dict.owner.status = (st.st_uid != 0); + + /* + * Warn if the source file is newer than the indexed file, except when + * the source file changed only seconds ago. + */ + if ((dict_flags & DICT_FLAG_LOCK) != 0 + && stat(path, &st) == 0 + && st.st_mtime > dict_dbm->dict.mtime + && st.st_mtime < time((time_t *) 0) - 100) + msg_warn("database %s is older than source file %s", dbm_path, path); + + close_on_exec(dbm_pagfno(dbm), CLOSE_ON_EXEC); + close_on_exec(dbm_dirfno(dbm), CLOSE_ON_EXEC); + dict_dbm->dict.flags = dict_flags | DICT_FLAG_FIXED; + if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0) + dict_dbm->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL); + if (dict_flags & DICT_FLAG_FOLD_FIX) + dict_dbm->dict.fold_buf = vstring_alloc(10); + dict_dbm->dbm = dbm; + dict_dbm->key_buf = 0; + dict_dbm->val_buf = 0; + + DICT_DBM_OPEN_RETURN(DICT_DEBUG (&dict_dbm->dict)); +} + +#endif diff --git a/src/util/dict_dbm.h b/src/util/dict_dbm.h new file mode 100644 index 0000000..ecaab62 --- /dev/null +++ b/src/util/dict_dbm.h @@ -0,0 +1,37 @@ +#ifndef _DICT_DBM_H_INCLUDED_ +#define _DICT_DBM_H_INCLUDED_ + +/*++ +/* NAME +/* dict_dbm 3h +/* SUMMARY +/* dictionary manager interface to DBM files +/* SYNOPSIS +/* #include <dict_dbm.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <dict.h> + + /* + * External interface. + */ +#define DICT_TYPE_DBM "dbm" + +extern DICT *dict_dbm_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_debug.c b/src/util/dict_debug.c new file mode 100644 index 0000000..46634d4 --- /dev/null +++ b/src/util/dict_debug.c @@ -0,0 +1,150 @@ +/*++ +/* NAME +/* dict_debug 3 +/* SUMMARY +/* dictionary manager, logging proxy +/* SYNOPSIS +/* #include <dict.h> +/* +/* DICT *dict_debug(dict_handle) +/* DICT *dict_handle; +/* +/* DICT *DICT_DEBUG(dict_handle) +/* DICT *dict_handle; +/* DESCRIPTION +/* dict_debug() encapsulates the given dictionary object and returns +/* a proxy object that logs all access to the encapsulated object. +/* This is more convenient than having to add logging capability +/* to each individual dictionary access method. +/* +/* DICT_DEBUG() is an unsafe macro that returns the original object if +/* the object's debugging flag is not set, and that otherwise encapsulates +/* the object with dict_debug(). This macro simplifies usage by avoiding +/* clumsy expressions. The macro evaluates its argument multiple times. +/* DIAGNOSTICS +/* Fatal errors: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include <sys_defs.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <dict.h> + +/* Application-specific. */ + +typedef struct { + DICT dict; /* the proxy service */ + DICT *real_dict; /* encapsulated object */ +} DICT_DEBUG; + +/* dict_debug_lookup - log lookup operation */ + +static const char *dict_debug_lookup(DICT *dict, const char *key) +{ + DICT_DEBUG *dict_debug = (DICT_DEBUG *) dict; + DICT *real_dict = dict_debug->real_dict; + const char *result; + + real_dict->flags = dict->flags; + result = dict_get(real_dict, key); + dict->flags = real_dict->flags; + msg_info("%s:%s lookup: \"%s\" = \"%s\"", dict->type, dict->name, key, + result ? result : real_dict->error ? "error" : "not_found"); + DICT_ERR_VAL_RETURN(dict, real_dict->error, result); +} + +/* dict_debug_update - log update operation */ + +static int dict_debug_update(DICT *dict, const char *key, const char *value) +{ + DICT_DEBUG *dict_debug = (DICT_DEBUG *) dict; + DICT *real_dict = dict_debug->real_dict; + int result; + + real_dict->flags = dict->flags; + result = dict_put(real_dict, key, value); + dict->flags = real_dict->flags; + msg_info("%s:%s update: \"%s\" = \"%s\": %s", dict->type, dict->name, + key, value, result == 0 ? "success" : real_dict->error ? + "error" : "failed"); + DICT_ERR_VAL_RETURN(dict, real_dict->error, result); +} + +/* dict_debug_delete - log delete operation */ + +static int dict_debug_delete(DICT *dict, const char *key) +{ + DICT_DEBUG *dict_debug = (DICT_DEBUG *) dict; + DICT *real_dict = dict_debug->real_dict; + int result; + + real_dict->flags = dict->flags; + result = dict_del(real_dict, key); + dict->flags = real_dict->flags; + msg_info("%s:%s delete: \"%s\": %s", dict->type, dict->name, key, + result == 0 ? "success" : real_dict->error ? + "error" : "failed"); + DICT_ERR_VAL_RETURN(dict, real_dict->error, result); +} + +/* dict_debug_sequence - log sequence operation */ + +static int dict_debug_sequence(DICT *dict, int function, + const char **key, const char **value) +{ + DICT_DEBUG *dict_debug = (DICT_DEBUG *) dict; + DICT *real_dict = dict_debug->real_dict; + int result; + + real_dict->flags = dict->flags; + result = dict_seq(real_dict, function, key, value); + dict->flags = real_dict->flags; + if (result == 0) + msg_info("%s:%s sequence: \"%s\" = \"%s\"", dict->type, dict->name, + *key, *value); + else + msg_info("%s:%s sequence: found EOF", dict->type, dict->name); + DICT_ERR_VAL_RETURN(dict, real_dict->error, result); +} + +/* dict_debug_close - log operation */ + +static void dict_debug_close(DICT *dict) +{ + DICT_DEBUG *dict_debug = (DICT_DEBUG *) dict; + + dict_close(dict_debug->real_dict); + dict_free(dict); +} + +/* dict_debug - encapsulate dictionary object and install proxies */ + +DICT *dict_debug(DICT *real_dict) +{ + DICT_DEBUG *dict_debug; + + dict_debug = (DICT_DEBUG *) dict_alloc(real_dict->type, + real_dict->name, sizeof(*dict_debug)); + dict_debug->dict.flags = real_dict->flags; /* XXX not synchronized */ + dict_debug->dict.lookup = dict_debug_lookup; + dict_debug->dict.update = dict_debug_update; + dict_debug->dict.delete = dict_debug_delete; + dict_debug->dict.sequence = dict_debug_sequence; + dict_debug->dict.close = dict_debug_close; + dict_debug->real_dict = real_dict; + return (&dict_debug->dict); +} diff --git a/src/util/dict_env.c b/src/util/dict_env.c new file mode 100644 index 0000000..1635b8d --- /dev/null +++ b/src/util/dict_env.c @@ -0,0 +1,112 @@ +/*++ +/* NAME +/* dict_env 3 +/* SUMMARY +/* dictionary manager interface to environment variables +/* SYNOPSIS +/* #include <dict_env.h> +/* +/* DICT *dict_env_open(name, dummy, dict_flags) +/* const char *name; +/* int dummy; +/* int dict_flags; +/* DESCRIPTION +/* dict_env_open() opens the environment variable array and +/* makes it accessible via the generic operations documented +/* in dict_open(3). The \fIname\fR and \fIdummy\fR arguments +/* are ignored. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* safe_getenv(3) safe getenv() interface +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <stdio.h> /* sprintf() prototype */ +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +/* Utility library. */ + +#include "mymalloc.h" +#include "msg.h" +#include "safe.h" +#include "stringops.h" +#include "dict.h" +#include "dict_env.h" + +/* dict_env_update - update environment array */ + +static int dict_env_update(DICT *dict, const char *name, const char *value) +{ + dict->error = 0; + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + if (setenv(name, value, 1)) + msg_fatal("setenv: %m"); + + return (DICT_STAT_SUCCESS); +} + +/* dict_env_lookup - access environment array */ + +static const char *dict_env_lookup(DICT *dict, const char *name) +{ + dict->error = 0; + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + return (safe_getenv(name)); +} + +/* dict_env_close - close environment dictionary */ + +static void dict_env_close(DICT *dict) +{ + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_env_open - make association with environment array */ + +DICT *dict_env_open(const char *name, int unused_flags, int dict_flags) +{ + DICT *dict; + + dict = dict_alloc(DICT_TYPE_ENVIRON, name, sizeof(*dict)); + dict->lookup = dict_env_lookup; + dict->update = dict_env_update; + dict->close = dict_env_close; + dict->flags = dict_flags | DICT_FLAG_FIXED; + if (dict_flags & DICT_FLAG_FOLD_FIX) + dict->fold_buf = vstring_alloc(10); + dict->owner.status = DICT_OWNER_TRUSTED; + return (DICT_DEBUG (dict)); +} diff --git a/src/util/dict_env.h b/src/util/dict_env.h new file mode 100644 index 0000000..9abfa4f --- /dev/null +++ b/src/util/dict_env.h @@ -0,0 +1,37 @@ +#ifndef _DICT_ENV_H_INCLUDED_ +#define _DICT_ENV_H_INCLUDED_ + +/*++ +/* NAME +/* dict_env 3h +/* SUMMARY +/* dictionary manager interface to environment variables +/* SYNOPSIS +/* #include <dict_env.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <dict.h> + + /* + * External interface. + */ +#define DICT_TYPE_ENVIRON "environ" + +extern DICT *dict_env_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_fail.c b/src/util/dict_fail.c new file mode 100644 index 0000000..c8d9a19 --- /dev/null +++ b/src/util/dict_fail.c @@ -0,0 +1,115 @@ +/*++ +/* NAME +/* dict_fail 3 +/* SUMMARY +/* dictionary manager interface to 'always fail' table +/* SYNOPSIS +/* #include <dict_fail.h> +/* +/* DICT *dict_fail_open(name, open_flags, dict_flags) +/* const char *name; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_fail_open() implements a dummy dictionary that fails +/* all operations. The name can be used for logging. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <errno.h> + +/* Utility library. */ + +#include <mymalloc.h> +#include <msg.h> +#include <dict.h> +#include <dict_fail.h> + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + int dict_errno; /* fixed error result */ +} DICT_FAIL; + +/* dict_fail_sequence - fail lookup */ + +static int dict_fail_sequence(DICT *dict, int unused_func, + const char **key, const char **value) +{ + DICT_FAIL *dp = (DICT_FAIL *) dict; + + errno = 0; + DICT_ERR_VAL_RETURN(dict, dp->dict_errno, DICT_STAT_ERROR); +} + +/* dict_fail_update - fail lookup */ + +static int dict_fail_update(DICT *dict, const char *unused_name, + const char *unused_value) +{ + DICT_FAIL *dp = (DICT_FAIL *) dict; + + errno = 0; + DICT_ERR_VAL_RETURN(dict, dp->dict_errno, DICT_STAT_ERROR); +} + +/* dict_fail_lookup - fail lookup */ + +static const char *dict_fail_lookup(DICT *dict, const char *unused_name) +{ + DICT_FAIL *dp = (DICT_FAIL *) dict; + + errno = 0; + DICT_ERR_VAL_RETURN(dict, dp->dict_errno, (char *) 0); +} + +/* dict_fail_delete - fail delete */ + +static int dict_fail_delete(DICT *dict, const char *unused_name) +{ + DICT_FAIL *dp = (DICT_FAIL *) dict; + + errno = 0; + DICT_ERR_VAL_RETURN(dict, dp->dict_errno, DICT_STAT_ERROR); +} + +/* dict_fail_close - close fail dictionary */ + +static void dict_fail_close(DICT *dict) +{ + dict_free(dict); +} + +/* dict_fail_open - make association with fail variable */ + +DICT *dict_fail_open(const char *name, int open_flags, int dict_flags) +{ + DICT_FAIL *dp; + + dp = (DICT_FAIL *) dict_alloc(DICT_TYPE_FAIL, name, sizeof(*dp)); + dp->dict.lookup = dict_fail_lookup; + if (open_flags & O_RDWR) { + dp->dict.update = dict_fail_update; + dp->dict.delete = dict_fail_delete; + } + dp->dict.sequence = dict_fail_sequence; + dp->dict.close = dict_fail_close; + dp->dict.flags = dict_flags | DICT_FLAG_PATTERN; + dp->dict_errno = DICT_ERR_RETRY; + dp->dict.owner.status = DICT_OWNER_TRUSTED; + return (DICT_DEBUG (&dp->dict)); +} diff --git a/src/util/dict_fail.h b/src/util/dict_fail.h new file mode 100644 index 0000000..b42a31b --- /dev/null +++ b/src/util/dict_fail.h @@ -0,0 +1,35 @@ +#ifndef _DICT_FAIL_H_INCLUDED_ +#define _DICT_FAIL_H_INCLUDED_ + +/*++ +/* NAME +/* dict_fail 3h +/* SUMMARY +/* dictionary manager interface to 'always fail' table +/* SYNOPSIS +/* #include <dict_fail.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <dict.h> + + /* + * External interface. + */ +#define DICT_TYPE_FAIL "fail" + +extern DICT *dict_fail_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* jeffm +/* ghostgun.com +/*--*/ + +#endif diff --git a/src/util/dict_file.c b/src/util/dict_file.c new file mode 100644 index 0000000..c6ea74c --- /dev/null +++ b/src/util/dict_file.c @@ -0,0 +1,231 @@ +/*++ +/* NAME +/* dict_file_to_buf 3 +/* SUMMARY +/* include content from file as blob +/* SYNOPSIS +/* #include <dict.h> +/* +/* VSTRING *dict_file_to_buf( +/* DICT *dict, +/* const char *pathnames) +/* +/* VSTRING *dict_file_to_b64( +/* DICT *dict, +/* const char *pathnames) +/* +/* VSTRING *dict_file_from_b64( +/* DICT *dict, +/* const char *value) +/* +/* char *dict_file_get_error( +/* DICT *dict) +/* +/* void dict_file_purge_buffers( +/* DICT *dict) +/* +/* const char *dict_file_lookup( +/* DICT *dict) +/* DESCRIPTION +/* dict_file_to_buf() reads the content of the specified files, +/* with names separated by CHARS_COMMA_SP, while inserting a +/* gratuitous newline character between files. It returns a +/* pointer to a buffer which is owned by the DICT, or a null +/* pointer in case of error. +/* +/* dict_file_to_b64() invokes dict_file_to_buf() and converts +/* the result to base64. It returns a pointer to a buffer which +/* is owned by the DICT, or a null pointer in case of error. +/* +/* dict_file_from_b64() converts a value from base64. It returns +/* a pointer to a buffer which is owned by the DICT, or a null +/* pointer in case of error. +/* +/* dict_file_purge_buffers() disposes of dict_file-related +/* memory that are associated with this DICT. +/* +/* dict_file_get_error() should be called only after error; +/* it returns a description of the problem. Storage is owned +/* by the caller. +/* +/* dict_file_lookup() wraps the dictionary lookup method and +/* decodes the base64 lookup result. The dictionary must be +/* opened with DICT_FLAG_SRC_RHS_IS_FILE. Sets dict->error to +/* DICT_ERR_CONFIG if the content is invalid. Decoding is not +/* built into the dict->lookup() method, because that would +/* complicate the implementation of map nesting (inline, thash), +/* map composition (pipemap, unionmap), and map proxying. +/* DIAGNOSTICS +/* Panic: interface violation. +/* +/* In case of error the VSTRING result value is a null pointer, and +/* an error description can be retrieved with dict_file_get_error(). +/* The storage is owned by the caller. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include <sys_defs.h> +#include <sys/stat.h> +#include <string.h> + + /* + * Utility library. + */ +#include <base64_code.h> +#include <dict.h> +#include <msg.h> +#include <mymalloc.h> +#include <vstream.h> +#include <vstring.h> + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* dict_file_to_buf - read files into a buffer */ + +VSTRING *dict_file_to_buf(DICT *dict, const char *pathnames) +{ + struct stat st; + VSTREAM *fp = 0; + ARGV *argv; + char **cpp; + + /* dict_file_to_buf() postcondition: dict->file_buf exists. */ + if (dict->file_buf == 0) + dict->file_buf = vstring_alloc(100); + +#define DICT_FILE_RETURN(retval) do { \ + argv_free(argv); \ + if (fp) vstream_fclose(fp); \ + return (retval); \ + } while (0); + + argv = argv_split(pathnames, CHARS_COMMA_SP); + if (argv->argc == 0) { + vstring_sprintf(dict->file_buf, "empty pathname list: >>%s<<'", + pathnames); + DICT_FILE_RETURN(0); + } + VSTRING_RESET(dict->file_buf); + for (cpp = argv->argv; *cpp; cpp++) { + if ((fp = vstream_fopen(*cpp, O_RDONLY, 0)) == 0 + || fstat(vstream_fileno(fp), &st) < 0) { + vstring_sprintf(dict->file_buf, "open %s: %m", *cpp); + DICT_FILE_RETURN(0); + } + if (st.st_size > SSIZE_T_MAX - LEN(dict->file_buf)) { + vstring_sprintf(dict->file_buf, "file too large: %s", pathnames); + DICT_FILE_RETURN(0); + } + if (vstream_fread_app(fp, dict->file_buf, st.st_size) != st.st_size) { + vstring_sprintf(dict->file_buf, "read %s: %m", *cpp); + DICT_FILE_RETURN(0); + } + (void) vstream_fclose(fp); + fp = 0; + if (cpp[1] != 0) + VSTRING_ADDCH(dict->file_buf, '\n'); + } + VSTRING_TERMINATE(dict->file_buf); + DICT_FILE_RETURN(dict->file_buf); +} + +/* dict_file_to_b64 - read files into a base64-encoded buffer */ + +VSTRING *dict_file_to_b64(DICT *dict, const char *pathnames) +{ + ssize_t helper; + + if (dict_file_to_buf(dict, pathnames) == 0) + return (0); + if (dict->file_b64 == 0) + dict->file_b64 = vstring_alloc(100); + helper = (LEN(dict->file_buf) + 2) / 3; + if (helper > SSIZE_T_MAX / 4) { + vstring_sprintf(dict->file_buf, "file too large: %s", pathnames); + return (0); + } + VSTRING_RESET(dict->file_b64); + VSTRING_SPACE(dict->file_b64, helper * 4); + return (base64_encode(dict->file_b64, STR(dict->file_buf), + LEN(dict->file_buf))); +} + +/* dict_file_from_b64 - convert value from base64 */ + +VSTRING *dict_file_from_b64(DICT *dict, const char *value) +{ + ssize_t helper; + VSTRING *result; + + if (dict->file_buf == 0) + dict->file_buf = vstring_alloc(100); + helper = strlen(value) / 4; + VSTRING_RESET(dict->file_buf); + VSTRING_SPACE(dict->file_buf, helper * 3); + result = base64_decode(dict->file_buf, value, strlen(value)); + if (result == 0) + vstring_sprintf(dict->file_buf, "malformed BASE64 value: %.30s", value); + return (result); +} + +/* dict_file_get_error - return error text */ + +char *dict_file_get_error(DICT *dict) +{ + if (dict->file_buf == 0) + msg_panic("dict_file_get_error: no buffer"); + return (mystrdup(STR(dict->file_buf))); +} + +/* dict_file_purge_buffers - purge file buffers */ + +void dict_file_purge_buffers(DICT *dict) +{ + if (dict->file_buf) { + vstring_free(dict->file_buf); + dict->file_buf = 0; + } + if (dict->file_b64) { + vstring_free(dict->file_b64); + dict->file_b64 = 0; + } +} + +/* dict_file_lookup - look up and decode dictionary entry */ + +const char *dict_file_lookup(DICT *dict, const char *key) +{ + const char myname[] = "dict_file_lookup"; + const char *res; + VSTRING *unb64; + char *err; + + if ((dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) == 0) + msg_panic("%s: dictionary opened without DICT_FLAG_SRC_RHS_IS_FILE", + myname); + if ((res = dict->lookup(dict, key)) == 0) + return (0); + if ((unb64 = dict_file_from_b64(dict, res)) == 0) { + err = dict_file_get_error(dict); + msg_warn("table %s:%s: key %s: %s", dict->type, dict->name, key, err); + myfree(err); + dict->error = DICT_ERR_CONFIG; + return (0); + } + return STR(unb64); +} diff --git a/src/util/dict_ht.c b/src/util/dict_ht.c new file mode 100644 index 0000000..74020d5 --- /dev/null +++ b/src/util/dict_ht.c @@ -0,0 +1,171 @@ +/*++ +/* NAME +/* dict_ht 3 +/* SUMMARY +/* dictionary manager interface to hash tables +/* SYNOPSIS +/* #include <dict_ht.h> +/* +/* DICT *dict_ht_open(name, open_flags, dict_flags) +/* const char *name; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_ht_open() creates a memory-resident hash table and +/* makes it accessible via the generic dictionary operations +/* documented in dict_open(3). The open_flags argument is +/* ignored. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" + +/* Utility library. */ + +#include "mymalloc.h" +#include "htable.h" +#include "dict.h" +#include "dict_ht.h" +#include "stringops.h" +#include "vstring.h" + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + HTABLE *table; /* hash table */ +} DICT_HT; + +/* dict_ht_delete - delete hash-table entry */ + +static int dict_ht_delete(DICT *dict, const char *name) +{ + DICT_HT *dict_ht = (DICT_HT *) dict; + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + if (htable_locate(dict_ht->table, name) == 0) { + DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_FAIL); + } else { + htable_delete(dict_ht->table, name, myfree); + DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_SUCCESS); + } +} + +/* dict_ht_lookup - find hash-table entry */ + +static const char *dict_ht_lookup(DICT *dict, const char *name) +{ + DICT_HT *dict_ht = (DICT_HT *) dict; + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, htable_find(dict_ht->table, name)); +} + +/* dict_ht_update - add or update hash-table entry */ + +static int dict_ht_update(DICT *dict, const char *name, const char *value) +{ + DICT_HT *dict_ht = (DICT_HT *) dict; + HTABLE_INFO *ht; + char *saved_value = mystrdup(value); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + if ((ht = htable_locate(dict_ht->table, name)) != 0) { + myfree(ht->value); + } else { + ht = htable_enter(dict_ht->table, name, (void *) 0); + } + ht->value = saved_value; + DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_SUCCESS); +} + +/* dict_ht_sequence - first/next iterator */ + +static int dict_ht_sequence(DICT *dict, int how, const char **name, + const char **value) +{ + DICT_HT *dict_ht = (DICT_HT *) dict; + HTABLE_INFO *ht; + + ht = htable_sequence(dict_ht->table, + how == DICT_SEQ_FUN_FIRST ? HTABLE_SEQ_FIRST : + how == DICT_SEQ_FUN_NEXT ? HTABLE_SEQ_NEXT : + HTABLE_SEQ_STOP); + if (ht != 0) { + *name = ht->key; + *value = ht->value; + DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_SUCCESS); + } else { + *name = 0; + *value = 0; + DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_FAIL); + } +} + +/* dict_ht_close - disassociate from hash table */ + +static void dict_ht_close(DICT *dict) +{ + DICT_HT *dict_ht = (DICT_HT *) dict; + + htable_free(dict_ht->table, myfree); + if (dict_ht->dict.fold_buf) + vstring_free(dict_ht->dict.fold_buf); + dict_free(dict); +} + +/* dict_ht_open - create association with hash table */ + +DICT *dict_ht_open(const char *name, int unused_open_flags, int dict_flags) +{ + DICT_HT *dict_ht; + + dict_ht = (DICT_HT *) dict_alloc(DICT_TYPE_HT, name, sizeof(*dict_ht)); + dict_ht->dict.lookup = dict_ht_lookup; + dict_ht->dict.update = dict_ht_update; + dict_ht->dict.delete = dict_ht_delete; + dict_ht->dict.sequence = dict_ht_sequence; + dict_ht->dict.close = dict_ht_close; + dict_ht->dict.flags = dict_flags | DICT_FLAG_FIXED; + if (dict_flags & DICT_FLAG_FOLD_FIX) + dict_ht->dict.fold_buf = vstring_alloc(10); + dict_ht->table = htable_create(0); + dict_ht->dict.owner.status = DICT_OWNER_TRUSTED; + return (&dict_ht->dict); +} diff --git a/src/util/dict_ht.h b/src/util/dict_ht.h new file mode 100644 index 0000000..7cb31aa --- /dev/null +++ b/src/util/dict_ht.h @@ -0,0 +1,38 @@ +#ifndef _DICT_HT_H_INCLUDED_ +#define _DICT_HT_H_INCLUDED_ + +/*++ +/* NAME +/* dict_ht 3h +/* SUMMARY +/* dictionary manager interface to hash tables +/* SYNOPSIS +/* #include <dict_ht.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <htable.h> +#include <dict.h> + + /* + * External interface. + */ +#define DICT_TYPE_HT "internal" + +extern DICT *dict_ht_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_inline.c b/src/util/dict_inline.c new file mode 100644 index 0000000..72339b2 --- /dev/null +++ b/src/util/dict_inline.c @@ -0,0 +1,150 @@ +/*++ +/* NAME +/* dict_inline 3 +/* SUMMARY +/* dictionary manager interface for inline table +/* SYNOPSIS +/* #include <dict_inline.h> +/* +/* DICT *dict_inline_open(name, open_flags, dict_flags) +/* const char *name; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_inline_open() opens a read-only, in-memory table. +/* Example: "\fBinline:{\fIkey_1=value_1, ..., key_n=value_n\fR}". +/* The longer form with { key = value } allows values that +/* contain whitespace or comma. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <stringops.h> +#include <dict.h> +#include <dict_ht.h> +#include <dict_inline.h> + +/* Application-specific. */ + +/* dict_inline_open - open inline table */ + +DICT *dict_inline_open(const char *name, int open_flags, int dict_flags) +{ + DICT *dict; + char *cp, *saved_name = 0; + size_t len; + char *nameval, *vname, *value; + const char *err = 0; + char *free_me = 0; + int count = 0; + + /* + * Clarity first. Let the optimizer worry about redundant code. + */ +#define DICT_INLINE_RETURN(x) do { \ + DICT *__d = (x); \ + if (saved_name != 0) \ + myfree(saved_name); \ + if (free_me != 0) \ + myfree(free_me); \ + return (__d); \ + } while (0) + + /* + * Sanity checks. + */ + if (open_flags != O_RDONLY) + DICT_INLINE_RETURN(dict_surrogate(DICT_TYPE_INLINE, name, + open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_INLINE, name)); + + /* + * UTF-8 syntax check. + */ + if (DICT_NEED_UTF8_ACTIVATION(util_utf8_enable, dict_flags) + && allascii(name) == 0 + && valid_utf8_string(name, strlen(name)) == 0) + DICT_INLINE_RETURN(dict_surrogate(DICT_TYPE_INLINE, name, + open_flags, dict_flags, + "bad UTF-8 syntax: \"%s:%s\"; " + "need \"%s:{name=value...}\"", + DICT_TYPE_INLINE, name, + DICT_TYPE_INLINE)); + + /* + * Parse the table into its constituent name=value pairs. + */ + if ((len = balpar(name, CHARS_BRACE)) == 0 || name[len] != 0 + || *(cp = saved_name = mystrndup(name + 1, len - 2)) == 0) + DICT_INLINE_RETURN(dict_surrogate(DICT_TYPE_INLINE, name, + open_flags, dict_flags, + "bad syntax: \"%s:%s\"; " + "need \"%s:{name=value...}\"", + DICT_TYPE_INLINE, name, + DICT_TYPE_INLINE)); + + /* + * Reuse the "internal" dictionary type. + */ + dict = dict_open3(DICT_TYPE_HT, name, open_flags, dict_flags); + dict_type_override(dict, DICT_TYPE_INLINE); + while ((nameval = mystrtokq(&cp, CHARS_COMMA_SP, CHARS_BRACE)) != 0) { + if (nameval[0] == CHARS_BRACE[0]) + err = free_me = extpar(&nameval, CHARS_BRACE, EXTPAR_FLAG_STRIP); + if (err != 0 || (err = split_qnameval(nameval, &vname, &value)) != 0) + break; + + if ((dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) != 0) { + VSTRING *base64_buf; + + if ((base64_buf = dict_file_to_b64(dict, value)) == 0) { + err = free_me = dict_file_get_error(dict); + break; + } + value = vstring_str(base64_buf); + } + /* No duplicate checks. See comments in dict_thash.c. */ + dict->update(dict, vname, value); + count += 1; + } + if (err != 0 || count == 0) { + dict->close(dict); + DICT_INLINE_RETURN(dict_surrogate(DICT_TYPE_INLINE, name, + open_flags, dict_flags, + "%s: \"%s:%s\"; " + "need \"%s:{name=%s...}\"", + err != 0 ? err : "empty table", + DICT_TYPE_INLINE, name, + DICT_TYPE_INLINE, + (dict_flags & DICT_FLAG_SRC_RHS_IS_FILE) ? + "filename" : "value")); + } + dict->owner.status = DICT_OWNER_TRUSTED; + + dict_file_purge_buffers(dict); + DICT_INLINE_RETURN(DICT_DEBUG (dict)); +} diff --git a/src/util/dict_inline.h b/src/util/dict_inline.h new file mode 100644 index 0000000..9879acf --- /dev/null +++ b/src/util/dict_inline.h @@ -0,0 +1,37 @@ +#ifndef _DICT_INLINE_H_INCLUDED_ +#define _DICT_INLINE_H_INCLUDED_ + +/*++ +/* NAME +/* dict_inline 3h +/* SUMMARY +/* dictionary manager interface for inline tables +/* SYNOPSIS +/* #include <dict_inline.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <dict.h> + + /* + * External interface. + */ +#define DICT_TYPE_INLINE "inline" + +extern DICT *dict_inline_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_inline.ref b/src/util/dict_inline.ref new file mode 100644 index 0000000..e64e6d0 --- /dev/null +++ b/src/util/dict_inline.ref @@ -0,0 +1,24 @@ +./dict_open: error: empty table: "inline:{ }"; need "inline:{name=value...}" +owner=trusted (uid=2147483647) +./dict_open: error: missing '=' after attribute name: "inline:{ foo = xx }"; need "inline:{name=value...}" +owner=trusted (uid=2147483647) +./dict_open: error: bad syntax: "inline:{ foo=xx }x"; need "inline:{name=value...}" +owner=trusted (uid=2147483647) +./dict_open: error: bad syntax: "inline:{ foo=xx x"; need "inline:{name=value...}" +owner=trusted (uid=2147483647) +./dict_open: error: syntax error after '}' in "{x=y}x": "inline:{ foo=xx {x=y}x}"; need "inline:{name=value...}" +owner=trusted (uid=2147483647) +owner=trusted (uid=2147483647) +> get foo +foo=XX +> get bar +bar=lotsa stuff +> get baz +baz: not found +owner=trusted (uid=2147483647) +> get foo +foo=XX +> get bar +bar=lotsa stuff +> get baz +baz: not found diff --git a/src/util/dict_inline_cidr.ref b/src/util/dict_inline_cidr.ref new file mode 100644 index 0000000..56c00c5 --- /dev/null +++ b/src/util/dict_inline_cidr.ref @@ -0,0 +1,4 @@ +> get 1.2.3.4 +1.2.3.4=got 1.2.3.4 +> get 4.3.2.1 +4.3.2.1: not found diff --git a/src/util/dict_inline_file.ref b/src/util/dict_inline_file.ref new file mode 100644 index 0000000..9d49e95 --- /dev/null +++ b/src/util/dict_inline_file.ref @@ -0,0 +1,12 @@ +./dict_open: error: open xx: No such file or directory: "inline:{ foo=xx, bar=yy }"; need "inline:{name=filename...}" +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: inline:{ foo=xx, bar=yy } is unavailable. open xx: No such file or directory: "inline:{ foo=xx, bar=yy }"; need "inline:{name=filename...}" +foo: error +owner=trusted (uid=2147483647) +> get file1 +file1=dGhpcy1pcy1maWxlMQo= +> get file2 +file2=dGhpcy1pcy1maWxlMgo= +> get file3 +file3: not found diff --git a/src/util/dict_inline_pcre.ref b/src/util/dict_inline_pcre.ref new file mode 100644 index 0000000..0c381f1 --- /dev/null +++ b/src/util/dict_inline_pcre.ref @@ -0,0 +1,4 @@ +> get foo +foo=got foo +> get bar +bar: not found diff --git a/src/util/dict_inline_regexp.ref b/src/util/dict_inline_regexp.ref new file mode 100644 index 0000000..0c381f1 --- /dev/null +++ b/src/util/dict_inline_regexp.ref @@ -0,0 +1,4 @@ +> get foo +foo=got foo +> get bar +bar: not found diff --git a/src/util/dict_lmdb.c b/src/util/dict_lmdb.c new file mode 100644 index 0000000..bed20e0 --- /dev/null +++ b/src/util/dict_lmdb.c @@ -0,0 +1,706 @@ +/*++ +/* NAME +/* dict_lmdb 3 +/* SUMMARY +/* dictionary manager interface to OpenLDAP LMDB files +/* SYNOPSIS +/* #include <dict_lmdb.h> +/* +/* extern size_t dict_lmdb_map_size; +/* +/* DEFINE_DICT_LMDB_MAP_SIZE; +/* +/* DICT *dict_lmdb_open(path, open_flags, dict_flags) +/* const char *name; +/* const char *path; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_lmdb_open() opens the named LMDB database and makes +/* it available via the generic interface described in +/* dict_open(3). +/* +/* The dict_lmdb_map_size variable specifies the initial +/* database memory map size. When a map becomes full its size +/* is doubled, and other programs pick up the size change. +/* +/* This variable cannot be exported via the dict(3) API and +/* must therefore be defined in the calling program by invoking +/* the DEFINE_DICT_LMDB_MAP_SIZE macro at the global level. +/* DIAGNOSTICS +/* Fatal errors: cannot open file, file write error, out of +/* memory. +/* +/* If a jump buffer is specified with dict_setjmp(), then the LMDB +/* client will call dict_longjmp() to return to that execution +/* context after a recoverable error. +/* BUGS +/* The on-the-fly map resize operations require no concurrent +/* activity in the same database by other threads in the same +/* memory address space. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Howard Chu +/* Symas Corporation +/* +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#include <sys_defs.h> + +#ifdef HAS_LMDB + +/* System library. */ + +#include <sys/stat.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <htable.h> +#include <iostuff.h> +#include <vstring.h> +#include <myflock.h> +#include <stringops.h> +#include <slmdb.h> +#include <dict.h> +#include <dict_lmdb.h> +#include <warn_stat.h> + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + SLMDB slmdb; /* sane LMDB API */ + VSTRING *key_buf; /* key buffer */ + VSTRING *val_buf; /* value buffer */ +} DICT_LMDB; + + /* + * The LMDB database filename suffix happens to equal our DICT_TYPE_LMDB + * prefix, but that doesn't mean it is kosher to use DICT_TYPE_LMDB where a + * suffix is needed, so we define an explicit suffix here. + */ +#define DICT_LMDB_SUFFIX "lmdb" + + /* + * Make a safe string copy that is guaranteed to be null-terminated. + */ +#define SCOPY(buf, data, size) \ + vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size)) + + /* + * Postfix writers recover from a "map full" error by increasing the memory + * map size with a factor DICT_LMDB_SIZE_INCR (up to some limit) and + * retrying the transaction. + * + * Each dict(3) API call is retried no more than a few times. For bulk-mode + * transactions the number of retries is proportional to the size of the + * address space. + * + * We do not expose these details to the Postfix user interface. The purpose of + * Postfix is to solve problems, not punt them to the user. + */ +#define DICT_LMDB_SIZE_INCR 2 /* Increase size by 1 bit on retry */ +#define DICT_LMDB_SIZE_MAX SSIZE_T_MAX + +#define DICT_LMDB_API_RETRY_LIMIT 2 /* Retries per dict(3) API call */ +#define DICT_LMDB_BULK_RETRY_LIMIT \ + ((int) (2 * sizeof(size_t) * CHAR_BIT)) /* Retries per bulk-mode + * transaction */ + +/* #define msg_verbose 1 */ + +/* dict_lmdb_lookup - find database entry */ + +static const char *dict_lmdb_lookup(DICT *dict, const char *name) +{ + DICT_LMDB *dict_lmdb = (DICT_LMDB *) dict; + MDB_val mdb_key; + MDB_val mdb_value; + const char *result = 0; + int status; + ssize_t klen; + + dict->error = 0; + klen = strlen(name); + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_lmdb_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + + /* + * Acquire a shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_SHARED) < 0) + msg_fatal("%s: lock dictionary: %m", dict->name); + + /* + * See if this LMDB file was written with one null byte appended to key + * and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + mdb_key.mv_data = (void *) name; + mdb_key.mv_size = klen + 1; + status = slmdb_get(&dict_lmdb->slmdb, &mdb_key, &mdb_value); + if (status == 0) { + dict->flags &= ~DICT_FLAG_TRY0NULL; + result = SCOPY(dict_lmdb->val_buf, mdb_value.mv_data, + mdb_value.mv_size); + } else if (status != MDB_NOTFOUND) { + msg_fatal("error reading %s:%s: %s", + dict_lmdb->dict.type, dict_lmdb->dict.name, + mdb_strerror(status)); + } + } + + /* + * See if this LMDB file was written with no null byte appended to key + * and value. + */ + if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { + mdb_key.mv_data = (void *) name; + mdb_key.mv_size = klen; + status = slmdb_get(&dict_lmdb->slmdb, &mdb_key, &mdb_value); + if (status == 0) { + dict->flags &= ~DICT_FLAG_TRY1NULL; + result = SCOPY(dict_lmdb->val_buf, mdb_value.mv_data, + mdb_value.mv_size); + } else if (status != MDB_NOTFOUND) { + msg_fatal("error reading %s:%s: %s", + dict_lmdb->dict.type, dict_lmdb->dict.name, + mdb_strerror(status)); + } + } + + /* + * Release the shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict->name); + + return (result); +} + +/* dict_lmdb_update - add or update database entry */ + +static int dict_lmdb_update(DICT *dict, const char *name, const char *value) +{ + DICT_LMDB *dict_lmdb = (DICT_LMDB *) dict; + MDB_val mdb_key; + MDB_val mdb_value; + int status; + + dict->error = 0; + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_lmdb_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + mdb_key.mv_data = (void *) name; + mdb_value.mv_data = (void *) value; + mdb_key.mv_size = strlen(name); + mdb_value.mv_size = strlen(value); + + /* + * If undecided about appending a null byte to key and value, choose a + * default depending on the platform. + */ + if ((dict->flags & DICT_FLAG_TRY1NULL) + && (dict->flags & DICT_FLAG_TRY0NULL)) { +#ifdef LMDB_NO_TRAILING_NULL + dict->flags &= ~DICT_FLAG_TRY1NULL; +#else + dict->flags &= ~DICT_FLAG_TRY0NULL; +#endif + } + + /* + * Optionally append a null byte to key and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + mdb_key.mv_size++; + mdb_value.mv_size++; + } + + /* + * Acquire an exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("%s: lock dictionary: %m", dict->name); + + /* + * Do the update. + */ + status = slmdb_put(&dict_lmdb->slmdb, &mdb_key, &mdb_value, + (dict->flags & DICT_FLAG_DUP_REPLACE) ? 0 : MDB_NOOVERWRITE); + if (status != 0) { + if (status == MDB_KEYEXIST) { + if (dict->flags & DICT_FLAG_DUP_IGNORE) + /* void */ ; + else if (dict->flags & DICT_FLAG_DUP_WARN) + msg_warn("%s:%s: duplicate entry: \"%s\"", + dict_lmdb->dict.type, dict_lmdb->dict.name, name); + else + msg_fatal("%s:%s: duplicate entry: \"%s\"", + dict_lmdb->dict.type, dict_lmdb->dict.name, name); + } else { + msg_fatal("error updating %s:%s: %s", + dict_lmdb->dict.type, dict_lmdb->dict.name, + mdb_strerror(status)); + } + } + + /* + * Release the exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict->name); + + return (status); +} + +/* dict_lmdb_delete - delete one entry from the dictionary */ + +static int dict_lmdb_delete(DICT *dict, const char *name) +{ + DICT_LMDB *dict_lmdb = (DICT_LMDB *) dict; + MDB_val mdb_key; + int status = 1; + ssize_t klen; + + dict->error = 0; + klen = strlen(name); + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_lmdb_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + + /* + * Acquire an exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("%s: lock dictionary: %m", dict->name); + + /* + * See if this LMDB file was written with one null byte appended to key + * and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + mdb_key.mv_data = (void *) name; + mdb_key.mv_size = klen + 1; + status = slmdb_del(&dict_lmdb->slmdb, &mdb_key); + if (status != 0) { + if (status == MDB_NOTFOUND) + status = 1; + else + msg_fatal("error deleting from %s:%s: %s", + dict_lmdb->dict.type, dict_lmdb->dict.name, + mdb_strerror(status)); + } else { + dict->flags &= ~DICT_FLAG_TRY0NULL; /* found */ + } + } + + /* + * See if this LMDB file was written with no null byte appended to key + * and value. + */ + if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { + mdb_key.mv_data = (void *) name; + mdb_key.mv_size = klen; + status = slmdb_del(&dict_lmdb->slmdb, &mdb_key); + if (status != 0) { + if (status == MDB_NOTFOUND) + status = 1; + else + msg_fatal("error deleting from %s:%s: %s", + dict_lmdb->dict.type, dict_lmdb->dict.name, + mdb_strerror(status)); + } else { + dict->flags &= ~DICT_FLAG_TRY1NULL; /* found */ + } + } + + /* + * Release the exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict->name); + + return (status); +} + +/* dict_lmdb_sequence - traverse the dictionary */ + +static int dict_lmdb_sequence(DICT *dict, int function, + const char **key, const char **value) +{ + const char *myname = "dict_lmdb_sequence"; + DICT_LMDB *dict_lmdb = (DICT_LMDB *) dict; + MDB_val mdb_key; + MDB_val mdb_value; + MDB_cursor_op op; + int status; + + dict->error = 0; + + /* + * Determine the seek function. + */ + switch (function) { + case DICT_SEQ_FUN_FIRST: + op = MDB_FIRST; + break; + case DICT_SEQ_FUN_NEXT: + op = MDB_NEXT; + break; + default: + msg_panic("%s: invalid function: %d", myname, function); + } + + /* + * Acquire a shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_SHARED) < 0) + msg_fatal("%s: lock dictionary: %m", dict->name); + + /* + * Database lookup. + */ + status = slmdb_cursor_get(&dict_lmdb->slmdb, &mdb_key, &mdb_value, op); + + switch (status) { + + /* + * Copy the key and value so they are guaranteed null terminated. + */ + case 0: + *key = SCOPY(dict_lmdb->key_buf, mdb_key.mv_data, mdb_key.mv_size); + if (mdb_value.mv_data != 0 && mdb_value.mv_size > 0) + *value = SCOPY(dict_lmdb->val_buf, mdb_value.mv_data, + mdb_value.mv_size); + else + *value = ""; /* XXX */ + break; + + /* + * End-of-database. + */ + case MDB_NOTFOUND: + status = 1; + /* Not: mdb_cursor_close(). Wrong abstraction level. */ + break; + + /* + * Bust. + */ + default: + msg_fatal("error seeking %s:%s: %s", + dict_lmdb->dict.type, dict_lmdb->dict.name, + mdb_strerror(status)); + } + + /* + * Release the shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict->name); + + return (status); +} + +/* dict_lmdb_close - disassociate from data base */ + +static void dict_lmdb_close(DICT *dict) +{ + DICT_LMDB *dict_lmdb = (DICT_LMDB *) dict; + + slmdb_close(&dict_lmdb->slmdb); + if (dict_lmdb->key_buf) + vstring_free(dict_lmdb->key_buf); + if (dict_lmdb->val_buf) + vstring_free(dict_lmdb->val_buf); + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_lmdb_longjmp - repeat bulk transaction */ + +static void dict_lmdb_longjmp(void *context, int val) +{ + DICT_LMDB *dict_lmdb = (DICT_LMDB *) context; + + dict_longjmp(&dict_lmdb->dict, val); +} + +/* dict_lmdb_notify - debug logging */ + +static void dict_lmdb_notify(void *context, int error_code,...) +{ + DICT_LMDB *dict_lmdb = (DICT_LMDB *) context; + va_list ap; + + va_start(ap, error_code); + switch (error_code) { + case MDB_SUCCESS: + msg_info("database %s:%s: using size limit %lu during open", + dict_lmdb->dict.type, dict_lmdb->dict.name, + (unsigned long) va_arg(ap, size_t)); + break; + case MDB_MAP_FULL: + msg_info("database %s:%s: using size limit %lu after MDB_MAP_FULL", + dict_lmdb->dict.type, dict_lmdb->dict.name, + (unsigned long) va_arg(ap, size_t)); + break; + case MDB_MAP_RESIZED: + msg_info("database %s:%s: using size limit %lu after MDB_MAP_RESIZED", + dict_lmdb->dict.type, dict_lmdb->dict.name, + (unsigned long) va_arg(ap, size_t)); + break; + case MDB_READERS_FULL: + msg_info("database %s:%s: pausing after MDB_READERS_FULL", + dict_lmdb->dict.type, dict_lmdb->dict.name); + break; + default: + msg_warn("unknown MDB error code: %d", error_code); + break; + } + va_end(ap); +} + +/* dict_lmdb_assert - report LMDB internal assertion failure */ + +static void dict_lmdb_assert(void *context, const char *text) +{ + DICT_LMDB *dict_lmdb = (DICT_LMDB *) context; + + msg_fatal("%s:%s: internal error: %s", + dict_lmdb->dict.type, dict_lmdb->dict.name, text); +} + +/* dict_lmdb_open - open LMDB data base */ + +DICT *dict_lmdb_open(const char *path, int open_flags, int dict_flags) +{ + DICT_LMDB *dict_lmdb; + DICT *dict; + struct stat st; + SLMDB slmdb; + char *mdb_path; + int mdb_flags, slmdb_flags, status; + int db_fd; + + /* + * Let the optimizer worry about eliminating redundant code. + */ +#define DICT_LMDB_OPEN_RETURN(d) do { \ + DICT *__d = (d); \ + myfree(mdb_path); \ + return (__d); \ + } while (0) + + mdb_path = concatenate(path, "." DICT_TYPE_LMDB, (char *) 0); + + /* + * Impedance adapters. + */ + mdb_flags = MDB_NOSUBDIR | MDB_NOLOCK; + if (open_flags == O_RDONLY) + mdb_flags |= MDB_RDONLY; + + slmdb_flags = 0; + if (dict_flags & DICT_FLAG_BULK_UPDATE) + slmdb_flags |= SLMDB_FLAG_BULK; + + /* + * Security violation. + * + * By default, LMDB 0.9.9 writes uninitialized heap memory to a + * world-readable database file, as chunks of up to 4096 bytes. This is a + * huge memory disclosure vulnerability: memory content that a program + * does not intend to share ends up in a world-readable file. The content + * of uninitialized heap memory depends on program execution history. + * That history includes code execution in other libraries that are + * linked into the program. + * + * This is a problem whenever the user who writes the database file differs + * from the user who reads the database file. For example, a privileged + * writer and an unprivileged reader. In the case of Postfix, the + * postmap(1) and postalias(1) commands would leak uninitialized heap + * memory, as chunks of up to 4096 bytes, from a root-privileged process + * that writes to a database file, to unprivileged processes that read + * from that database file. + * + * As a workaround the postmap(1) and postalias(1) commands turn on + * MDB_WRITEMAP which disables the use of malloc() in LMDB. However, that + * does not address several disclosures of stack memory. We don't enable + * this workaround for Postfix databases are maintained by Postfix daemon + * processes, because those are accessible only by the postfix user. + * + * LMDB 0.9.10 by default does not write uninitialized heap memory to file + * (specify MDB_NOMEMINIT to revert that change). We use the MDB_WRITEMAP + * workaround for older LMDB versions. + */ +#ifndef MDB_NOMEMINIT + if (dict_flags & DICT_FLAG_BULK_UPDATE) /* XXX Good enough */ + mdb_flags |= MDB_WRITEMAP; +#endif + + /* + * Gracefully handle most database open errors. + */ + if ((status = slmdb_init(&slmdb, dict_lmdb_map_size, DICT_LMDB_SIZE_INCR, + DICT_LMDB_SIZE_MAX)) != 0 + || (status = slmdb_open(&slmdb, mdb_path, open_flags, mdb_flags, + slmdb_flags)) != 0) { + /* This leaks a little memory that would have been used otherwise. */ + dict = dict_surrogate(DICT_TYPE_LMDB, path, open_flags, dict_flags, + "open database %s: %s", mdb_path, mdb_strerror(status)); + DICT_LMDB_OPEN_RETURN(dict); + } + + /* + * XXX Persistent locking belongs in mkmap_lmdb. + * + * We just need to acquire exclusive access momentarily. This establishes + * that no readers are accessing old (obsoleted by copy-on-write) txn + * snapshots, so we are free to reuse all eligible old pages. Downgrade + * the lock right after acquiring it. This is sufficient to keep out + * other writers until we are done. + */ + db_fd = slmdb_fd(&slmdb); + if (dict_flags & DICT_FLAG_BULK_UPDATE) { + if (myflock(db_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("%s: lock dictionary: %m", mdb_path); + if (myflock(db_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_SHARED) < 0) + msg_fatal("%s: unlock dictionary: %m", mdb_path); + } + + /* + * Bundle up. From here on no more assignments to slmdb. + */ + dict_lmdb = (DICT_LMDB *) dict_alloc(DICT_TYPE_LMDB, path, sizeof(*dict_lmdb)); + dict_lmdb->slmdb = slmdb; + dict_lmdb->dict.lookup = dict_lmdb_lookup; + dict_lmdb->dict.update = dict_lmdb_update; + dict_lmdb->dict.delete = dict_lmdb_delete; + dict_lmdb->dict.sequence = dict_lmdb_sequence; + dict_lmdb->dict.close = dict_lmdb_close; + + if (fstat(db_fd, &st) < 0) + msg_fatal("dict_lmdb_open: fstat: %m"); + dict_lmdb->dict.lock_fd = dict_lmdb->dict.stat_fd = db_fd; + dict_lmdb->dict.lock_type = MYFLOCK_STYLE_FCNTL; + dict_lmdb->dict.mtime = st.st_mtime; + dict_lmdb->dict.owner.uid = st.st_uid; + dict_lmdb->dict.owner.status = (st.st_uid != 0); + + dict_lmdb->key_buf = 0; + dict_lmdb->val_buf = 0; + + /* + * Warn if the source file is newer than the indexed file, except when + * the source file changed only seconds ago. + */ + if ((dict_flags & DICT_FLAG_LOCK) != 0 + && stat(path, &st) == 0 + && st.st_mtime > dict_lmdb->dict.mtime + && st.st_mtime < time((time_t *) 0) - 100) + msg_warn("database %s is older than source file %s", mdb_path, path); + +#define DICT_LMDB_IMPL_FLAGS (DICT_FLAG_FIXED | DICT_FLAG_MULTI_WRITER) + + dict_lmdb->dict.flags = dict_flags | DICT_LMDB_IMPL_FLAGS; + if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0) + dict_lmdb->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL); + if (dict_flags & DICT_FLAG_FOLD_FIX) + dict_lmdb->dict.fold_buf = vstring_alloc(10); + + if (dict_flags & DICT_FLAG_BULK_UPDATE) + dict_jmp_alloc(&dict_lmdb->dict); + + /* + * The following requests return an error result only if we have serious + * memory corruption problem. + */ + if (slmdb_control(&dict_lmdb->slmdb, + CA_SLMDB_CTL_API_RETRY_LIMIT(DICT_LMDB_API_RETRY_LIMIT), + CA_SLMDB_CTL_BULK_RETRY_LIMIT(DICT_LMDB_BULK_RETRY_LIMIT), + CA_SLMDB_CTL_LONGJMP_FN(dict_lmdb_longjmp), + CA_SLMDB_CTL_NOTIFY_FN(msg_verbose ? + dict_lmdb_notify : (SLMDB_NOTIFY_FN) 0), + CA_SLMDB_CTL_ASSERT_FN(dict_lmdb_assert), + CA_SLMDB_CTL_CB_CONTEXT((void *) dict_lmdb), + CA_SLMDB_CTL_END) != 0) + msg_panic("dict_lmdb_open: slmdb_control: %m"); + + if (msg_verbose) + dict_lmdb_notify((void *) dict_lmdb, MDB_SUCCESS, + slmdb_curr_limit(&dict_lmdb->slmdb)); + + DICT_LMDB_OPEN_RETURN(DICT_DEBUG (&dict_lmdb->dict)); +} + +#endif diff --git a/src/util/dict_lmdb.h b/src/util/dict_lmdb.h new file mode 100644 index 0000000..ccc165a --- /dev/null +++ b/src/util/dict_lmdb.h @@ -0,0 +1,43 @@ +#ifndef _DICT_LMDB_H_INCLUDED_ +#define _DICT_LMDB_H_INCLUDED_ + +/*++ +/* NAME +/* dict_lmdb 3h +/* SUMMARY +/* dictionary manager interface to OpenLDAP LMDB files +/* SYNOPSIS +/* #include <dict_lmdb.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <dict.h> + + /* + * External interface. + */ +#define DICT_TYPE_LMDB "lmdb" + +extern DICT *dict_lmdb_open(const char *, int, int); + + /* + * XXX Should be part of the DICT interface. + */ +extern size_t dict_lmdb_map_size; + + /* Minimum size without SIGSEGV. */ +#define DEFINE_DICT_LMDB_MAP_SIZE size_t dict_lmdb_map_size = 8192 + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Howard Chu +/* Symas Corporation +/*--*/ + +#endif diff --git a/src/util/dict_ni.c b/src/util/dict_ni.c new file mode 100644 index 0000000..3f62559 --- /dev/null +++ b/src/util/dict_ni.c @@ -0,0 +1,194 @@ +/*++ +/* NAME +/* dict_ni 3 +/* SUMMARY +/* dictionary manager interface to NetInfo +/* SYNOPSIS +/* #include <dict_ni.h> +/* +/* DICT *dict_ni_open(path, dummy, dict_flags) +/* char *path; +/* int dummy; +/* int dict_flags; +/* DESCRIPTION +/* dict_ni_open() `opens' the named NetInfo database. The result is +/* a pointer to a structure that can be used to access the dictionary +/* using the generic methods documented in dict_open(3). +/* DIAGNOSTICS +/* dict_ni_register() returns 0 in case of success, -1 in case +/* of problems. +/* Fatal errors: NetInfo errors, out of memory. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* netinfo(3N) data base subroutines +/* AUTHOR(S) +/* Pieter Schoenmakers +/* Eindhoven University of Technology +/* P.O. Box 513 +/* 5600 MB Eindhoven +/* The Netherlands +/*--*/ + +#include "sys_defs.h" + +#ifdef HAS_NETINFO + +/* System library. */ + +#include <stdio.h> +#include <netinfo/ni.h> + +/* Utility library. */ + +#include "dict.h" +#include "dict_ni.h" +#include "msg.h" +#include "mymalloc.h" +#include "stringops.h" + +typedef struct { + DICT dict; /* my super */ + char *path; /* directory path */ +} DICT_NI; + + /* + * We'd like other possibilities, but that is not possible in the current + * dictionary setup... An example of a different setup: use `members' for + * multi-valued lookups (to be compatible with /aliases), and `value' for + * single-valued tables. + */ +#define NETINFO_PROP_KEY "name" +#define NETINFO_PROP_VALUE "members" +#define NETINFO_VALUE_SEP "," + +#define NETINFO_MAX_DOMAIN_DEPTH 100 + +/* Hard worker doing lookups. Returned value is statically allocated and + reused each call. */ +static const char *dict_ni_do_lookup(char *path, char *key_prop, + const char *key_value, char *val_prop) +{ + unsigned int result_cap = 0; + static char *result = 0; + + char *return_val = 0; + ni_namelist values; + int depth = 0; + void *domain; + void *next_domain; + char *query; + ni_status r; + ni_id dir; + + if (msg_verbose) + msg_info("ni_lookup %s %s=%s", path, key_prop, key_value); + + r = ni_open(NULL, ".", &domain); + if (r != NI_OK) { + msg_warn("ni_open `.': %d", r); + return NULL; + } + query = mymalloc(strlen(path) + strlen(key_prop) + 3 + strlen(key_value)); + sprintf(query, "%s/%s=%s", path, key_prop, key_value); + + for (;;) { + + /* + * What does it _mean_ if we find the directory but not the value? + */ + if (ni_pathsearch(domain, &dir, query) == NI_OK + && ni_lookupprop(domain, &dir, val_prop, &values) == NI_OK) + if (values.ni_namelist_len <= 0) + ni_namelist_free(&values); + else { + unsigned int i, l, n; + + for (i = l = 0; i < values.ni_namelist_len; i++) + l += 1 + strlen(values.ni_namelist_val[i]); + if (result_cap < l) { + if (result) + myfree(result); + result_cap = l + 100; + result = mymalloc(result_cap); + } + for (i = l = 0; i < values.ni_namelist_len; i++) { + n = strlen(values.ni_namelist_val[i]); + memcpy(result + l, values.ni_namelist_val[i], n); + l += n; + if (i < values.ni_namelist_len - 1) + result[l++] = ','; + } + result[l] = '\0'; + return_val = result; + break; + } + + if (++depth >= NETINFO_MAX_DOMAIN_DEPTH) { + msg_warn("ni_open: domain depth limit"); + break; + } + r = ni_open(domain, "..", &next_domain); + if (r != NI_OK) { + if (r != NI_FAILED) + msg_warn("ni_open `..': %d", r); + break; + } + ni_free(domain); + domain = next_domain; + } + + ni_free(domain); + myfree(query); + + return return_val; +} + +/* dict_ni_lookup - find table entry */ + +static const char *dict_ni_lookup(DICT *dict, const char *key) +{ + DICT_NI *d = (DICT_NI *) dict; + + dict->error = 0; + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, key); + key = lowercase(vstring_str(dict->fold_buf)); + } + return dict_ni_do_lookup(d->dict.name, NETINFO_PROP_KEY, + key, NETINFO_PROP_VALUE); +} + +/* dict_ni_close - disassociate from NetInfo map */ + +static void dict_ni_close(DICT *dict) +{ + DICT_NI *d = (DICT_NI *) dict; + + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_ni_open - create association with NetInfo map */ + +DICT *dict_ni_open(const char *path, int unused_flags, int dict_flags) +{ + DICT_NI *d = (void *) dict_alloc(DICT_TYPE_NETINFO, path, sizeof(*d)); + + d->dict.lookup = dict_ni_lookup; + d->dict.close = dict_ni_close; + d->dict.flags = dict_flags | DICT_FLAG_FIXED; + if (dict_flags & DICT_FLAG_FOLD_FIX) + d->dict.fold_buf = vstring_alloc(10); + d->dict.owner.status = DICT_OWNER_TRUSTED; + + return (DICT_DEBUG (&d->dict)); +} + +#endif diff --git a/src/util/dict_ni.h b/src/util/dict_ni.h new file mode 100644 index 0000000..652b422 --- /dev/null +++ b/src/util/dict_ni.h @@ -0,0 +1,34 @@ +#ifndef _DICT_NI_H_INCLUDED_ +#define _DICT_NI_H_INCLUDED_ + +/*++ +/* NAME +/* dict_ni 3h +/* SUMMARY +/* dictionary manager interface to NetInfo maps +/* SYNOPSIS +/* #include <dict_ni.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <dict.h> + + /* + * External interface. + */ +#define DICT_TYPE_NETINFO "netinfo" + +extern DICT *dict_ni_open(const char *, int, int); + +/* AUTHOR(S) +/* Pieter Schoenmakers +/* Eindhoven University of Technology +/* P.O. Box 513 +/* 5600 MB Eindhoven +/* The Netherlands +/*--*/ + +#endif diff --git a/src/util/dict_nis.c b/src/util/dict_nis.c new file mode 100644 index 0000000..357011f --- /dev/null +++ b/src/util/dict_nis.c @@ -0,0 +1,247 @@ +/*++ +/* NAME +/* dict_nis 3 +/* SUMMARY +/* dictionary manager interface to NIS maps +/* SYNOPSIS +/* #include <dict_nis.h> +/* +/* DICT *dict_nis_open(map, open_flags, dict_flags) +/* const char *map; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_nis_open() makes the specified NIS map accessible via +/* the generic dictionary operations described in dict_open(3). +/* SEE ALSO +/* dict(3) generic dictionary manager +/* DIAGNOSTICS +/* Fatal errors: out of memory, attempt to update NIS map. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <string.h> + +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif + +#ifdef HAS_NIS + +#include <rpcsvc/ypclnt.h> +#ifndef YPERR_BUSY +#define YPERR_BUSY 16 +#endif +#ifndef YPERR_ACCESS +#define YPERR_ACCESS 15 +#endif + +#endif + +/* Utility library. */ + +#include "msg.h" +#include "mymalloc.h" +#include "vstring.h" +#include "stringops.h" +#include "dict.h" +#include "dict_nis.h" + +#ifdef HAS_NIS + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ +} DICT_NIS; + + /* + * Class variables, so that multiple maps can share this info. + */ +static char dict_nis_disabled[1]; +static char *dict_nis_domain; + +/* dict_nis_init - NIS binding */ + +static void dict_nis_init(void) +{ + const char *myname = "dict_nis_init"; + + if (yp_get_default_domain(&dict_nis_domain) != 0 + || dict_nis_domain == 0 || *dict_nis_domain == 0 + || strcasecmp(dict_nis_domain, "(none)") == 0) { + dict_nis_domain = dict_nis_disabled; + msg_warn("%s: NIS domain name not set - NIS lookups disabled", myname); + } + if (msg_verbose) + msg_info("%s: NIS domain %s", myname, dict_nis_domain); +} + +/* dict_nis_strerror - map error number to string */ + +static char *dict_nis_strerror(int err) +{ + + /* + * Grr. There should be a standard function for this. + */ + switch (err) { + case YPERR_BADARGS: + return ("args to function are bad"); + case YPERR_RPC: + return ("RPC failure - domain has been unbound"); + case YPERR_DOMAIN: + return ("can't bind to server on this domain"); + case YPERR_MAP: + return ("no such map in server's domain"); + case YPERR_KEY: + return ("no such key in map"); + case YPERR_YPERR: + return ("internal yp server or client error"); + case YPERR_RESRC: + return ("resource allocation failure"); + case YPERR_NOMORE: + return ("no more records in map database"); + case YPERR_PMAP: + return ("can't communicate with portmapper"); + case YPERR_YPBIND: + return ("can't communicate with ypbind"); + case YPERR_YPSERV: + return ("can't communicate with ypserv"); + case YPERR_NODOM: + return ("local domain name not set"); + case YPERR_BADDB: + return ("yp database is bad"); + case YPERR_VERS: + return ("yp version mismatch"); + case YPERR_ACCESS: + return ("access violation"); + case YPERR_BUSY: + return ("database busy"); + default: + return ("unknown NIS lookup error"); + } +} + +/* dict_nis_lookup - find table entry */ + +static const char *dict_nis_lookup(DICT *dict, const char *key) +{ + DICT_NIS *dict_nis = (DICT_NIS *) dict; + static char *result; + int result_len; + int err; + static VSTRING *buf; + + dict->error = 0; + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_nis_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + if (dict_nis_domain == dict_nis_disabled) + return (0); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, key); + key = lowercase(vstring_str(dict->fold_buf)); + } + + /* + * See if this NIS map was written with one null byte appended to key and + * value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + err = yp_match(dict_nis_domain, dict_nis->dict.name, + (void *) key, strlen(key) + 1, + &result, &result_len); + if (err == 0) { + dict->flags &= ~DICT_FLAG_TRY0NULL; + return (result); + } + } + + /* + * See if this NIS map was written with no null byte appended to key and + * value. This should never be the case, but better play safe. + */ + if (dict->flags & DICT_FLAG_TRY0NULL) { + err = yp_match(dict_nis_domain, dict_nis->dict.name, + (void *) key, strlen(key), + &result, &result_len); + if (err == 0) { + dict->flags &= ~DICT_FLAG_TRY1NULL; + if (buf == 0) + buf = vstring_alloc(10); + vstring_strncpy(buf, result, result_len); + return (vstring_str(buf)); + } + } + + /* + * When the NIS lookup fails for reasons other than "key not found", keep + * logging warnings, and hope that someone will eventually notice the + * problem and fix it. + */ + if (err != YPERR_KEY) { + msg_warn("lookup %s, NIS domain %s, map %s: %s", + key, dict_nis_domain, dict_nis->dict.name, + dict_nis_strerror(err)); + dict->error = DICT_ERR_RETRY; + } + return (0); +} + +/* dict_nis_close - close NIS map */ + +static void dict_nis_close(DICT *dict) +{ + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_nis_open - open NIS map */ + +DICT *dict_nis_open(const char *map, int open_flags, int dict_flags) +{ + DICT_NIS *dict_nis; + + if (open_flags != O_RDONLY) + return (dict_surrogate(DICT_TYPE_NIS, map, open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_NIS, map)); + + dict_nis = (DICT_NIS *) dict_alloc(DICT_TYPE_NIS, map, sizeof(*dict_nis)); + dict_nis->dict.lookup = dict_nis_lookup; + dict_nis->dict.close = dict_nis_close; + dict_nis->dict.flags = dict_flags | DICT_FLAG_FIXED; + if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + dict_nis->dict.flags |= (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL); + if (dict_flags & DICT_FLAG_FOLD_FIX) + dict_nis->dict.fold_buf = vstring_alloc(10); + if (dict_nis_domain == 0) + dict_nis_init(); + dict_nis->dict.owner.status = DICT_OWNER_TRUSTED; + return (DICT_DEBUG (&dict_nis->dict)); +} + +#endif diff --git a/src/util/dict_nis.h b/src/util/dict_nis.h new file mode 100644 index 0000000..97aa7cd --- /dev/null +++ b/src/util/dict_nis.h @@ -0,0 +1,37 @@ +#ifndef _DIST_NIS_H_INCLUDED_ +#define _DIST_NIS_H_INCLUDED_ + +/*++ +/* NAME +/* dict_nis 3h +/* SUMMARY +/* dictionary manager interface to NIS maps +/* SYNOPSIS +/* #include <dict_nis.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <dict.h> + + /* + * External interface. + */ +#define DICT_TYPE_NIS "nis" + +extern DICT *dict_nis_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_nisplus.c b/src/util/dict_nisplus.c new file mode 100644 index 0000000..1f5e126 --- /dev/null +++ b/src/util/dict_nisplus.c @@ -0,0 +1,304 @@ +/*++ +/* NAME +/* dict_nisplus 3 +/* SUMMARY +/* dictionary manager interface to NIS+ maps +/* SYNOPSIS +/* #include <dict_nisplus.h> +/* +/* DICT *dict_nisplus_open(map, open_flags, dict_flags) +/* const char *map; +/* int dummy; +/* int dict_flags; +/* DESCRIPTION +/* dict_nisplus_open() makes the specified NIS+ map accessible via +/* the generic dictionary operations described in dict_open(3). +/* The \fIdummy\fR argument is not used. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* DIAGNOSTICS +/* Fatal errors: +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Geoff Gibbs +/* UK-HGMP-RC +/* Hinxton +/* Cambridge +/* CB10 1SB, UK +/* +/* based on the code for dict_nis.c et al by :- +/* +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <stdlib.h> +#ifdef HAS_NISPLUS +#include <rpcsvc/nis.h> /* for nis_list */ +#endif + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstring.h> +#include <stringops.h> +#include <dict.h> +#include <dict_nisplus.h> + +#ifdef HAS_NISPLUS + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + char *template; /* parsed query template */ + int column; /* NIS+ field number (start at 1) */ +} DICT_NISPLUS; + + /* + * Begin quote from nis+(1): + * + * The following text represents a context-free grammar that defines the + * set of legal NIS+ names. The terminals in this grammar are the + * characters `.' (dot), `[' (open bracket), `]' (close bracket), `,' + * (comma), `=' (equals) and whitespace. Angle brackets (`<' and `>'), + * which delineate non- terminals, are not part of the grammar. The + * character `|' (vertical bar) is used to separate alternate productions + * and should be read as ``this production OR this production''. + * + * name ::= . | <simple name> | <indexed name> + * + * simple name ::= <string>. | <string>.<simple name> + * + * indexed name ::= <search criterion>,<simple name> + * + * search criterion ::= [ <attribute list> ] + * + * attribute list ::= <attribute> | <attribute>,<attribute list> + * + * attribute ::= <string> = <string> + * + * string ::= ISO Latin 1 character set except the character + * '/' (slash). The initial character may not be a terminal character or + * the characters '@' (at), '+' (plus), or (`-') hyphen. + * + * Terminals that appear in strings must be quoted with `"' (double quote). + * The `"' character may be quoted by quoting it with itself `""'. + * + * End quote fron nis+(1). + * + * This NIS client always quotes the entire query string (the value part of + * [attribute=value],file.domain.) so the issue with initial characters + * should not be applicable. One wonders what restrictions are applicable + * when a string is quoted, but the manual doesn't specify what can appear + * between quotes, and we don't want to get burned. + */ + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) + +/* dict_nisplus_lookup - find table entry */ + +static const char *dict_nisplus_lookup(DICT *dict, const char *key) +{ + const char *myname = "dict_nisplus_lookup"; + DICT_NISPLUS *dict_nisplus = (DICT_NISPLUS *) dict; + static VSTRING *quoted_key; + static VSTRING *query; + static VSTRING *retval; + nis_result *reply; + int count; + const char *cp; + int last_col; + int ch; + + dict->error = 0; + + /* + * Initialize. + */ + if (quoted_key == 0) { + query = vstring_alloc(100); + retval = vstring_alloc(100); + quoted_key = vstring_alloc(100); + } + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, key); + key = lowercase(vstring_str(dict->fold_buf)); + } + + /* + * Check that the lookup key does not contain characters disallowed by + * nis+(1). + * + * XXX Many client implementations don't seem to care about disallowed + * characters. + */ + VSTRING_RESET(quoted_key); + VSTRING_ADDCH(quoted_key, '"'); + for (cp = key; (ch = *(unsigned const char *) cp) != 0; cp++) { + if ((ISASCII(ch) && !ISPRINT(ch)) || (ch > 126 && ch < 160)) { + msg_warn("map %s:%s: lookup key with non-printing character 0x%x:" + " ignoring this request", + dict->type, dict->name, ch); + return (0); + } else if (ch == '"') { + VSTRING_ADDCH(quoted_key, '"'); + } + VSTRING_ADDCH(quoted_key, ch); + } + VSTRING_ADDCH(quoted_key, '"'); + VSTRING_TERMINATE(quoted_key); + + /* + * Plug the key into the query template, which typically looks something + * like the following: [alias=%s],mail_aliases.org_dir.my.nisplus.domain. + * + * XXX The nis+ documentation defines a length limit for simple names like + * a.b.c., but defines no length limit for (the components of) indexed + * names such as [x=y],a.b.c. Our query length is limited because Postfix + * addresses (in envelopes or in headers) have a finite length. + */ + vstring_sprintf(query, dict_nisplus->template, STR(quoted_key)); + reply = nis_list(STR(query), FOLLOW_LINKS | FOLLOW_PATH, NULL, NULL); + + /* + * When lookup succeeds, the result may be ambiguous, or the requested + * column may not exist. + */ + if (reply->status == NIS_SUCCESS) { + if ((count = NIS_RES_NUMOBJ(reply)) != 1) { + msg_warn("ambiguous match (%d results) for %s in NIS+ map %s:" + " ignoring this request", + count, key, dict_nisplus->dict.name); + nis_freeresult(reply); + return (0); + } else { + last_col = NIS_RES_OBJECT(reply)->zo_data + .objdata_u.en_data.en_cols.en_cols_len - 1; + if (dict_nisplus->column > last_col) + msg_fatal("requested column %d > max column %d in table %s", + dict_nisplus->column, last_col, + dict_nisplus->dict.name); + vstring_strcpy(retval, + NIS_RES_OBJECT(reply)->zo_data.objdata_u + .en_data.en_cols.en_cols_val[dict_nisplus->column] + .ec_value.ec_value_val); + if (msg_verbose) + msg_info("%s: %s, column %d -> %s", myname, STR(query), + dict_nisplus->column, STR(retval)); + nis_freeresult(reply); + return (STR(retval)); + } + } + + /* + * When the NIS+ lookup fails for reasons other than "key not found", + * keep logging warnings, and hope that someone will eventually notice + * the problem and fix it. + */ + else { + if (reply->status != NIS_NOTFOUND + && reply->status != NIS_PARTIAL) { + msg_warn("lookup %s, NIS+ map %s: %s", + key, dict_nisplus->dict.name, + nis_sperrno(reply->status)); + dict->error = DICT_ERR_RETRY; + } else { + if (msg_verbose) + msg_info("%s: not found: query %s", myname, STR(query)); + } + nis_freeresult(reply); + return (0); + } +} + +/* dict_nisplus_close - close NISPLUS map */ + +static void dict_nisplus_close(DICT *dict) +{ + DICT_NISPLUS *dict_nisplus = (DICT_NISPLUS *) dict; + + myfree(dict_nisplus->template); + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_nisplus_open - open NISPLUS map */ + +DICT *dict_nisplus_open(const char *map, int open_flags, int dict_flags) +{ + const char *myname = "dict_nisplus_open"; + DICT_NISPLUS *dict_nisplus; + char *col_field; + + /* + * Sanity check. + */ + if (open_flags != O_RDONLY) + return (dict_surrogate(DICT_TYPE_NISPLUS, map, open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_NISPLUS, map)); + + /* + * Initialize. This is a read-only map with fixed strings, not with + * regular expressions. + */ + dict_nisplus = (DICT_NISPLUS *) + dict_alloc(DICT_TYPE_NISPLUS, map, sizeof(*dict_nisplus)); + dict_nisplus->dict.lookup = dict_nisplus_lookup; + dict_nisplus->dict.close = dict_nisplus_close; + dict_nisplus->dict.flags = dict_flags | DICT_FLAG_FIXED; + if (dict_flags & DICT_FLAG_FOLD_FIX) + dict_nisplus->dict.fold_buf = vstring_alloc(10); + dict_nisplus->dict.owner.status = DICT_OWNER_TRUSTED; + + /* + * Convert the query template into an indexed name and column number. The + * query template looks like: + * + * [attribute=%s;attribute=value...];simple.name.:column + * + * One instance of %s gets to be replaced by a version of the lookup key; + * other attributes must specify fixed values. The reason for using ';' + * is that the comma character is special in main.cf. When no column + * number is given at the end of the map name, we use a default column. + */ + dict_nisplus->template = mystrdup(map); + translit(dict_nisplus->template, ";", ","); + if ((col_field = strstr(dict_nisplus->template, ".:")) != 0) { + col_field[1] = 0; + col_field += 2; + if (!alldig(col_field) || (dict_nisplus->column = atoi(col_field)) < 1) + msg_fatal("bad column field in NIS+ map name: %s", map); + } else { + dict_nisplus->column = 1; + } + if (msg_verbose) + msg_info("%s: opened NIS+ table %s for column %d", + myname, dict_nisplus->template, dict_nisplus->column); + return (DICT_DEBUG (&dict_nisplus->dict)); +} + +#endif diff --git a/src/util/dict_nisplus.h b/src/util/dict_nisplus.h new file mode 100644 index 0000000..1fd31c9 --- /dev/null +++ b/src/util/dict_nisplus.h @@ -0,0 +1,37 @@ +#ifndef _DICT_NISPLUS_H_INCLUDED_ +#define _DICT_NISPLUS_H_INCLUDED_ + +/*++ +/* NAME +/* dict_nisplus 3h +/* SUMMARY +/* dictionary manager interface to NIS+ maps +/* SYNOPSIS +/* #include <dict_nisplus.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <dict.h> + + /* + * External interface. + */ +#define DICT_TYPE_NISPLUS "nisplus" + +extern DICT *dict_nisplus_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_open.c b/src/util/dict_open.c new file mode 100644 index 0000000..afea391 --- /dev/null +++ b/src/util/dict_open.c @@ -0,0 +1,600 @@ +/*++ +/* NAME +/* dict_open 3 +/* SUMMARY +/* low-level dictionary interface +/* SYNOPSIS +/* #include <dict.h> +/* +/* DICT *dict_open(dict_spec, open_flags, dict_flags) +/* const char *dict_spec; +/* int open_flags; +/* int dict_flags; +/* +/* DICT *dict_open3(dict_type, dict_name, open_flags, dict_flags) +/* const char *dict_type; +/* const char *dict_name; +/* int open_flags; +/* int dict_flags; +/* +/* int dict_put(dict, key, value) +/* DICT *dict; +/* const char *key; +/* const char *value; +/* +/* const char *dict_get(dict, key) +/* DICT *dict; +/* const char *key; +/* +/* int dict_del(dict, key) +/* DICT *dict; +/* const char *key; +/* +/* int dict_seq(dict, func, key, value) +/* DICT *dict; +/* int func; +/* const char **key; +/* const char **value; +/* +/* void dict_close(dict) +/* DICT *dict; +/* +/* typedef DICT *(*DICT_OPEN_FN) (const char *, int, int); +/* +/* dict_open_register(type, open) +/* const char *type; +/* DICT_OPEN_FN open; +/* +/* typedef DICT_OPEN_FN (*DICT_OPEN_EXTEND_FN)(const char *type); +/* +/* DICT_OPEN_EXTEND_FN dict_open_extend(call_back) +/* DICT_OPEN_EXTEND_FN call_back; +/* +/* ARGV *dict_mapnames() +/* +/* typedef ARGV *(*DICT_MAPNAMES_EXTEND_FN)(ARGV *names); +/* +/* DICT_MAPNAMES_EXTEND_FN dict_mapnames_extend(call_back) +/* DICT_MAPNAMES_EXTEND_FN call_back; +/* +/* int dict_isjmp(dict) +/* DICT *dict; +/* +/* int dict_setjmp(dict) +/* DICT *dict; +/* +/* int dict_longjmp(dict, val) +/* DICT *dict; +/* int val; +/* +/* void dict_type_override(dict, type) +/* DICT *dict; +/* const char *type; +/* DESCRIPTION +/* This module implements a low-level interface to multiple +/* physical dictionary types. +/* +/* dict_open() takes a type:name pair that specifies a dictionary type +/* and dictionary name, opens the dictionary, and returns a dictionary +/* handle. The \fIopen_flags\fR arguments are as in open(2). The +/* \fIdict_flags\fR are the bit-wise OR of zero or more of the following: +/* .IP DICT_FLAG_DUP_WARN +/* Warn about duplicate keys, if the underlying database does not +/* support duplicate keys. The default is to terminate with a fatal +/* error. +/* .IP DICT_FLAG_DUP_IGNORE +/* Ignore duplicate keys if the underlying database does not +/* support duplicate keys. The default is to terminate with a fatal +/* error. +/* .IP DICT_FLAG_DUP_REPLACE +/* Replace duplicate keys if the underlying database supports such +/* an operation. The default is to terminate with a fatal error. +/* .IP DICT_FLAG_TRY0NULL +/* With maps where this is appropriate, append no null byte to +/* keys and values. +/* When neither DICT_FLAG_TRY0NULL nor DICT_FLAG_TRY1NULL are +/* specified, the software guesses what format to use for reading; +/* and in the absence of definite information, a system-dependent +/* default is chosen for writing. +/* .IP DICT_FLAG_TRY1NULL +/* With maps where this is appropriate, append one null byte to +/* keys and values. +/* When neither DICT_FLAG_TRY0NULL nor DICT_FLAG_TRY1NULL are +/* specified, the software guesses what format to use for reading; +/* and in the absence of definite information, a system-dependent +/* default is chosen for writing. +/* .IP DICT_FLAG_LOCK +/* With maps where this is appropriate, acquire an exclusive lock +/* before writing, and acquire a shared lock before reading. +/* Release the lock when the operation completes. +/* .IP DICT_FLAG_OPEN_LOCK +/* The behavior of this flag depends on whether a database +/* sets the DICT_FLAG_MULTI_WRITER flag to indicate that it +/* is multi-writer safe. +/* +/* With databases that are not multi-writer safe, dict_open() +/* acquires a persistent exclusive lock, or it terminates with +/* a fatal run-time error. +/* +/* With databases that are multi-writer safe, dict_open() +/* downgrades the DICT_FLAG_OPEN_LOCK flag (persistent lock) +/* to DICT_FLAG_LOCK (temporary lock). +/* .IP DICT_FLAG_FOLD_FIX +/* With databases whose lookup fields are fixed-case strings, +/* fold the search string to lower case before accessing the +/* database. This includes hash:, cdb:, dbm:. nis:, ldap:, +/* *sql. WARNING: case folding is supported only for ASCII or +/* valid UTF-8. +/* .IP DICT_FLAG_FOLD_MUL +/* With databases where one lookup field can match both upper +/* and lower case, fold the search key to lower case before +/* accessing the database. This includes regexp: and pcre:. +/* WARNING: case folding is supported only for ASCII or valid +/* UTF-8. +/* .IP DICT_FLAG_FOLD_ANY +/* Short-hand for (DICT_FLAG_FOLD_FIX | DICT_FLAG_FOLD_MUL). +/* .IP DICT_FLAG_SYNC_UPDATE +/* With file-based maps, flush I/O buffers to file after each update. +/* Thus feature is not supported with some file-based dictionaries. +/* .IP DICT_FLAG_NO_REGSUB +/* Disallow regular expression substitution from the lookup string +/* into the lookup result, to block data injection attacks. +/* .IP DICT_FLAG_NO_PROXY +/* Disallow access through the unprivileged \fBproxymap\fR +/* service, to block privilege escalation attacks. +/* .IP DICT_FLAG_NO_UNAUTH +/* Disallow lookup mechanisms that lack any form of authentication, +/* to block privilege escalation attacks (example: tcp_table; +/* even NIS can be secured to some extent by requiring that +/* the server binds to a privileged port). +/* .IP DICT_FLAG_PARANOID +/* A combination of all the paranoia flags: DICT_FLAG_NO_REGSUB, +/* DICT_FLAG_NO_PROXY and DICT_FLAG_NO_UNAUTH. +/* .IP DICT_FLAG_BULK_UPDATE +/* Enable preliminary code for bulk-mode database updates. +/* The caller must create an exception handler with dict_jmp_alloc() +/* and must trap exceptions from the database client with dict_setjmp(). +/* .IP DICT_FLAG_DEBUG +/* Enable additional logging. +/* .IP DICT_FLAG_UTF8_REQUEST +/* With util_utf8_enable != 0, require that lookup/update/delete +/* keys and values are valid UTF-8. Skip a lookup/update/delete +/* request with a non-UTF-8 key, skip an update request with +/* a non-UTF-8 value, and fail a lookup request with a non-UTF-8 +/* value. +/* .IP DICT_FLAG_SRC_RHS_IS_FILE +/* With dictionaries that are created from source text, each +/* value in the source of a dictionary specifies a list of +/* file names separated by comma and/or whitespace. The file +/* contents are concatenated with a newline inserted between +/* files, and the base64-encoded result is stored under the +/* key. +/* .sp +/* NOTE 1: it is up to the application to decode lookup results +/* with dict_file_lookup() or equivalent (this requires that +/* the dictionary is opened with DICT_FLAG_SRC_RHS_IS_FILE). +/* Decoding is not built into the normal dictionary lookup +/* method, because that would complicate dictionary nesting, +/* pipelining, and proxying. +/* .sp +/* NOTE 2: it is up to the application to convert file names +/* into base64-encoded file content before calling the dictionary +/* update method (see dict_file(3) for support). Automatic +/* file content encoding is available only when a dictionary +/* is created from source text. +/* .PP +/* Specify DICT_FLAG_NONE for no special processing. +/* +/* The dictionary types are as follows: +/* .IP environ +/* The process environment array. The \fIdict_name\fR argument is ignored. +/* .IP dbm +/* DBM file. +/* .IP hash +/* Berkeley DB file in hash format. +/* .IP btree +/* Berkeley DB file in btree format. +/* .IP nis +/* NIS map. Only read access is supported. +/* .IP nisplus +/* NIS+ map. Only read access is supported. +/* .IP netinfo +/* NetInfo table. Only read access is supported. +/* .IP ldap +/* LDAP ("light-weight" directory access protocol) database access. +/* .IP pcre +/* PERL-compatible regular expressions. +/* .IP regexp +/* POSIX-compatible regular expressions. +/* .IP texthash +/* Flat text in postmap(1) input format. +/* .PP +/* dict_open3() takes separate arguments for dictionary type and +/* name, but otherwise performs the same functions as dict_open(). +/* +/* The dict_get(), dict_put(), dict_del(), and dict_seq() +/* macros evaluate their first argument multiple times. +/* These names should have been in uppercase. +/* +/* dict_get() retrieves the value stored in the named dictionary +/* under the given key. A null pointer means the value was not found. +/* As with dict_lookup(), the result is owned by the lookup table +/* implementation. Make a copy if the result is to be modified, +/* or if the result is to survive multiple table lookups. +/* +/* dict_put() stores the specified key and value into the named +/* dictionary. A zero (DICT_STAT_SUCCESS) result means the +/* update was made. +/* +/* dict_del() removes a dictionary entry, and returns +/* DICT_STAT_SUCCESS in case of success. +/* +/* dict_seq() iterates over all members in the named dictionary. +/* func is define DICT_SEQ_FUN_FIRST (select first member) or +/* DICT_SEQ_FUN_NEXT (select next member). A zero (DICT_STAT_SUCCESS) +/* result means that an entry was found. +/* +/* dict_close() closes the specified dictionary and cleans up the +/* associated data structures. +/* +/* dict_open_register() adds support for a new dictionary type. +/* +/* dict_open_extend() registers a call-back function that looks +/* up the dictionary open() function for a type that is not +/* registered, or null in case of error. The result value is +/* the last previously-registered call-back or null. +/* +/* dict_mapnames() returns a sorted list with the names of all available +/* dictionary types. +/* +/* dict_mapnames_extend() registers a call-back function that +/* enumerates additional dictionary type names. The result +/* will be sorted by dict_mapnames(). The result value +/* is the last previously-registered call-back or null. +/* +/* dict_setjmp() saves processing context and makes that context +/* available for use with dict_longjmp(). Normally, dict_setjmp() +/* returns zero. A non-zero result means that dict_setjmp() +/* returned through a dict_longjmp() call; the result is the +/* \fIval\fR argument given to dict_longjmp(). dict_isjmp() +/* returns non-zero when dict_setjmp() and dict_longjmp() +/* are enabled for a given dictionary. +/* +/* NB: non-local jumps such as dict_longjmp() are not safe for +/* jumping out of any routine that manipulates DICT data. +/* longjmp() like calls are best avoided in signal handlers. +/* +/* dict_type_override() changes the symbolic dictionary type. +/* This is used by dictionaries whose internals are based on +/* some other dictionary type. +/* DIAGNOSTICS +/* Fatal error: open error, unsupported dictionary type, attempt to +/* update non-writable dictionary. +/* +/* The lookup routine returns non-null when the request is +/* satisfied. The update, delete and sequence routines return +/* zero (DICT_STAT_SUCCESS) when the request is satisfied. +/* The dict->errno value is non-zero only when the last operation +/* was not satisfied due to a dictionary access error. This +/* can have the following values: +/* .IP DICT_ERR_NONE(zero) +/* There was no dictionary access error. For example, the +/* request was satisfied, the requested information did not +/* exist in the dictionary, or the information already existed +/* when it should not exist (collision). +/* .IP DICT_ERR_RETRY(<0) +/* The dictionary was temporarily unavailable. This can happen +/* with network-based services. +/* .IP DICT_ERR_CONFIG(<0) +/* The dictionary was unavailable due to a configuration error. +/* .PP +/* Generally, a program is expected to test the function result +/* value for "success" first. If the operation was not successful, +/* a program is expected to test for a non-zero dict->error +/* status to distinguish between a data notfound/collision +/* condition or a dictionary access error. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> +#include <stdlib.h> + +/* Utility library. */ + +#include <argv.h> +#include <mymalloc.h> +#include <msg.h> +#include <dict.h> +#include <dict_cdb.h> +#include <dict_env.h> +#include <dict_unix.h> +#include <dict_tcp.h> +#include <dict_sdbm.h> +#include <dict_dbm.h> +#include <dict_db.h> +#include <dict_lmdb.h> +#include <dict_nis.h> +#include <dict_nisplus.h> +#include <dict_ni.h> +#include <dict_pcre.h> +#include <dict_regexp.h> +#include <dict_static.h> +#include <dict_cidr.h> +#include <dict_ht.h> +#include <dict_thash.h> +#include <dict_sockmap.h> +#include <dict_fail.h> +#include <dict_pipe.h> +#include <dict_random.h> +#include <dict_union.h> +#include <dict_inline.h> +#include <stringops.h> +#include <split_at.h> +#include <htable.h> +#include <myflock.h> + + /* + * lookup table for available map types. + */ +typedef struct { + char *type; + DICT_OPEN_FN open; +} DICT_OPEN_INFO; + +static const DICT_OPEN_INFO dict_open_info[] = { + DICT_TYPE_ENVIRON, dict_env_open, + DICT_TYPE_HT, dict_ht_open, + DICT_TYPE_UNIX, dict_unix_open, + DICT_TYPE_TCP, dict_tcp_open, +#ifdef HAS_DBM + DICT_TYPE_DBM, dict_dbm_open, +#endif +#ifdef HAS_DB + DICT_TYPE_HASH, dict_hash_open, + DICT_TYPE_BTREE, dict_btree_open, +#endif +#ifdef HAS_NIS + DICT_TYPE_NIS, dict_nis_open, +#endif +#ifdef HAS_NISPLUS + DICT_TYPE_NISPLUS, dict_nisplus_open, +#endif +#ifdef HAS_NETINFO + DICT_TYPE_NETINFO, dict_ni_open, +#endif +#ifdef HAS_POSIX_REGEXP + DICT_TYPE_REGEXP, dict_regexp_open, +#endif + DICT_TYPE_STATIC, dict_static_open, + DICT_TYPE_CIDR, dict_cidr_open, + DICT_TYPE_THASH, dict_thash_open, + DICT_TYPE_SOCKMAP, dict_sockmap_open, + DICT_TYPE_FAIL, dict_fail_open, + DICT_TYPE_PIPE, dict_pipe_open, + DICT_TYPE_RANDOM, dict_random_open, + DICT_TYPE_UNION, dict_union_open, + DICT_TYPE_INLINE, dict_inline_open, +#ifndef USE_DYNAMIC_MAPS +#ifdef HAS_PCRE + DICT_TYPE_PCRE, dict_pcre_open, +#endif +#ifdef HAS_CDB + DICT_TYPE_CDB, dict_cdb_open, +#endif +#ifdef HAS_SDBM + DICT_TYPE_SDBM, dict_sdbm_open, +#endif +#ifdef HAS_LMDB + DICT_TYPE_LMDB, dict_lmdb_open, +#endif +#endif /* !USE_DYNAMIC_MAPS */ + 0, +}; + +static HTABLE *dict_open_hash; + + /* + * Extension hooks. + */ +static DICT_OPEN_EXTEND_FN dict_open_extend_hook; +static DICT_MAPNAMES_EXTEND_FN dict_mapnames_extend_hook; + + /* + * Workaround. + */ +DEFINE_DICT_LMDB_MAP_SIZE; +DEFINE_DICT_DB_CACHE_SIZE; + +/* dict_open_init - one-off initialization */ + +static void dict_open_init(void) +{ + const char *myname = "dict_open_init"; + const DICT_OPEN_INFO *dp; + + if (dict_open_hash != 0) + msg_panic("%s: multiple initialization", myname); + dict_open_hash = htable_create(10); + + for (dp = dict_open_info; dp->type; dp++) + htable_enter(dict_open_hash, dp->type, (void *) dp); +} + +/* dict_open - open dictionary */ + +DICT *dict_open(const char *dict_spec, int open_flags, int dict_flags) +{ + char *saved_dict_spec = mystrdup(dict_spec); + char *dict_name; + DICT *dict; + + if ((dict_name = split_at(saved_dict_spec, ':')) == 0) + msg_fatal("open dictionary: expecting \"type:name\" form instead of \"%s\"", + dict_spec); + + dict = dict_open3(saved_dict_spec, dict_name, open_flags, dict_flags); + myfree(saved_dict_spec); + return (dict); +} + + +/* dict_open3 - open dictionary */ + +DICT *dict_open3(const char *dict_type, const char *dict_name, + int open_flags, int dict_flags) +{ + const char *myname = "dict_open"; + DICT_OPEN_INFO *dp; + DICT_OPEN_FN open_fn; + DICT *dict; + + if (*dict_type == 0 || *dict_name == 0) + msg_fatal("open dictionary: expecting \"type:name\" form instead of \"%s:%s\"", + dict_type, dict_name); + if (dict_open_hash == 0) + dict_open_init(); + if ((dp = (DICT_OPEN_INFO *) htable_find(dict_open_hash, dict_type)) == 0) { + if (dict_open_extend_hook != 0 + && (open_fn = dict_open_extend_hook(dict_type)) != 0) { + dict_open_register(dict_type, open_fn); + dp = (DICT_OPEN_INFO *) htable_find(dict_open_hash, dict_type); + } + if (dp == 0) + return (dict_surrogate(dict_type, dict_name, open_flags, dict_flags, + "unsupported dictionary type: %s", dict_type)); + } + if ((dict = dp->open(dict_name, open_flags, dict_flags)) == 0) + return (dict_surrogate(dict_type, dict_name, open_flags, dict_flags, + "cannot open %s:%s: %m", dict_type, dict_name)); + if (msg_verbose) + msg_info("%s: %s:%s", myname, dict_type, dict_name); + /* XXX The choice between wait-for-lock or no-wait is hard-coded. */ + if (dict->flags & DICT_FLAG_OPEN_LOCK) { + if (dict->flags & DICT_FLAG_LOCK) + msg_panic("%s: attempt to open %s:%s with both \"open\" lock and \"access\" lock", + myname, dict_type, dict_name); + /* Multi-writer safe map: downgrade persistent lock to temporary. */ + if (dict->flags & DICT_FLAG_MULTI_WRITER) { + dict->flags &= ~DICT_FLAG_OPEN_LOCK; + dict->flags |= DICT_FLAG_LOCK; + } + /* Multi-writer unsafe map: acquire exclusive lock or bust. */ + else if (dict->lock(dict, MYFLOCK_OP_EXCLUSIVE | MYFLOCK_OP_NOWAIT) < 0) + msg_fatal("%s:%s: unable to get exclusive lock: %m", + dict_type, dict_name); + } + /* Last step: insert proxy for UTF-8 syntax checks and casefolding. */ + if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) == 0 + && DICT_NEED_UTF8_ACTIVATION(util_utf8_enable, dict_flags)) + dict = dict_utf8_activate(dict); + return (dict); +} + +/* dict_open_register - register dictionary type */ + +void dict_open_register(const char *type, DICT_OPEN_FN open) +{ + const char *myname = "dict_open_register"; + DICT_OPEN_INFO *dp; + HTABLE_INFO *ht; + + if (dict_open_hash == 0) + dict_open_init(); + if (htable_find(dict_open_hash, type)) + msg_panic("%s: dictionary type exists: %s", myname, type); + dp = (DICT_OPEN_INFO *) mymalloc(sizeof(*dp)); + dp->open = open; + ht = htable_enter(dict_open_hash, type, (void *) dp); + dp->type = ht->key; +} + +/* dict_open_extend - register alternate dictionary search routine */ + +DICT_OPEN_EXTEND_FN dict_open_extend(DICT_OPEN_EXTEND_FN new_cb) +{ + DICT_OPEN_EXTEND_FN old_cb; + + old_cb = dict_open_extend_hook; + dict_open_extend_hook = new_cb; + return (old_cb); +} + +/* dict_sort_alpha_cpp - qsort() callback */ + +static int dict_sort_alpha_cpp(const void *a, const void *b) +{ + return (strcmp(((char **) a)[0], ((char **) b)[0])); +} + +/* dict_mapnames - return an ARGV of available map_names */ + +ARGV *dict_mapnames() +{ + HTABLE_INFO **ht_info; + HTABLE_INFO **ht; + DICT_OPEN_INFO *dp; + ARGV *mapnames; + + if (dict_open_hash == 0) + dict_open_init(); + mapnames = argv_alloc(dict_open_hash->used + 1); + for (ht_info = ht = htable_list(dict_open_hash); *ht; ht++) { + dp = (DICT_OPEN_INFO *) ht[0]->value; + argv_add(mapnames, dp->type, ARGV_END); + } + if (dict_mapnames_extend_hook != 0) + (void) dict_mapnames_extend_hook(mapnames); + qsort((void *) mapnames->argv, mapnames->argc, sizeof(mapnames->argv[0]), + dict_sort_alpha_cpp); + myfree((void *) ht_info); + argv_terminate(mapnames); + return mapnames; +} + +/* dict_mapnames_extend - register alternate dictionary type list routine */ + +DICT_MAPNAMES_EXTEND_FN dict_mapnames_extend(DICT_MAPNAMES_EXTEND_FN new_cb) +{ + DICT_MAPNAMES_EXTEND_FN old_cb; + + old_cb = dict_mapnames_extend_hook; + dict_mapnames_extend_hook = new_cb; + return (old_cb); +} + +/* dict_type_override - disguise a dictionary type */ + +void dict_type_override(DICT *dict, const char *type) +{ + myfree(dict->type); + dict->type = mystrdup(type); +} + +#ifdef TEST + + /* + * Proof-of-concept test program. + */ +int main(int argc, char **argv) +{ + dict_test(argc, argv); + return (0); +} + +#endif diff --git a/src/util/dict_pcre.c b/src/util/dict_pcre.c new file mode 100644 index 0000000..3cceda2 --- /dev/null +++ b/src/util/dict_pcre.c @@ -0,0 +1,1120 @@ +/*++ +/* NAME +/* dict_pcre 3 +/* SUMMARY +/* dictionary manager interface to PCRE regular expression library +/* SYNOPSIS +/* #include <dict_pcre.h> +/* +/* DICT *dict_pcre_open(name, dummy, dict_flags) +/* const char *name; +/* int dummy; +/* int dict_flags; +/* DESCRIPTION +/* dict_pcre_open() opens the named file and compiles the contained +/* regular expressions. The result object can be used to match strings +/* against the table. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* AUTHOR(S) +/* Andrew McNamara +/* andrewm@connect.com.au +/* connect.com.au Pty. Ltd. +/* Level 3, 213 Miller St +/* North Sydney, NSW, Australia +/* +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#include "sys_defs.h" + +#ifdef HAS_PCRE + +/* System library. */ + +#include <sys/stat.h> +#include <stdio.h> /* sprintf() prototype */ +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> + +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif + +#if HAS_PCRE == 1 +#include <pcre.h> +#elif HAS_PCRE == 2 +#define PCRE2_CODE_UNIT_WIDTH 8 +#include <pcre2.h> +#else +#error "define HAS_PCRE=2 or HAS_PCRE=1" +#endif + +/* Utility library. */ + +#include "mymalloc.h" +#include "msg.h" +#include "safe.h" +#include "vstream.h" +#include "vstring.h" +#include "stringops.h" +#include "readlline.h" +#include "dict.h" +#include "dict_pcre.h" +#include "mac_parse.h" +#include "warn_stat.h" +#include "mvect.h" + + /* + * Backwards compatibility. + */ +#if HAS_PCRE == 1 + /* PCRE Legacy JIT supprt. */ +#ifdef PCRE_STUDY_JIT_COMPILE +#define DICT_PCRE_FREE_STUDY(x) pcre_free_study(x) +#else +#define DICT_PCRE_FREE_STUDY(x) pcre_free((char *) (x)) +#endif + + /* PCRE Compiled pattern. */ +#define DICT_PCRE_CODE pcre +#define DICT_PCRE_CODE_FREE(x) myfree((void *) (x)) + + /* Old-style hints versus new-style match_data. */ +#define DICT_PCRE_MATCH_HINT_TYPE pcre_extra * +#define DICT_PCRE_MATCH_HINT_NAME hints +#define DICT_PCRE_MATCH_HINT(x) ((x)->DICT_PCRE_MATCH_HINT_NAME) +#define DICT_PCRE_MATCH_HINT_FREE(x) do { \ + if (DICT_PCRE_MATCH_HINT(x)) \ + DICT_PCRE_FREE_STUDY(DICT_PCRE_MATCH_HINT(x)); \ + } while (0) + + /* PCRE Pattern options. */ +#define DICT_PCRE_CASELESS PCRE_CASELESS +#define DICT_PCRE_MULTILINE PCRE_MULTILINE +#define DICT_PCRE_DOTALL PCRE_DOTALL +#define DICT_PCRE_EXTENDED PCRE_EXTENDED +#define DICT_PCRE_ANCHORED PCRE_ANCHORED +#define DICT_PCRE_DOLLAR_ENDONLY PCRE_DOLLAR_ENDONLY +#define DICT_PCRE_UNGREEDY PCRE_UNGREEDY +#define DICT_PCRE_EXTRA PCRE_EXTRA + + /* PCRE Number of captures in pattern. */ +#ifdef PCRE_INFO_CAPTURECOUNT +#define DICT_PCRE_CAPTURECOUNT_T int +#endif + +#else /* HAS_PCRE */ + + /* PCRE2 Compiled pattern. */ +#define DICT_PCRE_CODE pcre2_code +#define DICT_PCRE_CODE_FREE(x) pcre2_code_free(x) + + /* PCRE2 Old-style hints versus new-style match_data. */ +#define DICT_PCRE_MATCH_HINT_TYPE pcre2_match_data * +#define DICT_PCRE_MATCH_HINT_NAME match_data +#define DICT_PCRE_MATCH_HINT(x) ((x)->DICT_PCRE_MATCH_HINT_NAME) +#define DICT_PCRE_MATCH_HINT_FREE(x) \ + pcre2_match_data_free(DICT_PCRE_MATCH_HINT(x)) + + /* PCRE2 Pattern options. */ +#define DICT_PCRE_CASELESS PCRE2_CASELESS +#define DICT_PCRE_MULTILINE PCRE2_MULTILINE +#define DICT_PCRE_DOTALL PCRE2_DOTALL +#define DICT_PCRE_EXTENDED PCRE2_EXTENDED +#define DICT_PCRE_ANCHORED PCRE2_ANCHORED +#define DICT_PCRE_DOLLAR_ENDONLY PCRE2_DOLLAR_ENDONLY +#define DICT_PCRE_UNGREEDY PCRE2_UNGREEDY +#define DICT_PCRE_EXTRA 0 + + /* PCRE2 Number of captures in pattern. */ +#define DICT_PCRE_CAPTURECOUNT_T uint32_t + +#endif /* HAS_PCRE */ + + /* + * Support for IF/ENDIF based on an idea by Bert Driehuis. + */ +#define DICT_PCRE_OP_MATCH 1 /* Match this regexp */ +#define DICT_PCRE_OP_IF 2 /* Increase if/endif nesting on match */ +#define DICT_PCRE_OP_ENDIF 3 /* Decrease if/endif nesting on match */ + + /* + * Max strings captured by regexp - essentially the max number of (..) + */ +#if HAS_PCRE == 1 +#define PCRE_MAX_CAPTURE 99 +#endif + + /* + * Regular expression before and after compilation. + */ +typedef struct { + char *regexp; /* regular expression */ + int options; /* options */ + int match; /* positive or negative match */ +} DICT_PCRE_REGEXP; + +typedef struct { + DICT_PCRE_CODE *pattern; /* the compiled pattern */ + DICT_PCRE_MATCH_HINT_TYPE DICT_PCRE_MATCH_HINT_NAME; +} DICT_PCRE_ENGINE; + + /* + * Compiled generic rule, and subclasses that derive from it. + */ +typedef struct DICT_PCRE_RULE { + int op; /* DICT_PCRE_OP_MATCH/IF/ENDIF */ + int lineno; /* source file line number */ + struct DICT_PCRE_RULE *next; /* next rule in dict */ +} DICT_PCRE_RULE; + +typedef struct { + DICT_PCRE_RULE rule; /* generic part */ + DICT_PCRE_CODE *pattern; /* compiled pattern */ + DICT_PCRE_MATCH_HINT_TYPE DICT_PCRE_MATCH_HINT_NAME; + char *replacement; /* replacement string */ + int match; /* positive or negative match */ + size_t max_sub; /* largest $number in replacement */ +} DICT_PCRE_MATCH_RULE; + +typedef struct { + DICT_PCRE_RULE rule; /* generic members */ + DICT_PCRE_CODE *pattern; /* compiled pattern */ + DICT_PCRE_MATCH_HINT_TYPE DICT_PCRE_MATCH_HINT_NAME; + int match; /* positive or negative match */ + struct DICT_PCRE_RULE *endif_rule; /* matching endif rule */ +} DICT_PCRE_IF_RULE; + + /* + * PCRE map. + */ +typedef struct { + DICT dict; /* generic members */ + DICT_PCRE_RULE *head; + VSTRING *expansion_buf; /* lookup result */ +} DICT_PCRE; + +#if HAS_PCRE == 1 +static int dict_pcre_init = 0; /* flag need to init pcre library */ + +#endif + +/* + * Context for $number expansion callback. + */ +typedef struct { + DICT_PCRE *dict_pcre; /* the dictionary handle */ +#if HAS_PCRE == 1 + DICT_PCRE_MATCH_RULE *match_rule; /* the rule we matched */ +#endif + const char *lookup_string; /* string against which we match */ +#if HAS_PCRE == 1 + int offsets[PCRE_MAX_CAPTURE * 3]; /* Cut substrings */ +#else /* HAS_PCRE */ + PCRE2_SIZE *ovector; /* matched string offsets */ +#endif /* HAS_PCRE */ + int matches; /* Count of cuts */ +} DICT_PCRE_EXPAND_CONTEXT; + + /* + * Context for $number pre-scan callback. + */ +typedef struct { + const char *mapname; /* name of regexp map */ + int lineno; /* where in file */ + size_t max_sub; /* Largest $n seen */ + char *literal; /* constant result, $$ -> $ */ +} DICT_PCRE_PRESCAN_CONTEXT; + + /* + * Compatibility. + */ +#ifndef MAC_PARSE_OK +#define MAC_PARSE_OK 0 +#endif + + /* + * Macros to make dense code more accessible. + */ +#define NULL_STARTOFFSET (0) +#define NULL_EXEC_OPTIONS (0) + +/* dict_pcre_expand - replace $number with matched text */ + +static int dict_pcre_expand(int type, VSTRING *buf, void *ptr) +{ + DICT_PCRE_EXPAND_CONTEXT *ctxt = (DICT_PCRE_EXPAND_CONTEXT *) ptr; + DICT_PCRE *dict_pcre = ctxt->dict_pcre; + int n; + +#if HAS_PCRE == 1 + DICT_PCRE_MATCH_RULE *match_rule = ctxt->match_rule; + const char *pp; + int ret; + +#else + PCRE2_SPTR start; + PCRE2_SIZE length; + +#endif + + /* + * Replace $0-${99} with strings cut from matched text. + */ + if (type == MAC_PARSE_VARNAME) { + n = atoi(vstring_str(buf)); +#if HAS_PCRE == 1 + ret = pcre_get_substring(ctxt->lookup_string, ctxt->offsets, + ctxt->matches, n, &pp); + if (ret < 0) { + if (ret == PCRE_ERROR_NOSUBSTRING) + return (MAC_PARSE_UNDEF); + else + msg_fatal("pcre map %s, line %d: pcre_get_substring error: %d", + dict_pcre->dict.name, match_rule->rule.lineno, ret); + } + if (*pp == 0) { + myfree((void *) pp); + return (MAC_PARSE_UNDEF); + } + vstring_strcat(dict_pcre->expansion_buf, pp); + myfree((void *) pp); + return (MAC_PARSE_OK); +#else + start = (unsigned char *) ctxt->lookup_string + ctxt->ovector[2 * n]; + length = ctxt->ovector[2 * n + 1] - ctxt->ovector[2 * n]; + if (length == 0) + return (MAC_PARSE_UNDEF); + vstring_strncat(dict_pcre->expansion_buf, (char *) start, length); + return (MAC_PARSE_OK); +#endif + } + + /* + * Straight text - duplicate with no substitution. + */ + else { + vstring_strcat(dict_pcre->expansion_buf, vstring_str(buf)); + return (MAC_PARSE_OK); + } +} + +#if HAS_PCRE == 2 + +#define DICT_PCRE_GET_ERROR_BUF_LEN 256 + +/* dict_pcre_get_error - convert PCRE2 error number or text */ + +static char *dict_pcre_get_error(VSTRING *buf, int errval) +{ + ssize_t len; + + VSTRING_SPACE(buf, DICT_PCRE_GET_ERROR_BUF_LEN); + if ((len = pcre2_get_error_message(errval, + (unsigned char *) vstring_str(buf), + DICT_PCRE_GET_ERROR_BUF_LEN)) > 0) { + vstring_set_payload_size(buf, len); + } else + vstring_sprintf(buf, "unexpected pcre2 error code %d", errval); + return (vstring_str(buf)); +} + +#endif /* HAS_PCRE == 2 */ + +/* dict_pcre_exec_error - report matching error */ + +static void dict_pcre_exec_error(const char *mapname, int lineno, int errval) +{ +#if HAS_PCRE == 1 + switch (errval) { + case 0: + msg_warn("pcre map %s, line %d: too many (...)", + mapname, lineno); + return; + case PCRE_ERROR_NULL: + case PCRE_ERROR_BADOPTION: + msg_warn("pcre map %s, line %d: bad args to re_exec", + mapname, lineno); + return; + case PCRE_ERROR_BADMAGIC: + case PCRE_ERROR_UNKNOWN_NODE: + msg_warn("pcre map %s, line %d: corrupt compiled regexp", + mapname, lineno); + return; +#ifdef PCRE_ERROR_NOMEMORY + case PCRE_ERROR_NOMEMORY: + msg_warn("pcre map %s, line %d: out of memory", + mapname, lineno); + return; +#endif +#ifdef PCRE_ERROR_MATCHLIMIT + case PCRE_ERROR_MATCHLIMIT: + msg_warn("pcre map %s, line %d: backtracking limit exceeded", + mapname, lineno); + return; +#endif +#ifdef PCRE_ERROR_BADUTF8 + case PCRE_ERROR_BADUTF8: + msg_warn("pcre map %s, line %d: bad UTF-8 sequence in search string", + mapname, lineno); + return; +#endif +#ifdef PCRE_ERROR_BADUTF8_OFFSET + case PCRE_ERROR_BADUTF8_OFFSET: + msg_warn("pcre map %s, line %d: bad UTF-8 start offset in search string", + mapname, lineno); + return; +#endif + default: + msg_warn("pcre map %s, line %d: unknown pcre_exec error: %d", + mapname, lineno, errval); + return; + } +#else /* HAS_PCRE */ + VSTRING *buf = vstring_alloc(DICT_PCRE_GET_ERROR_BUF_LEN); + + msg_warn("pcre map %s, line %d: %s", mapname, lineno, + dict_pcre_get_error(buf, errval)); + vstring_free(buf); +#endif /* HAS_PCRE */ +} + + /* + * Inlined to reduce function call overhead in the time-critical loop. + */ +#if HAS_PCRE == 1 +#define DICT_PCRE_EXEC(ctxt, map, line, pattern, hints, match, str, len) \ + ((ctxt).matches = pcre_exec((pattern), (hints), (str), (len), \ + NULL_STARTOFFSET, NULL_EXEC_OPTIONS, \ + (ctxt).offsets, PCRE_MAX_CAPTURE * 3), \ + (ctxt).matches > 0 ? (match) : \ + (ctxt).matches == PCRE_ERROR_NOMATCH ? !(match) : \ + (dict_pcre_exec_error((map), (line), (ctxt).matches), 0)) +#else +#define DICT_PCRE_EXEC(ctxt, map, line, pattern, match_data, match, str, len) \ + ((ctxt).matches = pcre2_match((pattern), (unsigned char *) (str), (len), \ + NULL_STARTOFFSET, NULL_EXEC_OPTIONS, \ + (match_data), (pcre2_match_context *) 0), \ + (ctxt).matches > 0 ? (match) : \ + (ctxt).matches == PCRE2_ERROR_NOMATCH ? !(match) : \ + (dict_pcre_exec_error((map), (line), (ctxt).matches), 0)) +#endif + +/* dict_pcre_lookup - match string and perform optional substitution */ + +static const char *dict_pcre_lookup(DICT *dict, const char *lookup_string) +{ + DICT_PCRE *dict_pcre = (DICT_PCRE *) dict; + DICT_PCRE_RULE *rule; + DICT_PCRE_IF_RULE *if_rule; + DICT_PCRE_MATCH_RULE *match_rule; + int lookup_len = strlen(lookup_string); + DICT_PCRE_EXPAND_CONTEXT ctxt; + + dict->error = 0; + + if (msg_verbose) + msg_info("dict_pcre_lookup: %s: %s", dict->name, lookup_string); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_MUL) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, lookup_string); + lookup_string = lowercase(vstring_str(dict->fold_buf)); + } + for (rule = dict_pcre->head; rule; rule = rule->next) { + + switch (rule->op) { + + /* + * Search for a matching expression. + */ + case DICT_PCRE_OP_MATCH: + match_rule = (DICT_PCRE_MATCH_RULE *) rule; + if (!DICT_PCRE_EXEC(ctxt, dict->name, rule->lineno, + match_rule->pattern, + DICT_PCRE_MATCH_HINT(match_rule), + match_rule->match, lookup_string, lookup_len)) + continue; + + /* + * Skip $number substitutions when the replacement text contains + * no $number strings, as learned during the compile time + * pre-scan. The pre-scan already replaced $$ by $. + */ + if (match_rule->max_sub == 0) + return match_rule->replacement; + + /* + * We've got a match. Perform substitution on replacement string. + */ + if (dict_pcre->expansion_buf == 0) + dict_pcre->expansion_buf = vstring_alloc(10); + VSTRING_RESET(dict_pcre->expansion_buf); + ctxt.dict_pcre = dict_pcre; +#if HAS_PCRE == 1 + ctxt.match_rule = match_rule; +#else + ctxt.ovector = pcre2_get_ovector_pointer(match_rule->match_data); +#endif + ctxt.lookup_string = lookup_string; + + if (mac_parse(match_rule->replacement, dict_pcre_expand, + (void *) &ctxt) & MAC_PARSE_ERROR) + msg_fatal("pcre map %s, line %d: bad replacement syntax", + dict->name, rule->lineno); + + VSTRING_TERMINATE(dict_pcre->expansion_buf); + return (vstring_str(dict_pcre->expansion_buf)); + + /* + * Conditional. XXX We provide space for matched substring info + * because PCRE uses part of it as workspace for backtracking. + * PCRE will allocate memory if it runs out of backtracking + * storage. + */ + case DICT_PCRE_OP_IF: + if_rule = (DICT_PCRE_IF_RULE *) rule; + if (DICT_PCRE_EXEC(ctxt, dict->name, rule->lineno, + if_rule->pattern, + DICT_PCRE_MATCH_HINT(if_rule), + if_rule->match, lookup_string, lookup_len)) + continue; + /* An IF without matching ENDIF has no "endif" rule. */ + if ((rule = if_rule->endif_rule) == 0) + return (0); + /* FALLTHROUGH */ + + /* + * ENDIF after IF. + */ + case DICT_PCRE_OP_ENDIF: + continue; + + default: + msg_panic("dict_pcre_lookup: impossible operation %d", rule->op); + } + } + return (0); +} + +/* dict_pcre_close - close pcre dictionary */ + +static void dict_pcre_close(DICT *dict) +{ + DICT_PCRE *dict_pcre = (DICT_PCRE *) dict; + DICT_PCRE_RULE *rule; + DICT_PCRE_RULE *next; + DICT_PCRE_MATCH_RULE *match_rule; + DICT_PCRE_IF_RULE *if_rule; + + for (rule = dict_pcre->head; rule; rule = next) { + next = rule->next; + switch (rule->op) { + case DICT_PCRE_OP_MATCH: + match_rule = (DICT_PCRE_MATCH_RULE *) rule; + if (match_rule->pattern) + DICT_PCRE_CODE_FREE(match_rule->pattern); + DICT_PCRE_MATCH_HINT_FREE(match_rule); + if (match_rule->replacement) + myfree((void *) match_rule->replacement); + break; + case DICT_PCRE_OP_IF: + if_rule = (DICT_PCRE_IF_RULE *) rule; + if (if_rule->pattern) + DICT_PCRE_CODE_FREE(if_rule->pattern); + DICT_PCRE_MATCH_HINT_FREE(if_rule); + break; + case DICT_PCRE_OP_ENDIF: + break; + default: + msg_panic("dict_pcre_close: unknown operation %d", rule->op); + } + myfree((void *) rule); + } + if (dict_pcre->expansion_buf) + vstring_free(dict_pcre->expansion_buf); + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_pcre_get_pattern - extract pattern from rule */ + +static int dict_pcre_get_pattern(const char *mapname, int lineno, char **bufp, + DICT_PCRE_REGEXP *pattern) +{ + char *p = *bufp; + char re_delimiter; + + /* + * Process negation operators. + */ + pattern->match = 1; + for (;;) { + if (*p == '!') + pattern->match = !pattern->match; + else if (!ISSPACE(*p)) + break; + p++; + } + if (*p == 0) { + msg_warn("pcre map %s, line %d: no regexp: skipping this rule", + mapname, lineno); + return (0); + } + re_delimiter = *p++; + pattern->regexp = p; + + /* + * Search for second delimiter, handling backslash escape. + */ + while (*p) { + if (*p == '\\') { + ++p; + if (*p == 0) + break; + } else if (*p == re_delimiter) + break; + ++p; + } + + if (!*p) { + msg_warn("pcre map %s, line %d: no closing regexp delimiter \"%c\": " + "ignoring this rule", mapname, lineno, re_delimiter); + return (0); + } + *p++ = 0; /* Null term the regexp */ + + /* + * Parse any regexp options. + */ + pattern->options = DICT_PCRE_CASELESS | DICT_PCRE_DOTALL; + while (*p && !ISSPACE(*p)) { + switch (*p) { + case 'i': + pattern->options ^= DICT_PCRE_CASELESS; + break; + case 'm': + pattern->options ^= DICT_PCRE_MULTILINE; + break; + case 's': + pattern->options ^= DICT_PCRE_DOTALL; + break; + case 'x': + pattern->options ^= DICT_PCRE_EXTENDED; + break; + case 'A': + pattern->options ^= DICT_PCRE_ANCHORED; + break; + case 'E': + pattern->options ^= DICT_PCRE_DOLLAR_ENDONLY; + break; + case 'U': + pattern->options ^= DICT_PCRE_UNGREEDY; + break; + case 'X': +#if DICT_PCRE_EXTRA != 0 + pattern->options ^= DICT_PCRE_EXTRA; +#else + msg_warn("pcre map %s, line %d: ignoring obsolete regexp " + "option \"%c\"", mapname, lineno, *p); +#endif + break; + default: + msg_warn("pcre map %s, line %d: unknown regexp option \"%c\": " + "skipping this rule", mapname, lineno, *p); + return (0); + } + ++p; + } + *bufp = p; + return (1); +} + +/* dict_pcre_prescan - sanity check $number instances in replacement text */ + +static int dict_pcre_prescan(int type, VSTRING *buf, void *context) +{ + DICT_PCRE_PRESCAN_CONTEXT *ctxt = (DICT_PCRE_PRESCAN_CONTEXT *) context; + size_t n; + + /* + * Keep a copy of literal text (with $$ already replaced by $) if and + * only if the replacement text contains no $number expression. This way + * we can avoid having to scan the replacement text at lookup time. + */ + if (type == MAC_PARSE_VARNAME) { + if (ctxt->literal) { + myfree(ctxt->literal); + ctxt->literal = 0; + } + if (!alldig(vstring_str(buf))) { + msg_warn("pcre map %s, line %d: non-numeric replacement index \"%s\"", + ctxt->mapname, ctxt->lineno, vstring_str(buf)); + return (MAC_PARSE_ERROR); + } + n = atoi(vstring_str(buf)); + if (n < 1) { + msg_warn("pcre map %s, line %d: out of range replacement index \"%s\"", + ctxt->mapname, ctxt->lineno, vstring_str(buf)); + return (MAC_PARSE_ERROR); + } + if (n > ctxt->max_sub) + ctxt->max_sub = n; + } else if (type == MAC_PARSE_LITERAL && ctxt->max_sub == 0) { + if (ctxt->literal) + msg_panic("pcre map %s, line %d: multiple literals but no $number", + ctxt->mapname, ctxt->lineno); + ctxt->literal = mystrdup(vstring_str(buf)); + } + return (MAC_PARSE_OK); +} + +/* dict_pcre_compile - compile pattern */ + +static int dict_pcre_compile(const char *mapname, int lineno, + DICT_PCRE_REGEXP *pattern, + DICT_PCRE_ENGINE *engine) +{ +#if HAS_PCRE == 1 + const char *error; + int errptr; + + engine->pattern = pcre_compile(pattern->regexp, pattern->options, + &error, &errptr, NULL); + if (engine->pattern == 0) { + msg_warn("pcre map %s, line %d: error in regex at offset %d: %s", + mapname, lineno, errptr, error); + return (0); + } + engine->hints = pcre_study(engine->pattern, 0, &error); + if (error != 0) { + msg_warn("pcre map %s, line %d: error while studying regex: %s", + mapname, lineno, error); + DICT_PCRE_CODE_FREE(engine->pattern); + return (0); + } +#else + int error; + size_t errptr; + + engine->pattern = pcre2_compile((unsigned char *) pattern->regexp, + PCRE2_ZERO_TERMINATED, + pattern->options, &error, &errptr, NULL); + if (engine->pattern == 0) { + VSTRING *buf = vstring_alloc(DICT_PCRE_GET_ERROR_BUF_LEN); + + msg_warn("pcre map %s, line %d: error in regex at offset %lu: %s", + mapname, lineno, (unsigned long) errptr, + dict_pcre_get_error(buf, error)); + vstring_free(buf); + return (0); + } + engine->match_data = pcre2_match_data_create_from_pattern( + engine->pattern, (void *) 0); +#endif + return (1); +} + +/* dict_pcre_rule_alloc - fill in a generic rule structure */ + +static DICT_PCRE_RULE *dict_pcre_rule_alloc(int op, int lineno, size_t size) +{ + DICT_PCRE_RULE *rule; + + rule = (DICT_PCRE_RULE *) mymalloc(size); + rule->op = op; + rule->lineno = lineno; + rule->next = 0; + + return (rule); +} + +/* dict_pcre_parse_rule - parse and compile one rule */ + +static DICT_PCRE_RULE *dict_pcre_parse_rule(DICT *dict, const char *mapname, + int lineno, char *line, + int nesting) +{ + char *p; + +#ifdef DICT_PCRE_CAPTURECOUNT_T + DICT_PCRE_CAPTURECOUNT_T actual_sub; + +#endif +#if 0 + uint32_t namecount; + +#endif + + p = line; + + /* + * An ordinary match rule takes one pattern and replacement text. + */ + if (!ISALNUM(*p)) { + DICT_PCRE_REGEXP regexp; + DICT_PCRE_ENGINE engine; + DICT_PCRE_PRESCAN_CONTEXT prescan_context; + DICT_PCRE_MATCH_RULE *match_rule; + + /* + * Get the pattern string and options. + */ + if (dict_pcre_get_pattern(mapname, lineno, &p, ®exp) == 0) + return (0); + + /* + * Get the replacement text. + */ + while (*p && ISSPACE(*p)) + ++p; + if (!*p) + msg_warn("pcre map %s, line %d: no replacement text: " + "using empty string", mapname, lineno); + + /* + * Sanity check the $number instances in the replacement text. + */ + prescan_context.mapname = mapname; + prescan_context.lineno = lineno; + prescan_context.max_sub = 0; + prescan_context.literal = 0; + + /* + * The optimizer will eliminate code duplication and/or dead code. + */ +#define CREATE_MATCHOP_ERROR_RETURN(rval) do { \ + if (prescan_context.literal) \ + myfree(prescan_context.literal); \ + return (rval); \ + } while (0) + + if (dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) { + VSTRING *base64_buf; + char *err; + + if ((base64_buf = dict_file_to_b64(dict, p)) == 0) { + err = dict_file_get_error(dict); + msg_warn("pcre map %s, line %d: %s: skipping this rule", + mapname, lineno, err); + myfree(err); + CREATE_MATCHOP_ERROR_RETURN(0); + } + p = vstring_str(base64_buf); + } + if (mac_parse(p, dict_pcre_prescan, (void *) &prescan_context) + & MAC_PARSE_ERROR) { + msg_warn("pcre map %s, line %d: bad replacement syntax: " + "skipping this rule", mapname, lineno); + CREATE_MATCHOP_ERROR_RETURN(0); + } + + /* + * Substring replacement not possible with negative regexps. + */ + if (prescan_context.max_sub > 0 && regexp.match == 0) { + msg_warn("pcre map %s, line %d: $number found in negative match " + "replacement text: skipping this rule", mapname, lineno); + CREATE_MATCHOP_ERROR_RETURN(0); + } + if (prescan_context.max_sub > 0 && (dict->flags & DICT_FLAG_NO_REGSUB)) { + msg_warn("pcre map %s, line %d: " + "regular expression substitution is not allowed: " + "skipping this rule", mapname, lineno); + CREATE_MATCHOP_ERROR_RETURN(0); + } + + /* + * Compile the pattern. + */ + if (dict_pcre_compile(mapname, lineno, ®exp, &engine) == 0) + CREATE_MATCHOP_ERROR_RETURN(0); +#ifdef DICT_PCRE_CAPTURECOUNT_T +#if HAS_PCRE == 1 + if (pcre_fullinfo(engine.pattern, engine.hints, + PCRE_INFO_CAPTURECOUNT, + (void *) &actual_sub) != 0) + msg_panic("pcre map %s, line %d: pcre_fullinfo failed", + mapname, lineno); +#else /* HAS_PCRE */ +#if 0 + if (pcre2_pattern_info( + engine.pattern, PCRE2_INFO_NAMECOUNT, &namecount) != 0) + msg_panic("pcre map %s, line %d: pcre2_pattern_info failed", + mapname, lineno); + if (namecount > 0) { + msg_warn("pcre map %s, line %d: named substrings are not supported", + mapname, lineno); + if (engine.pattern) + DICT_PCRE_CODE_FREE(engine.pattern); + DICT_PCRE_MATCH_HINT_FREE(&engine); + CREATE_MATCHOP_ERROR_RETURN(0); + } +#endif + if (pcre2_pattern_info(engine.pattern, PCRE2_INFO_CAPTURECOUNT, + (void *) &actual_sub) != 0) + msg_panic("pcre map %s, line %d: pcre2_pattern_info failed", + mapname, lineno); +#endif /* HAS_PCRE */ + if (prescan_context.max_sub > actual_sub) { + msg_warn("pcre map %s, line %d: out of range replacement index \"%d\": " + "skipping this rule", mapname, lineno, + (int) prescan_context.max_sub); + if (engine.pattern) + DICT_PCRE_CODE_FREE(engine.pattern); + DICT_PCRE_MATCH_HINT_FREE(&engine); + CREATE_MATCHOP_ERROR_RETURN(0); + } +#endif /* DICT_PCRE_CAPTURECOUNT_T */ + + /* + * Save the result. + */ + match_rule = (DICT_PCRE_MATCH_RULE *) + dict_pcre_rule_alloc(DICT_PCRE_OP_MATCH, lineno, + sizeof(DICT_PCRE_MATCH_RULE)); + match_rule->match = regexp.match; + match_rule->max_sub = prescan_context.max_sub; + if (prescan_context.literal) + match_rule->replacement = prescan_context.literal; + else + match_rule->replacement = mystrdup(p); + match_rule->pattern = engine.pattern; + DICT_PCRE_MATCH_HINT(match_rule) = DICT_PCRE_MATCH_HINT(&engine); + return ((DICT_PCRE_RULE *) match_rule); + } + + /* + * The IF operator takes one pattern but no replacement text. + */ + else if (strncasecmp(p, "IF", 2) == 0 && !ISALNUM(p[2])) { + DICT_PCRE_REGEXP regexp; + DICT_PCRE_ENGINE engine; + DICT_PCRE_IF_RULE *if_rule; + + p += 2; + + /* + * Get the pattern. + */ + while (*p && ISSPACE(*p)) + p++; + if (!dict_pcre_get_pattern(mapname, lineno, &p, ®exp)) + return (0); + + /* + * Warn about out-of-place text. + */ + while (*p && ISSPACE(*p)) + ++p; + if (*p) { + msg_warn("pcre map %s, line %d: ignoring extra text after " + "IF statement: \"%s\"", mapname, lineno, p); + msg_warn("pcre map %s, line %d: do not prepend whitespace" + " to statements between IF and ENDIF", mapname, lineno); + } + + /* + * Compile the pattern. + */ + if (dict_pcre_compile(mapname, lineno, ®exp, &engine) == 0) + return (0); + + /* + * Save the result. + */ + if_rule = (DICT_PCRE_IF_RULE *) + dict_pcre_rule_alloc(DICT_PCRE_OP_IF, lineno, + sizeof(DICT_PCRE_IF_RULE)); + if_rule->match = regexp.match; + if_rule->pattern = engine.pattern; + DICT_PCRE_MATCH_HINT(if_rule) = DICT_PCRE_MATCH_HINT(&engine); + if_rule->endif_rule = 0; + return ((DICT_PCRE_RULE *) if_rule); + } + + /* + * The ENDIF operator takes no patterns and no replacement text. + */ + else if (strncasecmp(p, "ENDIF", 5) == 0 && !ISALNUM(p[5])) { + DICT_PCRE_RULE *rule; + + p += 5; + + /* + * Warn about out-of-place ENDIFs. + */ + if (nesting == 0) { + msg_warn("pcre map %s, line %d: ignoring ENDIF without matching IF", + mapname, lineno); + return (0); + } + + /* + * Warn about out-of-place text. + */ + while (*p && ISSPACE(*p)) + ++p; + if (*p) + msg_warn("pcre map %s, line %d: ignoring extra text after ENDIF", + mapname, lineno); + + /* + * Save the result. + */ + rule = dict_pcre_rule_alloc(DICT_PCRE_OP_ENDIF, lineno, + sizeof(DICT_PCRE_RULE)); + return (rule); + } + + /* + * Unrecognized input. + */ + else { + msg_warn("pcre map %s, line %d: ignoring unrecognized request", + mapname, lineno); + return (0); + } +} + +/* dict_pcre_open - load and compile a file containing regular expressions */ + +DICT *dict_pcre_open(const char *mapname, int open_flags, int dict_flags) +{ + const char myname[] = "dict_pcre_open"; + DICT_PCRE *dict_pcre; + VSTREAM *map_fp = 0; + struct stat st; + VSTRING *why = 0; + VSTRING *line_buffer = 0; + DICT_PCRE_RULE *last_rule = 0; + DICT_PCRE_RULE *rule; + int last_line = 0; + int lineno; + int nesting = 0; + char *p; + DICT_PCRE_RULE **rule_stack = 0; + MVECT mvect; + + /* + * Let the optimizer worry about eliminating redundant code. + */ +#define DICT_PCRE_OPEN_RETURN(d) do { \ + DICT *__d = (d); \ + if (map_fp != 0) \ + vstream_fclose(map_fp); \ + if (line_buffer != 0) \ + vstring_free(line_buffer); \ + if (why != 0) \ + vstring_free(why); \ + return (__d); \ + } while (0) + + /* + * Sanity checks. + */ + if (open_flags != O_RDONLY) + DICT_PCRE_OPEN_RETURN(dict_surrogate(DICT_TYPE_PCRE, mapname, + open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_PCRE, mapname)); + + /* + * Open the configuration file. + */ + if ((map_fp = dict_stream_open(DICT_TYPE_PCRE, mapname, O_RDONLY, + dict_flags, &st, &why)) == 0) + DICT_PCRE_OPEN_RETURN(dict_surrogate(DICT_TYPE_PCRE, mapname, + open_flags, dict_flags, + "%s", vstring_str(why))); + line_buffer = vstring_alloc(100); + + dict_pcre = (DICT_PCRE *) dict_alloc(DICT_TYPE_PCRE, mapname, + sizeof(*dict_pcre)); + dict_pcre->dict.lookup = dict_pcre_lookup; + dict_pcre->dict.close = dict_pcre_close; + dict_pcre->dict.flags = dict_flags | DICT_FLAG_PATTERN; + if (dict_flags & DICT_FLAG_FOLD_MUL) + dict_pcre->dict.fold_buf = vstring_alloc(10); + dict_pcre->head = 0; + dict_pcre->expansion_buf = 0; + +#if HAS_PCRE == 1 + if (dict_pcre_init == 0) { + pcre_malloc = (void *(*) (size_t)) mymalloc; + pcre_free = (void (*) (void *)) myfree; + dict_pcre_init = 1; + } +#endif + dict_pcre->dict.owner.uid = st.st_uid; + dict_pcre->dict.owner.status = (st.st_uid != 0); + + /* + * Parse the pcre table. + */ + while (readllines(line_buffer, map_fp, &last_line, &lineno)) { + p = vstring_str(line_buffer); + trimblanks(p, 0)[0] = 0; /* Trim space at end */ + if (*p == 0) + continue; + rule = dict_pcre_parse_rule(&dict_pcre->dict, mapname, lineno, + p, nesting); + if (rule == 0) + continue; + if (rule->op == DICT_PCRE_OP_IF) { + if (rule_stack == 0) + rule_stack = (DICT_PCRE_RULE **) mvect_alloc(&mvect, + sizeof(*rule_stack), nesting + 1, + (MVECT_FN) 0, (MVECT_FN) 0); + else + rule_stack = + (DICT_PCRE_RULE **) mvect_realloc(&mvect, nesting + 1); + rule_stack[nesting] = rule; + nesting++; + } else if (rule->op == DICT_PCRE_OP_ENDIF) { + DICT_PCRE_IF_RULE *if_rule; + + if (nesting-- <= 0) + /* Already handled in dict_pcre_parse_rule(). */ + msg_panic("%s: ENDIF without IF", myname); + if (rule_stack[nesting]->op != DICT_PCRE_OP_IF) + msg_panic("%s: unexpected rule stack element type %d", + myname, rule_stack[nesting]->op); + if_rule = (DICT_PCRE_IF_RULE *) rule_stack[nesting]; + if_rule->endif_rule = rule; + } + if (last_rule == 0) + dict_pcre->head = rule; + else + last_rule->next = rule; + last_rule = rule; + } + + while (nesting-- > 0) + msg_warn("pcre map %s, line %d: IF has no matching ENDIF", + mapname, rule_stack[nesting]->lineno); + + if (rule_stack) + (void) mvect_free(&mvect); + + dict_file_purge_buffers(&dict_pcre->dict); + DICT_PCRE_OPEN_RETURN(DICT_DEBUG (&dict_pcre->dict)); +} + +#endif /* HAS_PCRE */ diff --git a/src/util/dict_pcre.h b/src/util/dict_pcre.h new file mode 100644 index 0000000..3aa4955 --- /dev/null +++ b/src/util/dict_pcre.h @@ -0,0 +1,43 @@ +#ifndef _DICT_PCRE_H_INCLUDED_ +#define _DICT_PCRE_H_INCLUDED_ + +/*++ +/* NAME +/* dict_pcre 3h +/* SUMMARY +/* dictionary manager interface to PCRE regular expression library +/* SYNOPSIS +/* #include <dict_pcre.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <dict.h> + + /* + * External interface. + */ +#define DICT_TYPE_PCRE "pcre" + +extern DICT *dict_pcre_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Andrew McNamara +/* andrewm@connect.com.au +/* connect.com.au Pty. Ltd. +/* Level 3, 213 Miller St +/* North Sydney, NSW, Australia +/*--*/ + +#endif diff --git a/src/util/dict_pcre.in b/src/util/dict_pcre.in new file mode 100644 index 0000000..b0644bd --- /dev/null +++ b/src/util/dict_pcre.in @@ -0,0 +1,15 @@ +get true +get true1 +get true2 +get truefalse2 +get 3 +get true3 +get c +get d +get 1235 +get 1234 +get 123 +get bar/find +get bar/whynot +get bar/elbereth +get say/elbereth diff --git a/src/util/dict_pcre.map b/src/util/dict_pcre.map new file mode 100644 index 0000000..23c976a --- /dev/null +++ b/src/util/dict_pcre.map @@ -0,0 +1,28 @@ +if /true/ fodder +/1/ 1 +if /false/ +/2/ 2 +endif fodder +/3/ 3 +endif +/a/!/b/ a!b +/c/ +/(1)(2)(3)(5)/ ($1)($2)($3)($4)($5) +/(1)(2)(3)(4)/ ($1)($2)($3)($4) +/(1)(2)(3)/ ($1)($2)($3) +# trailing whitespace below +if /bar/ +if !/xyzzy/ +/(elbereth)/ ($1) +!/(bogus)/ ($1) +!/find/ Don't have a liquor license +endif +endif +# trailing whitespace above +! +# dangling endif and if +endif +endif +if /./ +if /./ +/(/ unused diff --git a/src/util/dict_pcre.ref b/src/util/dict_pcre.ref new file mode 100644 index 0000000..9b14678 --- /dev/null +++ b/src/util/dict_pcre.ref @@ -0,0 +1,44 @@ +./dict_open: warning: pcre map dict_pcre.map, line 1: ignoring extra text after IF statement: "fodder" +./dict_open: warning: pcre map dict_pcre.map, line 1: do not prepend whitespace to statements between IF and ENDIF +./dict_open: warning: pcre map dict_pcre.map, line 5: ignoring extra text after ENDIF +./dict_open: warning: pcre map dict_pcre.map, line 8: unknown regexp option "!": skipping this rule +./dict_open: warning: pcre map dict_pcre.map, line 9: no replacement text: using empty string +./dict_open: warning: pcre map dict_pcre.map, line 10: out of range replacement index "5": skipping this rule +./dict_open: warning: pcre map dict_pcre.map, line 17: $number found in negative match replacement text: skipping this rule +./dict_open: warning: pcre map dict_pcre.map, line 22: no regexp: skipping this rule +./dict_open: warning: pcre map dict_pcre.map, line 24: ignoring ENDIF without matching IF +./dict_open: warning: pcre map dict_pcre.map, line 25: ignoring ENDIF without matching IF +./dict_open: warning: pcre map dict_pcre.map, line 28: error in regex at offset 1: missing closing parenthesis +./dict_open: warning: pcre map dict_pcre.map, line 27: IF has no matching ENDIF +./dict_open: warning: pcre map dict_pcre.map, line 26: IF has no matching ENDIF +owner=untrusted (uid=USER) +> get true +true: not found +> get true1 +true1=1 +> get true2 +true2: not found +> get truefalse2 +truefalse2=2 +> get 3 +3: not found +> get true3 +true3=3 +> get c +c= +> get d +d: not found +> get 1235 +1235=(1)(2)(3) +> get 1234 +1234=(1)(2)(3)(4) +> get 123 +123=(1)(2)(3) +> get bar/find +bar/find: not found +> get bar/whynot +bar/whynot=Don't have a liquor license +> get bar/elbereth +bar/elbereth=(elbereth) +> get say/elbereth +say/elbereth: not found diff --git a/src/util/dict_pcre_file.in b/src/util/dict_pcre_file.in new file mode 100644 index 0000000..28c0bd5 --- /dev/null +++ b/src/util/dict_pcre_file.in @@ -0,0 +1,4 @@ +get file1 +get file2 +get file3 +get files12 diff --git a/src/util/dict_pcre_file.map b/src/util/dict_pcre_file.map new file mode 100644 index 0000000..4fd12e6 --- /dev/null +++ b/src/util/dict_pcre_file.map @@ -0,0 +1,6 @@ +/file1/ dict_pcre_file1 +/file2/ dict_pcre_file2 +/file3/ dict_pcre_file3 +/files12/ dict_pcre_file1, dict_pcre_file2 +/files13/ dict_pcre_file1, dict_pcre_file3 +/file-comma/ , diff --git a/src/util/dict_pcre_file.ref b/src/util/dict_pcre_file.ref new file mode 100644 index 0000000..727306d --- /dev/null +++ b/src/util/dict_pcre_file.ref @@ -0,0 +1,12 @@ +./dict_open: warning: pcre map dict_pcre_file.map, line 3: open dict_pcre_file3: No such file or directory: skipping this rule +./dict_open: warning: pcre map dict_pcre_file.map, line 5: open dict_pcre_file3: No such file or directory: skipping this rule +./dict_open: warning: pcre map dict_pcre_file.map, line 6: empty pathname list: >>,<<': skipping this rule +owner=untrusted (uid=USER) +> get file1 +file1=dGhpcy1pcy1maWxlMQo= +> get file2 +file2=dGhpcy1pcy1maWxlMgo= +> get file3 +file3: not found +> get files12 +files12=dGhpcy1pcy1maWxlMQoKdGhpcy1pcy1maWxlMgo= diff --git a/src/util/dict_pipe.c b/src/util/dict_pipe.c new file mode 100644 index 0000000..8ce0faa --- /dev/null +++ b/src/util/dict_pipe.c @@ -0,0 +1,189 @@ +/*++ +/* NAME +/* dict_pipe 3 +/* SUMMARY +/* dictionary manager interface for pipelined tables +/* SYNOPSIS +/* #include <dict_pipe.h> +/* +/* DICT *dict_pipe_open(name, open_flags, dict_flags) +/* const char *name; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_pipe_open() opens a pipeline of one or more tables. +/* Example: "\fBpipemap:{\fItype_1:name_1, ..., type_n:name_n\fR}". +/* +/* Each "pipemap:" query is given to the first table. Each +/* lookup result becomes the query for the next table in the +/* pipeline, and the last table produces the final result. +/* When any table lookup produces no result, the pipeline +/* produces no result. +/* +/* The first and last characters of the "pipemap:" table name +/* must be '{' and '}'. Within these, individual maps are +/* separated with comma or whitespace. +/* +/* The open_flags and dict_flags arguments are passed on to +/* the underlying dictionaries. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include "mymalloc.h" +#include "htable.h" +#include "dict.h" +#include "dict_pipe.h" +#include "stringops.h" +#include "vstring.h" + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + ARGV *map_pipe; /* pipelined tables */ + VSTRING *qr_buf; /* query/reply buffer */ +} DICT_PIPE; + +#define STR(x) vstring_str(x) + +/* dict_pipe_lookup - search pipelined tables */ + +static const char *dict_pipe_lookup(DICT *dict, const char *query) +{ + static const char myname[] = "dict_pipe_lookup"; + DICT_PIPE *dict_pipe = (DICT_PIPE *) dict; + DICT *map; + char **cpp; + char *dict_type_name; + const char *result = 0; + + vstring_strcpy(dict_pipe->qr_buf, query); + for (cpp = dict_pipe->map_pipe->argv; (dict_type_name = *cpp) != 0; cpp++) { + if ((map = dict_handle(dict_type_name)) == 0) + msg_panic("%s: dictionary \"%s\" not found", myname, dict_type_name); + if ((result = dict_get(map, STR(dict_pipe->qr_buf))) == 0) + DICT_ERR_VAL_RETURN(dict, map->error, result); + vstring_strcpy(dict_pipe->qr_buf, result); + } + DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, STR(dict_pipe->qr_buf)); +} + +/* dict_pipe_close - disassociate from pipelined tables */ + +static void dict_pipe_close(DICT *dict) +{ + DICT_PIPE *dict_pipe = (DICT_PIPE *) dict; + char **cpp; + char *dict_type_name; + + for (cpp = dict_pipe->map_pipe->argv; (dict_type_name = *cpp) != 0; cpp++) + dict_unregister(dict_type_name); + argv_free(dict_pipe->map_pipe); + vstring_free(dict_pipe->qr_buf); + dict_free(dict); +} + +/* dict_pipe_open - open pipelined tables */ + +DICT *dict_pipe_open(const char *name, int open_flags, int dict_flags) +{ + static const char myname[] = "dict_pipe_open"; + DICT_PIPE *dict_pipe; + char *saved_name = 0; + char *dict_type_name; + ARGV *argv = 0; + char **cpp; + DICT *dict; + int match_flags = 0; + struct DICT_OWNER aggr_owner; + size_t len; + + /* + * Clarity first. Let the optimizer worry about redundant code. + */ +#define DICT_PIPE_RETURN(x) do { \ + if (saved_name != 0) \ + myfree(saved_name); \ + if (argv != 0) \ + argv_free(argv); \ + return (x); \ + } while (0) + + /* + * Sanity checks. + */ + if (open_flags != O_RDONLY) + DICT_PIPE_RETURN(dict_surrogate(DICT_TYPE_PIPE, name, + open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_PIPE, name)); + + /* + * Split the table name into its constituent parts. + */ + if ((len = balpar(name, CHARS_BRACE)) == 0 || name[len] != 0 + || *(saved_name = mystrndup(name + 1, len - 2)) == 0 + || ((argv = argv_splitq(saved_name, CHARS_COMMA_SP, CHARS_BRACE)), + (argv->argc == 0))) + DICT_PIPE_RETURN(dict_surrogate(DICT_TYPE_PIPE, name, + open_flags, dict_flags, + "bad syntax: \"%s:%s\"; " + "need \"%s:{type:name...}\"", + DICT_TYPE_PIPE, name, + DICT_TYPE_PIPE)); + + /* + * The least-trusted table in the pipeline determines the over-all trust + * level. The first table determines the pattern-matching flags. + */ + DICT_OWNER_AGGREGATE_INIT(aggr_owner); + for (cpp = argv->argv; (dict_type_name = *cpp) != 0; cpp++) { + if (msg_verbose) + msg_info("%s: %s", myname, dict_type_name); + if (strchr(dict_type_name, ':') == 0) + DICT_PIPE_RETURN(dict_surrogate(DICT_TYPE_PIPE, name, + open_flags, dict_flags, + "bad syntax: \"%s:%s\"; " + "need \"%s:{type:name...}\"", + DICT_TYPE_PIPE, name, + DICT_TYPE_PIPE)); + if ((dict = dict_handle(dict_type_name)) == 0) + dict = dict_open(dict_type_name, open_flags, dict_flags); + dict_register(dict_type_name, dict); + DICT_OWNER_AGGREGATE_UPDATE(aggr_owner, dict->owner); + if (cpp == argv->argv) + match_flags = dict->flags & (DICT_FLAG_FIXED | DICT_FLAG_PATTERN); + } + + /* + * Bundle up the result. + */ + dict_pipe = + (DICT_PIPE *) dict_alloc(DICT_TYPE_PIPE, name, sizeof(*dict_pipe)); + dict_pipe->dict.lookup = dict_pipe_lookup; + dict_pipe->dict.close = dict_pipe_close; + dict_pipe->dict.flags = dict_flags | match_flags; + dict_pipe->dict.owner = aggr_owner; + dict_pipe->qr_buf = vstring_alloc(100); + dict_pipe->map_pipe = argv; + argv = 0; + DICT_PIPE_RETURN(DICT_DEBUG (&dict_pipe->dict)); +} diff --git a/src/util/dict_pipe.h b/src/util/dict_pipe.h new file mode 100644 index 0000000..8d03009 --- /dev/null +++ b/src/util/dict_pipe.h @@ -0,0 +1,37 @@ +#ifndef _DICT_PIPE_H_INCLUDED_ +#define _DICT_PIPE_H_INCLUDED_ + +/*++ +/* NAME +/* dict_pipe 3h +/* SUMMARY +/* dictionary manager interface for pipelined tables +/* SYNOPSIS +/* #include <dict_pipe.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <dict.h> + + /* + * External interface. + */ +#define DICT_TYPE_PIPE "pipemap" + +extern DICT *dict_pipe_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_pipe_test.in b/src/util/dict_pipe_test.in new file mode 100644 index 0000000..9626dcd --- /dev/null +++ b/src/util/dict_pipe_test.in @@ -0,0 +1,9 @@ +${VALGRIND} ./dict_open 'pipemap:{inline:{k1=v1,k2=v2},inline:{v2=v3}}' read <<EOF +get k0 +get k1 +get k2 +EOF +${VALGRIND} ./dict_open 'pipemap:{inline:{k1=v1},fail:fail}' read <<EOF +get k0 +get k1 +EOF diff --git a/src/util/dict_pipe_test.ref b/src/util/dict_pipe_test.ref new file mode 100644 index 0000000..ecb865a --- /dev/null +++ b/src/util/dict_pipe_test.ref @@ -0,0 +1,14 @@ ++ ./dict_open pipemap:{inline:{k1=v1,k2=v2},inline:{v2=v3}} read +owner=trusted (uid=2147483647) +> get k0 +k0: not found +> get k1 +k1: not found +> get k2 +k2=v3 ++ ./dict_open pipemap:{inline:{k1=v1},fail:fail} read +owner=trusted (uid=2147483647) +> get k0 +k0: not found +> get k1 +k1: error diff --git a/src/util/dict_random.c b/src/util/dict_random.c new file mode 100644 index 0000000..36f79b3 --- /dev/null +++ b/src/util/dict_random.c @@ -0,0 +1,179 @@ +/*++ +/* NAME +/* dict_random 3 +/* SUMMARY +/* dictionary manager interface for randomized tables +/* SYNOPSIS +/* #include <dict_random.h> +/* +/* DICT *dict_random_open(name, open_flags, dict_flags) +/* const char *name; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_random_open() opens an in-memory, read-only, table. +/* Example: "\fBrandmap:{\fIresult_1, ... ,result_n}\fR". +/* +/* Each table query returns a random choice from the specified +/* results. Other table access methods are not supported. +/* +/* The first and last characters of the "randmap:" table name +/* must be '{' and '}'. Within these, individual maps are +/* separated with comma or whitespace. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <myrand.h> +#include <stringops.h> +#include <dict_random.h> + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + ARGV *replies; /* reply values */ +} DICT_RANDOM; + +#define STR(x) vstring_str(x) + +/* dict_random_lookup - find randomized-table entry */ + +static const char *dict_random_lookup(DICT *dict, const char *unused_query) +{ + DICT_RANDOM *dict_random = (DICT_RANDOM *) dict; + + DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, + dict_random->replies->argv[myrand() % dict_random->replies->argc]); +} + +/* dict_random_close - disassociate from randomized table */ + +static void dict_random_close(DICT *dict) +{ + DICT_RANDOM *dict_random = (DICT_RANDOM *) dict; + + if (dict_random->replies) + argv_free(dict_random->replies); + dict_free(dict); +} + +static char *dict_random_parse_name(DICT *dict, ARGV **argv, + const char *string, + const char *delim, + const char *parens) +{ + ARGV *argvp = argv_alloc(1); + char *saved_string = mystrdup(string); + char *bp = saved_string; + char *arg; + VSTRING *b64 = 0; + char *err = 0; + + while ((arg = mystrtokq(&bp, delim, parens)) != 0) { + if (dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) { + if ((b64 = dict_file_to_b64(dict, arg)) != 0) { + argv_add(argvp, vstring_str(b64), (char *) 0); + } else { + err = dict_file_get_error(dict); + break; + } + } else { + argv_add(argvp, arg, (char *) 0); + } + } + argv_terminate(argvp); + myfree(saved_string); + *argv = argvp; + return (err); +} + +/* dict_random_open - open a randomized table */ + +DICT *dict_random_open(const char *name, int open_flags, int dict_flags) +{ + DICT_RANDOM *dict_random; + char *saved_name = 0; + size_t len; + char *err = 0; + + /* + * Clarity first. Let the optimizer worry about redundant code. + */ +#define DICT_RANDOM_RETURN(x) do { \ + DICT *__d = (x); \ + if (saved_name != 0) \ + myfree(saved_name); \ + if (err != 0) \ + myfree(err); \ + return (__d); \ + } while (0) + + /* + * Sanity checks. + */ + if (open_flags != O_RDONLY) + DICT_RANDOM_RETURN(dict_surrogate(DICT_TYPE_RANDOM, name, + open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_RANDOM, name)); + + /* + * Bundle up preliminary results. + */ + dict_random = + (DICT_RANDOM *) dict_alloc(DICT_TYPE_RANDOM, name, sizeof(*dict_random)); + dict_random->dict.lookup = dict_random_lookup; + dict_random->dict.close = dict_random_close; + dict_random->dict.flags = dict_flags | DICT_FLAG_PATTERN; + dict_random->replies = 0; + dict_random->dict.owner.status = DICT_OWNER_TRUSTED; + dict_random->dict.owner.uid = 0; + + /* + * Split the table name into its constituent parts. + */ + if ((len = balpar(name, CHARS_BRACE)) == 0 || name[len] != 0 + || *(saved_name = mystrndup(name + 1, len - 2)) == 0 + || (err = dict_random_parse_name(&dict_random->dict, + &dict_random->replies, saved_name, + CHARS_COMMA_SP, CHARS_BRACE)) != 0 + || dict_random->replies->argc == 0) { + dict_random_close(&dict_random->dict); + DICT_RANDOM_RETURN(err == 0 ? + dict_surrogate(DICT_TYPE_RANDOM, name, + open_flags, dict_flags, + "bad syntax: \"%s:%s\"; " + "need \"%s:{value...}\"", + DICT_TYPE_RANDOM, name, + DICT_TYPE_RANDOM) : + dict_surrogate(DICT_TYPE_RANDOM, name, + open_flags, dict_flags, + "%s", err)); + } + dict_file_purge_buffers(&dict_random->dict); + DICT_RANDOM_RETURN(DICT_DEBUG (&dict_random->dict)); +} diff --git a/src/util/dict_random.h b/src/util/dict_random.h new file mode 100644 index 0000000..b143d11 --- /dev/null +++ b/src/util/dict_random.h @@ -0,0 +1,37 @@ +#ifndef _DICT_RANDOM_H_INCLUDED_ +#define _DICT_RANDOM_H_INCLUDED_ + +/*++ +/* NAME +/* dict_random 3h +/* SUMMARY +/* dictionary manager interface for randomized tables +/* SYNOPSIS +/* #include <dict_random.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <dict.h> + + /* + * External interface. + */ +#define DICT_TYPE_RANDOM "randmap" + +extern DICT *dict_random_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_random.ref b/src/util/dict_random.ref new file mode 100644 index 0000000..0993409 --- /dev/null +++ b/src/util/dict_random.ref @@ -0,0 +1,20 @@ +owner=trusted (uid=0) +> get foo +foo=123 +> get bar +bar=123 +./dict_open: error: bad syntax: "randmap:123"; need "randmap:{value...}" +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: randmap:123 is unavailable. bad syntax: "randmap:123"; need "randmap:{value...}" +foo: error +./dict_open: error: bad syntax: "randmap:{ 123 "; need "randmap:{value...}" +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: randmap:{ 123 is unavailable. bad syntax: "randmap:{ 123 "; need "randmap:{value...}" +foo: error +./dict_open: error: bad syntax: "randmap:{ 123 }x"; need "randmap:{value...}" +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: randmap:{ 123 }x is unavailable. bad syntax: "randmap:{ 123 }x"; need "randmap:{value...}" +foo: error diff --git a/src/util/dict_random_file.ref b/src/util/dict_random_file.ref new file mode 100644 index 0000000..cb01873 --- /dev/null +++ b/src/util/dict_random_file.ref @@ -0,0 +1,10 @@ +./dict_open: error: open fooxx: No such file or directory +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: randmap:{fooxx} is unavailable. open fooxx: No such file or directory +foo: error +owner=trusted (uid=0) +> get foo +foo=dGhpcy1pcy1maWxlMQo= +> get bar +bar=dGhpcy1pcy1maWxlMQo= diff --git a/src/util/dict_regexp.c b/src/util/dict_regexp.c new file mode 100644 index 0000000..e5e95cf --- /dev/null +++ b/src/util/dict_regexp.c @@ -0,0 +1,874 @@ +/*++ +/* NAME +/* dict_regexp 3 +/* SUMMARY +/* dictionary manager interface to REGEXP regular expression library +/* SYNOPSIS +/* #include <dict_regexp.h> +/* +/* DICT *dict_regexp_open(name, dummy, dict_flags) +/* const char *name; +/* int dummy; +/* int dict_flags; +/* DESCRIPTION +/* dict_regexp_open() opens the named file and compiles the contained +/* regular expressions. The result object can be used to match strings +/* against the table. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* regexp_table(5) format of Postfix regular expression tables +/* AUTHOR(S) +/* LaMont Jones +/* lamont@hp.com +/* +/* Based on PCRE dictionary contributed by Andrew McNamara +/* andrewm@connect.com.au +/* connect.com.au Pty. Ltd. +/* Level 3, 213 Miller St +/* North Sydney, NSW, Australia +/* +/* Heavily rewritten by Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" + +#ifdef HAS_POSIX_REGEXP + +#include <sys/stat.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <regex.h> +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif + +/* Utility library. */ + +#include "mymalloc.h" +#include "msg.h" +#include "safe.h" +#include "vstream.h" +#include "vstring.h" +#include "stringops.h" +#include "readlline.h" +#include "dict.h" +#include "dict_regexp.h" +#include "mac_parse.h" +#include "warn_stat.h" +#include "mvect.h" + + /* + * Support for IF/ENDIF based on an idea by Bert Driehuis. + */ +#define DICT_REGEXP_OP_MATCH 1 /* Match this regexp */ +#define DICT_REGEXP_OP_IF 2 /* Increase if/endif nesting on match */ +#define DICT_REGEXP_OP_ENDIF 3 /* Decrease if/endif nesting on match */ + + /* + * Regular expression before compiling. + */ +typedef struct { + char *regexp; /* regular expression */ + int options; /* regcomp() options */ + int match; /* positive or negative match */ +} DICT_REGEXP_PATTERN; + + /* + * Compiled generic rule, and subclasses that derive from it. + */ +typedef struct DICT_REGEXP_RULE { + int op; /* DICT_REGEXP_OP_MATCH/IF/ENDIF */ + int lineno; /* source file line number */ + struct DICT_REGEXP_RULE *next; /* next rule in dict */ +} DICT_REGEXP_RULE; + +typedef struct { + DICT_REGEXP_RULE rule; /* generic part */ + regex_t *first_exp; /* compiled primary pattern */ + int first_match; /* positive or negative match */ + regex_t *second_exp; /* compiled secondary pattern */ + int second_match; /* positive or negative match */ + char *replacement; /* replacement text */ + size_t max_sub; /* largest $number in replacement */ +} DICT_REGEXP_MATCH_RULE; + +typedef struct { + DICT_REGEXP_RULE rule; /* generic members */ + regex_t *expr; /* the condition */ + int match; /* positive or negative match */ + struct DICT_REGEXP_RULE *endif_rule;/* matching endif rule */ +} DICT_REGEXP_IF_RULE; + + /* + * Regexp map. + */ +typedef struct { + DICT dict; /* generic members */ + regmatch_t *pmatch; /* matched substring info */ + DICT_REGEXP_RULE *head; /* first rule */ + VSTRING *expansion_buf; /* lookup result */ +} DICT_REGEXP; + + /* + * Macros to make dense code more readable. + */ +#define NULL_SUBSTITUTIONS (0) +#define NULL_MATCH_RESULT ((regmatch_t *) 0) + + /* + * Context for $number expansion callback. + */ +typedef struct { + DICT_REGEXP *dict_regexp; /* the dictionary handle */ + DICT_REGEXP_MATCH_RULE *match_rule; /* the rule we matched */ + const char *lookup_string; /* matched text */ +} DICT_REGEXP_EXPAND_CONTEXT; + + /* + * Context for $number pre-scan callback. + */ +typedef struct { + const char *mapname; /* name of regexp map */ + int lineno; /* where in file */ + size_t max_sub; /* largest $number seen */ + char *literal; /* constant result, $$ -> $ */ +} DICT_REGEXP_PRESCAN_CONTEXT; + + /* + * Compatibility. + */ +#ifndef MAC_PARSE_OK +#define MAC_PARSE_OK 0 +#endif + +/* dict_regexp_expand - replace $number with substring from matched text */ + +static int dict_regexp_expand(int type, VSTRING *buf, void *ptr) +{ + DICT_REGEXP_EXPAND_CONTEXT *ctxt = (DICT_REGEXP_EXPAND_CONTEXT *) ptr; + DICT_REGEXP_MATCH_RULE *match_rule = ctxt->match_rule; + DICT_REGEXP *dict_regexp = ctxt->dict_regexp; + regmatch_t *pmatch; + size_t n; + + /* + * Replace $number by the corresponding substring from the matched text. + * We pre-scanned the replacement text at compile time, so any out of + * range $number means that something impossible has happened. + */ + if (type == MAC_PARSE_VARNAME) { + n = atoi(vstring_str(buf)); + if (n < 1 || n > match_rule->max_sub) + msg_panic("regexp map %s, line %d: out of range replacement index \"%s\"", + dict_regexp->dict.name, match_rule->rule.lineno, + vstring_str(buf)); + pmatch = dict_regexp->pmatch + n; + if (pmatch->rm_so < 0 || pmatch->rm_so == pmatch->rm_eo) + return (MAC_PARSE_UNDEF); /* empty or not matched */ + vstring_strncat(dict_regexp->expansion_buf, + ctxt->lookup_string + pmatch->rm_so, + pmatch->rm_eo - pmatch->rm_so); + return (MAC_PARSE_OK); + } + + /* + * Straight text - duplicate with no substitution. + */ + else { + vstring_strcat(dict_regexp->expansion_buf, vstring_str(buf)); + return (MAC_PARSE_OK); + } +} + +/* dict_regexp_regerror - report regexp compile/execute error */ + +static void dict_regexp_regerror(const char *mapname, int lineno, int error, + const regex_t *expr) +{ + char errbuf[256]; + + (void) regerror(error, expr, errbuf, sizeof(errbuf)); + msg_warn("regexp map %s, line %d: %s", mapname, lineno, errbuf); +} + + /* + * Inlined to reduce function call overhead in the time-critical loop. + */ +#define DICT_REGEXP_REGEXEC(err, map, line, expr, match, str, nsub, pmatch) \ + ((err) = regexec((expr), (str), (nsub), (pmatch), 0), \ + ((err) == REG_NOMATCH ? !(match) : \ + (err) == 0 ? (match) : \ + (dict_regexp_regerror((map), (line), (err), (expr)), 0))) + +/* dict_regexp_lookup - match string and perform optional substitution */ + +static const char *dict_regexp_lookup(DICT *dict, const char *lookup_string) +{ + DICT_REGEXP *dict_regexp = (DICT_REGEXP *) dict; + DICT_REGEXP_RULE *rule; + DICT_REGEXP_IF_RULE *if_rule; + DICT_REGEXP_MATCH_RULE *match_rule; + DICT_REGEXP_EXPAND_CONTEXT expand_context; + int error; + + dict->error = 0; + + if (msg_verbose) + msg_info("dict_regexp_lookup: %s: %s", dict->name, lookup_string); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_MUL) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, lookup_string); + lookup_string = lowercase(vstring_str(dict->fold_buf)); + } + for (rule = dict_regexp->head; rule; rule = rule->next) { + + switch (rule->op) { + + /* + * Search for the first matching primary expression. Limit the + * overhead for substring substitution to the bare minimum. + */ + case DICT_REGEXP_OP_MATCH: + match_rule = (DICT_REGEXP_MATCH_RULE *) rule; + if (!DICT_REGEXP_REGEXEC(error, dict->name, rule->lineno, + match_rule->first_exp, + match_rule->first_match, + lookup_string, + match_rule->max_sub > 0 ? + match_rule->max_sub + 1 : 0, + dict_regexp->pmatch)) + continue; + if (match_rule->second_exp + && !DICT_REGEXP_REGEXEC(error, dict->name, rule->lineno, + match_rule->second_exp, + match_rule->second_match, + lookup_string, + NULL_SUBSTITUTIONS, + NULL_MATCH_RESULT)) + continue; + + /* + * Skip $number substitutions when the replacement text contains + * no $number strings, as learned during the compile time + * pre-scan. The pre-scan already replaced $$ by $. + */ + if (match_rule->max_sub == 0) + return (match_rule->replacement); + + /* + * Perform $number substitutions on the replacement text. We + * pre-scanned the replacement text at compile time. Any macro + * expansion errors at this point mean something impossible has + * happened. + */ + if (!dict_regexp->expansion_buf) + dict_regexp->expansion_buf = vstring_alloc(10); + VSTRING_RESET(dict_regexp->expansion_buf); + expand_context.lookup_string = lookup_string; + expand_context.match_rule = match_rule; + expand_context.dict_regexp = dict_regexp; + + if (mac_parse(match_rule->replacement, dict_regexp_expand, + (void *) &expand_context) & MAC_PARSE_ERROR) + msg_panic("regexp map %s, line %d: bad replacement syntax", + dict->name, rule->lineno); + VSTRING_TERMINATE(dict_regexp->expansion_buf); + return (vstring_str(dict_regexp->expansion_buf)); + + /* + * Conditional. + */ + case DICT_REGEXP_OP_IF: + if_rule = (DICT_REGEXP_IF_RULE *) rule; + if (DICT_REGEXP_REGEXEC(error, dict->name, rule->lineno, + if_rule->expr, if_rule->match, lookup_string, + NULL_SUBSTITUTIONS, NULL_MATCH_RESULT)) + continue; + /* An IF without matching ENDIF has no "endif" rule. */ + if ((rule = if_rule->endif_rule) == 0) + return (0); + /* FALLTHROUGH */ + + /* + * ENDIF after IF. + */ + case DICT_REGEXP_OP_ENDIF: + continue; + + default: + msg_panic("dict_regexp_lookup: impossible operation %d", rule->op); + } + } + return (0); +} + +/* dict_regexp_close - close regexp dictionary */ + +static void dict_regexp_close(DICT *dict) +{ + DICT_REGEXP *dict_regexp = (DICT_REGEXP *) dict; + DICT_REGEXP_RULE *rule; + DICT_REGEXP_RULE *next; + DICT_REGEXP_MATCH_RULE *match_rule; + DICT_REGEXP_IF_RULE *if_rule; + + for (rule = dict_regexp->head; rule; rule = next) { + next = rule->next; + switch (rule->op) { + case DICT_REGEXP_OP_MATCH: + match_rule = (DICT_REGEXP_MATCH_RULE *) rule; + if (match_rule->first_exp) { + regfree(match_rule->first_exp); + myfree((void *) match_rule->first_exp); + } + if (match_rule->second_exp) { + regfree(match_rule->second_exp); + myfree((void *) match_rule->second_exp); + } + if (match_rule->replacement) + myfree((void *) match_rule->replacement); + break; + case DICT_REGEXP_OP_IF: + if_rule = (DICT_REGEXP_IF_RULE *) rule; + if (if_rule->expr) { + regfree(if_rule->expr); + myfree((void *) if_rule->expr); + } + break; + case DICT_REGEXP_OP_ENDIF: + break; + default: + msg_panic("dict_regexp_close: unknown operation %d", rule->op); + } + myfree((void *) rule); + } + if (dict_regexp->pmatch) + myfree((void *) dict_regexp->pmatch); + if (dict_regexp->expansion_buf) + vstring_free(dict_regexp->expansion_buf); + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_regexp_get_pat - extract one pattern with options from rule */ + +static int dict_regexp_get_pat(const char *mapname, int lineno, char **bufp, + DICT_REGEXP_PATTERN *pat) +{ + char *p = *bufp; + char re_delim; + + /* + * Process negation operators. + */ + pat->match = 1; + for (;;) { + if (*p == '!') + pat->match = !pat->match; + else if (!ISSPACE(*p)) + break; + p++; + } + if (*p == 0) { + msg_warn("regexp map %s, line %d: no regexp: skipping this rule", + mapname, lineno); + return (0); + } + + /* + * Search for the closing delimiter, handling backslash escape. + */ + re_delim = *p++; + pat->regexp = p; + while (*p) { + if (*p == '\\') { + if (p[1]) + p++; + else + break; + } else if (*p == re_delim) { + break; + } + ++p; + } + if (!*p) { + msg_warn("regexp map %s, line %d: no closing regexp delimiter \"%c\": " + "skipping this rule", mapname, lineno, re_delim); + return (0); + } + *p++ = 0; /* null terminate */ + + /* + * Search for options. + */ + pat->options = REG_EXTENDED | REG_ICASE; + while (*p && !ISSPACE(*p) && *p != '!') { + switch (*p) { + case 'i': + pat->options ^= REG_ICASE; + break; + case 'm': + pat->options ^= REG_NEWLINE; + break; + case 'x': + pat->options ^= REG_EXTENDED; + break; + default: + msg_warn("regexp map %s, line %d: unknown regexp option \"%c\": " + "skipping this rule", mapname, lineno, *p); + return (0); + } + ++p; + } + *bufp = p; + return (1); +} + +/* dict_regexp_get_pats - get the primary and second patterns and flags */ + +static int dict_regexp_get_pats(const char *mapname, int lineno, char **p, + DICT_REGEXP_PATTERN *first_pat, + DICT_REGEXP_PATTERN *second_pat) +{ + + /* + * Get the primary and optional secondary patterns and their flags. + */ + if (dict_regexp_get_pat(mapname, lineno, p, first_pat) == 0) + return (0); + if (**p == '!') { +#if 0 + static int bitrot_warned = 0; + + if (bitrot_warned == 0) { + msg_warn("regexp file %s, line %d: /pattern1/!/pattern2/ goes away," + " use \"if !/pattern2/ ... /pattern1/ ... endif\" instead", + mapname, lineno); + bitrot_warned = 1; + } +#endif + if (dict_regexp_get_pat(mapname, lineno, p, second_pat) == 0) + return (0); + } else { + second_pat->regexp = 0; + } + return (1); +} + +/* dict_regexp_prescan - find largest $number in replacement text */ + +static int dict_regexp_prescan(int type, VSTRING *buf, void *context) +{ + DICT_REGEXP_PRESCAN_CONTEXT *ctxt = (DICT_REGEXP_PRESCAN_CONTEXT *) context; + size_t n; + + /* + * Keep a copy of literal text (with $$ already replaced by $) if and + * only if the replacement text contains no $number expression. This way + * we can avoid having to scan the replacement text at lookup time. + */ + if (type == MAC_PARSE_VARNAME) { + if (ctxt->literal) { + myfree(ctxt->literal); + ctxt->literal = 0; + } + if (!alldig(vstring_str(buf))) { + msg_warn("regexp map %s, line %d: non-numeric replacement index \"%s\"", + ctxt->mapname, ctxt->lineno, vstring_str(buf)); + return (MAC_PARSE_ERROR); + } + n = atoi(vstring_str(buf)); + if (n < 1) { + msg_warn("regexp map %s, line %d: out-of-range replacement index \"%s\"", + ctxt->mapname, ctxt->lineno, vstring_str(buf)); + return (MAC_PARSE_ERROR); + } + if (n > ctxt->max_sub) + ctxt->max_sub = n; + } else if (type == MAC_PARSE_LITERAL && ctxt->max_sub == 0) { + if (ctxt->literal) + msg_panic("regexp map %s, line %d: multiple literals but no $number", + ctxt->mapname, ctxt->lineno); + ctxt->literal = mystrdup(vstring_str(buf)); + } + return (MAC_PARSE_OK); +} + +/* dict_regexp_compile_pat - compile one pattern */ + +static regex_t *dict_regexp_compile_pat(const char *mapname, int lineno, + DICT_REGEXP_PATTERN *pat) +{ + int error; + regex_t *expr; + + expr = (regex_t *) mymalloc(sizeof(*expr)); + error = regcomp(expr, pat->regexp, pat->options); + if (error != 0) { + dict_regexp_regerror(mapname, lineno, error, expr); + myfree((void *) expr); + return (0); + } + return (expr); +} + +/* dict_regexp_rule_alloc - fill in a generic rule structure */ + +static DICT_REGEXP_RULE *dict_regexp_rule_alloc(int op, int lineno, size_t size) +{ + DICT_REGEXP_RULE *rule; + + rule = (DICT_REGEXP_RULE *) mymalloc(size); + rule->op = op; + rule->lineno = lineno; + rule->next = 0; + + return (rule); +} + +/* dict_regexp_parseline - parse one rule */ + +static DICT_REGEXP_RULE *dict_regexp_parseline(DICT *dict, const char *mapname, + int lineno, char *line, + int nesting) +{ + char *p; + + p = line; + + /* + * An ordinary rule takes one or two patterns and replacement text. + */ + if (!ISALNUM(*p)) { + DICT_REGEXP_PATTERN first_pat; + DICT_REGEXP_PATTERN second_pat; + DICT_REGEXP_PRESCAN_CONTEXT prescan_context; + regex_t *first_exp = 0; + regex_t *second_exp; + DICT_REGEXP_MATCH_RULE *match_rule; + + /* + * Get the primary and the optional secondary patterns. + */ + if (!dict_regexp_get_pats(mapname, lineno, &p, &first_pat, &second_pat)) + return (0); + + /* + * Get the replacement text. + */ + while (*p && ISSPACE(*p)) + ++p; + if (!*p) { + msg_warn("regexp map %s, line %d: no replacement text: " + "using empty string", mapname, lineno); + } + + /* + * Find the highest-numbered $number in the replacement text. We can + * speed up pattern matching 1) by passing hints to the regexp + * compiler, setting the REG_NOSUB flag when the replacement text + * contains no $number string; 2) by passing hints to the regexp + * execution code, limiting the amount of text that is made available + * for substitution. + */ + prescan_context.mapname = mapname; + prescan_context.lineno = lineno; + prescan_context.max_sub = 0; + prescan_context.literal = 0; + + /* + * The optimizer will eliminate code duplication and/or dead code. + */ +#define CREATE_MATCHOP_ERROR_RETURN(rval) do { \ + if (first_exp) { \ + regfree(first_exp); \ + myfree((void *) first_exp); \ + } \ + if (prescan_context.literal) \ + myfree(prescan_context.literal); \ + return (rval); \ + } while (0) + + if (dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) { + VSTRING *base64_buf; + char *err; + + if ((base64_buf = dict_file_to_b64(dict, p)) == 0) { + err = dict_file_get_error(dict); + msg_warn("regexp map %s, line %d: %s: skipping this rule", + mapname, lineno, err); + myfree(err); + CREATE_MATCHOP_ERROR_RETURN(0); + } + p = vstring_str(base64_buf); + } + if (mac_parse(p, dict_regexp_prescan, (void *) &prescan_context) + & MAC_PARSE_ERROR) { + msg_warn("regexp map %s, line %d: bad replacement syntax: " + "skipping this rule", mapname, lineno); + CREATE_MATCHOP_ERROR_RETURN(0); + } + + /* + * Compile the primary and the optional secondary pattern. Speed up + * execution when no matched text needs to be substituted into the + * result string, or when the highest numbered substring is less than + * the total number of () subpatterns. + */ + if (prescan_context.max_sub == 0) + first_pat.options |= REG_NOSUB; + if (prescan_context.max_sub > 0 && first_pat.match == 0) { + msg_warn("regexp map %s, line %d: $number found in negative match " + "replacement text: skipping this rule", mapname, lineno); + CREATE_MATCHOP_ERROR_RETURN(0); + } + if (prescan_context.max_sub > 0 && (dict->flags & DICT_FLAG_NO_REGSUB)) { + msg_warn("regexp map %s, line %d: " + "regular expression substitution is not allowed: " + "skipping this rule", mapname, lineno); + CREATE_MATCHOP_ERROR_RETURN(0); + } + if ((first_exp = dict_regexp_compile_pat(mapname, lineno, + &first_pat)) == 0) + CREATE_MATCHOP_ERROR_RETURN(0); + if (prescan_context.max_sub > first_exp->re_nsub) { + msg_warn("regexp map %s, line %d: out of range replacement index \"%d\": " + "skipping this rule", mapname, lineno, + (int) prescan_context.max_sub); + CREATE_MATCHOP_ERROR_RETURN(0); + } + if (second_pat.regexp != 0) { + second_pat.options |= REG_NOSUB; + if ((second_exp = dict_regexp_compile_pat(mapname, lineno, + &second_pat)) == 0) + CREATE_MATCHOP_ERROR_RETURN(0); + } else { + second_exp = 0; + } + match_rule = (DICT_REGEXP_MATCH_RULE *) + dict_regexp_rule_alloc(DICT_REGEXP_OP_MATCH, lineno, + sizeof(DICT_REGEXP_MATCH_RULE)); + match_rule->first_exp = first_exp; + match_rule->first_match = first_pat.match; + match_rule->max_sub = prescan_context.max_sub; + match_rule->second_exp = second_exp; + match_rule->second_match = second_pat.match; + if (prescan_context.literal) + match_rule->replacement = prescan_context.literal; + else + match_rule->replacement = mystrdup(p); + return ((DICT_REGEXP_RULE *) match_rule); + } + + /* + * The IF operator takes one pattern but no replacement text. + */ + else if (strncasecmp(p, "IF", 2) == 0 && !ISALNUM(p[2])) { + DICT_REGEXP_PATTERN pattern; + regex_t *expr; + DICT_REGEXP_IF_RULE *if_rule; + + p += 2; + while (*p && ISSPACE(*p)) + p++; + if (!dict_regexp_get_pat(mapname, lineno, &p, &pattern)) + return (0); + while (*p && ISSPACE(*p)) + ++p; + if (*p) { + msg_warn("regexp map %s, line %d: ignoring extra text after" + " IF statement: \"%s\"", mapname, lineno, p); + msg_warn("regexp map %s, line %d: do not prepend whitespace" + " to statements between IF and ENDIF", mapname, lineno); + } + if ((expr = dict_regexp_compile_pat(mapname, lineno, &pattern)) == 0) + return (0); + if_rule = (DICT_REGEXP_IF_RULE *) + dict_regexp_rule_alloc(DICT_REGEXP_OP_IF, lineno, + sizeof(DICT_REGEXP_IF_RULE)); + if_rule->expr = expr; + if_rule->match = pattern.match; + if_rule->endif_rule = 0; + return ((DICT_REGEXP_RULE *) if_rule); + } + + /* + * The ENDIF operator takes no patterns and no replacement text. + */ + else if (strncasecmp(p, "ENDIF", 5) == 0 && !ISALNUM(p[5])) { + DICT_REGEXP_RULE *rule; + + p += 5; + if (nesting == 0) { + msg_warn("regexp map %s, line %d: ignoring ENDIF without matching IF", + mapname, lineno); + return (0); + } + while (*p && ISSPACE(*p)) + ++p; + if (*p) + msg_warn("regexp map %s, line %d: ignoring extra text after ENDIF", + mapname, lineno); + rule = dict_regexp_rule_alloc(DICT_REGEXP_OP_ENDIF, lineno, + sizeof(DICT_REGEXP_RULE)); + return (rule); + } + + /* + * Unrecognized input. + */ + else { + msg_warn("regexp map %s, line %d: ignoring unrecognized request", + mapname, lineno); + return (0); + } +} + +/* dict_regexp_open - load and compile a file containing regular expressions */ + +DICT *dict_regexp_open(const char *mapname, int open_flags, int dict_flags) +{ + const char myname[] = "dict_regexp_open"; + DICT_REGEXP *dict_regexp; + VSTREAM *map_fp = 0; + struct stat st; + VSTRING *why = 0; + VSTRING *line_buffer = 0; + DICT_REGEXP_RULE *rule; + DICT_REGEXP_RULE *last_rule = 0; + int lineno; + int last_line = 0; + size_t max_sub = 0; + int nesting = 0; + char *p; + DICT_REGEXP_RULE **rule_stack = 0; + MVECT mvect; + + /* + * Let the optimizer worry about eliminating redundant code. + */ +#define DICT_REGEXP_OPEN_RETURN(d) do { \ + DICT *__d = (d); \ + if (line_buffer != 0) \ + vstring_free(line_buffer); \ + if (map_fp != 0) \ + vstream_fclose(map_fp); \ + if (why != 0) \ + vstring_free(why); \ + return (__d); \ + } while (0) + + /* + * Sanity checks. + */ + if (open_flags != O_RDONLY) + DICT_REGEXP_OPEN_RETURN(dict_surrogate(DICT_TYPE_REGEXP, + mapname, open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_REGEXP, mapname)); + + /* + * Open the configuration file. + */ + if ((map_fp = dict_stream_open(DICT_TYPE_REGEXP, mapname, O_RDONLY, + dict_flags, &st, &why)) == 0) + DICT_REGEXP_OPEN_RETURN(dict_surrogate(DICT_TYPE_REGEXP, mapname, + open_flags, dict_flags, + "%s", vstring_str(why))); + line_buffer = vstring_alloc(100); + + dict_regexp = (DICT_REGEXP *) dict_alloc(DICT_TYPE_REGEXP, mapname, + sizeof(*dict_regexp)); + dict_regexp->dict.lookup = dict_regexp_lookup; + dict_regexp->dict.close = dict_regexp_close; + dict_regexp->dict.flags = dict_flags | DICT_FLAG_PATTERN; + if (dict_flags & DICT_FLAG_FOLD_MUL) + dict_regexp->dict.fold_buf = vstring_alloc(10); + dict_regexp->head = 0; + dict_regexp->pmatch = 0; + dict_regexp->expansion_buf = 0; + dict_regexp->dict.owner.uid = st.st_uid; + dict_regexp->dict.owner.status = (st.st_uid != 0); + + /* + * Parse the regexp table. + */ + while (readllines(line_buffer, map_fp, &last_line, &lineno)) { + p = vstring_str(line_buffer); + trimblanks(p, 0)[0] = 0; + if (*p == 0) + continue; + rule = dict_regexp_parseline(&dict_regexp->dict, mapname, lineno, + p, nesting); + if (rule == 0) + continue; + if (rule->op == DICT_REGEXP_OP_MATCH) { + if (((DICT_REGEXP_MATCH_RULE *) rule)->max_sub > max_sub) + max_sub = ((DICT_REGEXP_MATCH_RULE *) rule)->max_sub; + } else if (rule->op == DICT_REGEXP_OP_IF) { + if (rule_stack == 0) + rule_stack = (DICT_REGEXP_RULE **) mvect_alloc(&mvect, + sizeof(*rule_stack), nesting + 1, + (MVECT_FN) 0, (MVECT_FN) 0); + else + rule_stack = + (DICT_REGEXP_RULE **) mvect_realloc(&mvect, nesting + 1); + rule_stack[nesting] = rule; + nesting++; + } else if (rule->op == DICT_REGEXP_OP_ENDIF) { + DICT_REGEXP_IF_RULE *if_rule; + + if (nesting-- <= 0) + /* Already handled in dict_regexp_parseline(). */ + msg_panic("%s: ENDIF without IF", myname); + if (rule_stack[nesting]->op != DICT_REGEXP_OP_IF) + msg_panic("%s: unexpected rule stack element type %d", + myname, rule_stack[nesting]->op); + if_rule = (DICT_REGEXP_IF_RULE *) rule_stack[nesting]; + if_rule->endif_rule = rule; + } + if (last_rule == 0) + dict_regexp->head = rule; + else + last_rule->next = rule; + last_rule = rule; + } + + while (nesting-- > 0) + msg_warn("regexp map %s, line %d: IF has no matching ENDIF", + mapname, rule_stack[nesting]->lineno); + + if (rule_stack) + (void) mvect_free(&mvect); + + /* + * Allocate space for only as many matched substrings as used in the + * replacement text. + */ + if (max_sub > 0) + dict_regexp->pmatch = + (regmatch_t *) mymalloc(sizeof(regmatch_t) * (max_sub + 1)); + + dict_file_purge_buffers(&dict_regexp->dict); + DICT_REGEXP_OPEN_RETURN(DICT_DEBUG (&dict_regexp->dict)); +} + +#endif diff --git a/src/util/dict_regexp.h b/src/util/dict_regexp.h new file mode 100644 index 0000000..2874b27 --- /dev/null +++ b/src/util/dict_regexp.h @@ -0,0 +1,42 @@ +#ifndef _DICT_REGEXP_H_INCLUDED_ +#define _DICT_REGEXP_H_INCLUDED_ + +/*++ +/* NAME +/* dict_regexp 3h +/* SUMMARY +/* dictionary manager interface to REGEXP regular expression library +/* SYNOPSIS +/* #include <dict_regexp.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <dict.h> + + /* + * External interface. + */ +#define DICT_TYPE_REGEXP "regexp" + +extern DICT *dict_regexp_open(const char *, int, int); + +/* AUTHOR(S) +/* LaMont Jones +/* lamont@hp.com +/* +/* Based on PCRE dictionary contributed by Andrew McNamara +/* andrewm@connect.com.au +/* connect.com.au Pty. Ltd. +/* Level 3, 213 Miller St +/* North Sydney, NSW, Australia +/* +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_regexp.in b/src/util/dict_regexp.in new file mode 100644 index 0000000..d5f3b2e --- /dev/null +++ b/src/util/dict_regexp.in @@ -0,0 +1,17 @@ +get true +get true1 +get true2 +get truefalse2 +get 3 +get true3 +get c +get d +get ab +get aa +get 1235 +get 1234 +get 123 +get bar/find +get bar/whynot +get bar/elbereth +get say/elbereth diff --git a/src/util/dict_regexp.map b/src/util/dict_regexp.map new file mode 100644 index 0000000..7a6cefb --- /dev/null +++ b/src/util/dict_regexp.map @@ -0,0 +1,27 @@ +if /true/ fodder +/1/ 1 +if /false/ +/2/ 2 +endif fodder +/3/ 3 +endif +/a/!/b/ a!b +/c/ +/(1)(2)(3)(5)/ ($1)($2)($3)($4)($5) +/(1)(2)(3)(4)/ ($1)($2)($3)($4) +/(1)(2)(3)/ ($1)($2)($3) +# trailing whitespace below +if /bar/ +if !/xyzzy/ +/(elbereth)/ ($1) +!/(bogus)/ ($1) +!/find/ Don't have a liquor license +endif +endif +# trailing whitespace above +! +# dangling endif and if +endif +endif +if /./ +if /./ diff --git a/src/util/dict_regexp.ref b/src/util/dict_regexp.ref new file mode 100644 index 0000000..2a08a3b --- /dev/null +++ b/src/util/dict_regexp.ref @@ -0,0 +1,46 @@ +./dict_open: warning: regexp map dict_regexp.map, line 1: ignoring extra text after IF statement: "fodder" +./dict_open: warning: regexp map dict_regexp.map, line 1: do not prepend whitespace to statements between IF and ENDIF +./dict_open: warning: regexp map dict_regexp.map, line 5: ignoring extra text after ENDIF +./dict_open: warning: regexp map dict_regexp.map, line 9: no replacement text: using empty string +./dict_open: warning: regexp map dict_regexp.map, line 10: out of range replacement index "5": skipping this rule +./dict_open: warning: regexp map dict_regexp.map, line 17: $number found in negative match replacement text: skipping this rule +./dict_open: warning: regexp map dict_regexp.map, line 22: no regexp: skipping this rule +./dict_open: warning: regexp map dict_regexp.map, line 24: ignoring ENDIF without matching IF +./dict_open: warning: regexp map dict_regexp.map, line 25: ignoring ENDIF without matching IF +./dict_open: warning: regexp map dict_regexp.map, line 27: IF has no matching ENDIF +./dict_open: warning: regexp map dict_regexp.map, line 26: IF has no matching ENDIF +owner=untrusted (uid=USER) +> get true +true: not found +> get true1 +true1=1 +> get true2 +true2: not found +> get truefalse2 +truefalse2=2 +> get 3 +3: not found +> get true3 +true3=3 +> get c +c= +> get d +d: not found +> get ab +ab: not found +> get aa +aa=a!b +> get 1235 +1235=(1)(2)(3) +> get 1234 +1234=(1)(2)(3)(4) +> get 123 +123=(1)(2)(3) +> get bar/find +bar/find: not found +> get bar/whynot +bar/whynot=Don't have a liquor license +> get bar/elbereth +bar/elbereth=(elbereth) +> get say/elbereth +say/elbereth: not found diff --git a/src/util/dict_regexp_file.in b/src/util/dict_regexp_file.in new file mode 100644 index 0000000..fef4146 --- /dev/null +++ b/src/util/dict_regexp_file.in @@ -0,0 +1,3 @@ +get file1 +get file2 +get file3 diff --git a/src/util/dict_regexp_file.map b/src/util/dict_regexp_file.map new file mode 100644 index 0000000..41c6b6a --- /dev/null +++ b/src/util/dict_regexp_file.map @@ -0,0 +1,3 @@ +/file1/ dict_regexp_file1 +/file2/ dict_regexp_file2 +/file3/ dict_regexp_file3 diff --git a/src/util/dict_regexp_file.ref b/src/util/dict_regexp_file.ref new file mode 100644 index 0000000..2218143 --- /dev/null +++ b/src/util/dict_regexp_file.ref @@ -0,0 +1,8 @@ +./dict_open: warning: regexp map dict_regexp_file.map, line 3: open dict_regexp_file3: No such file or directory: skipping this rule +owner=untrusted (uid=USER) +> get file1 +file1=dGhpcy1pcy1maWxlMQo= +> get file2 +file2=dGhpcy1pcy1maWxlMgo= +> get file3 +file3: not found diff --git a/src/util/dict_sdbm.c b/src/util/dict_sdbm.c new file mode 100644 index 0000000..23371dc --- /dev/null +++ b/src/util/dict_sdbm.c @@ -0,0 +1,483 @@ +/*++ +/* NAME +/* dict_sdbm 3 +/* SUMMARY +/* dictionary manager interface to SDBM files +/* SYNOPSIS +/* #include <dict_sdbm.h> +/* +/* DICT *dict_sdbm_open(path, open_flags, dict_flags) +/* const char *name; +/* const char *path; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_sdbm_open() opens the named SDBM database and makes it available +/* via the generic interface described in dict_open(3). +/* DIAGNOSTICS +/* Fatal errors: cannot open file, file write error, out of memory. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* sdbm(3) data base subroutines +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#include "sys_defs.h" + +/* System library. */ + +#include <sys/stat.h> +#include <string.h> +#include <unistd.h> +#ifdef HAS_SDBM +#include <sdbm.h> +#endif + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <htable.h> +#include <iostuff.h> +#include <vstring.h> +#include <myflock.h> +#include <stringops.h> +#include <dict.h> +#include <dict_sdbm.h> +#include <warn_stat.h> + +#ifdef HAS_SDBM + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + SDBM *dbm; /* open database */ + VSTRING *key_buf; /* key buffer */ + VSTRING *val_buf; /* result buffer */ +} DICT_SDBM; + +#define SCOPY(buf, data, size) \ + vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size)) + +/* dict_sdbm_lookup - find database entry */ + +static const char *dict_sdbm_lookup(DICT *dict, const char *name) +{ + DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict; + datum dbm_key; + datum dbm_value; + const char *result = 0; + + dict->error = 0; + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_sdbm_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + + /* + * Acquire an exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) + msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name); + + /* + * See if this DBM file was written with one null byte appended to key + * and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + dbm_key.dptr = (void *) name; + dbm_key.dsize = strlen(name) + 1; + dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key); + if (dbm_value.dptr != 0) { + dict->flags &= ~DICT_FLAG_TRY0NULL; + result = SCOPY(dict_sdbm->val_buf, dbm_value.dptr, dbm_value.dsize); + } + } + + /* + * See if this DBM file was written with no null byte appended to key and + * value. + */ + if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { + dbm_key.dptr = (void *) name; + dbm_key.dsize = strlen(name); + dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key); + if (dbm_value.dptr != 0) { + dict->flags &= ~DICT_FLAG_TRY1NULL; + result = SCOPY(dict_sdbm->val_buf, dbm_value.dptr, dbm_value.dsize); + } + } + + /* + * Release the exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name); + + return (result); +} + +/* dict_sdbm_update - add or update database entry */ + +static int dict_sdbm_update(DICT *dict, const char *name, const char *value) +{ + DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict; + datum dbm_key; + datum dbm_value; + int status; + + dict->error = 0; + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_sdbm_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + dbm_key.dptr = (void *) name; + dbm_value.dptr = (void *) value; + dbm_key.dsize = strlen(name); + dbm_value.dsize = strlen(value); + + /* + * If undecided about appending a null byte to key and value, choose a + * default depending on the platform. + */ + if ((dict->flags & DICT_FLAG_TRY1NULL) + && (dict->flags & DICT_FLAG_TRY0NULL)) { +#ifdef DBM_NO_TRAILING_NULL + dict->flags &= ~DICT_FLAG_TRY1NULL; +#else + dict->flags &= ~DICT_FLAG_TRY0NULL; +#endif + } + + /* + * Optionally append a null byte to key and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + dbm_key.dsize++; + dbm_value.dsize++; + } + + /* + * Acquire an exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name); + + /* + * Do the update. + */ + if ((status = sdbm_store(dict_sdbm->dbm, dbm_key, dbm_value, + (dict->flags & DICT_FLAG_DUP_REPLACE) ? DBM_REPLACE : DBM_INSERT)) < 0) + msg_fatal("error writing SDBM database %s: %m", dict_sdbm->dict.name); + if (status) { + if (dict->flags & DICT_FLAG_DUP_IGNORE) + /* void */ ; + else if (dict->flags & DICT_FLAG_DUP_WARN) + msg_warn("%s: duplicate entry: \"%s\"", dict_sdbm->dict.name, name); + else + msg_fatal("%s: duplicate entry: \"%s\"", dict_sdbm->dict.name, name); + } + + /* + * Release the exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name); + + return (status); +} + +/* dict_sdbm_delete - delete one entry from the dictionary */ + +static int dict_sdbm_delete(DICT *dict, const char *name) +{ + DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict; + datum dbm_key; + int status = 1; + + dict->error = 0; + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_sdbm_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + + /* + * Acquire an exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name); + + /* + * See if this DBM file was written with one null byte appended to key + * and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + dbm_key.dptr = (void *) name; + dbm_key.dsize = strlen(name) + 1; + sdbm_clearerr(dict_sdbm->dbm); + if ((status = sdbm_delete(dict_sdbm->dbm, dbm_key)) < 0) { + if (sdbm_error(dict_sdbm->dbm) != 0)/* fatal error */ + msg_fatal("error deleting from %s: %m", dict_sdbm->dict.name); + status = 1; /* not found */ + } else { + dict->flags &= ~DICT_FLAG_TRY0NULL; /* found */ + } + } + + /* + * See if this DBM file was written with no null byte appended to key and + * value. + */ + if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { + dbm_key.dptr = (void *) name; + dbm_key.dsize = strlen(name); + sdbm_clearerr(dict_sdbm->dbm); + if ((status = sdbm_delete(dict_sdbm->dbm, dbm_key)) < 0) { + if (sdbm_error(dict_sdbm->dbm) != 0)/* fatal error */ + msg_fatal("error deleting from %s: %m", dict_sdbm->dict.name); + status = 1; /* not found */ + } else { + dict->flags &= ~DICT_FLAG_TRY1NULL; /* found */ + } + } + + /* + * Release the exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name); + + return (status); +} + +/* traverse the dictionary */ + +static int dict_sdbm_sequence(DICT *dict, const int function, + const char **key, const char **value) +{ + const char *myname = "dict_sdbm_sequence"; + DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict; + datum dbm_key; + datum dbm_value; + int status; + + dict->error = 0; + + /* + * Acquire a shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) + msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name); + + /* + * Determine and execute the seek function. It returns the key. + */ + sdbm_clearerr(dict_sdbm->dbm); + switch (function) { + case DICT_SEQ_FUN_FIRST: + dbm_key = sdbm_firstkey(dict_sdbm->dbm); + break; + case DICT_SEQ_FUN_NEXT: + dbm_key = sdbm_nextkey(dict_sdbm->dbm); + break; + default: + msg_panic("%s: invalid function: %d", myname, function); + } + + if (dbm_key.dptr != 0 && dbm_key.dsize > 0) { + + /* + * Copy the key so that it is guaranteed null terminated. + */ + *key = SCOPY(dict_sdbm->key_buf, dbm_key.dptr, dbm_key.dsize); + + /* + * Fetch the corresponding value. + */ + dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key); + + if (dbm_value.dptr != 0 && dbm_value.dsize > 0) { + + /* + * Copy the value so that it is guaranteed null terminated. + */ + *value = SCOPY(dict_sdbm->val_buf, dbm_value.dptr, dbm_value.dsize); + status = 0; + } else { + + /* + * Determine if we have hit the last record or an error + * condition. + */ + if (sdbm_error(dict_sdbm->dbm)) + msg_fatal("error seeking %s: %m", dict_sdbm->dict.name); + status = 1; /* no error: eof/not found + * (should not happen!) */ + } + } else { + + /* + * Determine if we have hit the last record or an error condition. + */ + if (sdbm_error(dict_sdbm->dbm)) + msg_fatal("error seeking %s: %m", dict_sdbm->dict.name); + status = 1; /* no error: eof/not found */ + } + + /* + * Release the shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name); + + return (status); +} + +/* dict_sdbm_close - disassociate from data base */ + +static void dict_sdbm_close(DICT *dict) +{ + DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict; + + sdbm_close(dict_sdbm->dbm); + if (dict_sdbm->key_buf) + vstring_free(dict_sdbm->key_buf); + if (dict_sdbm->val_buf) + vstring_free(dict_sdbm->val_buf); + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_sdbm_open - open SDBM data base */ + +DICT *dict_sdbm_open(const char *path, int open_flags, int dict_flags) +{ + DICT_SDBM *dict_sdbm; + struct stat st; + SDBM *dbm; + char *dbm_path; + int lock_fd; + + /* + * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in + * the time domain) locking while accessing individual database records. + * + * Programs such as postmap/postalias use their own large-grained (in the + * time domain) locks while rewriting the entire file. + */ + if (dict_flags & DICT_FLAG_LOCK) { + dbm_path = concatenate(path, ".dir", (char *) 0); + if ((lock_fd = open(dbm_path, open_flags, 0644)) < 0) + msg_fatal("open database %s: %m", dbm_path); + if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) + msg_fatal("shared-lock database %s for open: %m", dbm_path); + } + + /* + * XXX sdbm_open() has no const in prototype. + */ + if ((dbm = sdbm_open((char *) path, open_flags, 0644)) == 0) + msg_fatal("open database %s.{dir,pag}: %m", path); + + if (dict_flags & DICT_FLAG_LOCK) { + if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("unlock database %s for open: %m", dbm_path); + if (close(lock_fd) < 0) + msg_fatal("close database %s: %m", dbm_path); + } + dict_sdbm = (DICT_SDBM *) dict_alloc(DICT_TYPE_SDBM, path, sizeof(*dict_sdbm)); + dict_sdbm->dict.lookup = dict_sdbm_lookup; + dict_sdbm->dict.update = dict_sdbm_update; + dict_sdbm->dict.delete = dict_sdbm_delete; + dict_sdbm->dict.sequence = dict_sdbm_sequence; + dict_sdbm->dict.close = dict_sdbm_close; + dict_sdbm->dict.lock_fd = sdbm_dirfno(dbm); + dict_sdbm->dict.stat_fd = sdbm_pagfno(dbm); + if (fstat(dict_sdbm->dict.stat_fd, &st) < 0) + msg_fatal("dict_sdbm_open: fstat: %m"); + dict_sdbm->dict.mtime = st.st_mtime; + dict_sdbm->dict.owner.uid = st.st_uid; + dict_sdbm->dict.owner.status = (st.st_uid != 0); + + /* + * Warn if the source file is newer than the indexed file, except when + * the source file changed only seconds ago. + */ + if ((dict_flags & DICT_FLAG_LOCK) != 0 + && stat(path, &st) == 0 + && st.st_mtime > dict_sdbm->dict.mtime + && st.st_mtime < time((time_t *) 0) - 100) + msg_warn("database %s is older than source file %s", dbm_path, path); + + close_on_exec(sdbm_pagfno(dbm), CLOSE_ON_EXEC); + close_on_exec(sdbm_dirfno(dbm), CLOSE_ON_EXEC); + dict_sdbm->dict.flags = dict_flags | DICT_FLAG_FIXED; + if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0) + dict_sdbm->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL); + if (dict_flags & DICT_FLAG_FOLD_FIX) + dict_sdbm->dict.fold_buf = vstring_alloc(10); + dict_sdbm->dbm = dbm; + dict_sdbm->key_buf = 0; + dict_sdbm->val_buf = 0; + + if ((dict_flags & DICT_FLAG_LOCK)) + myfree(dbm_path); + + return (DICT_DEBUG (&dict_sdbm->dict)); +} + +#endif diff --git a/src/util/dict_sdbm.h b/src/util/dict_sdbm.h new file mode 100644 index 0000000..6a1b281 --- /dev/null +++ b/src/util/dict_sdbm.h @@ -0,0 +1,37 @@ +#ifndef _DICT_SDBM_H_INCLUDED_ +#define _DICT_SDBM_H_INCLUDED_ + +/*++ +/* NAME +/* dict_dbm 3h +/* SUMMARY +/* dictionary manager interface to DBM files +/* SYNOPSIS +/* #include <dict_dbm.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <dict.h> + + /* + * External interface. + */ +#define DICT_TYPE_SDBM "sdbm" + +extern DICT *dict_sdbm_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_seq.in b/src/util/dict_seq.in new file mode 100644 index 0000000..998e7c4 --- /dev/null +++ b/src/util/dict_seq.in @@ -0,0 +1,11 @@ +put 5 5 +put 3 3 +put 4 4 +put 1 1 +put 2 2 +first +next +next +next +next +next diff --git a/src/util/dict_seq.ref b/src/util/dict_seq.ref new file mode 100644 index 0000000..ce7d744 --- /dev/null +++ b/src/util/dict_seq.ref @@ -0,0 +1,22 @@ +> put 5 5 +5=5 +> put 3 3 +3=3 +> put 4 4 +4=4 +> put 1 1 +1=1 +> put 2 2 +2=2 +> first +4=4 +> next +2=2 +> next +5=5 +> next +3=3 +> next +1=1 +> next +not found diff --git a/src/util/dict_sockmap.c b/src/util/dict_sockmap.c new file mode 100644 index 0000000..becad1a --- /dev/null +++ b/src/util/dict_sockmap.c @@ -0,0 +1,384 @@ +/*++ +/* NAME +/* dict_sockmap 3 +/* SUMMARY +/* dictionary manager interface to Sendmail-style socketmap server +/* SYNOPSIS +/* #include <dict_sockmap.h> +/* +/* DICT *dict_sockmap_open(map, open_flags, dict_flags) +/* const char *map; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_sockmap_open() makes a Sendmail-style socketmap server +/* accessible via the generic dictionary operations described +/* in dict_open(3). The only implemented operation is dictionary +/* lookup. This map type can be useful for simulating a dynamic +/* lookup table. +/* +/* Postfix socketmap names have the form inet:host:port:socketmap-name +/* or unix:pathname:socketmap-name, where socketmap-name +/* specifies the socketmap name that the socketmap server uses. +/* +/* To test this module, build the netstring and dict_open test +/* programs. Run "./netstring nc -l portnumber" as the server, +/* and "./dict_open socketmap:127.0.0.1:portnumber:socketmapname" +/* as the client. +/* PROTOCOL +/* .ad +/* .fi +/* The socketmap class implements a simple protocol: the client +/* sends one request, and the server sends one reply. +/* ENCODING +/* .ad +/* .fi +/* Each request and reply are sent as one netstring object. +/* REQUEST FORMAT +/* .ad +/* .fi +/* .IP "<mapname> <space> <key>" +/* Search the specified socketmap under the specified key. +/* REPLY FORMAT +/* .ad +/* .fi +/* Replies must be no longer than 100000 characters (not including +/* the netstring encapsulation), and must have the following +/* form: +/* .IP "OK <space> <data>" +/* The requested data was found. +/* .IP "NOTFOUND <space>" +/* The requested data was not found. +/* .IP "TEMP <space> <reason>" +/* .IP "TIMEOUT <space> <reason>" +/* .IP "PERM <space> <reason>" +/* The request failed. The reason, if non-empty, is descriptive +/* text. +/* SECURITY +/* This map cannot be used for security-sensitive information, +/* because neither the connection nor the server are authenticated. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* netstring(3) netstring stream I/O support +/* DIAGNOSTICS +/* Fatal errors: out of memory, unknown host or service name, +/* attempt to update or iterate over map. +/* BUGS +/* The protocol limits are not yet configurable. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + + /* + * System library. + */ +#include <sys_defs.h> +#include <errno.h> +#include <string.h> +#include <ctype.h> + + /* + * Utility library. + */ +#include <mymalloc.h> +#include <msg.h> +#include <vstream.h> +#include <auto_clnt.h> +#include <netstring.h> +#include <split_at.h> +#include <stringops.h> +#include <htable.h> +#include <dict_sockmap.h> + + /* + * Socket map data structure. + */ +typedef struct { + DICT dict; /* parent class */ + char *sockmap_name; /* on-the-wire socketmap name */ + VSTRING *rdwr_buf; /* read/write buffer */ + HTABLE_INFO *client_info; /* shared endpoint name and handle */ +} DICT_SOCKMAP; + + /* + * Default limits. + */ +#define DICT_SOCKMAP_DEF_TIMEOUT 100 /* connect/read/write timeout */ +#define DICT_SOCKMAP_DEF_MAX_REPLY 100000 /* reply size limit */ +#define DICT_SOCKMAP_DEF_MAX_IDLE 10 /* close idle socket */ +#define DICT_SOCKMAP_DEF_MAX_TTL 100 /* close old socket */ + + /* + * Class variables. + */ +static int dict_sockmap_timeout = DICT_SOCKMAP_DEF_TIMEOUT; +static int dict_sockmap_max_reply = DICT_SOCKMAP_DEF_MAX_REPLY; +static int dict_sockmap_max_idle = DICT_SOCKMAP_DEF_MAX_IDLE; +static int dict_sockmap_max_ttl = DICT_SOCKMAP_DEF_MAX_TTL; + + /* + * The client handle is shared between socketmap instances that have the + * same inet:host:port or unix:pathame information. This could be factored + * out as a general module for reference-counted handles of any kind. + */ +static HTABLE *dict_sockmap_handles; /* shared handles */ + +typedef struct { + AUTO_CLNT *client_handle; /* the client handle */ + int refcount; /* the reference count */ +} DICT_SOCKMAP_REFC_HANDLE; + +#define DICT_SOCKMAP_RH_NAME(ht) (ht)->key +#define DICT_SOCKMAP_RH_HANDLE(ht) \ + ((DICT_SOCKMAP_REFC_HANDLE *) (ht)->value)->client_handle +#define DICT_SOCKMAP_RH_REFCOUNT(ht) \ + ((DICT_SOCKMAP_REFC_HANDLE *) (ht)->value)->refcount + + /* + * Socketmap protocol elements. + */ +#define DICT_SOCKMAP_PROT_OK "OK" +#define DICT_SOCKMAP_PROT_NOTFOUND "NOTFOUND" +#define DICT_SOCKMAP_PROT_TEMP "TEMP" +#define DICT_SOCKMAP_PROT_TIMEOUT "TIMEOUT" +#define DICT_SOCKMAP_PROT_PERM "PERM" + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* dict_sockmap_lookup - socket map lookup */ + +static const char *dict_sockmap_lookup(DICT *dict, const char *key) +{ + const char *myname = "dict_sockmap_lookup"; + DICT_SOCKMAP *dp = (DICT_SOCKMAP *) dict; + AUTO_CLNT *sockmap_clnt = DICT_SOCKMAP_RH_HANDLE(dp->client_info); + VSTREAM *fp; + int netstring_err; + char *reply_payload; + int except_count; + const char *error_class; + + if (msg_verbose) + msg_info("%s: key %s", myname, key); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_MUL) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(100); + vstring_strcpy(dict->fold_buf, key); + key = lowercase(STR(dict->fold_buf)); + } + + /* + * We retry connection-level errors once, to make server restarts + * transparent. + */ + for (except_count = 0; /* see below */ ; except_count++) { + + /* + * Look up the stream. + */ + if ((fp = auto_clnt_access(sockmap_clnt)) == 0) { + msg_warn("table %s:%s lookup error: %m", dict->type, dict->name); + dict->error = DICT_ERR_RETRY; + return (0); + } + + /* + * Set up an exception handler. + */ + netstring_setup(fp, dict_sockmap_timeout); + if ((netstring_err = vstream_setjmp(fp)) == 0) { + + /* + * Send the query. This may raise an exception. + */ + vstring_sprintf(dp->rdwr_buf, "%s %s", dp->sockmap_name, key); + NETSTRING_PUT_BUF(fp, dp->rdwr_buf); + + /* + * Receive the response. This may raise an exception. + */ + netstring_get(fp, dp->rdwr_buf, dict_sockmap_max_reply); + + /* + * If we got here, then no exception was raised. + */ + break; + } + + /* + * Handle exceptions. + */ + else { + + /* + * We retry a broken connection only once. + */ + if (except_count == 0 && netstring_err == NETSTRING_ERR_EOF + && errno != ETIMEDOUT) { + auto_clnt_recover(sockmap_clnt); + continue; + } + + /* + * We do not retry other errors. + */ + else { + msg_warn("table %s:%s lookup error: %s", + dict->type, dict->name, + netstring_strerror(netstring_err)); + dict->error = DICT_ERR_RETRY; + return (0); + } + } + } + + /* + * Parse the reply. + */ + VSTRING_TERMINATE(dp->rdwr_buf); + reply_payload = split_at(STR(dp->rdwr_buf), ' '); + if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_OK) == 0) { + dict->error = 0; + return (reply_payload); + } else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_NOTFOUND) == 0) { + dict->error = 0; + return (0); + } + /* We got no definitive reply. */ + if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_TEMP) == 0) { + error_class = "temporary"; + dict->error = DICT_ERR_RETRY; + } else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_TIMEOUT) == 0) { + error_class = "timeout"; + dict->error = DICT_ERR_RETRY; + } else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_PERM) == 0) { + error_class = "permanent"; + dict->error = DICT_ERR_CONFIG; + } else { + error_class = "unknown"; + dict->error = DICT_ERR_RETRY; + } + while (reply_payload && ISSPACE(*reply_payload)) + reply_payload++; + msg_warn("%s:%s socketmap server %s error%s%.200s", + dict->type, dict->name, error_class, + reply_payload && *reply_payload ? ": " : "", + reply_payload && *reply_payload ? + printable(reply_payload, '?') : ""); + return (0); +} + +/* dict_sockmap_close - close socket map */ + +static void dict_sockmap_close(DICT *dict) +{ + const char *myname = "dict_sockmap_close"; + DICT_SOCKMAP *dp = (DICT_SOCKMAP *) dict; + + if (dict_sockmap_handles == 0 || dict_sockmap_handles->used == 0) + msg_panic("%s: attempt to close a non-existent map", myname); + vstring_free(dp->rdwr_buf); + myfree(dp->sockmap_name); + if (--DICT_SOCKMAP_RH_REFCOUNT(dp->client_info) == 0) { + auto_clnt_free(DICT_SOCKMAP_RH_HANDLE(dp->client_info)); + htable_delete(dict_sockmap_handles, + DICT_SOCKMAP_RH_NAME(dp->client_info), myfree); + } + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_sockmap_open - open socket map */ + +DICT *dict_sockmap_open(const char *mapname, int open_flags, int dict_flags) +{ + DICT_SOCKMAP *dp; + char *saved_name = 0; + char *sockmap; + DICT_SOCKMAP_REFC_HANDLE *ref_handle; + HTABLE_INFO *client_info; + + /* + * Let the optimizer worry about eliminating redundant code. + */ +#define DICT_SOCKMAP_OPEN_RETURN(d) do { \ + DICT *__d = (d); \ + if (saved_name != 0) \ + myfree(saved_name); \ + return (__d); \ + } while (0) + + /* + * Sanity checks. + */ + if (open_flags != O_RDONLY) + DICT_SOCKMAP_OPEN_RETURN(dict_surrogate(DICT_TYPE_SOCKMAP, mapname, + open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_SOCKMAP, mapname)); + if (dict_flags & DICT_FLAG_NO_UNAUTH) + DICT_SOCKMAP_OPEN_RETURN(dict_surrogate(DICT_TYPE_SOCKMAP, mapname, + open_flags, dict_flags, + "%s:%s map is not allowed for security-sensitive data", + DICT_TYPE_SOCKMAP, mapname)); + + /* + * Separate the socketmap name from the socketmap server name. + */ + saved_name = mystrdup(mapname); + if ((sockmap = split_at_right(saved_name, ':')) == 0) + DICT_SOCKMAP_OPEN_RETURN(dict_surrogate(DICT_TYPE_SOCKMAP, mapname, + open_flags, dict_flags, + "%s requires server:socketmap argument", + DICT_TYPE_SOCKMAP)); + + /* + * Use one reference-counted client handle for all socketmaps with the + * same inet:host:port or unix:pathname information. + * + * XXX Todo: graceful degradation after endpoint syntax error. + */ + if (dict_sockmap_handles == 0) + dict_sockmap_handles = htable_create(1); + if ((client_info = htable_locate(dict_sockmap_handles, saved_name)) == 0) { + ref_handle = (DICT_SOCKMAP_REFC_HANDLE *) mymalloc(sizeof(*ref_handle)); + client_info = htable_enter(dict_sockmap_handles, + saved_name, (void *) ref_handle); + /* XXX Late initialization, so we can reuse macros for consistency. */ + DICT_SOCKMAP_RH_REFCOUNT(client_info) = 1; + DICT_SOCKMAP_RH_HANDLE(client_info) = + auto_clnt_create(saved_name, dict_sockmap_timeout, + dict_sockmap_max_idle, dict_sockmap_max_ttl); + } else + DICT_SOCKMAP_RH_REFCOUNT(client_info) += 1; + + /* + * Instantiate a socket map handle. + */ + dp = (DICT_SOCKMAP *) dict_alloc(DICT_TYPE_SOCKMAP, mapname, sizeof(*dp)); + dp->rdwr_buf = vstring_alloc(100); + dp->sockmap_name = mystrdup(sockmap); + dp->client_info = client_info; + dp->dict.lookup = dict_sockmap_lookup; + dp->dict.close = dict_sockmap_close; + /* Don't look up parent domains or network superblocks. */ + dp->dict.flags = dict_flags | DICT_FLAG_PATTERN; + + DICT_SOCKMAP_OPEN_RETURN(DICT_DEBUG (&dp->dict)); +} diff --git a/src/util/dict_sockmap.h b/src/util/dict_sockmap.h new file mode 100644 index 0000000..b81212a --- /dev/null +++ b/src/util/dict_sockmap.h @@ -0,0 +1,37 @@ +#ifndef _DICT_SOCKMAP_H_INCLUDED_ +#define _DICT_SOCKMAP_H_INCLUDED_ + +/*++ +/* NAME +/* dict_sockmap 3h +/* SUMMARY +/* dictionary manager interface to Sendmail-stye socketmap. +/* SYNOPSIS +/* #include <dict_sockmap.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <dict.h> + + /* + * External interface. + */ +#define DICT_TYPE_SOCKMAP "socketmap" + +extern DICT *dict_sockmap_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_static.c b/src/util/dict_static.c new file mode 100644 index 0000000..448dde0 --- /dev/null +++ b/src/util/dict_static.c @@ -0,0 +1,151 @@ +/*++ +/* NAME +/* dict_static 3 +/* SUMMARY +/* dictionary manager interface to static variables +/* SYNOPSIS +/* #include <dict_static.h> +/* +/* DICT *dict_static_open(name, name, dict_flags) +/* const char *name; +/* int dummy; +/* int dict_flags; +/* DESCRIPTION +/* dict_static_open() implements a dummy dictionary that returns +/* as lookup result the dictionary name, regardless of the lookup +/* key value. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* jeffm +/* ghostgun.com +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <stdio.h> /* sprintf() prototype */ +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +/* Utility library. */ + +#include "mymalloc.h" +#include "msg.h" +#include "stringops.h" +#include "dict.h" +#include "dict_static.h" + + /* + * Subclass of DICT. + */ +typedef struct { + DICT dict; /* parent class */ + char *value; +} DICT_STATIC; + +#define STR(x) vstring_str(x) + +/* dict_static_lookup - access static value*/ + +static const char *dict_static_lookup(DICT *dict, const char *unused_name) +{ + DICT_STATIC *dict_static = (DICT_STATIC *) dict; + + DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, dict_static->value); +} + +/* dict_static_close - close static dictionary */ + +static void dict_static_close(DICT *dict) +{ + DICT_STATIC *dict_static = (DICT_STATIC *) dict; + + if (dict_static->value) + myfree(dict_static->value); + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_static_open - make association with static variable */ + +DICT *dict_static_open(const char *name, int open_flags, int dict_flags) +{ + DICT_STATIC *dict_static; + char *err = 0; + char *cp, *saved_name = 0; + const char *value; + VSTRING *base64_buf; + + /* + * Let the optimizer worry about eliminating redundant code. + */ +#define DICT_STATIC_OPEN_RETURN(d) do { \ + DICT *__d = (d); \ + if (saved_name != 0) \ + myfree(saved_name); \ + if (err != 0) \ + myfree(err); \ + return (__d); \ + } while (0) + + /* + * Optionally strip surrounding braces and whitespace. + */ + if (name[0] == CHARS_BRACE[0]) { + saved_name = cp = mystrdup(name); + if ((err = extpar(&cp, CHARS_BRACE, EXTPAR_FLAG_STRIP)) != 0) + DICT_STATIC_OPEN_RETURN(dict_surrogate(DICT_TYPE_STATIC, name, + open_flags, dict_flags, + "bad %s:name syntax: %s", + DICT_TYPE_STATIC, + err)); + value = cp; + } else { + value = name; + } + + /* + * Bundle up the preliminary result. + */ + dict_static = (DICT_STATIC *) dict_alloc(DICT_TYPE_STATIC, name, + sizeof(*dict_static)); + dict_static->dict.lookup = dict_static_lookup; + dict_static->dict.close = dict_static_close; + dict_static->dict.flags = dict_flags | DICT_FLAG_FIXED; + dict_static->dict.owner.status = DICT_OWNER_TRUSTED; + dict_static->value = 0; + + /* + * Optionally replace the value with file contents. + */ + if (dict_flags & DICT_FLAG_SRC_RHS_IS_FILE) { + if ((base64_buf = dict_file_to_b64(&dict_static->dict, value)) == 0) { + err = dict_file_get_error(&dict_static->dict); + dict_close(&dict_static->dict); + DICT_STATIC_OPEN_RETURN(dict_surrogate(DICT_TYPE_STATIC, name, + open_flags, dict_flags, + "%s", err)); + } + value = vstring_str(base64_buf); + } + + /* + * Finalize the result. + */ + dict_static->value = mystrdup(value); + dict_file_purge_buffers(&dict_static->dict); + + DICT_STATIC_OPEN_RETURN(DICT_DEBUG (&(dict_static->dict))); +} diff --git a/src/util/dict_static.h b/src/util/dict_static.h new file mode 100644 index 0000000..d4ad1cc --- /dev/null +++ b/src/util/dict_static.h @@ -0,0 +1,35 @@ +#ifndef _DICT_STATIC_H_INCLUDED_ +#define _DICT_STATIC_H_INCLUDED_ + +/*++ +/* NAME +/* dict_static 3h +/* SUMMARY +/* dictionary manager interface to static settings +/* SYNOPSIS +/* #include <dict_static.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <dict.h> + + /* + * External interface. + */ +#define DICT_TYPE_STATIC "static" + +extern DICT *dict_static_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* jeffm +/* ghostgun.com +/*--*/ + +#endif diff --git a/src/util/dict_static.ref b/src/util/dict_static.ref new file mode 100644 index 0000000..550264a --- /dev/null +++ b/src/util/dict_static.ref @@ -0,0 +1,14 @@ +owner=trusted (uid=2147483647) +> get foo +foo=fooxx +> get bar +bar=fooxx +./dict_open: error: bad static:name syntax: missing '}' in "{ foo xx " +owner=trusted (uid=2147483647) +./dict_open: error: bad static:name syntax: syntax error after '}' in "{ foo xx }x" +owner=trusted (uid=2147483647) +owner=trusted (uid=2147483647) +> get foo +foo=foo xx +> get bar +bar=foo xx diff --git a/src/util/dict_static_file.ref b/src/util/dict_static_file.ref new file mode 100644 index 0000000..259f9fb --- /dev/null +++ b/src/util/dict_static_file.ref @@ -0,0 +1,10 @@ +./dict_open: error: open fooxx: No such file or directory +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: static:fooxx is unavailable. open fooxx: No such file or directory +foo: error +owner=trusted (uid=2147483647) +> get file1 +file1=dGhpcy1pcy1maWxlMQo= +> get file2 +file2=dGhpcy1pcy1maWxlMQo= diff --git a/src/util/dict_stream.c b/src/util/dict_stream.c new file mode 100644 index 0000000..e28ad71 --- /dev/null +++ b/src/util/dict_stream.c @@ -0,0 +1,274 @@ +/*++ +/* NAME +/* dict_stream 3 +/* SUMMARY +/* +/* SYNOPSIS +/* #include <dict.h> +/* +/* VSTREAM *dict_stream_open( +/* const char *dict_type, +/* const char *mapname, +/* int open_flags, +/* int dict_flags, +/* struct stat * st, +/* VSTRING **why) +/* DESCRIPTION +/* dict_stream_open() opens a dictionary, which can be specified +/* as a file name, or as inline text enclosed with {}. If successful, +/* dict_stream_open() returns a non-null VSTREAM pointer. Otherwise, +/* it returns an error text through the why argument, allocating +/* storage for the error text if the why argument points to a +/* null pointer. +/* +/* When the dictionary file is specified inline, dict_stream_open() +/* removes the outer {} from the mapname value, and removes leading +/* or trailing comma or whitespace from the result. It then expects +/* to find zero or more rules enclosed in {}, separated by comma +/* and/or whitespace. dict_stream() writes each rule as one text +/* line to an in-memory stream, without its enclosing {} and without +/* leading or trailing whitespace. The result value is a VSTREAM +/* pointer for the in-memory stream that can be read as a regular +/* file. +/* .sp +/* inline-file = "{" 0*(0*wsp-comma rule-spec) 0*wsp-comma "}" +/* .sp +/* rule-spec = "{" 0*wsp rule-text 0*wsp "}" +/* .sp +/* rule-text = any text containing zero or more balanced {} +/* .sp +/* wsp-comma = wsp | "," +/* .sp +/* wsp = whitespace +/* +/* Arguments: +/* .IP dict_type +/* .IP open_flags +/* .IP dict_flags +/* The same as with dict_open(3). +/* .IP mapname +/* Pathname of a file with dictionary content, or inline dictionary +/* content as specified above. +/* .IP st +/* File metadata with the file owner, or fake metadata with the +/* real UID and GID of the dict_stream_open() caller. This is +/* used for "taint" tracking (zero=trusted, non-zero=untrusted). +/* IP why +/* Pointer to pointer to error message storage. dict_stream_open() +/* updates this storage when reporting an error, and allocates +/* memory if why points to a null pointer. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include <sys_defs.h> + + /* + * Utility library. + */ +#include <dict.h> +#include <msg.h> +#include <mymalloc.h> +#include <stringops.h> +#include <vstring.h> + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* dict_inline_to_multiline - convert inline map spec to multiline text */ + +static char *dict_inline_to_multiline(VSTRING *vp, const char *mapname) +{ + char *saved_name = mystrdup(mapname); + char *bp = saved_name; + char *cp; + char *err = 0; + + VSTRING_RESET(vp); + /* Strip the {} from the map "name". */ + err = extpar(&bp, CHARS_BRACE, EXTPAR_FLAG_NONE); + /* Extract zero or more rules inside {}. */ + while (err == 0 && (cp = mystrtokq(&bp, CHARS_COMMA_SP, CHARS_BRACE)) != 0) + if ((err = extpar(&cp, CHARS_BRACE, EXTPAR_FLAG_STRIP)) == 0) + /* Write rule to in-memory file. */ + vstring_sprintf_append(vp, "%s\n", cp); + VSTRING_TERMINATE(vp); + myfree(saved_name); + return (err); +} + +/* dict_stream_open - open inline configuration or configuration file */ + +VSTREAM *dict_stream_open(const char *dict_type, const char *mapname, + int open_flags, int dict_flags, + struct stat * st, VSTRING **why) +{ + VSTRING *inline_buf = 0; + VSTREAM *map_fp; + char *err = 0; + +#define RETURN_0_WITH_REASON(...) do { \ + if (*why == 0) \ + *why = vstring_alloc(100); \ + vstring_sprintf(*why, __VA_ARGS__); \ + if (inline_buf != 0) \ + vstring_free(inline_buf); \ + if (err != 0) \ + myfree(err); \ + return (0); \ + } while (0) + + if (mapname[0] == CHARS_BRACE[0]) { + inline_buf = vstring_alloc(100); + if ((err = dict_inline_to_multiline(inline_buf, mapname)) != 0) + RETURN_0_WITH_REASON("%s map: %s", dict_type, err); + map_fp = vstream_memopen(inline_buf, O_RDONLY); + vstream_control(map_fp, VSTREAM_CTL_OWN_VSTRING, VSTREAM_CTL_END); + st->st_uid = getuid(); /* geteuid()? */ + st->st_gid = getgid(); /* getegid()? */ + return (map_fp); + } else { + if ((map_fp = vstream_fopen(mapname, open_flags, 0)) == 0) + RETURN_0_WITH_REASON("open %s: %m", mapname); + if (fstat(vstream_fileno(map_fp), st) < 0) + msg_fatal("fstat %s: %m", mapname); + return (map_fp); + } +} + +#ifdef TEST + +#include <string.h> + +int main(int argc, char **argv) +{ + struct testcase { + const char *title; + const char *mapname; /* starts with brace */ + const char *expect_err; /* null or message */ + const char *expect_cont; /* null or content */ + }; + +#define EXP_NOERR 0 +#define EXP_NOCONT 0 + +#define STRING_OR(s, text_if_null) ((s) ? (s) : (text_if_null)) +#define DICT_TYPE_TEST "test" + + const char rule_spec_error[] = DICT_TYPE_TEST " map: " + "syntax error after '}' in \"{blah blah}x\""; + const char inline_config_error[] = DICT_TYPE_TEST " map: " + "syntax error after '}' in \"{{foo bar}, {blah blah}}x\""; + struct testcase testcases[] = { + {"normal", + "{{foo bar}, {blah blah}}", EXP_NOERR, "foo bar\nblah blah\n" + }, + {"trims leading/trailing wsp around rule-text", + "{{ foo bar }, { blah blah }}", EXP_NOERR, "foo bar\nblah blah\n" + }, + {"trims leading/trailing comma-wsp around rule-spec", + "{, ,{foo bar}, {blah blah}, ,}", EXP_NOERR, "foo bar\nblah blah\n" + }, + {"empty inline-file", + "{, }", EXP_NOERR, "" + }, + {"propagates extpar error for inline-file", + "{{foo bar}, {blah blah}}x", inline_config_error, EXP_NOCONT + }, + {"propagates extpar error for rule-spec", + "{{foo bar}, {blah blah}x}", rule_spec_error, EXP_NOCONT + }, + 0, + }; + struct testcase *tp; + VSTRING *act_err = 0; + VSTRING *act_cont = vstring_alloc(100); + VSTREAM *fp; + struct stat st; + ssize_t exp_len; + ssize_t act_len; + int pass; + int fail; + + for (pass = fail = 0, tp = testcases; tp->title; tp++) { + int test_passed = 0; + + msg_info("RUN test case %ld %s", (long) (tp - testcases), tp->title); + +#if 0 + msg_info("title=%s", tp->title); + msg_info("mapname=%s", tp->mapname); + msg_info("expect_err=%s", STRING_OR_NULL(tp->expect_err)); + msg_info("expect_cont=%s", STRINGOR_NULL(tp->expect_cont)); +#endif + + if (act_err) + VSTRING_RESET(act_err); + fp = dict_stream_open(DICT_TYPE_TEST, tp->mapname, O_RDONLY, + 0, &st, &act_err); + if (fp) { + if (tp->expect_err) { + msg_warn("test case %s: got stream, expected error", tp->title); + } else if (!tp->expect_err && act_err && LEN(act_err) > 0) { + msg_warn("test case %s: got error '%s', expected noerror", + tp->title, STR(act_err)); + } else if (!tp->expect_cont) { + msg_warn("test case %s: got stream, expected nostream", + tp->title); + } else { + exp_len = strlen(tp->expect_cont); + if ((act_len = vstream_fread_buf(fp, act_cont, 2 * exp_len)) < 0) { + msg_warn("test case %s: content read error", tp->title); + } else { + VSTRING_TERMINATE(act_cont); + if (strcmp(tp->expect_cont, STR(act_cont)) != 0) { + msg_warn("test case %s: got content '%s', expected '%s'", + tp->title, STR(act_cont), tp->expect_cont); + } else { + test_passed = 1; + } + } + } + } else { + if (!tp->expect_err) { + msg_warn("test case %s: got nostream, expected noerror", + tp->title); + } else if (tp->expect_cont) { + msg_warn("test case %s: got nostream, expected stream", + tp->title); + } else if (strcmp(STR(act_err), tp->expect_err) != 0) { + msg_warn("test case %s: got error '%s', expected '%s'", + tp->title, STR(act_err), tp->expect_err); + } else { + test_passed = 1; + } + + } + if (test_passed) { + msg_info("PASS test %ld", (long) (tp - testcases)); + pass++; + } else { + msg_info("FAIL test %ld", (long) (tp - testcases)); + fail++; + } + if (fp) + vstream_fclose(fp); + } + if (act_err) + vstring_free(act_err); + vstring_free(act_cont); + msg_info("PASS=%d FAIL=%d", pass, fail); + return (fail > 0); +} + +#endif /* TEST */ diff --git a/src/util/dict_stream.ref b/src/util/dict_stream.ref new file mode 100644 index 0000000..87c30e5 --- /dev/null +++ b/src/util/dict_stream.ref @@ -0,0 +1,13 @@ +unknown: RUN test case 0 normal +unknown: PASS test 0 +unknown: RUN test case 1 trims leading/trailing wsp around rule-text +unknown: PASS test 1 +unknown: RUN test case 2 trims leading/trailing comma-wsp around rule-spec +unknown: PASS test 2 +unknown: RUN test case 3 empty inline-file +unknown: PASS test 3 +unknown: RUN test case 4 propagates extpar error for inline-file +unknown: PASS test 4 +unknown: RUN test case 5 propagates extpar error for rule-spec +unknown: PASS test 5 +unknown: PASS=6 FAIL=0 diff --git a/src/util/dict_surrogate.c b/src/util/dict_surrogate.c new file mode 100644 index 0000000..a23cba3 --- /dev/null +++ b/src/util/dict_surrogate.c @@ -0,0 +1,180 @@ +/*++ +/* NAME +/* dict_surrogate 3 +/* SUMMARY +/* surrogate table for graceful "open" failure +/* SYNOPSIS +/* #include <dict_surrogate.h> +/* +/* DICT *dict_surrogate(dict_type, dict_name, +/* open_flags, dict_flags, +/* format, ...) +/* const char *dict_type; +/* const char *dict_name; +/* int open_flags; +/* int dict_flags; +/* const char *format; +/* +/* int dict_allow_surrogate; +/* DESCRIPTION +/* dict_surrogate() either terminates the program with a fatal +/* error, or provides a dummy dictionary that fails all +/* operations with an error message, allowing the program to +/* continue with reduced functionality. +/* +/* The global dict_allow_surrogate variable controls the choice +/* between fatal error or reduced functionality. The default +/* value is zero (fatal error). This is appropriate for user +/* commands; the non-default is more appropriate for daemons. +/* +/* Arguments: +/* .IP dict_type +/* .IP dict_name +/* .IP open_flags +/* .IP dict_flags +/* The parameters to the failed dictionary open() request. +/* .IP format, ... +/* The reason why the table could not be opened. This text is +/* logged immediately as an "error" class message, and is logged +/* as a "warning" class message upon every attempt to access the +/* surrogate dictionary, before returning a "failed" completion +/* status. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <errno.h> + +/* Utility library. */ + +#include <mymalloc.h> +#include <msg.h> +#include <compat_va_copy.h> +#include <dict.h> + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + char *reason; /* open failure reason */ +} DICT_SURROGATE; + +/* dict_surrogate_sequence - fail lookup */ + +static int dict_surrogate_sequence(DICT *dict, int unused_func, + const char **key, const char **value) +{ + DICT_SURROGATE *dp = (DICT_SURROGATE *) dict; + + msg_warn("%s:%s is unavailable. %s", + dict->type, dict->name, dp->reason); + DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, DICT_STAT_ERROR); +} + +/* dict_surrogate_update - fail lookup */ + +static int dict_surrogate_update(DICT *dict, const char *unused_name, + const char *unused_value) +{ + DICT_SURROGATE *dp = (DICT_SURROGATE *) dict; + + msg_warn("%s:%s is unavailable. %s", + dict->type, dict->name, dp->reason); + DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, DICT_STAT_ERROR); +} + +/* dict_surrogate_lookup - fail lookup */ + +static const char *dict_surrogate_lookup(DICT *dict, const char *unused_name) +{ + DICT_SURROGATE *dp = (DICT_SURROGATE *) dict; + + msg_warn("%s:%s is unavailable. %s", + dict->type, dict->name, dp->reason); + DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, (char *) 0); +} + +/* dict_surrogate_delete - fail delete */ + +static int dict_surrogate_delete(DICT *dict, const char *unused_name) +{ + DICT_SURROGATE *dp = (DICT_SURROGATE *) dict; + + msg_warn("%s:%s is unavailable. %s", + dict->type, dict->name, dp->reason); + DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, DICT_STAT_ERROR); +} + +/* dict_surrogate_close - close fail dictionary */ + +static void dict_surrogate_close(DICT *dict) +{ + DICT_SURROGATE *dp = (DICT_SURROGATE *) dict; + + myfree((void *) dp->reason); + dict_free(dict); +} + +int dict_allow_surrogate = 0; + +/* dict_surrogate - terminate or provide surrogate dictionary */ + +DICT *dict_surrogate(const char *dict_type, const char *dict_name, + int open_flags, int dict_flags, + const char *fmt,...) +{ + va_list ap; + va_list ap2; + DICT_SURROGATE *dp; + VSTRING *buf; + void (*log_fn) (const char *, va_list); + int saved_errno = errno; + + /* + * Initialize argument lists. + */ + va_start(ap, fmt); + VA_COPY(ap2, ap); + + /* + * Log the problem immediately when it is detected. The table may not be + * accessed in every program execution (that is the whole point of + * continuing with reduced functionality) but we don't want the problem + * to remain unnoticed until long after a configuration mistake is made. + */ + log_fn = dict_allow_surrogate ? vmsg_error : vmsg_fatal; + log_fn(fmt, ap); + va_end(ap); + + /* + * Log the problem upon each access. + */ + dp = (DICT_SURROGATE *) dict_alloc(dict_type, dict_name, sizeof(*dp)); + dp->dict.lookup = dict_surrogate_lookup; + if (open_flags & O_RDWR) { + dp->dict.update = dict_surrogate_update; + dp->dict.delete = dict_surrogate_delete; + } + dp->dict.sequence = dict_surrogate_sequence; + dp->dict.close = dict_surrogate_close; + dp->dict.flags = dict_flags | DICT_FLAG_PATTERN; + dp->dict.owner.status = DICT_OWNER_TRUSTED; + buf = vstring_alloc(10); + errno = saved_errno; + vstring_vsprintf(buf, fmt, ap2); + va_end(ap2); + dp->reason = vstring_export(buf); + return (DICT_DEBUG (&dp->dict)); +} diff --git a/src/util/dict_tcp.c b/src/util/dict_tcp.c new file mode 100644 index 0000000..922f449 --- /dev/null +++ b/src/util/dict_tcp.c @@ -0,0 +1,315 @@ +/*++ +/* NAME +/* dict_tcp 3 +/* SUMMARY +/* dictionary manager interface to tcp-based lookup tables +/* SYNOPSIS +/* #include <dict_tcp.h> +/* +/* DICT *dict_tcp_open(map, open_flags, dict_flags) +/* const char *map; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_tcp_open() makes a TCP server accessible via the generic +/* dictionary operations described in dict_open(3). +/* The only implemented operation is dictionary lookup. This map +/* type can be useful for simulating a dynamic lookup table. +/* +/* Map names have the form host:port. +/* +/* The TCP map class implements a very simple protocol: the client +/* sends a request, and the server sends one reply. Requests and +/* replies are sent as one line of ASCII text, terminated by the +/* ASCII newline character. Request and reply parameters (see below) +/* are separated by whitespace. +/* ENCODING +/* .ad +/* .fi +/* In request and reply parameters, the character % and any non-printing +/* and whitespace characters must be replaced by %XX, XX being the +/* corresponding ASCII hexadecimal character value. The hexadecimal codes +/* can be specified in any case (upper, lower, mixed). +/* REQUEST FORMAT +/* .ad +/* .fi +/* Requests are strings that serve as lookup key in the simulated +/* table. +/* .IP "get SPACE key NEWLINE" +/* Look up data under the specified key. +/* .IP "put SPACE key SPACE value NEWLINE" +/* This request is currently not implemented. +/* REPLY FORMAT +/* .ad +/* .fi +/* Replies must be no longer than 4096 characters including the +/* newline terminator, and must have the following form: +/* .IP "500 SPACE text NEWLINE" +/* In case of a lookup request, the requested data does not exist. +/* In case of an update request, the request was rejected. +/* The text gives the nature of the problem. +/* .IP "400 SPACE text NEWLINE" +/* This indicates an error condition. The text gives the nature of +/* the problem. The client should retry the request later. +/* .IP "200 SPACE text NEWLINE" +/* The request was successful. In the case of a lookup request, +/* the text contains an encoded version of the requested data. +/* SECURITY +/* This map must not be used for security sensitive information, +/* because neither the connection nor the server are authenticated. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* hex_quote(3) http-style quoting +/* DIAGNOSTICS +/* Fatal errors: out of memory, unknown host or service name, +/* attempt to update or iterate over map. +/* BUGS +/* Only the lookup method is currently implemented. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstring.h> +#include <vstream.h> +#include <vstring_vstream.h> +#include <connect.h> +#include <hex_quote.h> +#include <dict.h> +#include <stringops.h> +#include <dict_tcp.h> + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + VSTRING *raw_buf; /* raw I/O buffer */ + VSTRING *hex_buf; /* quoted I/O buffer */ + VSTREAM *fp; /* I/O stream */ +} DICT_TCP; + +#define DICT_TCP_MAXTRY 10 /* attempts before giving up */ +#define DICT_TCP_TMOUT 100 /* connect/read/write timeout */ +#define DICT_TCP_MAXLEN 4096 /* server reply size limit */ + +#define STR(x) vstring_str(x) + +/* dict_tcp_connect - connect to TCP server */ + +static int dict_tcp_connect(DICT_TCP *dict_tcp) +{ + int fd; + + /* + * Connect to the server. Enforce a time limit on all operations so that + * we do not get stuck. + */ + if ((fd = inet_connect(dict_tcp->dict.name, NON_BLOCKING, DICT_TCP_TMOUT)) < 0) { + msg_warn("connect to TCP map %s: %m", dict_tcp->dict.name); + return (-1); + } + dict_tcp->fp = vstream_fdopen(fd, O_RDWR); + vstream_control(dict_tcp->fp, + CA_VSTREAM_CTL_TIMEOUT(DICT_TCP_TMOUT), + CA_VSTREAM_CTL_END); + + /* + * Allocate per-map I/O buffers on the fly. + */ + if (dict_tcp->raw_buf == 0) { + dict_tcp->raw_buf = vstring_alloc(10); + dict_tcp->hex_buf = vstring_alloc(10); + } + return (0); +} + +/* dict_tcp_disconnect - disconnect from TCP server */ + +static void dict_tcp_disconnect(DICT_TCP *dict_tcp) +{ + (void) vstream_fclose(dict_tcp->fp); + dict_tcp->fp = 0; +} + +/* dict_tcp_lookup - request TCP server */ + +static const char *dict_tcp_lookup(DICT *dict, const char *key) +{ + DICT_TCP *dict_tcp = (DICT_TCP *) dict; + const char *myname = "dict_tcp_lookup"; + int tries; + char *start; + int last_ch; + +#define RETURN(errval, result) { dict->error = errval; return (result); } + + if (msg_verbose) + msg_info("%s: key %s", myname, key); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_MUL) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, key); + key = lowercase(vstring_str(dict->fold_buf)); + } + for (tries = 0; /* see below */ ; /* see below */ ) { + + /* + * Connect to the server, or use an existing connection. + */ + if (dict_tcp->fp != 0 || dict_tcp_connect(dict_tcp) == 0) { + + /* + * Send request and receive response. Both are %XX quoted and + * both are terminated by newline. This encoding is convenient + * for data that is mostly text. + */ + hex_quote(dict_tcp->hex_buf, key); + vstream_fprintf(dict_tcp->fp, "get %s\n", STR(dict_tcp->hex_buf)); + if (msg_verbose) + msg_info("%s: send: get %s", myname, STR(dict_tcp->hex_buf)); + last_ch = vstring_get_nonl_bound(dict_tcp->hex_buf, dict_tcp->fp, + DICT_TCP_MAXLEN); + if (last_ch == '\n') + break; + + /* + * Disconnect from the server if it can't talk to us. + */ + if (last_ch < 0) + msg_warn("read TCP map reply from %s: unexpected EOF (%m)", + dict_tcp->dict.name); + else + msg_warn("read TCP map reply from %s: text longer than %d", + dict_tcp->dict.name, DICT_TCP_MAXLEN); + dict_tcp_disconnect(dict_tcp); + } + + /* + * Try to connect a limited number of times before giving up. + */ + if (++tries >= DICT_TCP_MAXTRY) + RETURN(DICT_ERR_RETRY, 0); + + /* + * Sleep between attempts, instead of hammering the server. + */ + sleep(1); + } + if (msg_verbose) + msg_info("%s: recv: %s", myname, STR(dict_tcp->hex_buf)); + + /* + * Check the general reply syntax. If the reply is malformed, disconnect + * and try again later. + */ + if (start = STR(dict_tcp->hex_buf), + !ISDIGIT(start[0]) || !ISDIGIT(start[1]) + || !ISDIGIT(start[2]) || !ISSPACE(start[3]) + || !hex_unquote(dict_tcp->raw_buf, start + 4)) { + msg_warn("read TCP map reply from %s: malformed reply: %.100s", + dict_tcp->dict.name, printable(STR(dict_tcp->hex_buf), '_')); + dict_tcp_disconnect(dict_tcp); + RETURN(DICT_ERR_RETRY, 0); + } + + /* + * Examine the reply status code. If the reply is malformed, disconnect + * and try again later. + */ + switch (start[0]) { + default: + msg_warn("read TCP map reply from %s: bad status code: %.100s", + dict_tcp->dict.name, printable(STR(dict_tcp->hex_buf), '_')); + dict_tcp_disconnect(dict_tcp); + RETURN(DICT_ERR_RETRY, 0); + case '4': + if (msg_verbose) + msg_info("%s: soft error: %s", + myname, printable(STR(dict_tcp->hex_buf), '_')); + dict_tcp_disconnect(dict_tcp); + RETURN(DICT_ERR_RETRY, 0); + case '5': + if (msg_verbose) + msg_info("%s: not found: %s", + myname, printable(STR(dict_tcp->hex_buf), '_')); + RETURN(DICT_ERR_NONE, 0); + case '2': + if (msg_verbose) + msg_info("%s: found: %s", + myname, printable(STR(dict_tcp->raw_buf), '_')); + RETURN(DICT_ERR_NONE, STR(dict_tcp->raw_buf)); + } +} + +/* dict_tcp_close - close TCP map */ + +static void dict_tcp_close(DICT *dict) +{ + DICT_TCP *dict_tcp = (DICT_TCP *) dict; + + if (dict_tcp->fp) + (void) vstream_fclose(dict_tcp->fp); + if (dict_tcp->raw_buf) + vstring_free(dict_tcp->raw_buf); + if (dict_tcp->hex_buf) + vstring_free(dict_tcp->hex_buf); + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_tcp_open - open TCP map */ + +DICT *dict_tcp_open(const char *map, int open_flags, int dict_flags) +{ + DICT_TCP *dict_tcp; + + /* + * Sanity checks. + */ + if (dict_flags & DICT_FLAG_NO_UNAUTH) + return (dict_surrogate(DICT_TYPE_TCP, map, open_flags, dict_flags, + "%s:%s map is not allowed for security sensitive data", + DICT_TYPE_TCP, map)); + if (open_flags != O_RDONLY) + return (dict_surrogate(DICT_TYPE_TCP, map, open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_TCP, map)); + + /* + * Create the dictionary handle. Do not open the connection until the + * first request is made. + */ + dict_tcp = (DICT_TCP *) dict_alloc(DICT_TYPE_TCP, map, sizeof(*dict_tcp)); + dict_tcp->fp = 0; + dict_tcp->raw_buf = dict_tcp->hex_buf = 0; + dict_tcp->dict.lookup = dict_tcp_lookup; + dict_tcp->dict.close = dict_tcp_close; + dict_tcp->dict.flags = dict_flags | DICT_FLAG_PATTERN; + if (dict_flags & DICT_FLAG_FOLD_MUL) + dict_tcp->dict.fold_buf = vstring_alloc(10); + + return (DICT_DEBUG (&dict_tcp->dict)); +} diff --git a/src/util/dict_tcp.h b/src/util/dict_tcp.h new file mode 100644 index 0000000..9814c61 --- /dev/null +++ b/src/util/dict_tcp.h @@ -0,0 +1,37 @@ +#ifndef _DICT_TCP_H_INCLUDED_ +#define _DICT_TCP_H_INCLUDED_ + +/*++ +/* NAME +/* dict_tcp 3h +/* SUMMARY +/* dictionary manager interface to tcp-based lookup tables +/* SYNOPSIS +/* #include <dict_tcp.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <dict.h> + + /* + * External interface. + */ +#define DICT_TYPE_TCP "tcp" + +extern DICT *dict_tcp_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_test.c b/src/util/dict_test.c new file mode 100644 index 0000000..ead61b2 --- /dev/null +++ b/src/util/dict_test.c @@ -0,0 +1,166 @@ + /* + * Proof-of-concept test program. Create, update or read a database. Type + * '?' for a list of commands. + */ + +/* System library. */ + +#include <sys_defs.h> +#include <stdlib.h> +#include <fcntl.h> +#include <unistd.h> +#include <signal.h> +#include <string.h> + +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif + +/* Utility library. */ + +#include <msg.h> +#include <stringops.h> +#include <vstring.h> +#include <vstream.h> +#include <msg_vstream.h> +#include <vstring_vstream.h> +#include <dict.h> +#include <dict_lmdb.h> +#include <dict_db.h> + +static NORETURN usage(char *myname) +{ + msg_fatal("usage: %s type:file read|write|create [flags...]", myname); +} + +void dict_test(int argc, char **argv) +{ + VSTRING *keybuf = vstring_alloc(1); + VSTRING *inbuf = vstring_alloc(1); + DICT *dict; + char *dict_name; + int open_flags; + char *bufp; + char *cmd; + const char *key; + const char *value; + int ch; + int dict_flags = 0; + int n; + int rc; + +#define USAGE "verbose|del key|get key|put key=value|first|next|masks|flags" + + signal(SIGPIPE, SIG_IGN); + + msg_vstream_init(argv[0], VSTREAM_ERR); + while ((ch = GETOPT(argc, argv, "v")) > 0) { + switch (ch) { + default: + usage(argv[0]); + case 'v': + msg_verbose++; + break; + } + } + optind = OPTIND; + if (argc - optind < 2) + usage(argv[0]); + if (strcasecmp(argv[optind + 1], "create") == 0) + open_flags = O_CREAT | O_RDWR | O_TRUNC; + else if (strcasecmp(argv[optind + 1], "write") == 0) + open_flags = O_RDWR; + else if (strcasecmp(argv[optind + 1], "read") == 0) + open_flags = O_RDONLY; + else + msg_fatal("unknown access mode: %s", argv[2]); + for (n = 2; argv[optind + n]; n++) + dict_flags |= dict_flags_mask(argv[optind + 2]); + if ((dict_flags & DICT_FLAG_OPEN_LOCK) == 0) + dict_flags |= DICT_FLAG_LOCK; + if ((dict_flags & (DICT_FLAG_DUP_WARN | DICT_FLAG_DUP_IGNORE)) == 0) + dict_flags |= DICT_FLAG_DUP_REPLACE; + dict_flags |= DICT_FLAG_UTF8_REQUEST; + vstream_fflush(VSTREAM_OUT); + dict_name = argv[optind]; + dict_allow_surrogate = 1; + util_utf8_enable = 1; + dict = dict_open(dict_name, open_flags, dict_flags); + dict_register(dict_name, dict); + vstream_printf("owner=%s (uid=%ld)\n", + dict->owner.status == DICT_OWNER_TRUSTED ? "trusted" : + dict->owner.status == DICT_OWNER_UNTRUSTED ? "untrusted" : + dict->owner.status == DICT_OWNER_UNKNOWN ? "unspecified" : + "error", (long) dict->owner.uid); + vstream_fflush(VSTREAM_OUT); + + while (vstring_fgets_nonl(inbuf, VSTREAM_IN)) { + bufp = vstring_str(inbuf); + if (!isatty(0)) { + vstream_printf("> %s\n", bufp); + vstream_fflush(VSTREAM_OUT); + } + if (*bufp == '#') + continue; + if ((cmd = mystrtok(&bufp, " ")) == 0) { + vstream_printf("usage: %s\n", USAGE); + vstream_fflush(VSTREAM_OUT); + continue; + } + if (dict_changed_name()) + msg_warn("dictionary has changed"); + key = *bufp ? vstring_str(unescape(keybuf, mystrtok(&bufp, " ="))) : 0; + value = mystrtok(&bufp, " ="); + if (strcmp(cmd, "verbose") == 0 && !key) { + msg_verbose++; + } else if (strcmp(cmd, "del") == 0 && key && !value) { + if ((rc = dict_del(dict, key)) > 0) + vstream_printf("%s: not found\n", key); + else if (rc < 0) + vstream_printf("%s: error\n", key); + else + vstream_printf("%s: deleted\n", key); + } else if (strcmp(cmd, "get") == 0 && key && !value) { + if ((value = dict_get(dict, key)) == 0) { + vstream_printf("%s: %s\n", key, dict->error ? + "error" : "not found"); + } else { + vstream_printf("%s=%s\n", key, value); + } + } else if (strcmp(cmd, "put") == 0 && key && value) { + if (dict_put(dict, key, value) != 0) + vstream_printf("%s: %s\n", key, dict->error ? + "error" : "not updated"); + } else if (strcmp(cmd, "first") == 0 && !key && !value) { + if (dict_seq(dict, DICT_SEQ_FUN_FIRST, &key, &value) == 0) + vstream_printf("%s=%s\n", key, value); + else + vstream_printf("%s\n", dict->error ? + "error" : "not found"); + } else if (strcmp(cmd, "next") == 0 && !key && !value) { + if (dict_seq(dict, DICT_SEQ_FUN_NEXT, &key, &value) == 0) + vstream_printf("%s=%s\n", key, value); + else + vstream_printf("%s\n", dict->error ? + "error" : "not found"); + } else if (strcmp(cmd, "flags") == 0 && !key && !value) { + vstream_printf("dict flags %s\n", + dict_flags_str(dict->flags)); + } else if (strcmp(cmd, "masks") == 0 && !key && !value) { + vstream_printf("DICT_FLAG_IMPL_MASK %s\n", + dict_flags_str(DICT_FLAG_IMPL_MASK)); + vstream_printf("DICT_FLAG_PARANOID %s\n", + dict_flags_str(DICT_FLAG_PARANOID)); + vstream_printf("DICT_FLAG_RQST_MASK %s\n", + dict_flags_str(DICT_FLAG_RQST_MASK)); + vstream_printf("DICT_FLAG_INST_MASK %s\n", + dict_flags_str(DICT_FLAG_INST_MASK)); + } else { + vstream_printf("usage: %s\n", USAGE); + } + vstream_fflush(VSTREAM_OUT); + } + vstring_free(keybuf); + vstring_free(inbuf); + dict_close(dict); +} diff --git a/src/util/dict_test.in b/src/util/dict_test.in new file mode 100644 index 0000000..ef838a1 --- /dev/null +++ b/src/util/dict_test.in @@ -0,0 +1,10 @@ +del bar +get bar +get nonexist +del nonexist +get foo +del foo +put baz bazval +get baz +del baz +get baz diff --git a/src/util/dict_test.ref b/src/util/dict_test.ref new file mode 100644 index 0000000..54e91f8 --- /dev/null +++ b/src/util/dict_test.ref @@ -0,0 +1,20 @@ +owner=untrusted (uid=USER) +> del bar +bar: deleted +> get bar +bar: not found +> get nonexist +nonexist: not found +> del nonexist +nonexist: not found +> get foo +foo=fooval +> del foo +foo: deleted +> put baz bazval +> get baz +baz=bazval +> del baz +baz: deleted +> get baz +baz: not found diff --git a/src/util/dict_thash.c b/src/util/dict_thash.c new file mode 100644 index 0000000..69eb17b --- /dev/null +++ b/src/util/dict_thash.c @@ -0,0 +1,255 @@ +/*++ +/* NAME +/* dict_thash 3 +/* SUMMARY +/* dictionary manager interface to hashed flat text files +/* SYNOPSIS +/* #include <dict_thash.h> +/* +/* DICT *dict_thash_open(path, open_flags, dict_flags) +/* const char *name; +/* const char *path; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_thash_open() opens the named flat text file, creates +/* an in-memory hash table, and makes it available via the +/* generic interface described in dict_open(3). The input +/* format is as with postmap(1). +/* DIAGNOSTICS +/* Fatal errors: cannot open file, out of memory. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/stat.h> +#include <ctype.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <iostuff.h> +#include <vstring.h> +#include <stringops.h> +#include <readlline.h> +#include <dict.h> +#include <dict_ht.h> +#include <dict_thash.h> + +/* Application-specific. */ + +#define STR vstring_str +#define LEN VSTRING_LEN + +/* dict_thash_open - open flat text data base */ + +DICT *dict_thash_open(const char *path, int open_flags, int dict_flags) +{ + DICT *dict; + VSTREAM *fp = 0; /* DICT_THASH_OPEN_RETURN() */ + struct stat st; + time_t before; + time_t after; + VSTRING *line_buffer = 0; /* DICT_THASH_OPEN_RETURN() */ + int lineno; + int last_line; + char *key; + char *value; + + /* + * Let the optimizer worry about eliminating redundant code. + */ +#define DICT_THASH_OPEN_RETURN(d) do { \ + DICT *__d = (d); \ + if (fp != 0) \ + vstream_fclose(fp); \ + if (line_buffer != 0) \ + vstring_free(line_buffer); \ + return (__d); \ + } while (0) + + /* + * Sanity checks. + */ + if (open_flags != O_RDONLY) + DICT_THASH_OPEN_RETURN(dict_surrogate(DICT_TYPE_THASH, path, + open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_THASH, path)); + + /* + * Read the flat text file into in-memory hash. Read the file again if it + * may have changed while we were reading. + */ + for (before = time((time_t *) 0); /* see below */ ; before = after) { + if ((fp = vstream_fopen(path, open_flags, 0644)) == 0) { + DICT_THASH_OPEN_RETURN(dict_surrogate(DICT_TYPE_THASH, path, + open_flags, dict_flags, + "open database %s: %m", path)); + } + + /* + * Reuse the "internal" dictionary type. + */ + dict = dict_open3(DICT_TYPE_HT, path, open_flags, dict_flags); + dict_type_override(dict, DICT_TYPE_THASH); + + /* + * XXX This duplicates the parser in postmap.c. + */ + if (line_buffer == 0) + line_buffer = vstring_alloc(100); + last_line = 0; + while (readllines(line_buffer, fp, &last_line, &lineno)) { + int in_quotes = 0; + + /* + * First some UTF-8 checks sans casefolding. + */ + if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) + && allascii(STR(line_buffer)) == 0 + && valid_utf8_string(STR(line_buffer), LEN(line_buffer)) == 0) { + msg_warn("%s, line %d: non-UTF-8 input \"%s\"" + " -- ignoring this line", + VSTREAM_PATH(fp), lineno, STR(line_buffer)); + continue; + } + + /* + * Split on the first whitespace character, then trim leading and + * trailing whitespace from key and value. + */ + for (value = STR(line_buffer); *value; value++) { + if (*value == '\\') { + if (*++value == 0) + break; + } else if (ISSPACE(*value)) { + if (!in_quotes) + break; + } else if (*value == '"') { + in_quotes = !in_quotes; + } + } + if (in_quotes) { + msg_warn("%s, line %d: unbalanced '\"' in '%s'" + " -- ignoring this line", + VSTREAM_PATH(fp), lineno, STR(line_buffer)); + continue; + } + if (*value) + *value++ = 0; + while (ISSPACE(*value)) + value++; + trimblanks(value, 0)[0] = 0; + + /* + * Leave the key in quoted form, for consistency with postmap.c + * and dict_inline.c. + */ + key = STR(line_buffer); + + /* + * Enforce the "key whitespace value" format. Disallow missing + * keys or missing values. + */ + if (*key == 0 || *value == 0) { + msg_warn("%s, line %d: expected format: key whitespace value" + " -- ignoring this line", path, lineno); + continue; + } + if (key[strlen(key) - 1] == ':') + msg_warn("%s, line %d: record is in \"key: value\" format;" + " is this an alias file?", path, lineno); + + /* + * Optionally treat the value as a filename, and replace the value + * with the BASE64-encoded content of the named file. + */ + if (dict_flags & DICT_FLAG_SRC_RHS_IS_FILE) { + VSTRING *base64_buf; + char *err; + + if ((base64_buf = dict_file_to_b64(dict, value)) == 0) { + err = dict_file_get_error(dict); + msg_warn("%s, line %d: %s: skipping this entry", + VSTREAM_PATH(fp), lineno, err); + myfree(err); + continue; + } + value = vstring_str(base64_buf); + } + + /* + * Store the value under the key. Handle duplicates + * appropriately. XXX Move this into dict_ht, but 1) that map + * ignores duplicates by default and we would have to check that + * we won't break existing code that depends on such behavior; 2) + * by inlining the checks here we can degrade gracefully instead + * of terminating with a fatal error. See comment in + * dict_inline.c. + */ + if (dict->lookup(dict, key) != 0) { + if (dict_flags & DICT_FLAG_DUP_IGNORE) { + /* void */ ; + } else if (dict_flags & DICT_FLAG_DUP_REPLACE) { + dict->update(dict, key, value); + } else if (dict_flags & DICT_FLAG_DUP_WARN) { + msg_warn("%s, line %d: duplicate entry: \"%s\"", + path, lineno, key); + } else { + dict->close(dict); + DICT_THASH_OPEN_RETURN(dict_surrogate(DICT_TYPE_THASH, path, + open_flags, dict_flags, + "%s, line %d: duplicate entry: \"%s\"", + path, lineno, key)); + } + } else { + dict->update(dict, key, value); + } + } + + /* + * See if the source file is hot. + */ + if (fstat(vstream_fileno(fp), &st) < 0) + msg_fatal("fstat %s: %m", path); + if (vstream_fclose(fp)) + msg_fatal("read %s: %m", path); + fp = 0; /* DICT_THASH_OPEN_RETURN() */ + after = time((time_t *) 0); + if (st.st_mtime < before - 1 || st.st_mtime > after) + break; + + /* + * Yes, it is hot. Discard the result and read the file again. + */ + dict->close(dict); + if (msg_verbose > 1) + msg_info("pausing to let file %s cool down", path); + doze(300000); + } + + dict->owner.uid = st.st_uid; + dict->owner.status = (st.st_uid != 0); + + DICT_THASH_OPEN_RETURN(DICT_DEBUG (dict)); +} diff --git a/src/util/dict_thash.h b/src/util/dict_thash.h new file mode 100644 index 0000000..878366a --- /dev/null +++ b/src/util/dict_thash.h @@ -0,0 +1,37 @@ +#ifndef _DICT_THASH_H_INCLUDED_ +#define _DICT_THASH_H_INCLUDED_ + +/*++ +/* NAME +/* dict_thash 3h +/* SUMMARY +/* dictionary manager interface to flat text files +/* SYNOPSIS +/* #include <dict_thash.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <dict.h> + + /* + * External interface. + */ +#define DICT_TYPE_THASH "texthash" + +extern DICT *dict_thash_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_thash.in b/src/util/dict_thash.in new file mode 100644 index 0000000..89938f6 --- /dev/null +++ b/src/util/dict_thash.in @@ -0,0 +1,5 @@ +"the answer is 42 +xxx: yyy +xxx +aaa bbb +aaa bbb diff --git a/src/util/dict_thash.map b/src/util/dict_thash.map new file mode 100644 index 0000000..cce1144 --- /dev/null +++ b/src/util/dict_thash.map @@ -0,0 +1,17 @@ +"the answer is" 42 +ABCDEF 012345 +allascii.c 915 +alldig.c 928 +allprint.c 943 +allspace.c 931 +argv.c 5271 +argv_split.c 2780 +attr_clnt.c 5813 +attr_print0.c 7243 +attr_print64.c 8104 +attr_print_plain.c 7086 +attr_scan0.c 15454 +attr_scan64.c 17256 +attr_scan_plain.c 16924 +auto_clnt.c 9819 +the answer is 42 diff --git a/src/util/dict_thash.ref b/src/util/dict_thash.ref new file mode 100644 index 0000000..efdc207 --- /dev/null +++ b/src/util/dict_thash.ref @@ -0,0 +1,6 @@ +postmap: warning: dict_thash.in, line 1: unbalanced '"' in '"the answer is 42' -- ignoring this line +postmap: warning: dict_thash.in, line 2: record is in "key: value" format; is this an alias file? +postmap: warning: dict_thash.in, line 3: expected format: key whitespace value -- ignoring this line +postmap: warning: dict_thash.in, line 5: duplicate entry: "aaa" +aaa bbb +xxx: yyy diff --git a/src/util/dict_union.c b/src/util/dict_union.c new file mode 100644 index 0000000..80df03b --- /dev/null +++ b/src/util/dict_union.c @@ -0,0 +1,202 @@ +/*++ +/* NAME +/* dict_union 3 +/* SUMMARY +/* dictionary manager interface for union of tables +/* SYNOPSIS +/* #include <dict_union.h> +/* +/* DICT *dict_union_open(name, open_flags, dict_flags) +/* const char *name; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_union_open() opens a sequence of one or more tables. +/* Example: "\fBunionmap:{\fItype_1:name_1, ..., type_n:name_n\fR}". +/* +/* Each "unionmap:" query is given to each table in the specified +/* order. All found results are concatenated, separated by +/* comma. The unionmap table produces no result when all +/* lookup tables return no result. +/* +/* The first and last characters of a "unionmap:" table name +/* must be '{' and '}'. Within these, individual maps are +/* separated with comma or whitespace. +/* +/* The open_flags and dict_flags arguments are passed on to +/* the underlying dictionaries. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <htable.h> +#include <dict.h> +#include <dict_union.h> +#include <stringops.h> +#include <vstring.h> + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + ARGV *map_union; /* pipelined tables */ + VSTRING *re_buf; /* reply buffer */ +} DICT_UNION; + +#define STR(x) vstring_str(x) + +/* dict_union_lookup - search a bunch of tables and combine the results */ + +static const char *dict_union_lookup(DICT *dict, const char *query) +{ + static const char myname[] = "dict_union_lookup"; + DICT_UNION *dict_union = (DICT_UNION *) dict; + DICT *map; + char **cpp; + char *dict_type_name; + const char *result = 0; + + /* + * After Roel van Meer, postfix-users mailing list, Sept 2014. + */ + VSTRING_RESET(dict_union->re_buf); + for (cpp = dict_union->map_union->argv; (dict_type_name = *cpp) != 0; cpp++) { + if ((map = dict_handle(dict_type_name)) == 0) + msg_panic("%s: dictionary \"%s\" not found", myname, dict_type_name); + if ((result = dict_get(map, query)) != 0) { + if (VSTRING_LEN(dict_union->re_buf) > 0) + VSTRING_ADDCH(dict_union->re_buf, ','); + vstring_strcat(dict_union->re_buf, result); + } else if (map->error != 0) { + DICT_ERR_VAL_RETURN(dict, map->error, 0); + } + } + DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, + VSTRING_LEN(dict_union->re_buf) > 0 ? + STR(dict_union->re_buf) : 0); +} + +/* dict_union_close - disassociate from a bunch of tables */ + +static void dict_union_close(DICT *dict) +{ + DICT_UNION *dict_union = (DICT_UNION *) dict; + char **cpp; + char *dict_type_name; + + for (cpp = dict_union->map_union->argv; (dict_type_name = *cpp) != 0; cpp++) + dict_unregister(dict_type_name); + argv_free(dict_union->map_union); + vstring_free(dict_union->re_buf); + dict_free(dict); +} + +/* dict_union_open - open a bunch of tables */ + +DICT *dict_union_open(const char *name, int open_flags, int dict_flags) +{ + static const char myname[] = "dict_union_open"; + DICT_UNION *dict_union; + char *saved_name = 0; + char *dict_type_name; + ARGV *argv = 0; + char **cpp; + DICT *dict; + int match_flags = 0; + struct DICT_OWNER aggr_owner; + size_t len; + + /* + * Clarity first. Let the optimizer worry about redundant code. + */ +#define DICT_UNION_RETURN(x) do { \ + if (saved_name != 0) \ + myfree(saved_name); \ + if (argv != 0) \ + argv_free(argv); \ + return (x); \ + } while (0) + + /* + * Sanity checks. + */ + if (open_flags != O_RDONLY) + DICT_UNION_RETURN(dict_surrogate(DICT_TYPE_UNION, name, + open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_UNION, name)); + + /* + * Split the table name into its constituent parts. + */ + if ((len = balpar(name, CHARS_BRACE)) == 0 || name[len] != 0 + || *(saved_name = mystrndup(name + 1, len - 2)) == 0 + || ((argv = argv_splitq(saved_name, CHARS_COMMA_SP, CHARS_BRACE)), + (argv->argc == 0))) + DICT_UNION_RETURN(dict_surrogate(DICT_TYPE_UNION, name, + open_flags, dict_flags, + "bad syntax: \"%s:%s\"; " + "need \"%s:{type:name...}\"", + DICT_TYPE_UNION, name, + DICT_TYPE_UNION)); + + /* + * The least-trusted table in the set determines the over-all trust + * level. The first table determines the pattern-matching flags. + */ + DICT_OWNER_AGGREGATE_INIT(aggr_owner); + for (cpp = argv->argv; (dict_type_name = *cpp) != 0; cpp++) { + if (msg_verbose) + msg_info("%s: %s", myname, dict_type_name); + if (strchr(dict_type_name, ':') == 0) + DICT_UNION_RETURN(dict_surrogate(DICT_TYPE_UNION, name, + open_flags, dict_flags, + "bad syntax: \"%s:%s\"; " + "need \"%s:{type:name...}\"", + DICT_TYPE_UNION, name, + DICT_TYPE_UNION)); + if ((dict = dict_handle(dict_type_name)) == 0) + dict = dict_open(dict_type_name, open_flags, dict_flags); + dict_register(dict_type_name, dict); + DICT_OWNER_AGGREGATE_UPDATE(aggr_owner, dict->owner); + if (cpp == argv->argv) + match_flags = dict->flags & (DICT_FLAG_FIXED | DICT_FLAG_PATTERN); + } + + /* + * Bundle up the result. + */ + dict_union = + (DICT_UNION *) dict_alloc(DICT_TYPE_UNION, name, sizeof(*dict_union)); + dict_union->dict.lookup = dict_union_lookup; + dict_union->dict.close = dict_union_close; + dict_union->dict.flags = dict_flags | match_flags; + dict_union->dict.owner = aggr_owner; + dict_union->re_buf = vstring_alloc(100); + dict_union->map_union = argv; + argv = 0; + DICT_UNION_RETURN(DICT_DEBUG (&dict_union->dict)); +} diff --git a/src/util/dict_union.h b/src/util/dict_union.h new file mode 100644 index 0000000..9554f84 --- /dev/null +++ b/src/util/dict_union.h @@ -0,0 +1,37 @@ +#ifndef _DICT_UNION_H_INCLUDED_ +#define _DICT_UNION_H_INCLUDED_ + +/*++ +/* NAME +/* dict_union 3h +/* SUMMARY +/* dictionary manager interface for union of tables +/* SYNOPSIS +/* #include <dict_union.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <dict.h> + + /* + * External interface. + */ +#define DICT_TYPE_UNION "unionmap" + +extern DICT *dict_union_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_union_test.in b/src/util/dict_union_test.in new file mode 100644 index 0000000..9d111d4 --- /dev/null +++ b/src/util/dict_union_test.in @@ -0,0 +1,7 @@ +${VALGRIND} ./dict_open 'unionmap:{static:one,static:two,inline:{foo=three}}' read <<EOF +get foo +get bar +EOF +${VALGRIND} ./dict_open 'unionmap:{static:one,fail:fail}' read <<EOF +get foo +EOF diff --git a/src/util/dict_union_test.ref b/src/util/dict_union_test.ref new file mode 100644 index 0000000..b609410 --- /dev/null +++ b/src/util/dict_union_test.ref @@ -0,0 +1,10 @@ ++ ./dict_open unionmap:{static:one,static:two,inline:{foo=three}} read +owner=trusted (uid=2147483647) +> get foo +foo=one,two,three +> get bar +bar=one,two ++ ./dict_open unionmap:{static:one,fail:fail} read +owner=trusted (uid=2147483647) +> get foo +foo: error diff --git a/src/util/dict_unix.c b/src/util/dict_unix.c new file mode 100644 index 0000000..4635344 --- /dev/null +++ b/src/util/dict_unix.c @@ -0,0 +1,204 @@ +/*++ +/* NAME +/* dict_unix 3 +/* SUMMARY +/* dictionary manager interface to UNIX tables +/* SYNOPSIS +/* #include <dict_unix.h> +/* +/* DICT *dict_unix_open(map, dummy, dict_flags) +/* const char *map; +/* int dummy; +/* int dict_flags; +/* DESCRIPTION +/* dict_unix_open() makes the specified UNIX table accessible via +/* the generic dictionary operations described in dict_open(3). +/* The \fIdummy\fR argument is not used. +/* +/* Known map names: +/* .IP passwd.byname +/* The table is the UNIX password database. The key is a login name. +/* The result is a password file entry in passwd(5) format. +/* .IP group.byname +/* The table is the UNIX group database. The key is a group name. +/* The result is a group file entry in group(5) format. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* DIAGNOSTICS +/* Fatal errors: out of memory, unknown map name, attempt to update map. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <pwd.h> +#include <grp.h> + +/* Utility library. */ + +#include "msg.h" +#include "mymalloc.h" +#include "vstring.h" +#include "stringops.h" +#include "dict.h" +#include "dict_unix.h" + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ +} DICT_UNIX; + +/* dict_unix_getpwnam - find password table entry */ + +static const char *dict_unix_getpwnam(DICT *dict, const char *key) +{ + struct passwd *pwd; + static VSTRING *buf; + static int sanity_checked; + + dict->error = 0; + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, key); + key = lowercase(vstring_str(dict->fold_buf)); + } + if ((pwd = getpwnam(key)) == 0) { + if (sanity_checked == 0) { + sanity_checked = 1; + errno = 0; + if (getpwuid(0) == 0) { + msg_warn("cannot access UNIX password database: %m"); + dict->error = DICT_ERR_RETRY; + } + } + return (0); + } else { + if (buf == 0) + buf = vstring_alloc(10); + sanity_checked = 1; + vstring_sprintf(buf, "%s:%s:%ld:%ld:%s:%s:%s", + pwd->pw_name, pwd->pw_passwd, (long) pwd->pw_uid, + (long) pwd->pw_gid, pwd->pw_gecos, pwd->pw_dir, + pwd->pw_shell); + return (vstring_str(buf)); + } +} + +/* dict_unix_getgrnam - find group table entry */ + +static const char *dict_unix_getgrnam(DICT *dict, const char *key) +{ + struct group *grp; + static VSTRING *buf; + char **cpp; + static int sanity_checked; + + dict->error = 0; + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, key); + key = lowercase(vstring_str(dict->fold_buf)); + } + if ((grp = getgrnam(key)) == 0) { + if (sanity_checked == 0) { + sanity_checked = 1; + errno = 0; + if (getgrgid(0) == 0) { + msg_warn("cannot access UNIX group database: %m"); + dict->error = DICT_ERR_RETRY; + } + } + return (0); + } else { + if (buf == 0) + buf = vstring_alloc(10); + sanity_checked = 1; + vstring_sprintf(buf, "%s:%s:%ld:", + grp->gr_name, grp->gr_passwd, (long) grp->gr_gid); + for (cpp = grp->gr_mem; *cpp; cpp++) { + vstring_strcat(buf, *cpp); + if (cpp[1]) + VSTRING_ADDCH(buf, ','); + } + VSTRING_TERMINATE(buf); + return (vstring_str(buf)); + } +} + +/* dict_unix_close - close UNIX map */ + +static void dict_unix_close(DICT *dict) +{ + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_unix_open - open UNIX map */ + +DICT *dict_unix_open(const char *map, int open_flags, int dict_flags) +{ + DICT_UNIX *dict_unix; + struct dict_unix_lookup { + char *name; + const char *(*lookup) (DICT *, const char *); + }; + static struct dict_unix_lookup dict_unix_lookup[] = { + "passwd.byname", dict_unix_getpwnam, + "group.byname", dict_unix_getgrnam, + 0, + }; + struct dict_unix_lookup *lp; + + /* + * Sanity checks. + */ + if (open_flags != O_RDONLY) + return (dict_surrogate(DICT_TYPE_UNIX, map, open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_UNIX, map)); + + /* + * "Open" the database. + */ + for (lp = dict_unix_lookup; /* void */ ; lp++) { + if (lp->name == 0) + return (dict_surrogate(DICT_TYPE_UNIX, map, open_flags, dict_flags, + "unknown table: %s:%s", DICT_TYPE_UNIX, map)); + if (strcmp(map, lp->name) == 0) + break; + } + dict_unix = (DICT_UNIX *) dict_alloc(DICT_TYPE_UNIX, map, + sizeof(*dict_unix)); + dict_unix->dict.lookup = lp->lookup; + dict_unix->dict.close = dict_unix_close; + dict_unix->dict.flags = dict_flags | DICT_FLAG_FIXED; + if (dict_flags & DICT_FLAG_FOLD_FIX) + dict_unix->dict.fold_buf = vstring_alloc(10); + dict_unix->dict.owner.status = DICT_OWNER_TRUSTED; + + return (DICT_DEBUG (&dict_unix->dict)); +} diff --git a/src/util/dict_unix.h b/src/util/dict_unix.h new file mode 100644 index 0000000..b5674b2 --- /dev/null +++ b/src/util/dict_unix.h @@ -0,0 +1,37 @@ +#ifndef _DICT_UNIX_H_INCLUDED_ +#define _DICT_UNIX_H_INCLUDED_ + +/*++ +/* NAME +/* dict_unix 3h +/* SUMMARY +/* dictionary manager interface to UNIX maps +/* SYNOPSIS +/* #include <dict_unix.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <dict.h> + + /* + * External interface. + */ +#define DICT_TYPE_UNIX "unix" + +extern DICT *dict_unix_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_utf8.c b/src/util/dict_utf8.c new file mode 100644 index 0000000..f1fc65a --- /dev/null +++ b/src/util/dict_utf8.c @@ -0,0 +1,300 @@ +/*++ +/* NAME +/* dict_utf8 3 +/* SUMMARY +/* dictionary UTF-8 helpers +/* SYNOPSIS +/* #include <dict.h> +/* +/* DICT *dict_utf8_activate( +/* DICT *dict) +/* DESCRIPTION +/* dict_utf8_activate() wraps a dictionary's lookup/update/delete +/* methods with code that enforces UTF-8 checks on keys and +/* values, and that logs a warning when incorrect UTF-8 is +/* encountered. The original dictionary handle becomes invalid. +/* +/* The wrapper code enforces a policy that maximizes application +/* robustness (it avoids the need for new error-handling code +/* paths in application code). Attempts to store non-UTF-8 +/* keys or values are skipped while reporting a non-error +/* status, attempts to look up or delete non-UTF-8 keys are +/* skipped while reporting a non-error status, and lookup +/* results that contain a non-UTF-8 value are blocked while +/* reporting a configuration error. +/* BUGS +/* dict_utf8_activate() does not nest. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include <sys_defs.h> +#include <string.h> + + /* + * Utility library. + */ +#include <msg.h> +#include <stringops.h> +#include <dict.h> +#include <mymalloc.h> +#include <msg.h> + + /* + * The goal is to maximize robustness: bad UTF-8 should not appear in keys, + * because those are derived from controlled inputs, and values should be + * printable before they are stored. But if we failed to check something + * then it should not result in fatal errors and thus open up the system for + * a denial-of-service attack. + * + * Proposed over-all policy: skip attempts to store invalid UTF-8 lookup keys + * or values. Rationale: some storage may not permit malformed UTF-8. This + * maximizes program robustness. If we get an invalid lookup result, report + * a configuration error. + * + * LOOKUP + * + * If the key is invalid, log a warning and skip the request. Rationale: the + * item cannot exist. + * + * If the lookup result is invalid, log a warning and return a configuration + * error. + * + * UPDATE + * + * If the key is invalid, then log a warning and skip the request. Rationale: + * the item cannot exist. + * + * If the value is invalid, log a warning and skip the request. Rationale: + * storage may not permit malformed UTF-8. This maximizes program + * robustness. + * + * DELETE + * + * If the key is invalid, then skip the request. Rationale: the item cannot + * exist. + */ + +/* dict_utf8_check_fold - casefold or validate string */ + +static char *dict_utf8_check_fold(DICT *dict, const char *string, + CONST_CHAR_STAR *err) +{ + int fold_flag = (dict->flags & DICT_FLAG_FOLD_ANY); + + /* + * Validate UTF-8 without casefolding. + */ + if (!allascii(string) && valid_utf8_string(string, strlen(string)) == 0) { + if (err) + *err = "malformed UTF-8 or invalid codepoint"; + return (0); + } + + /* + * Casefold UTF-8. + */ + if (fold_flag != 0 + && (fold_flag & ((dict->flags & DICT_FLAG_FIXED) ? + DICT_FLAG_FOLD_FIX : DICT_FLAG_FOLD_MUL))) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + return (casefold(dict->fold_buf, string)); + } + return ((char *) string); +} + +/* dict_utf8_check validate UTF-8 string */ + +static int dict_utf8_check(const char *string, CONST_CHAR_STAR *err) +{ + if (!allascii(string) && valid_utf8_string(string, strlen(string)) == 0) { + if (err) + *err = "malformed UTF-8 or invalid codepoint"; + return (0); + } + return (1); +} + +/* dict_utf8_lookup - UTF-8 lookup method wrapper */ + +static const char *dict_utf8_lookup(DICT *dict, const char *key) +{ + DICT_UTF8_BACKUP *backup; + const char *utf8_err; + const char *fold_res; + const char *value; + int saved_flags; + + /* + * Validate and optionally fold the key, and if invalid skip the request. + */ + if ((fold_res = dict_utf8_check_fold(dict, key, &utf8_err)) == 0) { + msg_warn("%s:%s: non-UTF-8 key \"%s\": %s", + dict->type, dict->name, key, utf8_err); + dict->error = DICT_ERR_NONE; + return (0); + } + + /* + * Proxy the request with casefolding turned off. + */ + saved_flags = (dict->flags & DICT_FLAG_FOLD_ANY); + dict->flags &= ~DICT_FLAG_FOLD_ANY; + backup = dict->utf8_backup; + value = backup->lookup(dict, fold_res); + dict->flags |= saved_flags; + + /* + * Validate the result, and if invalid fail the request. + */ + if (value != 0 && dict_utf8_check(value, &utf8_err) == 0) { + msg_warn("%s:%s: key \"%s\": non-UTF-8 value \"%s\": %s", + dict->type, dict->name, key, value, utf8_err); + dict->error = DICT_ERR_CONFIG; + return (0); + } else { + return (value); + } +} + +/* dict_utf8_update - UTF-8 update method wrapper */ + +static int dict_utf8_update(DICT *dict, const char *key, const char *value) +{ + DICT_UTF8_BACKUP *backup; + const char *utf8_err; + const char *fold_res; + int saved_flags; + int status; + + /* + * Validate or fold the key, and if invalid skip the request. + */ + if ((fold_res = dict_utf8_check_fold(dict, key, &utf8_err)) == 0) { + msg_warn("%s:%s: non-UTF-8 key \"%s\": %s", + dict->type, dict->name, key, utf8_err); + dict->error = DICT_ERR_NONE; + return (DICT_STAT_SUCCESS); + } + + /* + * Validate the value, and if invalid skip the request. + */ + else if (dict_utf8_check(value, &utf8_err) == 0) { + msg_warn("%s:%s: key \"%s\": non-UTF-8 value \"%s\": %s", + dict->type, dict->name, key, value, utf8_err); + dict->error = DICT_ERR_NONE; + return (DICT_STAT_SUCCESS); + } + + /* + * Proxy the request with casefolding turned off. + */ + else { + saved_flags = (dict->flags & DICT_FLAG_FOLD_ANY); + dict->flags &= ~DICT_FLAG_FOLD_ANY; + backup = dict->utf8_backup; + status = backup->update(dict, fold_res, value); + dict->flags |= saved_flags; + return (status); + } +} + +/* dict_utf8_delete - UTF-8 delete method wrapper */ + +static int dict_utf8_delete(DICT *dict, const char *key) +{ + DICT_UTF8_BACKUP *backup; + const char *utf8_err; + const char *fold_res; + int saved_flags; + int status; + + /* + * Validate and optionally fold the key, and if invalid skip the request. + */ + if ((fold_res = dict_utf8_check_fold(dict, key, &utf8_err)) == 0) { + msg_warn("%s:%s: non-UTF-8 key \"%s\": %s", + dict->type, dict->name, key, utf8_err); + dict->error = DICT_ERR_NONE; + return (DICT_STAT_SUCCESS); + } + + /* + * Proxy the request with casefolding turned off. + */ + else { + saved_flags = (dict->flags & DICT_FLAG_FOLD_ANY); + dict->flags &= ~DICT_FLAG_FOLD_ANY; + backup = dict->utf8_backup; + status = backup->delete(dict, fold_res); + dict->flags |= saved_flags; + return (status); + } +} + +/* dict_utf8_activate - wrap a legacy dict object for UTF-8 processing */ + +DICT *dict_utf8_activate(DICT *dict) +{ + const char myname[] = "dict_utf8_activate"; + DICT_UTF8_BACKUP *backup; + + /* + * Sanity check. + */ + if (util_utf8_enable == 0) + msg_panic("%s: Unicode support is not available", myname); + if ((dict->flags & DICT_FLAG_UTF8_REQUEST) == 0) + msg_panic("%s: %s:%s does not request Unicode support", + myname, dict->type, dict->name); + if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) || dict->utf8_backup != 0) + msg_panic("%s: %s:%s Unicode support is already activated", + myname, dict->type, dict->name); + + /* + * Unlike dict_debug(3) we do not put a proxy dict object in front of the + * encapsulated object, because then we would have to bidirectionally + * propagate changes in the data members (errors, flags, jbuf, and so on) + * between proxy object and encapsulated object. + * + * Instead we attach ourselves behind the encapsulated dict object, and + * redirect some function pointers to ourselves. + */ + backup = dict->utf8_backup = (DICT_UTF8_BACKUP *) mymalloc(sizeof(*backup)); + + /* + * Interpose on the lookup/update/delete methods. It is a conscious + * decision not to tinker with the iterator or destructor. + */ + backup->lookup = dict->lookup; + backup->update = dict->update; + backup->delete = dict->delete; + + dict->lookup = dict_utf8_lookup; + dict->update = dict_utf8_update; + dict->delete = dict_utf8_delete; + + /* + * Leave our mark. See sanity check above. + */ + dict->flags |= DICT_FLAG_UTF8_ACTIVE; + + return (dict); +} diff --git a/src/util/dict_utf8_test.in b/src/util/dict_utf8_test.in new file mode 100644 index 0000000..f8d4536 --- /dev/null +++ b/src/util/dict_utf8_test.in @@ -0,0 +1,14 @@ +#!/bin/sh + +LC_ALL=C awk 'BEGIN { + print "flags" + print "verbose" + printf "get foo\n" + printf "put Δημοσθένους.example.com aaa\n" + printf "get Δημοσθένους.example.com\n" + printf "put %c%c%c xxx\n", 128, 128, 128 + printf "get %c%c%c\n", 128, 128, 128 + printf "put xxx %c%c%c\n", 128, 128, 128 + printf "get xxx\n" + exit +}' | ${VALGRIND} ./dict_open internal:whatever write utf8_request diff --git a/src/util/dict_utf8_test.ref b/src/util/dict_utf8_test.ref new file mode 100644 index 0000000..7b825f9 --- /dev/null +++ b/src/util/dict_utf8_test.ref @@ -0,0 +1,18 @@ +owner=trusted (uid=2147483647) +> flags +dict flags fixed|lock|replace|utf8_request|utf8_active +> verbose +> get foo +foo: not found +> put Δημοσθένους.example.com aaa +> get Δημοσθένους.example.com +Δημοσθένους.example.com=aaa +> put €€€ xxx +./dict_open: warning: internal:whatever: non-UTF-8 key "???": malformed UTF-8 or invalid codepoint +> get €€€ +./dict_open: warning: internal:whatever: non-UTF-8 key "???": malformed UTF-8 or invalid codepoint +€€€: not found +> put xxx €€€ +./dict_open: warning: internal:whatever: key "xxx": non-UTF-8 value "???": malformed UTF-8 or invalid codepoint +> get xxx +xxx: not found diff --git a/src/util/dir_forest.c b/src/util/dir_forest.c new file mode 100644 index 0000000..0070177 --- /dev/null +++ b/src/util/dir_forest.c @@ -0,0 +1,110 @@ +/*++ +/* NAME +/* dir_forest 3 +/* SUMMARY +/* file name to directory forest +/* SYNOPSIS +/* #include <dir_forest.h> +/* +/* char *dir_forest(buf, path, depth) +/* VSTRING *buf; +/* const char *path; +/* int depth; +/* DESCRIPTION +/* This module implements support for directory forests: a file +/* organization that introduces one or more levels of intermediate +/* subdirectories in order to reduce the number of files per directory. +/* +/* dir_forest() maps a file basename to a directory forest and +/* returns the resulting string: file name "abcd" becomes "a/b/" +/* and so on. The number of subdirectory levels is adjustable. +/* +/* Arguments: +/* .IP buf +/* A buffer that is overwritten with the result. The result +/* ends in "/" and is null terminated. If a null pointer is +/* specified, the result is written to a private buffer that +/* is overwritten upon each call. +/* .IP path +/* A null-terminated string of printable characters. Characters +/* special to the file system are not permitted. +/* The first subdirectory is named after the first character +/* in \fIpath\fR, and so on. When the path is shorter than the +/* desired number of subdirectory levels, directory names +/* of '_' (underscore) are used as replacement. +/* .IP depth +/* The desired number of subdirectory levels. +/* DIAGNOSTICS +/* Panic: interface violations. Fatal error: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <ctype.h> + +/* Utility library. */ + +#include "msg.h" +#include "dir_forest.h" + +/* dir_forest - translate base name to directory forest */ + +char *dir_forest(VSTRING *buf, const char *path, int depth) +{ + const char *myname = "dir_forest"; + static VSTRING *private_buf = 0; + int n; + const char *cp; + int ch; + + /* + * Sanity checks. + */ + if (*path == 0) + msg_panic("%s: empty path", myname); + if (depth < 1) + msg_panic("%s: depth %d", myname, depth); + + /* + * Your buffer or mine? + */ + if (buf == 0) { + if (private_buf == 0) + private_buf = vstring_alloc(1); + buf = private_buf; + } + + /* + * Generate one or more subdirectory levels, depending on the pathname + * contents. When the pathname is short, use underscores instead. + * Disallow non-printable characters or characters that are special to + * the file system. + */ + VSTRING_RESET(buf); + for (cp = path, n = 0; n < depth; n++) { + if ((ch = *cp) == 0) { + ch = '_'; + } else { + if (!ISPRINT(ch) || ch == '.' || ch == '/') + msg_panic("%s: invalid pathname: %s", myname, path); + cp++; + } + VSTRING_ADDCH(buf, ch); + VSTRING_ADDCH(buf, '/'); + } + VSTRING_TERMINATE(buf); + + if (msg_verbose > 1) + msg_info("%s: %s -> %s", myname, path, vstring_str(buf)); + return (vstring_str(buf)); +} diff --git a/src/util/dir_forest.h b/src/util/dir_forest.h new file mode 100644 index 0000000..2c4c363 --- /dev/null +++ b/src/util/dir_forest.h @@ -0,0 +1,35 @@ +#ifndef _DIR_FOREST_H_INCLUDED_ +#define _DIR_FOREST_H_INCLUDED_ + +/*++ +/* NAME +/* dir_forest 3h +/* SUMMARY +/* file name to directory forest +/* SYNOPSIS +/* #include <dir_forest.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <vstring.h> + + /* + * External interface. + */ +extern char *dir_forest(VSTRING *, const char *, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/doze.c b/src/util/doze.c new file mode 100644 index 0000000..28d5669 --- /dev/null +++ b/src/util/doze.c @@ -0,0 +1,71 @@ +/*++ +/* NAME +/* doze 3 +/* SUMMARY +/* take a nap +/* SYNOPSIS +/* #include <iostuff.h> +/* +/* void doze(microseconds) +/* unsigned microseconds; +/* DESCRIPTION +/* doze() sleeps for the specified number of microseconds. It is +/* a simple alternative for systems that lack usleep(). +/* DIAGNOSTICS +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <sys/time.h> +#include <unistd.h> +#include <errno.h> +#ifdef USE_SYS_SELECT_H +#include <sys/select.h> +#endif + +/* Utility library. */ + +#include <msg.h> +#include <iostuff.h> + +/* doze - sleep a while */ + +void doze(unsigned delay) +{ + struct timeval tv; + +#define MILLION 1000000 + + tv.tv_sec = delay / MILLION; + tv.tv_usec = delay % MILLION; + while (select(0, (fd_set *) 0, (fd_set *) 0, (fd_set *) 0, &tv) < 0) + if (errno != EINTR) + msg_fatal("doze: select: %m"); +} + +#ifdef TEST + +#include <stdlib.h> + +int main(int argc, char **argv) +{ + unsigned delay; + + if (argc != 2 || (delay = atol(argv[1])) == 0) + msg_fatal("usage: %s microseconds", argv[0]); + doze(delay); + exit(0); +} + +#endif diff --git a/src/util/dummy_read.c b/src/util/dummy_read.c new file mode 100644 index 0000000..639004f --- /dev/null +++ b/src/util/dummy_read.c @@ -0,0 +1,61 @@ +/*++ +/* NAME +/* dummy_read 3 +/* SUMMARY +/* dummy read operation +/* SYNOPSIS +/* #include <iostuff.h> +/* +/* ssize_t dummy_read(fd, buf, buf_len, timeout, context) +/* int fd; +/* void *buf; +/* size_t len; +/* int timeout; +/* void *context; +/* DESCRIPTION +/* dummy_read() reports an EOF condition without side effects. +/* +/* Arguments: +/* .IP fd +/* File descriptor whose value is logged when verbose logging +/* is turned on. +/* .IP buf +/* Read buffer pointer. Not used. +/* .IP buf_len +/* Read buffer size. Its value is logged when verbose logging is +/* turned on. +/* .IP timeout +/* The deadline in seconds. Not used. +/* .IP context +/* Application context. Not used. +/* DIAGNOSTICS +/* None. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +/* Utility library. */ + +#include <msg.h> +#include <iostuff.h> + +/* dummy_read - dummy read operation */ + +ssize_t dummy_read(int fd, void *unused_buf, size_t len, + int unused_timeout, void *unused_context) +{ + if (msg_verbose) + msg_info("dummy_read: fd %d, len %lu", fd, (unsigned long) len); + return (0); +} diff --git a/src/util/dummy_write.c b/src/util/dummy_write.c new file mode 100644 index 0000000..943e2ad --- /dev/null +++ b/src/util/dummy_write.c @@ -0,0 +1,61 @@ +/*++ +/* NAME +/* dummy_write 3 +/* SUMMARY +/* dummy write operation +/* SYNOPSIS +/* #include <iostuff.h> +/* +/* ssize_t dummy_write(fd, buf, buf_len, timeout, context) +/* int fd; +/* void *buf; +/* size_t len; +/* int timeout; +/* void *context; +/* DESCRIPTION +/* dummy_write() implements a data sink without side effects. +/* +/* Arguments: +/* .IP fd +/* File descriptor whose value is logged when verbose logging +/* is turned on. +/* .IP buf +/* Write buffer pointer. Not used. +/* .IP buf_len +/* Write buffer size. Its value is logged when verbose logging is +/* turned on. +/* .IP timeout +/* The deadline in seconds. Not used. +/* .IP context +/* Application context. Not used. +/* DIAGNOSTICS +/* None. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +/* Utility library. */ + +#include <msg.h> +#include <iostuff.h> + +/* dummy_write - dummy write operation */ + +ssize_t dummy_write(int fd, void *unused_buf, size_t len, + int unused_timeout, void *unused_context) +{ + if (msg_verbose) + msg_info("dummy_write: fd %d, len %lu", fd, (unsigned long) len); + return (len); +} diff --git a/src/util/dup2_pass_on_exec.c b/src/util/dup2_pass_on_exec.c new file mode 100644 index 0000000..5286e5b --- /dev/null +++ b/src/util/dup2_pass_on_exec.c @@ -0,0 +1,64 @@ +/*++ +/* NAME +/* dup2_pass_on_exec 1 +/* SUMMARY +/* dup2 close-on-exec behavior test program +/* SYNOPSIS +/* dup2_pass_on_exec +/* DESCRIPTION +/* dup2_pass_on_exec sets the close-on-exec flag on its +/* standard input and then dup2() to duplicate it. +/* Posix-1003.1 specifies in section 6.2.1.2 that dup2(o,n) should behave +/* as: close(n); n = fcntl(o, F_DUPFD, n); as long as o is a valid +/* file-descriptor, n!=o, and 0<=n<=[OPEN_MAX]. +/* Section 6.5.2.2 states that the close-on-exec flag of the result of a +/* successful fcntl(o, F_DUPFD, n) is cleared. +/* +/* At least Ultrix4.3a does not clear the close-on-exec flag of n on +/* dup2(o, n). +/* DIAGNOSTICS +/* Problems are reported to the standard error stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Christian von Roques <roques@pond.sub.org> +/* Forststrasse 71 +/* 76131 Karlsruhe, GERMANY +/*--*/ + +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> + +#define DO(s) if (s < 0) { perror(#s); exit(1); } + +int main(int unused_argc, char **unused_argv) +{ + int res; + + printf("Setting the close-on-exec flag of file-descriptor 0.\n"); + DO(fcntl(0, F_SETFD, 1)); + + printf("Duplicating file-descriptor 0 to 3.\n"); + DO(dup2(0, 3)); + + printf("Testing if the close-on-exec flag of file-descriptor 3 is set.\n"); + DO((res = fcntl(3, F_GETFD, 0))); + if (res & 1) + printf( +"Yes, a newly dup2()ed file-descriptor has the close-on-exec \ +flag cloned.\n\ +THIS VIOLATES Posix1003.1 section 6.2.1.2 or 6.5.2.2!\n\ +You should #define DUP2_DUPS_CLOSE_ON_EXEC in sys_defs.h \ +for your OS.\n"); + else + printf( +"No, a newly dup2()ed file-descriptor has the close-on-exec \ +flag cleared.\n\ +This complies with Posix1003.1 section 6.2.1.2 and 6.5.2.2!\n"); + + return 0; +} diff --git a/src/util/duplex_pipe.c b/src/util/duplex_pipe.c new file mode 100644 index 0000000..04f23f6 --- /dev/null +++ b/src/util/duplex_pipe.c @@ -0,0 +1,49 @@ +/*++ +/* NAME +/* duplex_pipe 3 +/* SUMMARY +/* local IPD +/* SYNOPSIS +/* #include <iostuff.h> +/* +/* int duplex_pipe(fds) +/* int *fds; +/* DESCRIPTION +/* duplex_pipe() uses whatever local primitive it takes +/* to get a two-way I/O channel. +/* DIAGNOSTICS +/* A null result means success. In case of error, the result +/* is -1 and errno is set to the appropriate number. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <unistd.h> + +/* Utility library. */ + +#include "iostuff.h" +#include "sane_socketpair.h" + +/* duplex_pipe - give me a duplex pipe or bust */ + +int duplex_pipe(int *fds) +{ +#ifdef HAS_DUPLEX_PIPE + return (pipe(fds)); +#else + return (sane_socketpair(AF_UNIX, SOCK_STREAM, 0, fds)); +#endif +} + diff --git a/src/util/edit_file.c b/src/util/edit_file.c new file mode 100644 index 0000000..9d76b93 --- /dev/null +++ b/src/util/edit_file.c @@ -0,0 +1,353 @@ +/*++ +/* NAME +/* edit_file 3 +/* SUMMARY +/* simple cooperative file updating protocol +/* SYNOPSIS +/* #include <edit_file.h> +/* +/* typedef struct { +/* .in +4 +/* char *tmp_path; /* temp. pathname */ +/* VSTREAM *tmp_fp; /* temp. stream */ +/* /* private members... */ +/* .in -4 +/* } EDIT_FILE; +/* +/* EDIT_FILE *edit_file_open(original_path, output_flags, output_mode) +/* const char *original_path; +/* int output_flags; +/* mode_t output_mode; +/* +/* int edit_file_close(edit_file) +/* EDIT_FILE *edit_file; +/* +/* void edit_file_cleanup(edit_file) +/* EDIT_FILE *edit_file; +/* DESCRIPTION +/* This module implements a simple protocol for cooperative +/* processes to update one file. The idea is to 1) create a +/* new file under a deterministic temporary pathname, 2) +/* populate the new file with updated information, and 3) +/* rename the new file into the place of the original file. +/* This module provides 1) and 3), and leaves 2) to the +/* application. The temporary pathname is deterministic to +/* avoid accumulation of thrash after program crashes. +/* +/* edit_file_open() implements the first phase of the protocol. +/* It creates or opens an output file with a deterministic +/* temporary pathname, obtained by appending the suffix defined +/* with EDIT_FILE_SUFFIX to the specified original file pathname. +/* The original file itself is not opened. edit_file_open() +/* then locks the output file for exclusive access, and verifies +/* that the file still exists under the temporary pathname. +/* At this point in the protocol, the current process controls +/* both the output file content and its temporary pathname. +/* +/* In the second phase, the application opens the original +/* file if needed, and updates the output file via the +/* \fBtmp_fp\fR member of the EDIT_FILE data structure. This +/* phase is not implemented by the edit_file() module. +/* +/* edit_file_close() implements the third and final phase of +/* the protocol. It flushes the output file to persistent +/* storage, and renames the output file from its temporary +/* pathname into the place of the original file. When any of +/* these operations fails, edit_file_close() behaves as if +/* edit_file_cleanup() was called. Regardless of whether these +/* operations succeed, edit_file_close() releases the exclusive +/* lock, closes the output file, and frees up memory that was +/* allocated by edit_file_open(). +/* +/* edit_file_cleanup() aborts the protocol. It discards the +/* output file, releases the exclusive lock, closes the output +/* file, and frees up memory that was allocated by edit_file_open(). +/* +/* Arguments: +/* .IP original_path +/* The pathname of the original file that will be replaced by +/* the output file. The temporary pathname for the output file +/* is obtained by appending the suffix defined with EDIT_FILE_SUFFIX +/* to a copy of the specified original file pathname, and is +/* made available via the \fBtmp_path\fR member of the EDIT_FILE +/* data structure. +/* .IP output_flags +/* Flags for opening the output file. These are as with open(2), +/* except that the O_TRUNC flag is ignored. edit_file_open() +/* always truncates the output file after it has obtained +/* exclusive control over the output file content and temporary +/* pathname. +/* .IP output_mode +/* Permissions for the output file. These are as with open(2), +/* except that the output file is initially created with no +/* group or other access permissions. The specified output +/* file permissions are applied by edit_file_close(). +/* .IP edit_file +/* Pointer to data structure that is returned upon successful +/* completion by edit_file_open(), and that must be passed to +/* edit_file_close() or edit_file_cleanup(). +/* DIAGNOSTICS +/* Fatal errors: memory allocation failure, fstat() failure, +/* unlink() failure, lock failure, ftruncate() failure. +/* +/* edit_file_open() immediately returns a null pointer when +/* it cannot open the output file. +/* +/* edit_file_close() returns zero on success, VSTREAM_EOF on +/* failure. +/* +/* With both functions, the global errno variable indicates +/* the nature of the problem. All errors are relative to the +/* temporary output's pathname. With both functions, this +/* pathname is not available via the EDIT_FILE data structure, +/* because that structure was already destroyed, or not created. +/* BUGS +/* In the non-error case, edit_file_open() will not return +/* until it obtains exclusive control over the output file +/* content and temporary pathname. Applications that are +/* concerned about deadlock should protect the edit_file_open() +/* call with a watchdog timer. +/* +/* When interrupted, edit_file_close() may leave behind a +/* world-readable output file under the temporary pathname. +/* On some systems this can be used to inflict a shared-lock +/* DOS on the protocol. Applications that are concerned about +/* maximal safety should protect the edit_file_close() call +/* with sigdelay() and sigresume() calls, but this introduces +/* the risk that the program will get stuck forever. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Based on code originally by: +/* Victor Duchovni +/* Morgan Stanley +/* +/* Packaged into one module with minor improvements by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/stat.h> +#include <stdio.h> /* rename(2) */ +#include <errno.h> + + /* + * This mask selects all permission bits in the st_mode stat data. There is + * no portable definition (unlike S_IFMT, which is defined for the file type + * bits). For example, BSD / Linux have ALLPERMS, while Solaris has S_IAMB. + */ +#define FILE_PERM_MASK \ + (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO) + +/* Utility Library. */ + +#include <msg.h> +#include <vstream.h> +#include <mymalloc.h> +#include <stringops.h> +#include <myflock.h> +#include <edit_file.h> +#include <warn_stat.h> + + /* + * Do we reuse and truncate an output file that persists after a crash, or + * do we unlink it and create a new file? + */ +#define EDIT_FILE_REUSE_AFTER_CRASH + + /* + * Protocol internals: the temporary file permissions. + */ +#define EDIT_FILE_MODE (S_IRUSR | S_IWUSR) /* temp file mode */ + + /* + * Make complex operations more readable. We could use functions, instead. + * The main thing is that we keep the _alloc and _free code together. + */ +#define EDIT_FILE_ALLOC(ep, path, mode) do { \ + (ep) = (EDIT_FILE *) mymalloc(sizeof(EDIT_FILE)); \ + (ep)->final_path = mystrdup(path); \ + (ep)->final_mode = (mode); \ + (ep)->tmp_path = concatenate((path), EDIT_FILE_SUFFIX, (char *) 0); \ + (ep)->tmp_fp = 0; \ + } while (0) + +#define EDIT_FILE_FREE(ep) do { \ + myfree((ep)->final_path); \ + myfree((ep)->tmp_path); \ + myfree((void *) (ep)); \ + } while (0) + +/* edit_file_open - open and lock file with deterministic temporary pathname */ + +EDIT_FILE *edit_file_open(const char *path, int flags, mode_t mode) +{ + struct stat before_lock; + struct stat after_lock; + int saved_errno; + EDIT_FILE *ep; + + /* + * Initialize. Do not bother to optimize for the error case. + */ + EDIT_FILE_ALLOC(ep, path, mode); + + /* + * As long as the output file can be opened under the temporary pathname, + * this code can loop or block forever. + * + * Applications that are concerned about deadlock should protect the + * edit_file_open() call with a watchdog timer. + */ + for ( /* void */ ; /* void */ ; (void) vstream_fclose(ep->tmp_fp)) { + + /* + * Try to open the output file under the temporary pathname. This + * succeeds or fails immediately. To avoid creating a shared-lock DOS + * opportunity after we crash, we create the output file with no + * group or other permissions, and set the final permissions at the + * end (this is one reason why we try to get exclusive control over + * the output file instead of the original file). We postpone file + * truncation until we have obtained exclusive control over the file + * content and temporary pathname. If the open operation fails, we + * give up immediately. The caller can retry the call if desirable. + * + * XXX If we replace the vstream_fopen() call by safe_open(), then we + * should replace the stat() call below by lstat(). + */ + if ((ep->tmp_fp = vstream_fopen(ep->tmp_path, flags & ~(O_TRUNC), + EDIT_FILE_MODE)) == 0) { + saved_errno = errno; + EDIT_FILE_FREE(ep); + errno = saved_errno; + return (0); + } + + /* + * At this point we may have opened an existing output file that was + * already locked. Try to lock the open file exclusively. This may + * take some time. + */ + if (myflock(vstream_fileno(ep->tmp_fp), INTERNAL_LOCK, + MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("lock %s: %m", ep->tmp_path); + + /* + * At this point we have an exclusive lock, but some other process + * may have renamed or removed the output file while we were waiting + * for the lock. If that is the case, back out and try again. + */ + if (fstat(vstream_fileno(ep->tmp_fp), &before_lock) < 0) + msg_fatal("open %s: %m", ep->tmp_path); + if (stat(ep->tmp_path, &after_lock) < 0 + || before_lock.st_dev != after_lock.st_dev + || before_lock.st_ino != after_lock.st_ino +#ifdef HAS_ST_GEN + || before_lock.st_gen != after_lock.st_gen +#endif + /* No need to compare st_rdev or st_nlink here. */ + ) { + continue; + } + + /* + * At this point we have exclusive control over the output file + * content and its temporary pathname (within the rules of the + * cooperative protocol). But wait, there is more. + * + * There are many opportunities for trouble when opening a pre-existing + * output file. Here are just a few. + * + * - Victor observes that a system crash in the middle of the + * final-phase rename() operation may result in the output file + * having both the temporary pathname and the final pathname. In that + * case we must not write to the output file. + * + * - Wietse observes that crashes may also leave the output file in + * other inconsistent states. To avoid permission-related trouble, we + * simply refuse to work with an output file that has the wrong + * temporary permissions. This won't stop the shared-lock DOS if we + * crash after changing the file permissions, though. + * + * To work around these crash-related problems, remove the temporary + * pathname, back out, and try again. + */ + if (!S_ISREG(after_lock.st_mode) +#ifndef EDIT_FILE_REUSE_AFTER_CRASH + || after_lock.st_size > 0 +#endif + || after_lock.st_nlink > 1 + || (after_lock.st_mode & FILE_PERM_MASK) != EDIT_FILE_MODE) { + if (unlink(ep->tmp_path) < 0 && errno != ENOENT) + msg_fatal("unlink %s: %m", ep->tmp_path); + continue; + } + + /* + * Settle the final details. + */ +#ifdef EDIT_FILE_REUSE_AFTER_CRASH + if (ftruncate(vstream_fileno(ep->tmp_fp), 0) < 0) + msg_fatal("truncate %s: %m", ep->tmp_path); +#endif + return (ep); + } +} + +/* edit_file_cleanup - clean up without completing the protocol */ + +void edit_file_cleanup(EDIT_FILE *ep) +{ + + /* + * Don't touch the file after we lose the exclusive lock! + */ + if (unlink(ep->tmp_path) < 0 && errno != ENOENT) + msg_fatal("unlink %s: %m", ep->tmp_path); + (void) vstream_fclose(ep->tmp_fp); + EDIT_FILE_FREE(ep); +} + +/* edit_file_close - rename the file into place and close the file */ + +int edit_file_close(EDIT_FILE *ep) +{ + VSTREAM *fp = ep->tmp_fp; + int fd = vstream_fileno(fp); + int saved_errno; + + /* + * The rename/unlock portion of the protocol is relatively simple. The + * only things that really matter here are that we change permissions as + * late as possible, and that we rename the file to its final pathname + * before we lose the exclusive lock. + * + * Applications that are concerned about maximal safety should protect the + * edit_file_close() call with sigdelay() and sigresume() calls. It is + * not safe for us to call these functions directly, because the calls do + * not nest. It is also not nice to force every caller to run with + * interrupts turned off. + */ + if (vstream_fflush(fp) < 0 + || fchmod(fd, ep->final_mode) < 0 +#ifdef HAS_FSYNC + || fsync(fd) < 0 +#endif + || rename(ep->tmp_path, ep->final_path) < 0) { + saved_errno = errno; + edit_file_cleanup(ep); + errno = saved_errno; + return (VSTREAM_EOF); + } else { + (void) vstream_fclose(ep->tmp_fp); + EDIT_FILE_FREE(ep); + return (0); + } +} diff --git a/src/util/edit_file.h b/src/util/edit_file.h new file mode 100644 index 0000000..bd13a29 --- /dev/null +++ b/src/util/edit_file.h @@ -0,0 +1,53 @@ +#ifndef _EDIT_FILE_H_INCLUDED_ +#define _EDIT_FILE_H_INCLUDED_ + +/*++ +/* NAME +/* edit_file 3h +/* SUMMARY +/* simple cooperative file updating protocol +/* SYNOPSIS +/* #include <edit_file.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <vstream.h> + + /* + * External interface. + */ +typedef struct { + /* Private. */ + char *final_path; + mode_t final_mode; + /* Public. */ + char *tmp_path; + VSTREAM *tmp_fp; +} EDIT_FILE; + +#define EDIT_FILE_SUFFIX ".tmp" + +extern EDIT_FILE *edit_file_open(const char *, int, mode_t); +extern int WARN_UNUSED_RESULT edit_file_close(EDIT_FILE *); +extern void edit_file_cleanup(EDIT_FILE *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/environ.c b/src/util/environ.c new file mode 100644 index 0000000..4b6c59e --- /dev/null +++ b/src/util/environ.c @@ -0,0 +1,156 @@ + /* + * From: TCP Wrapper. + * + * Many systems have putenv() but no setenv(). Other systems have setenv() but + * no putenv() (MIPS). Still other systems have neither (NeXT). This is a + * re-implementation that hopefully ends all problems. + * + * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands. + */ +#include "sys_defs.h" + +#ifdef MISSING_SETENV_PUTENV + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +extern char **environ; + +static int addenv(char *); /* append entry to environment */ +static int allocated = 0; /* environ is, or is not, allocated */ + +#define DO_CLOBBER 1 + +/* namelength - determine length of name in "name=whatever" */ + +static ssize_t namelength(const char *name) +{ + char *equal; + + equal = strchr(name, '='); + return ((equal == 0) ? strlen(name) : (equal - name)); +} + +/* findenv - given name, locate name=value */ + +static char **findenv(const char *name, ssize_t len) +{ + char **envp; + + for (envp = environ; envp && *envp; envp++) + if (strncmp(name, *envp, len) == 0 && (*envp)[len] == '=') + return (envp); + return (0); +} + +#if 0 + +/* getenv - given name, locate value */ + +char *getenv(const char *name) +{ + ssize_t len = namelength(name); + char **envp = findenv(name, len); + + return (envp ? *envp + len + 1 : 0); +} + +/* putenv - update or append environment (name,value) pair */ + +int putenv(const char *nameval) +{ + char *equal = strchr(nameval, '='); + char *value = (equal ? equal : ""); + + return (setenv(nameval, value, DO_CLOBBER)); +} + +/* unsetenv - remove variable from environment */ + +void unsetenv(const char *name) +{ + char **envp; + + while ((envp = findenv(name, namelength(name))) != 0) + while (envp[0] = envp[1]) + envp++; +} + +#endif + +/* setenv - update or append environment (name,value) pair */ + +int setenv(const char *name, const char *value, int clobber) +{ + char *destination; + char **envp; + ssize_t l_name; /* length of name part */ + unsigned int l_nameval; /* length of name=value */ + + /* Permit name= and =value. */ + + l_name = namelength(name); + envp = findenv(name, l_name); + if (envp != 0 && clobber == 0) + return (0); + if (*value == '=') + value++; + l_nameval = l_name + strlen(value) + 1; + + /* + * Use available memory if the old value is long enough. Never free an + * old name=value entry because it may not be allocated. + */ + + destination = (envp != 0 && strlen(*envp) >= l_nameval) ? + *envp : malloc(l_nameval + 1); + if (destination == 0) + return (-1); + strncpy(destination, name, l_name); + destination[l_name] = '='; + strcpy(destination + l_name + 1, value); + return ((envp == 0) ? addenv(destination) : (*envp = destination, 0)); +} + +/* cmalloc - malloc and copy block of memory */ + +static char *cmalloc(ssize_t new_len, char *old, ssize_t old_len) +{ + char *new = malloc(new_len); + + if (new != 0) + memcpy(new, old, old_len); + return (new); +} + +/* addenv - append environment entry */ + +static int addenv(char *nameval) +{ + char **envp; + ssize_t n_used; /* number of environment entries */ + ssize_t l_used; /* bytes used excl. terminator */ + ssize_t l_need; /* bytes needed incl. terminator */ + + for (envp = environ; envp && *envp; envp++) + /* void */ ; + n_used = envp - environ; + l_used = n_used * sizeof(*envp); + l_need = l_used + 2 * sizeof(*envp); + + envp = allocated ? + (char **) realloc((char *) environ, l_need) : + (char **) cmalloc(l_need, (char *) environ, l_used); + if (envp == 0) { + return (-1); + } else { + allocated = 1; + environ = envp; + environ[n_used++] = nameval; /* add new entry */ + environ[n_used] = 0; /* terminate list */ + return (0); + } +} + +#endif diff --git a/src/util/events.c b/src/util/events.c new file mode 100644 index 0000000..c2157bb --- /dev/null +++ b/src/util/events.c @@ -0,0 +1,1261 @@ +/*++ +/* NAME +/* events 3 +/* SUMMARY +/* event manager +/* SYNOPSIS +/* #include <events.h> +/* +/* time_t event_time() +/* +/* void event_loop(delay) +/* int delay; +/* +/* time_t event_request_timer(callback, context, delay) +/* void (*callback)(int event, void *context); +/* void *context; +/* int delay; +/* +/* int event_cancel_timer(callback, context) +/* void (*callback)(int event, void *context); +/* void *context; +/* +/* void event_enable_read(fd, callback, context) +/* int fd; +/* void (*callback)(int event, void *context); +/* void *context; +/* +/* void event_enable_write(fd, callback, context) +/* int fd; +/* void (*callback)(int event, void *context); +/* void *context; +/* +/* void event_disable_readwrite(fd) +/* int fd; +/* +/* void event_drain(time_limit) +/* int time_limit; +/* +/* void event_fork(void) +/* DESCRIPTION +/* This module delivers I/O and timer events. +/* Multiple I/O streams and timers can be monitored simultaneously. +/* Events are delivered via callback routines provided by the +/* application. When requesting an event, the application can provide +/* private context that is passed back when the callback routine is +/* executed. +/* +/* event_time() returns a cached value of the current time. +/* +/* event_loop() monitors all I/O channels for which the application has +/* expressed interest, and monitors the timer request queue. +/* It notifies the application whenever events of interest happen. +/* A negative delay value causes the function to pause until something +/* happens; a positive delay value causes event_loop() to return when +/* the next event happens or when the delay time in seconds is over, +/* whatever happens first. A zero delay effectuates a poll. +/* +/* Note: in order to avoid race conditions, event_loop() cannot +/* not be called recursively. +/* +/* event_request_timer() causes the specified callback function to +/* be called with the specified context argument after \fIdelay\fR +/* seconds, or as soon as possible thereafter. The delay should +/* not be negative (the manifest EVENT_NULL_DELAY provides for +/* convenient zero-delay notification). +/* The event argument is equal to EVENT_TIME. +/* Only one timer request can be active per (callback, context) pair. +/* Calling event_request_timer() with an existing (callback, context) +/* pair does not schedule a new event, but updates the time of event +/* delivery. The result is the absolute time at which the timer is +/* scheduled to go off. +/* +/* event_cancel_timer() cancels the specified (callback, context) request. +/* The application is allowed to cancel non-existing requests. The result +/* value is the amount of time left before the timer would have gone off, +/* or -1 in case of no pending timer. +/* +/* event_enable_read() (event_enable_write()) enables read (write) events +/* on the named I/O channel. It is up to the application to assemble +/* partial reads or writes. +/* An I/O channel cannot handle more than one request at the +/* same time. The application is allowed to enable an event that +/* is already enabled (same channel, same read or write operation, +/* but perhaps a different callback or context). On systems with +/* kernel-based event filters this is preferred usage, because +/* each disable and enable request would cost a system call. +/* +/* The manifest constants EVENT_NULL_CONTEXT and EVENT_NULL_TYPE +/* provide convenient null values. +/* +/* The callback routine has the following arguments: +/* .IP fd +/* The stream on which the event happened. +/* .IP event +/* An indication of the event type: +/* .RS +/* .IP EVENT_READ +/* read event, +/* .IP EVENT_WRITE +/* write event, +/* .IP EVENT_XCPT +/* exception (actually, any event other than read or write). +/* .RE +/* .IP context +/* Application context given to event_enable_read() (event_enable_write()). +/* .PP +/* event_disable_readwrite() disables further I/O events on the specified +/* I/O channel. The application is allowed to cancel non-existing +/* I/O event requests. +/* +/* event_drain() repeatedly calls event_loop() until no more timer +/* events or I/O events are pending or until the time limit is reached. +/* This routine must not be called from an event_whatever() callback +/* routine. Note: this function assumes that no new I/O events +/* will be registered. +/* +/* event_fork() must be called by a child process after it is +/* created with fork(), to re-initialize event processing. +/* DIAGNOSTICS +/* Panics: interface violations. Fatal errors: out of memory, +/* system call failure. Warnings: the number of available +/* file descriptors is much less than FD_SETSIZE. +/* BUGS +/* This module is based on event selection. It assumes that the +/* event_loop() routine is called frequently. This approach is +/* not suitable for applications with compute-bound loops that +/* take a significant amount of time. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include "sys_defs.h" +#include <sys/time.h> /* XXX: 44BSD uses bzero() */ +#include <time.h> +#include <errno.h> +#include <unistd.h> +#include <stddef.h> /* offsetof() */ +#include <string.h> /* bzero() prototype for 44BSD */ +#include <limits.h> /* INT_MAX */ + +#ifdef USE_SYS_SELECT_H +#include <sys/select.h> +#endif + +/* Application-specific. */ + +#include "mymalloc.h" +#include "msg.h" +#include "iostuff.h" +#include "ring.h" +#include "events.h" + +#if !defined(EVENTS_STYLE) +#error "must define EVENTS_STYLE" +#endif + + /* + * Traditional BSD-style select(2). Works everywhere, but has a built-in + * upper bound on the number of file descriptors, and that limit is hard to + * change on Linux. Is sometimes emulated with SYSV-style poll(2) which + * doesn't have the file descriptor limit, but unfortunately does not help + * to improve the performance of servers with lots of connections. + */ +#define EVENT_ALLOC_INCR 10 + +#if (EVENTS_STYLE == EVENTS_STYLE_SELECT) +typedef fd_set EVENT_MASK; + +#define EVENT_MASK_BYTE_COUNT(mask) sizeof(*(mask)) +#define EVENT_MASK_ZERO(mask) FD_ZERO(mask) +#define EVENT_MASK_SET(fd, mask) FD_SET((fd), (mask)) +#define EVENT_MASK_ISSET(fd, mask) FD_ISSET((fd), (mask)) +#define EVENT_MASK_CLR(fd, mask) FD_CLR((fd), (mask)) +#define EVENT_MASK_CMP(m1, m2) memcmp((m1), (m2), EVENT_MASK_BYTE_COUNT(m1)) +#else + + /* + * Kernel-based event filters (kqueue, /dev/poll, epoll). We use the + * following file descriptor mask structure which is expanded on the fly. + */ +typedef struct { + char *data; /* bit mask */ + size_t data_len; /* data byte count */ +} EVENT_MASK; + + /* Bits per byte, byte in vector, bit offset in byte, bytes per set. */ +#define EVENT_MASK_NBBY (8) +#define EVENT_MASK_FD_BYTE(fd, mask) \ + (((unsigned char *) (mask)->data)[(fd) / EVENT_MASK_NBBY]) +#define EVENT_MASK_FD_BIT(fd) (1 << ((fd) % EVENT_MASK_NBBY)) +#define EVENT_MASK_BYTES_NEEDED(len) \ + (((len) + (EVENT_MASK_NBBY -1)) / EVENT_MASK_NBBY) +#define EVENT_MASK_BYTE_COUNT(mask) ((mask)->data_len) + + /* Memory management. */ +#define EVENT_MASK_ALLOC(mask, bit_len) do { \ + size_t _byte_len = EVENT_MASK_BYTES_NEEDED(bit_len); \ + (mask)->data = mymalloc(_byte_len); \ + memset((mask)->data, 0, _byte_len); \ + (mask)->data_len = _byte_len; \ + } while (0) +#define EVENT_MASK_REALLOC(mask, bit_len) do { \ + size_t _byte_len = EVENT_MASK_BYTES_NEEDED(bit_len); \ + size_t _old_len = (mask)->data_len; \ + (mask)->data = myrealloc((mask)->data, _byte_len); \ + if (_byte_len > _old_len) \ + memset((mask)->data + _old_len, 0, _byte_len - _old_len); \ + (mask)->data_len = _byte_len; \ + } while (0) +#define EVENT_MASK_FREE(mask) myfree((mask)->data) + + /* Set operations, modeled after FD_ZERO/SET/ISSET/CLR. */ +#define EVENT_MASK_ZERO(mask) \ + memset((mask)->data, 0, (mask)->data_len) +#define EVENT_MASK_SET(fd, mask) \ + (EVENT_MASK_FD_BYTE((fd), (mask)) |= EVENT_MASK_FD_BIT(fd)) +#define EVENT_MASK_ISSET(fd, mask) \ + (EVENT_MASK_FD_BYTE((fd), (mask)) & EVENT_MASK_FD_BIT(fd)) +#define EVENT_MASK_CLR(fd, mask) \ + (EVENT_MASK_FD_BYTE((fd), (mask)) &= ~EVENT_MASK_FD_BIT(fd)) +#define EVENT_MASK_CMP(m1, m2) \ + memcmp((m1)->data, (m2)->data, EVENT_MASK_BYTE_COUNT(m1)) +#endif + + /* + * I/O events. + */ +typedef struct EVENT_FDTABLE EVENT_FDTABLE; + +struct EVENT_FDTABLE { + EVENT_NOTIFY_RDWR_FN callback; + char *context; +}; +static EVENT_MASK event_rmask; /* enabled read events */ +static EVENT_MASK event_wmask; /* enabled write events */ +static EVENT_MASK event_xmask; /* for bad news mostly */ +static int event_fdlimit; /* per-process open file limit */ +static EVENT_FDTABLE *event_fdtable; /* one slot per file descriptor */ +static int event_fdslots; /* number of file descriptor slots */ +static int event_max_fd = -1; /* highest fd number seen */ + + /* + * FreeBSD kqueue supports no system call to find out what descriptors are + * registered in the kernel-based filter. To implement our own sanity checks + * we maintain our own descriptor bitmask. + * + * FreeBSD kqueue does support application context pointers. Unfortunately, + * changing that information would cost a system call, and some of the + * competitors don't support application context. To keep the implementation + * simple we maintain our own table with call-back information. + * + * FreeBSD kqueue silently unregisters a descriptor from its filter when the + * descriptor is closed, so our information could get out of sync with the + * kernel. But that will never happen, because we have to meticulously + * unregister a file descriptor before it is closed, to avoid errors on + * systems that are built with EVENTS_STYLE == EVENTS_STYLE_SELECT. + */ +#if (EVENTS_STYLE == EVENTS_STYLE_KQUEUE) +#include <sys/event.h> + + /* + * Some early FreeBSD implementations don't have the EV_SET macro. + */ +#ifndef EV_SET +#define EV_SET(kp, id, fi, fl, ffl, da, ud) do { \ + (kp)->ident = (id); \ + (kp)->filter = (fi); \ + (kp)->flags = (fl); \ + (kp)->fflags = (ffl); \ + (kp)->data = (da); \ + (kp)->udata = (ud); \ + } while(0) +#endif + + /* + * Macros to initialize the kernel-based filter; see event_init(). + */ +static int event_kq; /* handle to event filter */ + +#define EVENT_REG_INIT_HANDLE(er, n) do { \ + er = event_kq = kqueue(); \ + } while (0) +#define EVENT_REG_INIT_TEXT "kqueue" + +#define EVENT_REG_FORK_HANDLE(er, n) do { \ + (void) close(event_kq); \ + EVENT_REG_INIT_HANDLE(er, (n)); \ + } while (0) + + /* + * Macros to update the kernel-based filter; see event_enable_read(), + * event_enable_write() and event_disable_readwrite(). + */ +#define EVENT_REG_FD_OP(er, fh, ev, op) do { \ + struct kevent dummy; \ + EV_SET(&dummy, (fh), (ev), (op), 0, 0, 0); \ + (er) = kevent(event_kq, &dummy, 1, 0, 0, 0); \ + } while (0) + +#define EVENT_REG_ADD_OP(e, f, ev) EVENT_REG_FD_OP((e), (f), (ev), EV_ADD) +#define EVENT_REG_ADD_READ(e, f) EVENT_REG_ADD_OP((e), (f), EVFILT_READ) +#define EVENT_REG_ADD_WRITE(e, f) EVENT_REG_ADD_OP((e), (f), EVFILT_WRITE) +#define EVENT_REG_ADD_TEXT "kevent EV_ADD" + +#define EVENT_REG_DEL_OP(e, f, ev) EVENT_REG_FD_OP((e), (f), (ev), EV_DELETE) +#define EVENT_REG_DEL_READ(e, f) EVENT_REG_DEL_OP((e), (f), EVFILT_READ) +#define EVENT_REG_DEL_WRITE(e, f) EVENT_REG_DEL_OP((e), (f), EVFILT_WRITE) +#define EVENT_REG_DEL_TEXT "kevent EV_DELETE" + + /* + * Macros to retrieve event buffers from the kernel; see event_loop(). + */ +typedef struct kevent EVENT_BUFFER; + +#define EVENT_BUFFER_READ(event_count, event_buf, buflen, delay) do { \ + struct timespec ts; \ + struct timespec *tsp; \ + if ((delay) < 0) { \ + tsp = 0; \ + } else { \ + tsp = &ts; \ + ts.tv_nsec = 0; \ + ts.tv_sec = (delay); \ + } \ + (event_count) = kevent(event_kq, (struct kevent *) 0, 0, (event_buf), \ + (buflen), (tsp)); \ + } while (0) +#define EVENT_BUFFER_READ_TEXT "kevent" + + /* + * Macros to process event buffers from the kernel; see event_loop(). + */ +#define EVENT_GET_FD(bp) ((bp)->ident) +#define EVENT_GET_TYPE(bp) ((bp)->filter) +#define EVENT_TEST_READ(bp) (EVENT_GET_TYPE(bp) == EVFILT_READ) +#define EVENT_TEST_WRITE(bp) (EVENT_GET_TYPE(bp) == EVFILT_WRITE) + +#endif + + /* + * Solaris /dev/poll does not support application context, so we have to + * maintain our own. This has the benefit of avoiding an expensive system + * call just to change a call-back function or argument. + * + * Solaris /dev/poll does have a way to query if a specific descriptor is + * registered. However, we maintain a descriptor mask anyway because a) it + * avoids having to make an expensive system call to find out if something + * is registered, b) some EVENTS_STYLE_MUMBLE implementations need a + * descriptor bitmask anyway and c) we use the bitmask already to implement + * sanity checks. + */ +#if (EVENTS_STYLE == EVENTS_STYLE_DEVPOLL) +#include <sys/devpoll.h> +#include <fcntl.h> + + /* + * Macros to initialize the kernel-based filter; see event_init(). + */ +static int event_pollfd; /* handle to file descriptor set */ + +#define EVENT_REG_INIT_HANDLE(er, n) do { \ + er = event_pollfd = open("/dev/poll", O_RDWR); \ + if (event_pollfd >= 0) close_on_exec(event_pollfd, CLOSE_ON_EXEC); \ + } while (0) +#define EVENT_REG_INIT_TEXT "open /dev/poll" + +#define EVENT_REG_FORK_HANDLE(er, n) do { \ + (void) close(event_pollfd); \ + EVENT_REG_INIT_HANDLE(er, (n)); \ + } while (0) + + /* + * Macros to update the kernel-based filter; see event_enable_read(), + * event_enable_write() and event_disable_readwrite(). + */ +#define EVENT_REG_FD_OP(er, fh, ev) do { \ + struct pollfd dummy; \ + dummy.fd = (fh); \ + dummy.events = (ev); \ + (er) = write(event_pollfd, (void *) &dummy, \ + sizeof(dummy)) != sizeof(dummy) ? -1 : 0; \ + } while (0) + +#define EVENT_REG_ADD_READ(e, f) EVENT_REG_FD_OP((e), (f), POLLIN) +#define EVENT_REG_ADD_WRITE(e, f) EVENT_REG_FD_OP((e), (f), POLLOUT) +#define EVENT_REG_ADD_TEXT "write /dev/poll" + +#define EVENT_REG_DEL_BOTH(e, f) EVENT_REG_FD_OP((e), (f), POLLREMOVE) +#define EVENT_REG_DEL_TEXT "write /dev/poll" + + /* + * Macros to retrieve event buffers from the kernel; see event_loop(). + */ +typedef struct pollfd EVENT_BUFFER; + +#define EVENT_BUFFER_READ(event_count, event_buf, buflen, delay) do { \ + struct dvpoll dvpoll; \ + dvpoll.dp_fds = (event_buf); \ + dvpoll.dp_nfds = (buflen); \ + dvpoll.dp_timeout = (delay) < 0 ? -1 : (delay) * 1000; \ + (event_count) = ioctl(event_pollfd, DP_POLL, &dvpoll); \ + } while (0) +#define EVENT_BUFFER_READ_TEXT "ioctl DP_POLL" + + /* + * Macros to process event buffers from the kernel; see event_loop(). + */ +#define EVENT_GET_FD(bp) ((bp)->fd) +#define EVENT_GET_TYPE(bp) ((bp)->revents) +#define EVENT_TEST_READ(bp) (EVENT_GET_TYPE(bp) & POLLIN) +#define EVENT_TEST_WRITE(bp) (EVENT_GET_TYPE(bp) & POLLOUT) + +#endif + + /* + * Linux epoll supports no system call to find out what descriptors are + * registered in the kernel-based filter. To implement our own sanity checks + * we maintain our own descriptor bitmask. + * + * Linux epoll does support application context pointers. Unfortunately, + * changing that information would cost a system call, and some of the + * competitors don't support application context. To keep the implementation + * simple we maintain our own table with call-back information. + * + * Linux epoll silently unregisters a descriptor from its filter when the + * descriptor is closed, so our information could get out of sync with the + * kernel. But that will never happen, because we have to meticulously + * unregister a file descriptor before it is closed, to avoid errors on + * systems that are built with EVENTS_STYLE == EVENTS_STYLE_SELECT. + */ +#if (EVENTS_STYLE == EVENTS_STYLE_EPOLL) +#include <sys/epoll.h> + + /* + * Macros to initialize the kernel-based filter; see event_init(). + */ +static int event_epollfd; /* epoll handle */ + +#define EVENT_REG_INIT_HANDLE(er, n) do { \ + er = event_epollfd = epoll_create(n); \ + if (event_epollfd >= 0) close_on_exec(event_epollfd, CLOSE_ON_EXEC); \ + } while (0) +#define EVENT_REG_INIT_TEXT "epoll_create" + +#define EVENT_REG_FORK_HANDLE(er, n) do { \ + (void) close(event_epollfd); \ + EVENT_REG_INIT_HANDLE(er, (n)); \ + } while (0) + + /* + * Macros to update the kernel-based filter; see event_enable_read(), + * event_enable_write() and event_disable_readwrite(). + */ +#define EVENT_REG_FD_OP(er, fh, ev, op) do { \ + struct epoll_event dummy; \ + dummy.events = (ev); \ + dummy.data.fd = (fh); \ + (er) = epoll_ctl(event_epollfd, (op), (fh), &dummy); \ + } while (0) + +#define EVENT_REG_ADD_OP(e, f, ev) EVENT_REG_FD_OP((e), (f), (ev), EPOLL_CTL_ADD) +#define EVENT_REG_ADD_READ(e, f) EVENT_REG_ADD_OP((e), (f), EPOLLIN) +#define EVENT_REG_ADD_WRITE(e, f) EVENT_REG_ADD_OP((e), (f), EPOLLOUT) +#define EVENT_REG_ADD_TEXT "epoll_ctl EPOLL_CTL_ADD" + +#define EVENT_REG_DEL_OP(e, f, ev) EVENT_REG_FD_OP((e), (f), (ev), EPOLL_CTL_DEL) +#define EVENT_REG_DEL_READ(e, f) EVENT_REG_DEL_OP((e), (f), EPOLLIN) +#define EVENT_REG_DEL_WRITE(e, f) EVENT_REG_DEL_OP((e), (f), EPOLLOUT) +#define EVENT_REG_DEL_TEXT "epoll_ctl EPOLL_CTL_DEL" + + /* + * Macros to retrieve event buffers from the kernel; see event_loop(). + */ +typedef struct epoll_event EVENT_BUFFER; + +#define EVENT_BUFFER_READ(event_count, event_buf, buflen, delay) do { \ + (event_count) = epoll_wait(event_epollfd, (event_buf), (buflen), \ + (delay) < 0 ? -1 : (delay) * 1000); \ + } while (0) +#define EVENT_BUFFER_READ_TEXT "epoll_wait" + + /* + * Macros to process event buffers from the kernel; see event_loop(). + */ +#define EVENT_GET_FD(bp) ((bp)->data.fd) +#define EVENT_GET_TYPE(bp) ((bp)->events) +#define EVENT_TEST_READ(bp) (EVENT_GET_TYPE(bp) & EPOLLIN) +#define EVENT_TEST_WRITE(bp) (EVENT_GET_TYPE(bp) & EPOLLOUT) + +#endif + + /* + * Timer events. Timer requests are kept sorted, in a circular list. We use + * the RING abstraction, so we get to use a couple ugly macros. + * + * When a call-back function adds a timer request, we label the request with + * the event_loop() call instance that invoked the call-back. We use this to + * prevent zero-delay timer requests from running in a tight loop and + * starving I/O events. + */ +typedef struct EVENT_TIMER EVENT_TIMER; + +struct EVENT_TIMER { + time_t when; /* when event is wanted */ + EVENT_NOTIFY_TIME_FN callback; /* callback function */ + char *context; /* callback context */ + long loop_instance; /* event_loop() call instance */ + RING ring; /* linkage */ +}; + +static RING event_timer_head; /* timer queue head */ +static long event_loop_instance; /* event_loop() call instance */ + +#define RING_TO_TIMER(r) \ + ((EVENT_TIMER *) ((void *) (r) - offsetof(EVENT_TIMER, ring))) + +#define FOREACH_QUEUE_ENTRY(entry, head) \ + for (entry = ring_succ(head); entry != (head); entry = ring_succ(entry)) + +#define FIRST_TIMER(head) \ + (ring_succ(head) != (head) ? RING_TO_TIMER(ring_succ(head)) : 0) + + /* + * Other private data structures. + */ +static time_t event_present; /* cached time of day */ + +#define EVENT_INIT_NEEDED() (event_present == 0) + +/* event_init - set up tables and such */ + +static void event_init(void) +{ + EVENT_FDTABLE *fdp; + int err; + + if (!EVENT_INIT_NEEDED()) + msg_panic("event_init: repeated call"); + + /* + * Initialize the file descriptor masks and the call-back table. Where + * possible we extend these data structures on the fly. With select(2) + * based implementations we can only handle FD_SETSIZE open files. + */ +#if (EVENTS_STYLE == EVENTS_STYLE_SELECT) + if ((event_fdlimit = open_limit(FD_SETSIZE)) < 0) + msg_fatal("unable to determine open file limit"); +#else + if ((event_fdlimit = open_limit(INT_MAX)) < 0) + msg_fatal("unable to determine open file limit"); +#endif + if (event_fdlimit < FD_SETSIZE / 2 && event_fdlimit < 256) + msg_warn("could allocate space for only %d open files", event_fdlimit); + event_fdslots = EVENT_ALLOC_INCR; + event_fdtable = (EVENT_FDTABLE *) + mymalloc(sizeof(EVENT_FDTABLE) * event_fdslots); + for (fdp = event_fdtable; fdp < event_fdtable + event_fdslots; fdp++) { + fdp->callback = 0; + fdp->context = 0; + } + + /* + * Initialize the I/O event request masks. + */ +#if (EVENTS_STYLE == EVENTS_STYLE_SELECT) + EVENT_MASK_ZERO(&event_rmask); + EVENT_MASK_ZERO(&event_wmask); + EVENT_MASK_ZERO(&event_xmask); +#else + EVENT_MASK_ALLOC(&event_rmask, event_fdslots); + EVENT_MASK_ALLOC(&event_wmask, event_fdslots); + EVENT_MASK_ALLOC(&event_xmask, event_fdslots); + + /* + * Initialize the kernel-based filter. + */ + EVENT_REG_INIT_HANDLE(err, event_fdslots); + if (err < 0) + msg_fatal("%s: %m", EVENT_REG_INIT_TEXT); +#endif + + /* + * Initialize timer stuff. + */ + ring_init(&event_timer_head); + (void) time(&event_present); + + /* + * Avoid an infinite initialization loop. + */ + if (EVENT_INIT_NEEDED()) + msg_panic("event_init: unable to initialize"); +} + +/* event_extend - make room for more descriptor slots */ + +static void event_extend(int fd) +{ + const char *myname = "event_extend"; + int old_slots = event_fdslots; + int new_slots = (event_fdslots > fd / 2 ? + 2 * old_slots : fd + EVENT_ALLOC_INCR); + EVENT_FDTABLE *fdp; + +#ifdef EVENT_REG_UPD_HANDLE + int err; + +#endif + + if (msg_verbose > 2) + msg_info("%s: fd %d", myname, fd); + event_fdtable = (EVENT_FDTABLE *) + myrealloc((void *) event_fdtable, sizeof(EVENT_FDTABLE) * new_slots); + event_fdslots = new_slots; + for (fdp = event_fdtable + old_slots; + fdp < event_fdtable + new_slots; fdp++) { + fdp->callback = 0; + fdp->context = 0; + } + + /* + * Initialize the I/O event request masks. + */ +#if (EVENTS_STYLE != EVENTS_STYLE_SELECT) + EVENT_MASK_REALLOC(&event_rmask, new_slots); + EVENT_MASK_REALLOC(&event_wmask, new_slots); + EVENT_MASK_REALLOC(&event_xmask, new_slots); +#endif +#ifdef EVENT_REG_UPD_HANDLE + EVENT_REG_UPD_HANDLE(err, new_slots); + if (err < 0) + msg_fatal("%s: %s: %m", myname, EVENT_REG_UPD_TEXT); +#endif +} + +/* event_time - look up cached time of day */ + +time_t event_time(void) +{ + if (EVENT_INIT_NEEDED()) + event_init(); + + return (event_present); +} + +/* event_drain - loop until all pending events are done */ + +void event_drain(int time_limit) +{ + EVENT_MASK zero_mask; + time_t max_time; + + if (EVENT_INIT_NEEDED()) + return; + +#if (EVENTS_STYLE == EVENTS_STYLE_SELECT) + EVENT_MASK_ZERO(&zero_mask); +#else + EVENT_MASK_ALLOC(&zero_mask, event_fdslots); +#endif + (void) time(&event_present); + max_time = event_present + time_limit; + while (event_present < max_time + && (event_timer_head.pred != &event_timer_head + || EVENT_MASK_CMP(&zero_mask, &event_xmask) != 0)) { + event_loop(1); +#if (EVENTS_STYLE != EVENTS_STYLE_SELECT) + if (EVENT_MASK_BYTE_COUNT(&zero_mask) + != EVENT_MASK_BYTES_NEEDED(event_fdslots)) + EVENT_MASK_REALLOC(&zero_mask, event_fdslots); +#endif + } +#if (EVENTS_STYLE != EVENTS_STYLE_SELECT) + EVENT_MASK_FREE(&zero_mask); +#endif +} + +/* event_fork - resume event processing after fork() */ + +void event_fork(void) +{ +#if (EVENTS_STYLE != EVENTS_STYLE_SELECT) + EVENT_FDTABLE *fdp; + int err; + int fd; + + /* + * No event was ever registered, so there's nothing to be done. + */ + if (EVENT_INIT_NEEDED()) + return; + + /* + * Close the existing filter handle and open a new kernel-based filter. + */ + EVENT_REG_FORK_HANDLE(err, event_fdslots); + if (err < 0) + msg_fatal("%s: %m", EVENT_REG_INIT_TEXT); + + /* + * Populate the new kernel-based filter with events that were registered + * in the parent process. + */ + for (fd = 0; fd <= event_max_fd; fd++) { + if (EVENT_MASK_ISSET(fd, &event_wmask)) { + EVENT_MASK_CLR(fd, &event_wmask); + fdp = event_fdtable + fd; + event_enable_write(fd, fdp->callback, fdp->context); + } else if (EVENT_MASK_ISSET(fd, &event_rmask)) { + EVENT_MASK_CLR(fd, &event_rmask); + fdp = event_fdtable + fd; + event_enable_read(fd, fdp->callback, fdp->context); + } + } +#endif +} + +/* event_enable_read - enable read events */ + +void event_enable_read(int fd, EVENT_NOTIFY_RDWR_FN callback, void *context) +{ + const char *myname = "event_enable_read"; + EVENT_FDTABLE *fdp; + int err; + + if (EVENT_INIT_NEEDED()) + event_init(); + + /* + * Sanity checks. + */ + if (fd < 0 || fd >= event_fdlimit) + msg_panic("%s: bad file descriptor: %d", myname, fd); + + if (msg_verbose > 2) + msg_info("%s: fd %d", myname, fd); + + if (fd >= event_fdslots) + event_extend(fd); + + /* + * Disallow mixed (i.e. read and write) requests on the same descriptor. + */ + if (EVENT_MASK_ISSET(fd, &event_wmask)) + msg_panic("%s: fd %d: read/write I/O request", myname, fd); + + /* + * Postfix 2.4 allows multiple event_enable_read() calls on the same + * descriptor without requiring event_disable_readwrite() calls between + * them. With kernel-based filters (kqueue, /dev/poll, epoll) it's + * wasteful to make system calls when we change only application + * call-back information. It has a noticeable effect on smtp-source + * performance. + */ + if (EVENT_MASK_ISSET(fd, &event_rmask) == 0) { + EVENT_MASK_SET(fd, &event_xmask); + EVENT_MASK_SET(fd, &event_rmask); + if (event_max_fd < fd) + event_max_fd = fd; +#if (EVENTS_STYLE != EVENTS_STYLE_SELECT) + EVENT_REG_ADD_READ(err, fd); + if (err < 0) + msg_fatal("%s: %s: %m", myname, EVENT_REG_ADD_TEXT); +#endif + } + fdp = event_fdtable + fd; + if (fdp->callback != callback || fdp->context != context) { + fdp->callback = callback; + fdp->context = context; + } +} + +/* event_enable_write - enable write events */ + +void event_enable_write(int fd, EVENT_NOTIFY_RDWR_FN callback, void *context) +{ + const char *myname = "event_enable_write"; + EVENT_FDTABLE *fdp; + int err; + + if (EVENT_INIT_NEEDED()) + event_init(); + + /* + * Sanity checks. + */ + if (fd < 0 || fd >= event_fdlimit) + msg_panic("%s: bad file descriptor: %d", myname, fd); + + if (msg_verbose > 2) + msg_info("%s: fd %d", myname, fd); + + if (fd >= event_fdslots) + event_extend(fd); + + /* + * Disallow mixed (i.e. read and write) requests on the same descriptor. + */ + if (EVENT_MASK_ISSET(fd, &event_rmask)) + msg_panic("%s: fd %d: read/write I/O request", myname, fd); + + /* + * Postfix 2.4 allows multiple event_enable_write() calls on the same + * descriptor without requiring event_disable_readwrite() calls between + * them. With kernel-based filters (kqueue, /dev/poll, epoll) it's + * incredibly wasteful to make unregister and register system calls when + * we change only application call-back information. It has a noticeable + * effect on smtp-source performance. + */ + if (EVENT_MASK_ISSET(fd, &event_wmask) == 0) { + EVENT_MASK_SET(fd, &event_xmask); + EVENT_MASK_SET(fd, &event_wmask); + if (event_max_fd < fd) + event_max_fd = fd; +#if (EVENTS_STYLE != EVENTS_STYLE_SELECT) + EVENT_REG_ADD_WRITE(err, fd); + if (err < 0) + msg_fatal("%s: %s: %m", myname, EVENT_REG_ADD_TEXT); +#endif + } + fdp = event_fdtable + fd; + if (fdp->callback != callback || fdp->context != context) { + fdp->callback = callback; + fdp->context = context; + } +} + +/* event_disable_readwrite - disable request for read or write events */ + +void event_disable_readwrite(int fd) +{ + const char *myname = "event_disable_readwrite"; + EVENT_FDTABLE *fdp; + int err; + + if (EVENT_INIT_NEEDED()) + event_init(); + + /* + * Sanity checks. + */ + if (fd < 0 || fd >= event_fdlimit) + msg_panic("%s: bad file descriptor: %d", myname, fd); + + if (msg_verbose > 2) + msg_info("%s: fd %d", myname, fd); + + /* + * Don't complain when there is nothing to cancel. The request may have + * been canceled from another thread. + */ + if (fd >= event_fdslots) + return; +#if (EVENTS_STYLE != EVENTS_STYLE_SELECT) +#ifdef EVENT_REG_DEL_BOTH + /* XXX Can't seem to disable READ and WRITE events selectively. */ + if (EVENT_MASK_ISSET(fd, &event_rmask) + || EVENT_MASK_ISSET(fd, &event_wmask)) { + EVENT_REG_DEL_BOTH(err, fd); + if (err < 0) + msg_fatal("%s: %s: %m", myname, EVENT_REG_DEL_TEXT); + } +#else + if (EVENT_MASK_ISSET(fd, &event_rmask)) { + EVENT_REG_DEL_READ(err, fd); + if (err < 0) + msg_fatal("%s: %s: %m", myname, EVENT_REG_DEL_TEXT); + } else if (EVENT_MASK_ISSET(fd, &event_wmask)) { + EVENT_REG_DEL_WRITE(err, fd); + if (err < 0) + msg_fatal("%s: %s: %m", myname, EVENT_REG_DEL_TEXT); + } +#endif /* EVENT_REG_DEL_BOTH */ +#endif /* != EVENTS_STYLE_SELECT */ + EVENT_MASK_CLR(fd, &event_xmask); + EVENT_MASK_CLR(fd, &event_rmask); + EVENT_MASK_CLR(fd, &event_wmask); + fdp = event_fdtable + fd; + fdp->callback = 0; + fdp->context = 0; +} + +/* event_request_timer - (re)set timer */ + +time_t event_request_timer(EVENT_NOTIFY_TIME_FN callback, void *context, int delay) +{ + const char *myname = "event_request_timer"; + RING *ring; + EVENT_TIMER *timer; + + if (EVENT_INIT_NEEDED()) + event_init(); + + /* + * Sanity checks. + */ + if (delay < 0) + msg_panic("%s: invalid delay: %d", myname, delay); + + /* + * Make sure we schedule this event at the right time. + */ + time(&event_present); + + /* + * See if they are resetting an existing timer request. If so, take the + * request away from the timer queue so that it can be inserted at the + * right place. + */ + FOREACH_QUEUE_ENTRY(ring, &event_timer_head) { + timer = RING_TO_TIMER(ring); + if (timer->callback == callback && timer->context == context) { + timer->when = event_present + delay; + timer->loop_instance = event_loop_instance; + ring_detach(ring); + if (msg_verbose > 2) + msg_info("%s: reset 0x%lx 0x%lx %d", myname, + (long) callback, (long) context, delay); + break; + } + } + + /* + * If not found, schedule a new timer request. + */ + if (ring == &event_timer_head) { + timer = (EVENT_TIMER *) mymalloc(sizeof(EVENT_TIMER)); + timer->when = event_present + delay; + timer->callback = callback; + timer->context = context; + timer->loop_instance = event_loop_instance; + if (msg_verbose > 2) + msg_info("%s: set 0x%lx 0x%lx %d", myname, + (long) callback, (long) context, delay); + } + + /* + * Timer requests are kept sorted to reduce lookup overhead in the event + * loop. + * + * XXX Append the new request after existing requests for the same time + * slot. The event_loop() routine depends on this to avoid starving I/O + * events when a call-back function schedules a zero-delay timer request. + */ + FOREACH_QUEUE_ENTRY(ring, &event_timer_head) { + if (timer->when < RING_TO_TIMER(ring)->when) + break; + } + ring_prepend(ring, &timer->ring); + + return (timer->when); +} + +/* event_cancel_timer - cancel timer */ + +int event_cancel_timer(EVENT_NOTIFY_TIME_FN callback, void *context) +{ + const char *myname = "event_cancel_timer"; + RING *ring; + EVENT_TIMER *timer; + int time_left = -1; + + if (EVENT_INIT_NEEDED()) + event_init(); + + /* + * See if they are canceling an existing timer request. Do not complain + * when the request is not found. It might have been canceled from some + * other thread. + */ + FOREACH_QUEUE_ENTRY(ring, &event_timer_head) { + timer = RING_TO_TIMER(ring); + if (timer->callback == callback && timer->context == context) { + if ((time_left = timer->when - event_present) < 0) + time_left = 0; + ring_detach(ring); + myfree((void *) timer); + break; + } + } + if (msg_verbose > 2) + msg_info("%s: 0x%lx 0x%lx %d", myname, + (long) callback, (long) context, time_left); + return (time_left); +} + +/* event_loop - wait for the next event */ + +void event_loop(int delay) +{ + const char *myname = "event_loop"; + static int nested; + +#if (EVENTS_STYLE == EVENTS_STYLE_SELECT) + fd_set rmask; + fd_set wmask; + fd_set xmask; + struct timeval tv; + struct timeval *tvp; + int new_max_fd; + +#else + EVENT_BUFFER event_buf[100]; + EVENT_BUFFER *bp; + +#endif + int event_count; + EVENT_TIMER *timer; + int fd; + EVENT_FDTABLE *fdp; + int select_delay; + + if (EVENT_INIT_NEEDED()) + event_init(); + + /* + * XXX Also print the select() masks? + */ + if (msg_verbose > 2) { + RING *ring; + + FOREACH_QUEUE_ENTRY(ring, &event_timer_head) { + timer = RING_TO_TIMER(ring); + msg_info("%s: time left %3d for 0x%lx 0x%lx", myname, + (int) (timer->when - event_present), + (long) timer->callback, (long) timer->context); + } + } + + /* + * Find out when the next timer would go off. Timer requests are sorted. + * If any timer is scheduled, adjust the delay appropriately. + */ + if ((timer = FIRST_TIMER(&event_timer_head)) != 0) { + event_present = time((time_t *) 0); + if ((select_delay = timer->when - event_present) < 0) { + select_delay = 0; + } else if (delay >= 0 && select_delay > delay) { + select_delay = delay; + } + } else { + select_delay = delay; + } + if (msg_verbose > 2) + msg_info("event_loop: select_delay %d", select_delay); + + /* + * Negative delay means: wait until something happens. Zero delay means: + * poll. Positive delay means: wait at most this long. + */ +#if (EVENTS_STYLE == EVENTS_STYLE_SELECT) + if (select_delay < 0) { + tvp = 0; + } else { + tvp = &tv; + tv.tv_usec = 0; + tv.tv_sec = select_delay; + } + + /* + * Pause until the next event happens. When select() has a problem, don't + * go into a tight loop. Allow select() to be interrupted due to the + * arrival of a signal. + */ + rmask = event_rmask; + wmask = event_wmask; + xmask = event_xmask; + + event_count = select(event_max_fd + 1, &rmask, &wmask, &xmask, tvp); + if (event_count < 0) { + if (errno != EINTR) + msg_fatal("event_loop: select: %m"); + return; + } +#else + EVENT_BUFFER_READ(event_count, event_buf, + sizeof(event_buf) / sizeof(event_buf[0]), + select_delay); + if (event_count < 0) { + if (errno != EINTR) + msg_fatal("event_loop: " EVENT_BUFFER_READ_TEXT ": %m"); + return; + } +#endif + + /* + * Before entering the application call-back routines, make sure we + * aren't being called from a call-back routine. Doing so would make us + * vulnerable to all kinds of race conditions. + */ + if (nested++ > 0) + msg_panic("event_loop: recursive call"); + + /* + * Deliver timer events. Allow the application to add/delete timer queue + * requests while it is being called back. Requests are sorted: we keep + * running over the timer request queue from the start, and stop when we + * reach the future or the list end. We also stop when we reach a timer + * request that was added by a call-back that was invoked from this + * event_loop() call instance, for reasons that are explained below. + * + * To avoid dangling pointer problems 1) we must remove a request from the + * timer queue before delivering its event to the application and 2) we + * must look up the next timer request *after* calling the application. + * The latter complicates the handling of zero-delay timer requests that + * are added by event_loop() call-back functions. + * + * XXX When a timer event call-back function adds a new timer request, + * event_request_timer() labels the request with the event_loop() call + * instance that invoked the timer event call-back. We use this instance + * label here to prevent zero-delay timer requests from running in a + * tight loop and starving I/O events. To make this solution work, + * event_request_timer() appends a new request after existing requests + * for the same time slot. + */ + event_present = time((time_t *) 0); + event_loop_instance += 1; + + while ((timer = FIRST_TIMER(&event_timer_head)) != 0) { + if (timer->when > event_present) + break; + if (timer->loop_instance == event_loop_instance) + break; + ring_detach(&timer->ring); /* first this */ + if (msg_verbose > 2) + msg_info("%s: timer 0x%lx 0x%lx", myname, + (long) timer->callback, (long) timer->context); + timer->callback(EVENT_TIME, timer->context); /* then this */ + myfree((void *) timer); + } + + /* + * Deliver I/O events. Allow the application to cancel event requests + * while it is being called back. To this end, we keep an eye on the + * contents of event_xmask, so that we deliver only events that are still + * wanted. We do not change the event request masks. It is up to the + * application to determine when a read or write is complete. + */ +#if (EVENTS_STYLE == EVENTS_STYLE_SELECT) + if (event_count > 0) { + for (new_max_fd = 0, fd = 0; fd <= event_max_fd; fd++) { + if (FD_ISSET(fd, &event_xmask)) { + new_max_fd = fd; + /* In case event_fdtable is updated. */ + fdp = event_fdtable + fd; + if (FD_ISSET(fd, &xmask)) { + if (msg_verbose > 2) + msg_info("%s: exception fd=%d act=0x%lx 0x%lx", myname, + fd, (long) fdp->callback, (long) fdp->context); + fdp->callback(EVENT_XCPT, fdp->context); + } else if (FD_ISSET(fd, &wmask)) { + if (msg_verbose > 2) + msg_info("%s: write fd=%d act=0x%lx 0x%lx", myname, + fd, (long) fdp->callback, (long) fdp->context); + fdp->callback(EVENT_WRITE, fdp->context); + } else if (FD_ISSET(fd, &rmask)) { + if (msg_verbose > 2) + msg_info("%s: read fd=%d act=0x%lx 0x%lx", myname, + fd, (long) fdp->callback, (long) fdp->context); + fdp->callback(EVENT_READ, fdp->context); + } + } + } + event_max_fd = new_max_fd; + } +#else + for (bp = event_buf; bp < event_buf + event_count; bp++) { + fd = EVENT_GET_FD(bp); + if (fd < 0 || fd > event_max_fd) + msg_panic("%s: bad file descriptor: %d", myname, fd); + if (EVENT_MASK_ISSET(fd, &event_xmask)) { + fdp = event_fdtable + fd; + if (EVENT_TEST_READ(bp)) { + if (msg_verbose > 2) + msg_info("%s: read fd=%d act=0x%lx 0x%lx", myname, + fd, (long) fdp->callback, (long) fdp->context); + fdp->callback(EVENT_READ, fdp->context); + } else if (EVENT_TEST_WRITE(bp)) { + if (msg_verbose > 2) + msg_info("%s: write fd=%d act=0x%lx 0x%lx", myname, + fd, (long) fdp->callback, + (long) fdp->context); + fdp->callback(EVENT_WRITE, fdp->context); + } else { + if (msg_verbose > 2) + msg_info("%s: other fd=%d act=0x%lx 0x%lx", myname, + fd, (long) fdp->callback, (long) fdp->context); + fdp->callback(EVENT_XCPT, fdp->context); + } + } + } +#endif + nested--; +} + +#ifdef TEST + + /* + * Proof-of-concept test program for the event manager. Schedule a series of + * events at one-second intervals and let them happen, while echoing any + * lines read from stdin. + */ +#include <stdio.h> +#include <ctype.h> +#include <stdlib.h> + +/* timer_event - display event */ + +static void timer_event(int unused_event, void *context) +{ + printf("%ld: %s\n", (long) event_present, context); + fflush(stdout); +} + +/* echo - echo text received on stdin */ + +static void echo(int unused_event, void *unused_context) +{ + char buf[BUFSIZ]; + + if (fgets(buf, sizeof(buf), stdin) == 0) + exit(0); + printf("Result: %s", buf); +} + +/* request - request a bunch of timer events */ + +static void request(int unused_event, void *unused_context) +{ + event_request_timer(timer_event, "3 first", 3); + event_request_timer(timer_event, "3 second", 3); + event_request_timer(timer_event, "4 first", 4); + event_request_timer(timer_event, "4 second", 4); + event_request_timer(timer_event, "2 first", 2); + event_request_timer(timer_event, "2 second", 2); + event_request_timer(timer_event, "1 first", 1); + event_request_timer(timer_event, "1 second", 1); + event_request_timer(timer_event, "0 first", 0); + event_request_timer(timer_event, "0 second", 0); +} + +int main(int argc, void **argv) +{ + if (argv[1]) + msg_verbose = atoi(argv[1]); + event_request_timer(request, (void *) 0, 0); + event_enable_read(fileno(stdin), echo, (void *) 0); + event_drain(10); + exit(0); +} + +#endif diff --git a/src/util/events.h b/src/util/events.h new file mode 100644 index 0000000..b7f2047 --- /dev/null +++ b/src/util/events.h @@ -0,0 +1,67 @@ +#ifndef _EVENTS_H_INCLUDED_ +#define _EVENTS_H_INCLUDED_ + +/*++ +/* NAME +/* events 3h +/* SUMMARY +/* event manager +/* SYNOPSIS +/* #include <events.h> +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include <time.h> + + /* + * External interface. + */ +typedef void (*EVENT_NOTIFY_FN) (int, void *); + +#define EVENT_NOTIFY_TIME_FN EVENT_NOTIFY_FN /* legacy */ +#define EVENT_NOTIFY_RDWR_FN EVENT_NOTIFY_FN /* legacy */ + +extern time_t event_time(void); +extern void event_enable_read(int, EVENT_NOTIFY_RDWR_FN, void *); +extern void event_enable_write(int, EVENT_NOTIFY_RDWR_FN, void *); +extern void event_disable_readwrite(int); +extern time_t event_request_timer(EVENT_NOTIFY_TIME_FN, void *, int); +extern int event_cancel_timer(EVENT_NOTIFY_TIME_FN, void *); +extern void event_loop(int); +extern void event_drain(int); +extern void event_fork(void); + + /* + * Event codes. + */ +#define EVENT_READ (1<<0) /* read event */ +#define EVENT_WRITE (1<<1) /* write event */ +#define EVENT_XCPT (1<<2) /* exception */ +#define EVENT_TIME (1<<3) /* timer event */ + +#define EVENT_ERROR EVENT_XCPT + + /* + * Dummies. + */ +#define EVENT_NULL_TYPE (0) +#define EVENT_NULL_CONTEXT ((void *) 0) +#define EVENT_NULL_DELAY (0) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* CREATION DATE +/* Wed Jan 29 17:00:03 EST 1997 +/*--*/ + +#endif diff --git a/src/util/exec_command.c b/src/util/exec_command.c new file mode 100644 index 0000000..8629b0c --- /dev/null +++ b/src/util/exec_command.c @@ -0,0 +1,112 @@ +/*++ +/* NAME +/* exec_command 3 +/* SUMMARY +/* execute command +/* SYNOPSIS +/* #include <exec_command.h> +/* +/* NORETURN exec_command(command) +/* const char *command; +/* DESCRIPTION +/* \fIexec_command\fR() replaces the current process by an instance +/* of \fIcommand\fR. This routine uses a simple heuristic to avoid +/* the overhead of running a command shell interpreter. +/* DIAGNOSTICS +/* This routine never returns. All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <unistd.h> +#include <string.h> +#ifdef USE_PATHS_H +#include <paths.h> +#endif +#include <errno.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <argv.h> +#include <exec_command.h> + +/* Application-specific. */ + +#define SPACE_TAB " \t" + +/* exec_command - exec command */ + +NORETURN exec_command(const char *command) +{ + ARGV *argv; + + /* + * Character filter. In this particular case, we allow space and tab in + * addition to the regular character set. + */ + static char ok_chars[] = "1234567890!@%-_=+:,./\ +abcdefghijklmnopqrstuvwxyz\ +ABCDEFGHIJKLMNOPQRSTUVWXYZ" SPACE_TAB; + + /* + * See if this command contains any shell magic characters. + */ + if (command[strspn(command, ok_chars)] == 0 + && command[strspn(command, SPACE_TAB)] != 0) { + + /* + * No shell meta characters found, so we can try to avoid the overhead + * of running a shell. Just split the command on whitespace and exec + * the result directly. + */ + argv = argv_split(command, SPACE_TAB); + (void) execvp(argv->argv[0], argv->argv); + + /* + * Auch. Perhaps they're using some shell built-in command. + */ + if (errno != ENOENT || strchr(argv->argv[0], '/') != 0) + msg_fatal("execvp %s: %m", argv->argv[0]); + + /* + * Not really necessary, but... + */ + argv_free(argv); + } + + /* + * Pass the command to a shell. + */ + (void) execl(_PATH_BSHELL, "sh", "-c", command, (char *) 0); + msg_fatal("execl %s: %m", _PATH_BSHELL); +} + +#ifdef TEST + + /* + * Yet another proof-of-concept test program. + */ +#include <vstream.h> +#include <msg_vstream.h> + +int main(int argc, char **argv) +{ + msg_vstream_init(argv[0], VSTREAM_ERR); + if (argc != 2) + msg_fatal("usage: %s 'command'", argv[0]); + exec_command(argv[1]); +} + +#endif diff --git a/src/util/exec_command.h b/src/util/exec_command.h new file mode 100644 index 0000000..4e77211 --- /dev/null +++ b/src/util/exec_command.h @@ -0,0 +1,30 @@ +#ifndef _EXEC_COMMAND_H_INCLUDED_ +#define _EXEC_COMMAND_H_INCLUDED_ + +/*++ +/* NAME +/* exec_command 3h +/* SUMMARY +/* execute command +/* SYNOPSIS +/* #include <exec_command.h> +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern NORETURN exec_command(const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/extpar.c b/src/util/extpar.c new file mode 100644 index 0000000..0b106ba --- /dev/null +++ b/src/util/extpar.c @@ -0,0 +1,109 @@ +/*++ +/* NAME +/* extpar 3 +/* SUMMARY +/* extract text from parentheses +/* SYNOPSIS +/* #include <stringops.h> +/* +/* char *extpar(bp, parens, flags) +/* char **bp; +/* const char *parens; +/* int flags; +/* DESCRIPTION +/* extpar() extracts text from an input string that is enclosed +/* in the specified parentheses, and updates the buffer pointer +/* to point to that text. +/* +/* Arguments: +/* .IP bp +/* Pointer to buffer pointer. Both the buffer and the buffer +/* pointer are modified. +/* .IP parens +/* One matching pair of parentheses, opening parenthesis first. +/* .IP flags +/* EXTPAR_FLAG_NONE, or the bitwise OR of one or more flags: +/* .RS +/* .IP EXTPAR_FLAG_EXTRACT +/* This flag is intended to instruct extpar() callers that +/* extpar() should be invoked. It has no effect on expar() +/* itself. +/* .IP EXTPAR_FLAG_STRIP +/* Skip whitespace after the opening parenthesis, and trim +/* whitespace before the closing parenthesis. +/* .RE +/* DIAGNOSTICS +/* In case of error the result value is a dynamically-allocated +/* string with a description of the problem that includes a +/* copy of the offending input. A non-null result value should +/* be destroyed with myfree(). The following describes the errors +/* and the state of the buffer and buffer pointer. +/* .IP "no opening parenthesis at start of text" +/* The buffer pointer points to the input text. +/* .IP "missing closing parenthesis" +/* The buffer pointer points to text as if a closing parenthesis +/* were present at the end of the input. +/* .IP "text after closing parenthesis" +/* The buffer pointer points to text as if the offending text +/* were not present. +/* SEE ALSO +/* balpar(3) determine length of string in parentheses +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include <sys_defs.h> +#include <ctype.h> + + /* + * Utility library. + */ +#include <vstring.h> +#include <stringops.h> + +/* extpar - extract text from parentheses */ + +char *extpar(char **bp, const char *parens, int flags) +{ + char *cp = *bp; + char *err = 0; + size_t len; + + if (cp[0] != parens[0]) { + err = vstring_export(vstring_sprintf(vstring_alloc(100), + "no '%c' at start of text in \"%s\"", parens[0], cp)); + len = 0; + } else if ((len = balpar(cp, parens)) == 0) { + err = concatenate("missing '", parens + 1, "' in \"", + cp, "\"", (char *) 0); + cp += 1; + } else { + if (cp[len] != 0) + err = concatenate("syntax error after '", parens + 1, "' in \"", + cp, "\"", (char *) 0); + cp += 1; + cp[len -= 2] = 0; + } + if (flags & EXTPAR_FLAG_STRIP) { + trimblanks(cp, len)[0] = 0; + while (ISSPACE(*cp)) + cp++; + } + *bp = cp; + return (err); +} diff --git a/src/util/fifo_listen.c b/src/util/fifo_listen.c new file mode 100644 index 0000000..9ad3440 --- /dev/null +++ b/src/util/fifo_listen.c @@ -0,0 +1,111 @@ +/*++ +/* NAME +/* fifo_listen 3 +/* SUMMARY +/* start fifo listener +/* SYNOPSIS +/* #include <listen.h> +/* +/* int fifo_listen(path, permissions, block_mode) +/* const char *path; +/* int permissions; +/* int block_mode; +/* DESCRIPTION +/* The \fBfifo_listen\fR routine creates the specified named pipe with +/* the specified permissions, opens the FIFO read-write or read-only, +/* depending on the host operating system, and returns the resulting +/* file descriptor. +/* The \fIblock_mode\fR argument is either NON_BLOCKING for +/* a non-blocking socket, or BLOCKING for blocking mode. +/* DIAGNOSTICS +/* Fatal errors: all system call failures. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System interfaces. */ + +#include <sys_defs.h> +#include <sys/stat.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> + +/* Utility library. */ + +#include "msg.h" +#include "iostuff.h" +#include "listen.h" +#include "warn_stat.h" + +#define BUF_LEN 100 + +/* fifo_listen - create fifo listener */ + +int fifo_listen(const char *path, int permissions, int block_mode) +{ + char buf[BUF_LEN]; + static int open_mode = 0; + const char *myname = "fifo_listen"; + struct stat st; + int fd; + int count; + + /* + * Create a named pipe (fifo). Do whatever we can so we don't run into + * trouble when this process is restarted after crash. Make sure that we + * open a fifo and not something else, then change permissions to what we + * wanted them to be, because mkfifo() is subject to umask settings. + * Instead we could zero the umask temporarily before creating the FIFO, + * but that would cost even more system calls. Figure out if the fifo + * needs to be opened O_RDWR or O_RDONLY. Some systems need one, some + * need the other. If we choose the wrong mode, the fifo will stay + * readable, causing the program to go into a loop. + */ + if (unlink(path) && errno != ENOENT) + msg_fatal("%s: remove %s: %m", myname, path); + if (mkfifo(path, permissions) < 0) + msg_fatal("%s: create fifo %s: %m", myname, path); + switch (open_mode) { + case 0: + if ((fd = open(path, O_RDWR | O_NONBLOCK, 0)) < 0) + msg_fatal("%s: open %s: %m", myname, path); + if (readable(fd) == 0) { + open_mode = O_RDWR | O_NONBLOCK; + break; + } else { + open_mode = O_RDONLY | O_NONBLOCK; + if (msg_verbose) + msg_info("open O_RDWR makes fifo readable - trying O_RDONLY"); + (void) close(fd); + /* FALLTRHOUGH */ + } + default: + if ((fd = open(path, open_mode, 0)) < 0) + msg_fatal("%s: open %s: %m", myname, path); + break; + } + + /* + * Make sure we opened a FIFO and skip any cruft that might have + * accumulated before we opened it. + */ + if (fstat(fd, &st) < 0) + msg_fatal("%s: fstat %s: %m", myname, path); + if (S_ISFIFO(st.st_mode) == 0) + msg_fatal("%s: not a fifo: %s", myname, path); + if (fchmod(fd, permissions) < 0) + msg_fatal("%s: fchmod %s: %m", myname, path); + non_blocking(fd, block_mode); + while ((count = peekfd(fd)) > 0 + && read(fd, buf, BUF_LEN < count ? BUF_LEN : count) > 0) + /* void */ ; + return (fd); +} diff --git a/src/util/fifo_open.c b/src/util/fifo_open.c new file mode 100644 index 0000000..1587779 --- /dev/null +++ b/src/util/fifo_open.c @@ -0,0 +1,67 @@ +/*++ +/* NAME +/* fifo_open 1 +/* SUMMARY +/* fifo client test program +/* SYNOPSIS +/* fifo_open +/* DESCRIPTION +/* fifo_open creates a FIFO, then attempts to open it for writing +/* with non-blocking mode enabled. According to the POSIX standard +/* the open should succeed. +/* DIAGNOSTICS +/* Problems are reported to the standard error stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#include <sys/stat.h> +#include <stdio.h> +#include <fcntl.h> +#include <signal.h> +#include <unistd.h> +#include <stdlib.h> + +#define FIFO_PATH "test-fifo" +#define perrorexit(s) { perror(s); exit(1); } + +static void cleanup(void) +{ + printf("Removing fifo %s...\n", FIFO_PATH); + if (unlink(FIFO_PATH)) + perrorexit("unlink"); + printf("Done.\n"); +} + +static void stuck(int unused_sig) +{ + printf("Non-blocking, write-only open of FIFO blocked\n"); + cleanup(); + exit(1); +} + +int main(int unused_argc, char **unused_argv) +{ + (void) unlink(FIFO_PATH); + printf("Creating fifo %s...\n", FIFO_PATH); + if (mkfifo(FIFO_PATH, 0600) < 0) + perrorexit("mkfifo"); + signal(SIGALRM, stuck); + alarm(5); + printf("Opening fifo %s, non-blocking, write-only mode...\n", FIFO_PATH); + if (open(FIFO_PATH, O_WRONLY | O_NONBLOCK, 0) < 0) { + perror("open"); + cleanup(); + exit(1); + } + printf("Non-blocking, write-only open of FIFO succeeded\n"); + cleanup(); + exit(0); +} diff --git a/src/util/fifo_rdonly_bug.c b/src/util/fifo_rdonly_bug.c new file mode 100644 index 0000000..bf93467 --- /dev/null +++ b/src/util/fifo_rdonly_bug.c @@ -0,0 +1,127 @@ +/*++ +/* NAME +/* fifo_rdonly_bug 1 +/* SUMMARY +/* fifo server test program +/* SYNOPSIS +/* fifo_rdonly_bug +/* DESCRIPTION +/* fifo_rdonly_bug creates a FIFO and opens it read only. It +/* then opens the FIFO for writing, writes one byte, and closes +/* the writing end. On Linux Redhat 4.2 and 5.0, and HP-UX 9.05 +/* and 10.20, select() will report that the FIFO remains readable +/* even after multiple read operations. +/* DIAGNOSTICS +/* Problems are reported to the standard error stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#include <sys_defs.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <fcntl.h> +#include <string.h> + +#define FIFO_PATH "test-fifo" +#define TRIGGER_DELAY 5 + +#define perrorexit(s) { perror(s); exit(1); } + +static void cleanup(void) +{ + printf("Removing fifo %s...\n", FIFO_PATH); + if (unlink(FIFO_PATH)) + perrorexit("unlink"); + printf("Done.\n"); +} + +static void perrorcleanup(char *str) +{ + perror(str); + cleanup(); + exit(0); +} + +static void readable_event(int fd) +{ + char ch; + static int count = 0; + + if (read(fd, &ch, 1) < 0) { + perror("read"); + sleep(1); + } + if (count++ > 5) { + printf("FIFO remains readable after multiple reads.\n"); + cleanup(); + exit(1); + } +} + +int main(int unused_argc, char **unused_argv) +{ + struct timeval tv; + fd_set read_fds; + fd_set except_fds; + int fd; + int fd2; + + (void) unlink(FIFO_PATH); + + printf("Create fifo %s...\n", FIFO_PATH); + if (mkfifo(FIFO_PATH, 0600) < 0) + perrorexit("mkfifo"); + + printf("Open fifo %s, read-only mode...\n", FIFO_PATH); + if ((fd = open(FIFO_PATH, O_RDONLY | O_NONBLOCK, 0)) < 0) + perrorcleanup("open"); + + printf("Write one byte to the fifo, then close it...\n"); + if ((fd2 = open(FIFO_PATH, O_WRONLY, 0)) < 0) + perrorcleanup("open fifo O_WRONLY"); + if (write(fd2, "", 1) < 1) + perrorcleanup("write one byte to fifo"); + if (close(fd2) < 0) + perrorcleanup("close fifo"); + + printf("Selecting the fifo for readability...\n"); + + for (;;) { + FD_ZERO(&read_fds); + FD_SET(fd, &read_fds); + FD_ZERO(&except_fds); + FD_SET(fd, &except_fds); + tv.tv_sec = 1; + tv.tv_usec = 0; + + switch (select(fd + 1, &read_fds, (fd_set *) 0, &except_fds, &tv)) { + case -1: + perrorexit("select"); + default: + if (FD_ISSET(fd, &except_fds)) { + printf("Exceptional fifo condition! You are not normal!\n"); + readable_event(fd); + } else if (FD_ISSET(fd, &read_fds)) { + printf("Readable fifo condition\n"); + readable_event(fd); + } + break; + case 0: + printf("The fifo is not readable. You're normal.\n"); + cleanup(); + exit(0); + break; + } + } +} diff --git a/src/util/fifo_rdwr_bug.c b/src/util/fifo_rdwr_bug.c new file mode 100644 index 0000000..2486876 --- /dev/null +++ b/src/util/fifo_rdwr_bug.c @@ -0,0 +1,89 @@ +/*++ +/* NAME +/* fifo_rdwr_bug 1 +/* SUMMARY +/* fifo server test program +/* SYNOPSIS +/* fifo_rdwr_bug +/* DESCRIPTION +/* fifo_rdwr_bug creates a FIFO and opens it read-write mode. +/* On BSD/OS 3.1 select() will report that the FIFO is readable +/* even before any data is written to it. Doing an actual read +/* causes the read to block; a non-blocking read fails. +/* DIAGNOSTICS +/* Problems are reported to the standard error stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#include <sys_defs.h> +#include <sys/time.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <stdio.h> +#include <fcntl.h> +#include <signal.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> + +#define FIFO_PATH "test-fifo" +#define perrorexit(s) { perror(s); exit(1); } + +static void cleanup(void) +{ + printf("Removing fifo %s...\n", FIFO_PATH); + if (unlink(FIFO_PATH)) + perrorexit("unlink"); + printf("Done.\n"); +} + +int main(int unused_argc, char **unused_argv) +{ + struct timeval tv; + fd_set read_fds; + fd_set except_fds; + int fd; + + (void) unlink(FIFO_PATH); + + printf("Creating fifo %s...\n", FIFO_PATH); + if (mkfifo(FIFO_PATH, 0600) < 0) + perrorexit("mkfifo"); + + printf("Opening fifo %s, read-write mode...\n", FIFO_PATH); + if ((fd = open(FIFO_PATH, O_RDWR, 0)) < 0) { + perror("open"); + cleanup(); + exit(1); + } + printf("Selecting the fifo for readability...\n"); + FD_ZERO(&read_fds); + FD_SET(fd, &read_fds); + FD_ZERO(&except_fds); + FD_SET(fd, &except_fds); + tv.tv_sec = 1; + tv.tv_usec = 0; + + switch (select(fd + 1, &read_fds, (fd_set *) 0, &except_fds, &tv)) { + case -1: + perrorexit("select"); + default: + if (FD_ISSET(fd, &read_fds)) { + printf("Opening a fifo read-write makes it readable!!\n"); + break; + } + case 0: + printf("The fifo is not readable, as it should be.\n"); + break; + } + cleanup(); + exit(0); +} diff --git a/src/util/fifo_trigger.c b/src/util/fifo_trigger.c new file mode 100644 index 0000000..e5c5604 --- /dev/null +++ b/src/util/fifo_trigger.c @@ -0,0 +1,168 @@ +/*++ +/* NAME +/* fifo_trigger 3 +/* SUMMARY +/* wakeup fifo server +/* SYNOPSIS +/* #include <trigger.h> +/* +/* int fifo_trigger(service, buf, len, timeout) +/* const char *service; +/* const char *buf; +/* ssize_t len; +/* int timeout; +/* DESCRIPTION +/* fifo_trigger() wakes up the named fifo server by writing +/* the contents of the specified buffer to the fifo. There is +/* no guarantee that the written data will actually be received. +/* +/* Arguments: +/* .IP service +/* Name of the communication endpoint. +/* .IP buf +/* Address of data to be written. +/* .IP len +/* Amount of data to be written. +/* .IP timeout +/* Deadline in seconds. Specify a value <= 0 to disable +/* the time limit. +/* DIAGNOSTICS +/* The result is zero when the fifo could be opened, -1 otherwise. +/* BUGS +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <fcntl.h> +#include <unistd.h> + +/* Utility library. */ + +#include <msg.h> +#include <iostuff.h> +#include <safe_open.h> +#include <trigger.h> + +/* fifo_trigger - wakeup fifo server */ + +int fifo_trigger(const char *service, const char *buf, ssize_t len, int timeout) +{ + static VSTRING *why; + const char *myname = "fifo_trigger"; + VSTREAM *fp; + int fd; + + if (why == 0) + why = vstring_alloc(1); + + /* + * Write the request to the service fifo. According to POSIX, the open + * shall always return immediately, and shall return an error when no + * process is reading from the FIFO. + * + * Use safe_open() so that we don't follow symlinks, and so that we don't + * open files with multiple hard links. We're not (yet) going to bother + * the caller with safe_open() specific quirks such as the why argument. + */ + if ((fp = safe_open(service, O_WRONLY | O_NONBLOCK, 0, + (struct stat *) 0, -1, -1, why)) == 0) { + if (msg_verbose) + msg_info("%s: open %s: %s", myname, service, vstring_str(why)); + return (-1); + } + fd = vstream_fileno(fp); + + /* + * Write the request... + */ + non_blocking(fd, timeout > 0 ? NON_BLOCKING : BLOCKING); + if (write_buf(fd, buf, len, timeout) < 0) + if (msg_verbose) + msg_warn("%s: write %s: %m", myname, service); + + /* + * Disconnect. + */ + if (vstream_fclose(fp)) + if (msg_verbose) + msg_warn("%s: close %s: %m", myname, service); + return (0); +} + +#ifdef TEST + + /* + * Set up a FIFO listener, and keep triggering until the listener becomes + * idle, which should never happen. + */ +#include <signal.h> +#include <stdlib.h> + +#include "events.h" +#include "listen.h" + +#define TEST_FIFO "test-fifo" + +int trig_count; +int wakeup_count; + +static void cleanup(void) +{ + unlink(TEST_FIFO); + exit(1); +} + +static void handler(int sig) +{ + msg_fatal("got signal %d after %d triggers %d wakeups", + sig, trig_count, wakeup_count); +} + +static void read_event(int unused_event, char *context) +{ + int fd = CAST_ANY_PTR_TO_INT(context); + char ch; + + wakeup_count++; + + if (read(fd, &ch, 1) != 1) + msg_fatal("read %s: %m", TEST_FIFO); +} + +int main(int unused_argc, char **unused_argv) +{ + int listen_fd; + + listen_fd = fifo_listen(TEST_FIFO, 0600, NON_BLOCKING); + msg_cleanup(cleanup); + event_enable_read(listen_fd, read_event, CAST_INT_TO_VOID_PTR(listen_fd)); + signal(SIGINT, handler); + signal(SIGALRM, handler); + for (;;) { + alarm(10); + if (fifo_trigger(TEST_FIFO, "", 1, 0) < 0) + msg_fatal("trigger %s: %m", TEST_FIFO); + trig_count++; + if (fifo_trigger(TEST_FIFO, "", 1, 0) < 0) + msg_fatal("trigger %s: %m", TEST_FIFO); + trig_count++; + if (fifo_trigger(TEST_FIFO, "", 1, 0) < 0) + msg_fatal("trigger %s: %m", TEST_FIFO); + trig_count++; + event_loop(-1); + event_loop(-1); + event_loop(-1); + } +} + +#endif diff --git a/src/util/file_limit.c b/src/util/file_limit.c new file mode 100644 index 0000000..5cfae4e --- /dev/null +++ b/src/util/file_limit.c @@ -0,0 +1,96 @@ +/*++ +/* NAME +/* file_limit 3 +/* SUMMARY +/* limit the file size +/* SYNOPSIS +/* #include <iostuff.h> +/* +/* off_t get_file_limit() +/* +/* void set_file_limit(limit) +/* off_t limit; +/* DESCRIPTION +/* This module manipulates the process-wide file size limit. +/* The limit is specified in bytes. +/* +/* get_file_limit() looks up the process-wide file size limit. +/* +/* set_file_limit() sets the process-wide file size limit to +/* \fIlimit\fR. +/* DIAGNOSTICS +/* All errors are fatal. +/* SEE ALSO +/* setrlimit(2) +/* ulimit(2) +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#ifdef USE_ULIMIT +#include <ulimit.h> +#else +#include <sys/time.h> +#include <sys/resource.h> +#include <signal.h> +#endif +#include <limits.h> + +/* Utility library. */ + +#include <msg.h> +#include <iostuff.h> + +#define ULIMIT_BLOCK_SIZE 512 + +/* get_file_limit - get process-wide file size limit */ + +off_t get_file_limit(void) +{ + off_t limit; + +#ifdef USE_ULIMIT + if ((limit = ulimit(UL_GETFSIZE, 0)) < 0) + msg_fatal("ulimit: %m"); + if (limit > OFF_T_MAX / ULIMIT_BLOCK_SIZE) + limit = OFF_T_MAX / ULIMIT_BLOCK_SIZE; + return (limit * ULIMIT_BLOCK_SIZE); +#else + struct rlimit rlim; + + if (getrlimit(RLIMIT_FSIZE, &rlim) < 0) + msg_fatal("getrlimit: %m"); + limit = rlim.rlim_cur; + return (limit < 0 ? OFF_T_MAX : rlim.rlim_cur); +#endif /* USE_ULIMIT */ +} + +/* set_file_limit - process-wide file size limit */ + +void set_file_limit(off_t limit) +{ +#ifdef USE_ULIMIT + if (ulimit(UL_SETFSIZE, limit / ULIMIT_BLOCK_SIZE) < 0) + msg_fatal("ulimit: %m"); +#else + struct rlimit rlim; + + rlim.rlim_cur = rlim.rlim_max = limit; + if (setrlimit(RLIMIT_FSIZE, &rlim) < 0) + msg_fatal("setrlimit: %m"); +#ifdef SIGXFSZ + if (signal(SIGXFSZ, SIG_IGN) == SIG_ERR) + msg_fatal("signal(SIGXFSZ,SIG_IGN): %m"); +#endif +#endif /* USE_ULIMIT */ +} diff --git a/src/util/find_inet.c b/src/util/find_inet.c new file mode 100644 index 0000000..294634c --- /dev/null +++ b/src/util/find_inet.c @@ -0,0 +1,253 @@ +/*++ +/* NAME +/* find_inet 3 +/* SUMMARY +/* inet-domain name services +/* SYNOPSIS +/* #include <find_inet.h> +/* +/* unsigned find_inet_addr(host) +/* const char *host; +/* +/* int find_inet_port(port, proto) +/* const char *port; +/* const char *proto; +/* DESCRIPTION +/* These functions translate network address information from +/* between printable form to the internal the form used by the +/* BSD TCP/IP network software. +/* +/* find_inet_addr() translates a symbolic or numerical hostname. +/* This function is deprecated. Use hostname_to_hostaddr() instead. +/* +/* find_inet_port() translates a symbolic or numerical port name. +/* BUGS +/* find_inet_addr() ignores all but the first address listed for +/* a symbolic hostname. +/* DIAGNOSTICS +/* Lookup and conversion errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System libraries. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <stdlib.h> +#include <string.h> + +/* Application-specific. */ + +#include "msg.h" +#include "stringops.h" +#include "find_inet.h" +#include "known_tcp_ports.h" + +#ifndef INADDR_NONE +#define INADDR_NONE 0xffffffff +#endif + +#ifdef TEST +extern NORETURN PRINTFLIKE(1, 2) test_msg_fatal(const char *,...); + +#define msg_fatal test_msg_fatal +#endif + +/* find_inet_addr - translate numerical or symbolic host name */ + +unsigned find_inet_addr(const char *host) +{ + struct in_addr addr; + struct hostent *hp; + + addr.s_addr = inet_addr(host); + if ((addr.s_addr == INADDR_NONE) || (addr.s_addr == 0)) { + if ((hp = gethostbyname(host)) == 0) + msg_fatal("host not found: %s", host); + if (hp->h_addrtype != AF_INET) + msg_fatal("unexpected address family: %d", hp->h_addrtype); + if (hp->h_length != sizeof(addr)) + msg_fatal("unexpected address length %d", hp->h_length); + memcpy((void *) &addr, hp->h_addr, hp->h_length); + } + return (addr.s_addr); +} + +/* find_inet_port - translate numerical or symbolic service name */ + +int find_inet_port(const char *service, const char *protocol) +{ + struct servent *sp; + int port; + + service = filter_known_tcp_port(service); + if (alldig(service) && (port = atoi(service)) != 0) { + if (port < 0 || port > 65535) + msg_fatal("bad port number: %s", service); + return (htons(port)); + } else { + if ((sp = getservbyname(service, protocol)) == 0) + msg_fatal("unknown service: %s/%s", service, protocol); + return (sp->s_port); + } +} + +#ifdef TEST + +#include <stdlib.h> +#include <setjmp.h> +#include <string.h> + +#include <vstream.h> +#include <vstring.h> +#include <msg_vstream.h> + +#define STR(x) vstring_str(x) + + /* TODO(wietse) make this a proper VSTREAM interface */ + +/* vstream_swap - kludge to capture output for testing */ + +static void vstream_swap(VSTREAM *one, VSTREAM *two) +{ + VSTREAM save; + + save = *one; + *one = *two; + *two = save; +} + +jmp_buf test_fatal_jbuf; + +#undef msg_fatal + +/* test_msg_fatal - does not return, and does not terminate */ + +void test_msg_fatal(const char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + vmsg_warn(fmt, ap); + va_end(ap); + longjmp(test_fatal_jbuf, 1); +} + +struct association { + const char *lhs; /* service name */ + const char *rhs; /* service port */ +}; + +struct test_case { + const char *label; /* identifies test case */ + struct association associations[10]; + const char *service; + const char *proto; + const char *exp_warning; /* expected error */ + int exp_hport; /* expected port, host byte order */ +}; + +struct test_case test_cases[] = { + {"good-symbolic", + /* association */ {{"foobar", "25252"}, 0}, + /* service */ "foobar", + /* proto */ "tcp", + /* exp_warning */ "", + /* exp_hport */ 25252, + }, + {"good-numeric", + /* association */ {{"foobar", "25252"}, 0}, + /* service */ "25252", + /* proto */ "tcp", + /* exp_warning */ "", + /* exp_hport */ 25252, + }, + {"bad-symbolic", + /* association */ {{"foobar", "25252"}, 0}, + /* service */ "an-impossible-name", + /* proto */ "tcp", + /* exp_warning */ "find_inet: warning: unknown service: an-impossible-name/tcp\n", + }, + {"bad-numeric", + /* association */ {{"foobar", "25252"}, 0}, + /* service */ "123456", + /* proto */ "tcp", + /* exp_warning */ "find_inet: warning: bad port number: 123456\n", + }, +}; + +int main(int argc, char **argv) { + struct test_case *tp; + struct association *ap; + int pass = 0; + int fail = 0; + const char *err; + int test_failed; + int nport; + VSTRING *msg_buf; + VSTREAM *memory_stream; + + msg_vstream_init("find_inet", VSTREAM_ERR); + msg_buf = vstring_alloc(100); + + for (tp = test_cases; tp->label != 0; tp++) { + test_failed = 0; + VSTRING_RESET(msg_buf); + VSTRING_TERMINATE(msg_buf); + clear_known_tcp_ports(); + for (err = 0, ap = tp->associations; err == 0 && ap->lhs != 0; ap++) + err = add_known_tcp_port(ap->lhs, ap->rhs); + if (err != 0) { + msg_warn("test case %s: got err: \"%s\"", tp->label, err); + test_failed = 1; + } else { + if ((memory_stream = vstream_memopen(msg_buf, O_WRONLY)) == 0) + msg_fatal("open memory stream: %m"); + vstream_swap(VSTREAM_ERR, memory_stream); + if (setjmp(test_fatal_jbuf) == 0) + nport = find_inet_port(tp->service, tp->proto); + vstream_swap(memory_stream, VSTREAM_ERR); + if (vstream_fclose(memory_stream)) + msg_fatal("close memory stream: %m"); + if (strcmp(STR(msg_buf), tp->exp_warning) != 0) { + msg_warn("test case %s: got error: \"%s\", want: \"%s\"", + tp->label, STR(msg_buf), tp->exp_warning); + test_failed = 1; + } else if (tp->exp_warning[0] == 0) { + if (ntohs(nport) != tp->exp_hport) { + msg_warn("test case %s: got port \"%d\", want: \"%d\"", + tp->label, ntohs(nport), tp->exp_hport); + test_failed = 1; + } + } + } + if (test_failed) { + msg_info("%s: FAIL", tp->label); + fail++; + } else { + msg_info("%s: PASS", tp->label); + pass++; + } + } + msg_info("PASS=%d FAIL=%d", pass, fail); + vstring_free(msg_buf); + exit(fail != 0); +} + +#endif diff --git a/src/util/find_inet.h b/src/util/find_inet.h new file mode 100644 index 0000000..0e5ce72 --- /dev/null +++ b/src/util/find_inet.h @@ -0,0 +1,33 @@ +#ifndef _FIND_INET_H_INCLUDED_ +#define _FIND_INET_H_INCLUDED_ + +/*++ +/* NAME +/* find_inet 3h +/* SUMMARY +/* inet-domain name services +/* SYNOPSIS +/* #include <find_inet.h> +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern unsigned find_inet_addr(const char *); +extern int find_inet_port(const char *, const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* LAST MODIFICATION +/* Thu Feb 6 12:46:36 EST 1997 +/*--*/ + +#endif diff --git a/src/util/find_inet.ref b/src/util/find_inet.ref new file mode 100644 index 0000000..a6f9cac --- /dev/null +++ b/src/util/find_inet.ref @@ -0,0 +1,5 @@ +find_inet: good-symbolic: PASS +find_inet: good-numeric: PASS +find_inet: bad-symbolic: PASS +find_inet: bad-numeric: PASS +find_inet: PASS=4 FAIL=0 diff --git a/src/util/format_tv.c b/src/util/format_tv.c new file mode 100644 index 0000000..a932bdb --- /dev/null +++ b/src/util/format_tv.c @@ -0,0 +1,160 @@ +/*++ +/* NAME +/* format_tv 3 +/* SUMMARY +/* format time value with sane precision +/* SYNOPSIS +/* #include <format_tv.h> +/* +/* VSTRING *format_tv(buffer, sec, usec, sig_dig, max_dig) +/* VSTRING *buffer; +/* long sec; +/* long usec; +/* int sig_dig; +/* int max_dig; +/* DESCRIPTION +/* format_tv() formats the specified time as a floating-point +/* number while suppressing irrelevant digits in the output. +/* Large numbers are always rounded up to an integral number +/* of seconds. Small numbers are produced with a limited number +/* of significant digits, provided that the result does not +/* exceed the limit on the total number of digits after the +/* decimal point. Trailing zeros are always omitted from the +/* output. +/* +/* Arguments: +/* .IP buffer +/* The buffer to which the result is appended. +/* .IP sec +/* The seconds portion of the time value. +/* .IP usec +/* The microseconds portion of the time value. +/* .IP sig_dig +/* The maximal number of significant digits when formatting +/* small numbers. Leading nulls don't count as significant, +/* and trailing nulls are not included in the output. Specify +/* a number in the range 1..6. +/* .IP max_dig +/* The maximal number of all digits after the decimal point. +/* Specify a number in the range 0..6. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this +/* software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#include <sys_defs.h> + +/* Utility library. */ + +#include <msg.h> +#include <format_tv.h> + +/* Application-specific. */ + +#define MILLION 1000000 + +/* format_tv - print time with limited precision */ + +VSTRING *format_tv(VSTRING *buf, long sec, long usec, + int sig_dig, int max_dig) +{ + static int pow10[] = {1, 10, 100, 1000, 10000, 100000, 1000000}; + int n; + int rem; + int wid; + int ures; + + /* + * Sanity check. + */ + if (max_dig < 0 || max_dig > 6) + msg_panic("format_tv: bad maximum decimal count %d", max_dig); + if (sec < 0 || usec < 0 || usec > MILLION) + msg_panic("format_tv: bad time %lds %ldus", sec, usec); + if (sig_dig < 1 || sig_dig > 6) + msg_panic("format_tv: bad significant decimal count %d", sig_dig); + ures = MILLION / pow10[max_dig]; + wid = pow10[sig_dig]; + + /* + * Adjust the resolution to suppress irrelevant digits. + */ + if (ures < MILLION) { + if (sec > 0) { + for (n = 1; sec >= n && n <= wid / 10; n *= 10) + /* void */ ; + ures = (MILLION / wid) * n; + } else { + while (usec >= wid * ures) + ures *= 10; + } + } + + /* + * Round up the number if necessary. Leave thrash below the resolution. + */ + if (ures > 1) { + usec += ures / 2; + if (usec >= MILLION) { + sec += 1; + usec -= MILLION; + } + } + + /* + * Format the number. Truncate trailing null and thrash below resolution. + */ + vstring_sprintf_append(buf, "%ld", sec); + if (usec >= ures) { + VSTRING_ADDCH(buf, '.'); + for (rem = usec, n = MILLION / 10; rem >= ures && n > 0; n /= 10) { + VSTRING_ADDCH(buf, "0123456789"[rem / n]); + rem %= n; + } + } + VSTRING_TERMINATE(buf); + return (buf); +} + +#ifdef TEST + +#include <stdio.h> +#include <stdlib.h> +#include <vstring_vstream.h> + +int main(int argc, char **argv) +{ + VSTRING *in = vstring_alloc(10); + VSTRING *out = vstring_alloc(10); + double tval; + int sec; + int usec; + int sig_dig; + int max_dig; + + while (vstring_get_nonl(in, VSTREAM_IN) > 0) { + vstream_printf(">> %s\n", vstring_str(in)); + if (vstring_str(in)[0] == 0 || vstring_str(in)[0] == '#') + continue; + if (sscanf(vstring_str(in), "%lf %d %d", &tval, &sig_dig, &max_dig) != 3) + msg_fatal("bad input: %s", vstring_str(in)); + sec = (int) tval; /* raw seconds */ + usec = (tval - sec) * MILLION; /* raw microseconds */ + VSTRING_RESET(out); + format_tv(out, sec, usec, sig_dig, max_dig); + vstream_printf("%s\n", vstring_str(out)); + vstream_fflush(VSTREAM_OUT); + } + vstring_free(in); + vstring_free(out); + return (0); +} + +#endif diff --git a/src/util/format_tv.h b/src/util/format_tv.h new file mode 100644 index 0000000..bef787a --- /dev/null +++ b/src/util/format_tv.h @@ -0,0 +1,35 @@ +#ifndef _FORMAT_TV_H_INCLUDED_ +#define _FORMAT_TV_H_INCLUDED_ + +/*++ +/* NAME +/* format_tv 3h +/* SUMMARY +/* format time with limited precision +/* SYNOPSIS +/* #include <format_tv.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <vstring.h> + + /* + * External interface. + */ +extern VSTRING *format_tv(VSTRING *, long, long, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/format_tv.in b/src/util/format_tv.in new file mode 100644 index 0000000..a6b5848 --- /dev/null +++ b/src/util/format_tv.in @@ -0,0 +1,68 @@ +# Three digits in, 2/6 digits out, rounding down. +1110 2 6 +111 2 6 +11.1 2 6 +1.11 2 6 +0.111 2 6 +0.0111 2 6 +0.00111 2 6 +0.000111 2 6 +0.0000111 2 6 + +# One digit in. Must not produce spurious digits or trailing nulls. + +1000 2 6 +100 2 6 +10 2 6 +1 2 6 +0.1 2 6 +0.01 2 6 +0.001 2 6 +0.0001 2 6 +0.00001 2 6 +0.0000011 2 6 + +# Three digits in, 2/6 digits out, rounding up. + +996 2 6 +99.6 2 6 +9.96 2 6 +.996 2 6 +.0996 2 6 +.00996 2 6 +.000996 2 6 + +# Three digits in, 1/6 digits out, rounding down. + +1110 1 6 +111 1 6 +11.1 1 6 +1.11 1 6 +0.111 1 6 +0.0111 1 6 +0.00111 1 6 +0.000111 1 6 +0.000011 1 6 + +# One digit in. Must not produce trailing nulls. + +1000 1 6 +100 1 6 +10 1 6 +1 1 6 +0.1 1 6 +0.01 1 6 +0.001 1 6 +0.0001 1 6 +0.00001 1 6 +0.0000011 1 6 + +# Three digits in, 1/6 digits out, rounding up. + +996 1 6 +99.6 1 6 +9.96 1 6 +.996 1 6 +.0996 1 6 +.00996 1 6 +.000996 1 6 diff --git a/src/util/format_tv.ref b/src/util/format_tv.ref new file mode 100644 index 0000000..718c3c7 --- /dev/null +++ b/src/util/format_tv.ref @@ -0,0 +1,120 @@ +>> # Three digits in, 2/6 digits out, rounding down. +>> 1110 2 6 +1110 +>> 111 2 6 +111 +>> 11.1 2 6 +11 +>> 1.11 2 6 +1.1 +>> 0.111 2 6 +0.11 +>> 0.0111 2 6 +0.011 +>> 0.00111 2 6 +0.0011 +>> 0.000111 2 6 +0.00011 +>> 0.0000111 2 6 +0.000011 +>> +>> # One digit in. Must not produce spurious digits or trailing nulls. +>> +>> 1000 2 6 +1000 +>> 100 2 6 +100 +>> 10 2 6 +10 +>> 1 2 6 +1 +>> 0.1 2 6 +0.1 +>> 0.01 2 6 +0.01 +>> 0.001 2 6 +0.001 +>> 0.0001 2 6 +0.0001 +>> 0.00001 2 6 +0.00001 +>> 0.0000011 2 6 +0.000001 +>> +>> # Three digits in, 2/6 digits out, rounding up. +>> +>> 996 2 6 +996 +>> 99.6 2 6 +100 +>> 9.96 2 6 +10 +>> .996 2 6 +1 +>> .0996 2 6 +0.1 +>> .00996 2 6 +0.01 +>> .000996 2 6 +0.001 +>> +>> # Three digits in, 1/6 digits out, rounding down. +>> +>> 1110 1 6 +1110 +>> 111 1 6 +111 +>> 11.1 1 6 +11 +>> 1.11 1 6 +1 +>> 0.111 1 6 +0.1 +>> 0.0111 1 6 +0.01 +>> 0.00111 1 6 +0.001 +>> 0.000111 1 6 +0.0001 +>> 0.000011 1 6 +0.00001 +>> +>> # One digit in. Must not produce trailing nulls. +>> +>> 1000 1 6 +1000 +>> 100 1 6 +100 +>> 10 1 6 +10 +>> 1 1 6 +1 +>> 0.1 1 6 +0.1 +>> 0.01 1 6 +0.01 +>> 0.001 1 6 +0.001 +>> 0.0001 1 6 +0.0001 +>> 0.00001 1 6 +0.00001 +>> 0.0000011 1 6 +0.000001 +>> +>> # Three digits in, 1/6 digits out, rounding up. +>> +>> 996 1 6 +996 +>> 99.6 1 6 +100 +>> 9.96 1 6 +10 +>> .996 1 6 +1 +>> .0996 1 6 +0.1 +>> .00996 1 6 +0.01 +>> .000996 1 6 +0.001 diff --git a/src/util/fsspace.c b/src/util/fsspace.c new file mode 100644 index 0000000..50a4aa7 --- /dev/null +++ b/src/util/fsspace.c @@ -0,0 +1,127 @@ +/*++ +/* NAME +/* fsspace 3 +/* SUMMARY +/* determine available file system space +/* SYNOPSIS +/* #include <fsspace.h> +/* +/* struct fsspace { +/* .in +4 +/* unsigned long block_size; +/* unsigned long block_free; +/* .in -4 +/* }; +/* +/* void fsspace(path, sp) +/* const char *path; +/* struct fsspace *sp; +/* DESCRIPTION +/* fsspace() returns the amount of available space in the file +/* system specified in \fIpath\fR, in terms of the block size and +/* of the number of available blocks. +/* DIAGNOSTICS +/* All errors are fatal. +/* BUGS +/* Use caution when doing computations with the result from fsspace(). +/* It is easy to cause overflow (by multiplying large numbers) or to +/* cause underflow (by subtracting unsigned numbers). +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +#if defined(STATFS_IN_SYS_MOUNT_H) +#include <sys/param.h> +#include <sys/mount.h> +#elif defined(STATFS_IN_SYS_VFS_H) +#include <sys/vfs.h> +#elif defined(STATVFS_IN_SYS_STATVFS_H) +#include <sys/statvfs.h> +#elif defined(STATFS_IN_SYS_STATFS_H) +#include <sys/statfs.h> +#else +#ifdef USE_STATFS +#error "please specify the include file with `struct statfs'" +#else +#error "please specify the include file with `struct statvfs'" +#endif +#endif + +/* Utility library. */ + +#include <msg.h> +#include <fsspace.h> + +/* fsspace - find amount of available file system space */ + +void fsspace(const char *path, struct fsspace * sp) +{ + const char *myname = "fsspace"; + +#ifdef USE_STATFS +#ifdef USE_STRUCT_FS_DATA /* Ultrix */ + struct fs_data fsbuf; + + if (statfs(path, &fsbuf) < 0) + msg_fatal("statfs %s: %m", path); + sp->block_size = 1024; + sp->block_free = fsbuf.fd_bfreen; +#else + struct statfs fsbuf; + + if (statfs(path, &fsbuf) < 0) + msg_fatal("statfs %s: %m", path); + sp->block_size = fsbuf.f_bsize; + sp->block_free = fsbuf.f_bavail; +#endif +#endif +#ifdef USE_STATVFS + struct statvfs fsbuf; + + if (statvfs(path, &fsbuf) < 0) + msg_fatal("statvfs %s: %m", path); + sp->block_size = fsbuf.f_frsize; + sp->block_free = fsbuf.f_bavail; +#endif + if (msg_verbose) + msg_info("%s: %s: block size %lu, blocks free %lu", + myname, path, sp->block_size, sp->block_free); +} + +#ifdef TEST + + /* + * Proof-of-concept test program: print free space unit and count for all + * listed file systems. + */ + +#include <vstream.h> + +int main(int argc, char **argv) +{ + struct fsspace sp; + + if (argc == 1) + msg_fatal("usage: %s filesystem...", argv[0]); + + while (--argc && *++argv) { + fsspace(*argv, &sp); + vstream_printf("%10s: block size %lu, blocks free %lu\n", + *argv, sp.block_size, sp.block_free); + vstream_fflush(VSTREAM_OUT); + } + return (0); +} + +#endif diff --git a/src/util/fsspace.h b/src/util/fsspace.h new file mode 100644 index 0000000..c118e50 --- /dev/null +++ b/src/util/fsspace.h @@ -0,0 +1,33 @@ +#ifndef _FSSPACE_H_INCLUDED_ +#define _FSSPACE_H_INCLUDED_ + +/*++ +/* NAME +/* fsspace 3h +/* SUMMARY +/* determine available file system space +/* SYNOPSIS +/* #include <fsspace.h> +/* DESCRIPTION +/* .nf + + /* External interface. */ +struct fsspace { + unsigned long block_size; /* block size */ + unsigned long block_free; /* free space */ +}; + +extern void fsspace(const char *, struct fsspace *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/fullname.c b/src/util/fullname.c new file mode 100644 index 0000000..a9d4b32 --- /dev/null +++ b/src/util/fullname.c @@ -0,0 +1,111 @@ +/*++ +/* NAME +/* fullname 3 +/* SUMMARY +/* lookup personal name of invoking user +/* SYNOPSIS +/* #include <fullname.h> +/* +/* const char *fullname() +/* DESCRIPTION +/* fullname() looks up the personal name of the invoking user. +/* The result is volatile. Make a copy if it is to be used for +/* an appreciable amount of time. +/* +/* On UNIX systems, fullname() first tries to use the NAME environment +/* variable, provided that the environment can be trusted. +/* If that fails, fullname() extracts the username from the GECOS +/* field of the user's password-file entry, replacing any occurrence +/* of "&" by the login name, first letter capitalized. +/* +/* A null result means that no full name information was found. +/* SEE ALSO +/* safe_getenv(3) safe getenv() interface +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <unistd.h> +#include <pwd.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + +/* Utility library. */ + +#include "vstring.h" +#include "safe.h" +#include "fullname.h" + +/* fullname - get name of user */ + +const char *fullname(void) +{ + static VSTRING *result; + char *cp; + int ch; + uid_t uid; + struct passwd *pwd; + + if (result == 0) + result = vstring_alloc(10); + + /* + * Try the environment. + */ + if ((cp = safe_getenv("NAME")) != 0) + return (vstring_str(vstring_strcpy(result, cp))); + + /* + * Try the password file database. + */ + uid = getuid(); + if ((pwd = getpwuid(uid)) == 0) + return (0); + + /* + * Replace all `&' characters by the login name of this user, first + * letter capitalized. Although the full name comes from the protected + * password file, the actual data is specified by the user so we should + * not trust its sanity. + */ + VSTRING_RESET(result); + for (cp = pwd->pw_gecos; (ch = *(unsigned char *) cp) != 0; cp++) { + if (ch == ',' || ch == ';' || ch == '%') + break; + if (ch == '&') { + if (pwd->pw_name[0]) { + VSTRING_ADDCH(result, TOUPPER(pwd->pw_name[0])); + vstring_strcat(result, pwd->pw_name + 1); + } + } else { + VSTRING_ADDCH(result, ch); + } + } + VSTRING_TERMINATE(result); + return (vstring_str(result)); +} + +#ifdef TEST + +#include <stdio.h> + +int main(int unused_argc, char **unused_argv) +{ + const char *cp = fullname(); + + printf("%s\n", cp ? cp : "null!"); + return (0); +} + +#endif diff --git a/src/util/fullname.h b/src/util/fullname.h new file mode 100644 index 0000000..f24492a --- /dev/null +++ b/src/util/fullname.h @@ -0,0 +1,29 @@ +#ifndef _FULLNAME_H_INCLUDED_ +#define _FULLNAME_H_INCLUDED_ + +/*++ +/* NAME +/* fullname 3h +/* SUMMARY +/* lookup personal name of invoking user +/* SYNOPSIS +/* #include <fullname.h> +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern const char *fullname(void); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/gccw.c b/src/util/gccw.c new file mode 100644 index 0000000..d3fd985 --- /dev/null +++ b/src/util/gccw.c @@ -0,0 +1,58 @@ + /* + * This is a regression test for all the things that gcc is meant to warn + * about. + * + * gcc version 3 breaks several tests: + * + * -W does not report missing return value + * + * -Wunused does not report unused parameter + */ + +#include <stdio.h> +#include <setjmp.h> + +jmp_buf jbuf; + + /* -Wmissing-prototypes: no previous prototype for 'test1' */ + /* -Wimplicit: return type defaults to `int' */ +test1(void) +{ + /* -Wunused: unused variable `foo' */ + int foo; + + /* -Wparentheses: suggest parentheses around && within || */ + printf("%d\n", 1 && 2 || 3 && 4); + /* -W: statement with no effect */ + 0; + /* BROKEN in gcc 3 */ + /* -W: control reaches end of non-void function */ +} + + + /* -W??????: unused parameter `foo' */ +void test2(int foo) +{ + enum { + a = 10, b = 15} moe; + int bar; + + /* -Wuninitialized: 'bar' might be used uninitialized in this function */ + /* -Wformat: format argument is not a pointer (arg 2) */ + printf("%s\n", bar); + /* -Wformat: too few arguments for format */ + printf("%s%s\n", "bar"); + /* -Wformat: too many arguments for format */ + printf("%s\n", "bar", "bar"); + + /* -Wswitch: enumeration value `b' not handled in switch */ + switch (moe) { + case a: + return; + } +} + + /* -Wstrict-prototypes: function declaration isn't a prototype */ +void test3() +{ +} diff --git a/src/util/gccw.ref b/src/util/gccw.ref new file mode 100644 index 0000000..7616c29 --- /dev/null +++ b/src/util/gccw.ref @@ -0,0 +1,18 @@ +gcc -Wall -Wno-comment -Wformat -Wimplicit -Wmissing-prototypes -Wparentheses -Wstrict-prototypes -Wswitch -Wuninitialized -Wunused -DUSE_TLS -DHAS_PCRE -I/usr/local/include -DSNAPSHOT -g -O -I. -DFREEBSD5 -c gccw.c +gccw.c: At top level: +gccw.c: At top level: +gccw.c: In function 'test1': +gccw.c: In function 'test2': +gccw.c:20: warning: no previous prototype for 'test1' +gccw.c:20: warning: return type defaults to 'int' +gccw.c:22: warning: unused variable 'foo' +gccw.c:25: warning: suggest parentheses around && within || +gccw.c:27: warning: statement with no effect +gccw.c:30: warning: control reaches end of non-void function +gccw.c:35: warning: no previous prototype for 'test2' +gccw.c:38: warning: 'bar' might be used uninitialized in this function +gccw.c:42: warning: format argument is not a pointer (arg 2) +gccw.c:44: warning: too few arguments for format +gccw.c:46: warning: too many arguments for format +gccw.c:52: warning: enumeration value 'b' not handled in switch +gccw.c:57: warning: function declaration isn't a prototype diff --git a/src/util/get_domainname.c b/src/util/get_domainname.c new file mode 100644 index 0000000..0897f52 --- /dev/null +++ b/src/util/get_domainname.c @@ -0,0 +1,66 @@ +/*++ +/* NAME +/* get_domainname 3 +/* SUMMARY +/* network domain name lookup +/* SYNOPSIS +/* #include <get_domainname.h> +/* +/* const char *get_domainname() +/* DESCRIPTION +/* get_domainname() returns the local domain name as obtained +/* by stripping the hostname component from the result from +/* get_hostname(). The result is the hostname when get_hostname() +/* does not return a FQDN form ("foo"), or its result has only two +/* components ("foo.com"). +/* DIAGNOSTICS +/* Fatal errors: no hostname, invalid hostname. +/* SEE ALSO +/* get_hostname(3) +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> + +/* Utility library. */ + +#include "mymalloc.h" +#include "get_hostname.h" +#include "get_domainname.h" + +/* Local stuff. */ + +static char *my_domain_name; + +/* get_domainname - look up my domain name */ + +const char *get_domainname(void) +{ + const char *host; + const char *dot; + + /* + * Use the hostname when it is not a FQDN ("foo"), or when the hostname + * actually is a domain name ("foo.com"). + */ + if (my_domain_name == 0) { + host = get_hostname(); + if ((dot = strchr(host, '.')) == 0 || strchr(dot + 1, '.') == 0) { + my_domain_name = mystrdup(host); + } else { + my_domain_name = mystrdup(dot + 1); + } + } + return (my_domain_name); +} diff --git a/src/util/get_domainname.h b/src/util/get_domainname.h new file mode 100644 index 0000000..177e3fd --- /dev/null +++ b/src/util/get_domainname.h @@ -0,0 +1,29 @@ +#ifndef _GET_DOMAINNAME_H_INCLUDED_ +#define _GET_DOMAINNAME_H_INCLUDED_ + +/*++ +/* NAME +/* get_domainname 3h +/* SUMMARY +/* network domain name lookup +/* SYNOPSIS +/* #include <get_domainname.h> +/* DESCRIPTION +/* .nf + + /* External interface */ + +extern const char *get_domainname(void); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/get_hostname.c b/src/util/get_hostname.c new file mode 100644 index 0000000..eb2cfea --- /dev/null +++ b/src/util/get_hostname.c @@ -0,0 +1,84 @@ +/*++ +/* NAME +/* get_hostname 3 +/* SUMMARY +/* network name lookup +/* SYNOPSIS +/* #include <get_hostname.h> +/* +/* const char *get_hostname() +/* DESCRIPTION +/* get_hostname() returns the local hostname as obtained +/* via gethostname() or its moral equivalent. This routine +/* goes to great length to avoid dependencies on any network +/* services. +/* DIAGNOSTICS +/* Fatal errors: no hostname, invalid hostname. +/* SEE ALSO +/* valid_hostname(3) +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/param.h> +#include <string.h> +#include <unistd.h> + +#if (MAXHOSTNAMELEN < 256) +#undef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 256 +#endif + +/* Utility library. */ + +#include "mymalloc.h" +#include "msg.h" +#include "valid_hostname.h" +#include "get_hostname.h" + +/* Local stuff. */ + +static char *my_host_name; + +/* get_hostname - look up my host name */ + +const char *get_hostname(void) +{ + char namebuf[MAXHOSTNAMELEN + 1]; + + /* + * The gethostname() call is not (or not yet) in ANSI or POSIX, but it is + * part of the socket interface library. We avoid the more politically- + * correct uname() routine because that has no portable way of dealing + * with long (FQDN) hostnames. + * + * DO NOT CALL GETHOSTBYNAME FROM THIS FUNCTION. IT BREAKS MAILDIR DELIVERY + * AND OTHER THINGS WHEN THE MACHINE NAME IS NOT FOUND IN /ETC/HOSTS OR + * CAUSES PROCESSES TO HANG WHEN THE NETWORK IS DISCONNECTED. + * + * POSTFIX NO LONGER NEEDS A FULLY QUALIFIED HOSTNAME. INSTEAD POSTFIX WILL + * USE A DEFAULT DOMAIN NAME "LOCALDOMAIN". + */ + if (my_host_name == 0) { + /* DO NOT CALL GETHOSTBYNAME FROM THIS FUNCTION */ + if (gethostname(namebuf, sizeof(namebuf)) < 0) + msg_fatal("gethostname: %m"); + namebuf[MAXHOSTNAMELEN] = 0; + /* DO NOT CALL GETHOSTBYNAME FROM THIS FUNCTION */ + if (valid_hostname(namebuf, DO_GRIPE) == 0) + msg_fatal("unable to use my own hostname"); + /* DO NOT CALL GETHOSTBYNAME FROM THIS FUNCTION */ + my_host_name = mystrdup(namebuf); + } + return (my_host_name); +} diff --git a/src/util/get_hostname.h b/src/util/get_hostname.h new file mode 100644 index 0000000..32d2ab2 --- /dev/null +++ b/src/util/get_hostname.h @@ -0,0 +1,29 @@ +#ifndef _GET_HOSTNAME_H_INCLUDED_ +#define _GET_HOSTNAME_H_INCLUDED_ + +/*++ +/* NAME +/* get_hostname 3h +/* SUMMARY +/* network name lookup +/* SYNOPSIS +/* #include <get_hostname.h> +/* DESCRIPTION +/* .nf + + /* External interface */ + +extern const char *get_hostname(void); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/hash_fnv.c b/src/util/hash_fnv.c new file mode 100644 index 0000000..10e97f0 --- /dev/null +++ b/src/util/hash_fnv.c @@ -0,0 +1,107 @@ +/*++ +/* NAME +/* hash_fnv 3 +/* SUMMARY +/* Fowler/Noll/Vo hash function +/* SYNOPSIS +/* #include <hash_fnv.h> +/* +/* HASH_FNV_T hash_fnv( +/* const void *src, +/* size_t len) +/* DESCRIPTION +/* hash_fnv() implements a modified FNV type 1a hash function. +/* +/* To thwart collision attacks, the hash function is seeded +/* once from /dev/urandom, and if that is unavailable, from +/* wallclock time, monotonic system clocks, and the process +/* ID. To disable seeding (typically, for regression tests), +/* specify the NORANDOMIZE environment variable; the value +/* does not matter. +/* +/* This function implements a workaround for a "sticky state" +/* problem with FNV hash functions: when an input produces a +/* zero intermediate hash state, and the next input byte is +/* zero, then the operations "hash ^= 0" and "hash *= FNV_prime" +/* would not change the hash value. To avoid this, hash_fnv() +/* adds 1 to each input byte. Compile with -DSTRICT_FNV1A to +/* get the standard behavior. +/* +/* The default HASH_FNV_T result type is uint64_t. When compiled +/* with -DUSE_FNV_32BIT, the result type is uint32_t. On ancient +/* systems without <stdint.h>, define HASH_FNV_T on the compiler +/* command line as an unsigned 32-bit or 64-bit integer type, +/* and specify -DUSE_FNV_32BIT when HASH_FNV_T is a 32-bit type. +/* SEE ALSO +/* http://www.isthe.com/chongo/tech/comp/fnv/index.html +/* https://softwareengineering.stackexchange.com/questions/49550/ +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library + */ +#include <sys_defs.h> +#include <stdlib.h> +#include <unistd.h> + + /* + * Utility library. + */ +#include <msg.h> +#include <ldseed.h> +#include <hash_fnv.h> + + /* + * Application-specific. + */ +#ifdef USE_FNV_32BIT +#define FNV_prime 0x01000193UL +#define FNV_offset_basis 0x811c9dc5UL +#else +#define FNV_prime 0x00000100000001B3ULL +#define FNV_offset_basis 0xcbf29ce484222325ULL +#endif + +/* hash_fnv - modified FNV 1a hash */ + +HASH_FNV_T hash_fnv(const void *src, size_t len) +{ + static HASH_FNV_T basis = FNV_offset_basis; + static int randomize = 1; + HASH_FNV_T hash; + + /* + * Initialize. + */ + if (randomize) { + if (!getenv("NORANDOMIZE")) { + HASH_FNV_T seed; + + ldseed(&seed, sizeof(seed)); + basis ^= seed; + } + randomize = 0; + } + +#ifdef STRICT_FNV1A +#define FNV_NEXT_BYTE(s) ((HASH_FNV_T) * (const unsigned char *) s++) +#else +#define FNV_NEXT_BYTE(s) (1 + (HASH_FNV_T) * (const unsigned char *) s++) +#endif + + hash = basis; + while (len-- > 0) { + hash ^= FNV_NEXT_BYTE(src); + hash *= FNV_prime; + } + return (hash); +} diff --git a/src/util/hash_fnv.h b/src/util/hash_fnv.h new file mode 100644 index 0000000..dbbb383 --- /dev/null +++ b/src/util/hash_fnv.h @@ -0,0 +1,39 @@ +#ifndef _HASH_FNV_H_INCLUDED_ +#define _HASH_FNV_H_INCLUDED_ + +/*++ +/* NAME +/* hash_fnv 3h +/* SUMMARY +/* Fowler/Noll/Vo hash function +/* SYNOPSIS +/* #include <hash_fnv.h> +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +#ifndef HASH_FNV_T +#include <stdint.h> +#ifdef USE_FNV_32BIT +#define HASH_FNV_T uint32_t +#else /* USE_FNV_32BIT */ +#define HASH_FNV_T uint64_t +#endif /* USE_FNV_32BIT */ +#endif /* HASH_FNV_T */ + +extern HASH_FNV_T hash_fnv(const void *, size_t); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/hex_code.c b/src/util/hex_code.c new file mode 100644 index 0000000..3dfcb98 --- /dev/null +++ b/src/util/hex_code.c @@ -0,0 +1,243 @@ +/*++ +/* NAME +/* hex_code 3 +/* SUMMARY +/* encode/decode data, hexadecimal style +/* SYNOPSIS +/* #include <hex_code.h> +/* +/* VSTRING *hex_encode(result, in, len) +/* VSTRING *result; +/* const char *in; +/* ssize_t len; +/* +/* VSTRING *hex_decode(result, in, len) +/* VSTRING *result; +/* const char *in; +/* ssize_t len; +/* +/* VSTRING *hex_encode_opt(result, in, len, flags) +/* VSTRING *result; +/* const char *in; +/* ssize_t len; +/* int flags; +/* +/* VSTRING *hex_decode_opt(result, in, len, flags) +/* VSTRING *result; +/* const char *in; +/* ssize_t len; +/* int flags; +/* DESCRIPTION +/* hex_encode() takes a block of len bytes and encodes it as one +/* upper-case null-terminated string. The result value is +/* the result argument. +/* +/* hex_decode() performs the opposite transformation on +/* lower-case, upper-case or mixed-case input. The result +/* value is the result argument. The result is null terminated, +/* whether or not that makes sense. +/* +/* hex_encode_opt() enables extended functionality as controlled +/* with \fIflags\fR. +/* .IP HEX_ENCODE_FLAG_NONE +/* The default: a self-documenting flag that enables no +/* functionality. +/* .IP HEX_ENCODE_FLAG_USE_COLON +/* Inserts one ":" between bytes. +/* .PP +/* hex_decode_opt() enables extended functionality as controlled +/* with \fIflags\fR. +/* .IP HEX_DECODE_FLAG_NONE +/* The default: a self-documenting flag that enables no +/* functionality. +/* .IP HEX_DECODE_FLAG_ALLOW_COLON +/* Allows, but does not require, one ":" between bytes. +/* DIAGNOSTICS +/* hex_decode() returns a null pointer when the input contains +/* characters not in the hexadecimal alphabet. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <ctype.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstring.h> +#include <hex_code.h> + +/* Application-specific. */ + +static const unsigned char hex_chars[] = "0123456789ABCDEF"; + +#define UCHAR_PTR(x) ((const unsigned char *)(x)) + +/* hex_encode - ABI compatibility */ + +#undef hex_encode + +VSTRING *hex_encode(VSTRING *result, const char *in, ssize_t len) +{ + return (hex_encode_opt(result, in, len, HEX_ENCODE_FLAG_NONE)); +} + +/* hex_encode_opt - raw data to encoded */ + +VSTRING *hex_encode_opt(VSTRING *result, const char *in, ssize_t len, int flags) +{ + const unsigned char *cp; + int ch; + ssize_t count; + + VSTRING_RESET(result); + for (cp = UCHAR_PTR(in), count = len; count > 0; count--, cp++) { + ch = *cp; + VSTRING_ADDCH(result, hex_chars[(ch >> 4) & 0xf]); + VSTRING_ADDCH(result, hex_chars[ch & 0xf]); + if ((flags & HEX_ENCODE_FLAG_USE_COLON) && count > 1) + VSTRING_ADDCH(result, ':'); + } + VSTRING_TERMINATE(result); + return (result); +} + +/* hex_decode - ABI compatibility wrapper */ + +#undef hex_decode + +VSTRING *hex_decode(VSTRING *result, const char *in, ssize_t len) +{ + return (hex_decode_opt(result, in, len, HEX_DECODE_FLAG_NONE)); +} + +/* hex_decode_opt - encoded data to raw */ + +VSTRING *hex_decode_opt(VSTRING *result, const char *in, ssize_t len, int flags) +{ + const unsigned char *cp; + ssize_t count; + unsigned int hex; + unsigned int bin; + + VSTRING_RESET(result); + for (cp = UCHAR_PTR(in), count = len; count > 0; cp += 2, count -= 2) { + if (count < 2) + return (0); + hex = cp[0]; + if (hex >= '0' && hex <= '9') + bin = (hex - '0') << 4; + else if (hex >= 'A' && hex <= 'F') + bin = (hex - 'A' + 10) << 4; + else if (hex >= 'a' && hex <= 'f') + bin = (hex - 'a' + 10) << 4; + else + return (0); + hex = cp[1]; + if (hex >= '0' && hex <= '9') + bin |= (hex - '0'); + else if (hex >= 'A' && hex <= 'F') + bin |= (hex - 'A' + 10); + else if (hex >= 'a' && hex <= 'f') + bin |= (hex - 'a' + 10); + else + return (0); + VSTRING_ADDCH(result, bin); + + /* + * Support *colon-separated* input (no leading or trailing colons). + * After decoding "xx", skip a possible ':' preceding "yy" in + * "xx:yy". + */ + if ((flags & HEX_DECODE_FLAG_ALLOW_COLON) + && count > 4 && cp[2] == ':') { + ++cp; + --count; + } + } + VSTRING_TERMINATE(result); + return (result); +} + +#ifdef TEST +#include <argv.h> + + /* + * Proof-of-concept test program: convert to hexadecimal and back. + */ + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +int main(int unused_argc, char **unused_argv) +{ + VSTRING *b1 = vstring_alloc(1); + VSTRING *b2 = vstring_alloc(1); + char *test = "this is a test"; + ARGV *argv; + +#define DECODE(b,x,l) { \ + if (hex_decode((b),(x),(l)) == 0) \ + msg_panic("bad hex: %s", (x)); \ + } +#define VERIFY(b,t) { \ + if (strcmp((b), (t)) != 0) \ + msg_panic("bad test: %s", (b)); \ + } + + hex_encode(b1, test, strlen(test)); + DECODE(b2, STR(b1), LEN(b1)); + VERIFY(STR(b2), test); + + hex_encode(b1, test, strlen(test)); + hex_encode(b2, STR(b1), LEN(b1)); + hex_encode(b1, STR(b2), LEN(b2)); + DECODE(b2, STR(b1), LEN(b1)); + DECODE(b1, STR(b2), LEN(b2)); + DECODE(b2, STR(b1), LEN(b1)); + VERIFY(STR(b2), test); + + hex_encode(b1, test, strlen(test)); + hex_encode(b2, STR(b1), LEN(b1)); + hex_encode(b1, STR(b2), LEN(b2)); + hex_encode(b2, STR(b1), LEN(b1)); + hex_encode(b1, STR(b2), LEN(b2)); + DECODE(b2, STR(b1), LEN(b1)); + DECODE(b1, STR(b2), LEN(b2)); + DECODE(b2, STR(b1), LEN(b1)); + DECODE(b1, STR(b2), LEN(b2)); + DECODE(b2, STR(b1), LEN(b1)); + VERIFY(STR(b2), test); + + hex_encode_opt(b1, test, strlen(test), HEX_ENCODE_FLAG_USE_COLON); + argv = argv_split(STR(b1), ":"); + if (argv->argc != strlen(test)) + msg_panic("HEX_ENCODE_FLAG_USE_COLON"); + if (hex_decode_opt(b2, STR(b1), LEN(b1), HEX_DECODE_FLAG_ALLOW_COLON) == 0) + msg_panic("HEX_DECODE_FLAG_ALLOW_COLON"); + VERIFY(STR(b2), test); + argv_free(argv); + + vstring_free(b1); + vstring_free(b2); + return (0); +} + +#endif diff --git a/src/util/hex_code.h b/src/util/hex_code.h new file mode 100644 index 0000000..720977a --- /dev/null +++ b/src/util/hex_code.h @@ -0,0 +1,54 @@ +#ifndef _HEX_CODE_H_INCLUDED_ +#define _HEX_CODE_H_INCLUDED_ + +/*++ +/* NAME +/* hex_code 3h +/* SUMMARY +/* encode/decode data, hexadecimal style +/* SYNOPSIS +/* #include <hex_code.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <vstring.h> + + /* + * External interface. + */ +#define HEX_ENCODE_FLAG_NONE (0) +#define HEX_ENCODE_FLAG_USE_COLON (1<<0) + +#define HEX_DECODE_FLAG_NONE (0) +#define HEX_DECODE_FLAG_ALLOW_COLON (1<<0) + +extern VSTRING *hex_encode(VSTRING *, const char *, ssize_t); +extern VSTRING *WARN_UNUSED_RESULT hex_decode(VSTRING *, const char *, ssize_t); +extern VSTRING *hex_encode_opt(VSTRING *, const char *, ssize_t, int); +extern VSTRING *WARN_UNUSED_RESULT hex_decode_opt(VSTRING *, const char *, ssize_t, int); + +#define hex_encode(res, in, len) \ + hex_encode_opt((res), (in), (len), HEX_ENCODE_FLAG_NONE) +#define hex_decode(res, in, len) \ + hex_decode_opt((res), (in), (len), HEX_DECODE_FLAG_NONE) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/hex_quote.c b/src/util/hex_quote.c new file mode 100644 index 0000000..7089385 --- /dev/null +++ b/src/util/hex_quote.c @@ -0,0 +1,153 @@ +/*++ +/* NAME +/* hex_quote 3 +/* SUMMARY +/* quote/unquote text, HTTP style. +/* SYNOPSIS +/* #include <hex_quote.h> +/* +/* VSTRING *hex_quote(hex, raw) +/* VSTRING *hex; +/* const char *raw; +/* +/* VSTRING *hex_unquote(raw, hex) +/* VSTRING *raw; +/* const char *hex; +/* DESCRIPTION +/* hex_quote() takes a null-terminated string and replaces non-printable +/* and whitespace characters and the % by %XX, XX being the two-digit +/* hexadecimal equivalent. +/* The hexadecimal codes are produced as upper-case characters. The result +/* value is the hex argument. +/* +/* hex_unquote() performs the opposite transformation. This function +/* understands lowercase, uppercase, and mixed case %XX sequences. The +/* result value is the raw argument in case of success, a null pointer +/* otherwise. +/* BUGS +/* hex_quote() cannot process null characters in data. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <ctype.h> + +/* Utility library. */ + +#include "msg.h" +#include "vstring.h" +#include "hex_quote.h" + +/* Application-specific. */ + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* hex_quote - raw data to quoted */ + +VSTRING *hex_quote(VSTRING *hex, const char *raw) +{ + const char *cp; + int ch; + + VSTRING_RESET(hex); + for (cp = raw; (ch = *(unsigned const char *) cp) != 0; cp++) { + if (ch != '%' && !ISSPACE(ch) && ISPRINT(ch)) { + VSTRING_ADDCH(hex, ch); + } else { + vstring_sprintf_append(hex, "%%%02X", ch); + } + } + VSTRING_TERMINATE(hex); + return (hex); +} + +/* hex_unquote - quoted data to raw */ + +VSTRING *hex_unquote(VSTRING *raw, const char *hex) +{ + const char *cp; + int ch; + + VSTRING_RESET(raw); + for (cp = hex; (ch = *cp) != 0; cp++) { + if (ch == '%') { + if (ISDIGIT(cp[1])) + ch = (cp[1] - '0') << 4; + else if (cp[1] >= 'a' && cp[1] <= 'f') + ch = (cp[1] - 'a' + 10) << 4; + else if (cp[1] >= 'A' && cp[1] <= 'F') + ch = (cp[1] - 'A' + 10) << 4; + else + return (0); + if (ISDIGIT(cp[2])) + ch |= (cp[2] - '0'); + else if (cp[2] >= 'a' && cp[2] <= 'f') + ch |= (cp[2] - 'a' + 10); + else if (cp[2] >= 'A' && cp[2] <= 'F') + ch |= (cp[2] - 'A' + 10); + else + return (0); + cp += 2; + } + VSTRING_ADDCH(raw, ch); + } + VSTRING_TERMINATE(raw); + return (raw); +} + +#ifdef TEST + + /* + * Proof-of-concept test program: convert to hex and back. + */ +#include <vstream.h> + +#define BUFLEN 1024 + +static ssize_t read_buf(VSTREAM *fp, VSTRING *buf) +{ + ssize_t len; + + len = vstream_fread_buf(fp, buf, BUFLEN); + VSTRING_TERMINATE(buf); + return (len); +} + +int main(int unused_argc, char **unused_argv) +{ + VSTRING *raw = vstring_alloc(BUFLEN); + VSTRING *hex = vstring_alloc(100); + ssize_t len; + + while ((len = read_buf(VSTREAM_IN, raw)) > 0) { + hex_quote(hex, STR(raw)); + if (hex_unquote(raw, STR(hex)) == 0) + msg_fatal("bad input: %.100s", STR(hex)); + if (LEN(raw) != len) + msg_fatal("len %ld != raw len %ld", (long) len, (long) LEN(raw)); + if (vstream_fwrite(VSTREAM_OUT, STR(raw), LEN(raw)) != LEN(raw)) + msg_fatal("write error: %m"); + } + vstream_fflush(VSTREAM_OUT); + vstring_free(raw); + vstring_free(hex); + return (0); +} + +#endif diff --git a/src/util/hex_quote.h b/src/util/hex_quote.h new file mode 100644 index 0000000..d72ec57 --- /dev/null +++ b/src/util/hex_quote.h @@ -0,0 +1,36 @@ +#ifndef _HEX_QUOTE_H_INCLUDED_ +#define _HEX_QUOTE_H_INCLUDED_ + +/*++ +/* NAME +/* hex_quote 3h +/* SUMMARY +/* quote/unquote text, HTTP style. +/* SYNOPSIS +/* #include <hex_quote.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <vstring.h> + + /* + * External interface. + */ +extern VSTRING *hex_quote(VSTRING *, const char *); +extern VSTRING *hex_unquote(VSTRING *, const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/host_port.c b/src/util/host_port.c new file mode 100644 index 0000000..c4e8616 --- /dev/null +++ b/src/util/host_port.c @@ -0,0 +1,203 @@ +/*++ +/* NAME +/* host_port 3 +/* SUMMARY +/* split string into host and port, destroy string +/* SYNOPSIS +/* #include <host_port.h> +/* +/* const char *host_port(string, host, def_host, port, def_service) +/* char *string; +/* char **host; +/* char *def_host; +/* char **port; +/* char *def_service; +/* DESCRIPTION +/* host_port() splits a string into substrings with the host +/* name or address, and the service name or port number. +/* The input string is modified. +/* +/* Host/domain names are validated with valid_utf8_hostname(), +/* and host addresses are validated with valid_hostaddr(). +/* +/* The following input formats are understood (null means +/* a null pointer argument): +/* +/* When def_service is not null, and def_host is null: +/* +/* [host]:port, [host]:, [host] +/* +/* host:port, host:, host +/* +/* When def_host is not null, and def_service is null: +/* +/* :port, port +/* +/* Other combinations of def_service and def_host are +/* not supported and produce undefined results. +/* DIAGNOSTICS +/* The result is a null pointer in case of success. +/* In case of problems the result is a string pointer with +/* the problem type. +/* CLIENT EXAMPLE +/* .ad +/* .fi +/* Typical client usage allows the user to omit the service port, +/* in which case the client connects to a pre-determined default +/* port: +/* .nf +/* .na +/* +/* buf = mystrdup(endpoint); +/* if ((parse_error = host_port(buf, &host, NULL, &port, defport)) != 0) +/* msg_fatal("%s in \"%s\"", parse_error, endpoint); +/* if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0) +/* msg_fatal("%s: %s", endpoint, MAI_STRERROR(aierr)); +/* myfree(buf); +/* SERVER EXAMPLE +/* .ad +/* .fi +/* Typical server usage allows the user to omit the host, meaning +/* listen on all available network addresses: +/* .nf +/* .na +/* +/* buf = mystrdup(endpoint); +/* if ((parse_error = host_port(buf, &host, "", &port, NULL)) != 0) +/* msg_fatal("%s in \"%s\"", parse_error, endpoint); +/* if (*host == 0) +/* host = 0; +/* if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0) +/* msg_fatal("%s: %s", endpoint, MAI_STRERROR(aierr)); +/* myfree(buf); +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> +#include <ctype.h> + +/* Utility library. */ + +#include <msg.h> +#include <split_at.h> +#include <stringops.h> /* XXX util_utf8_enable */ +#include <valid_utf8_hostname.h> + +/* Global library. */ + +#include <host_port.h> + + /* + * Point-fix workaround. The libutil library should be email agnostic, but + * we can't rip up the library APIs in the stable releases. + */ +#include <string.h> +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif +#define IPV6_COL "IPv6:" /* RFC 2821 */ +#define IPV6_COL_LEN (sizeof(IPV6_COL) - 1) +#define HAS_IPV6_COL(str) (strncasecmp((str), IPV6_COL, IPV6_COL_LEN) == 0) + +/* host_port - parse string into host and port, destroy string */ + +const char *host_port(char *buf, char **host, char *def_host, + char **port, char *def_service) +{ + char *cp = buf; + int ipv6 = 0; + + /*- + * [host]:port, [host]:, [host]. + * [ipv6:ipv6addr]:port, [ipv6:ipv6addr]:, [ipv6:ipv6addr]. + */ + if (*cp == '[') { + ++cp; + if ((ipv6 = HAS_IPV6_COL(cp)) != 0) + cp += IPV6_COL_LEN; + *host = cp; + if ((cp = split_at(cp, ']')) == 0) + return ("missing \"]\""); + if (*cp && *cp++ != ':') + return ("garbage after \"]\""); + if (ipv6 && !valid_ipv6_hostaddr(*host, DONT_GRIPE)) + return ("malformed IPv6 address"); + *port = *cp ? cp : def_service; + } + + /* + * host:port, host:, host, :port, port. + */ + else { + if ((cp = split_at_right(buf, ':')) != 0) { + *host = *buf ? buf : def_host; + *port = *cp ? cp : def_service; + } else { + *host = def_host ? def_host : (*buf ? buf : 0); + *port = def_service ? def_service : (*buf ? buf : 0); + } + } + if (*host == 0) + return ("missing host information"); + if (*port == 0) + return ("missing service information"); + + /* + * Final sanity checks. We're still sloppy, allowing bare numerical + * network addresses instead of requiring proper [ipaddress] forms. + */ + if (*host != def_host + && !valid_utf8_hostname(util_utf8_enable, *host, DONT_GRIPE) + && !valid_hostaddr(*host, DONT_GRIPE)) + return ("valid hostname or network address required"); + if (*port != def_service && ISDIGIT(**port) && !alldig(*port)) + return ("garbage after numerical service"); + return (0); +} + +#ifdef TEST + +#include <vstream.h> +#include <vstring.h> +#include <vstring_vstream.h> + +#define STR(x) vstring_str(x) + +int main(int unused_argc, char **unused_argv) +{ + VSTRING *in_buf = vstring_alloc(10); + VSTRING *parse_buf = vstring_alloc(10); + char *host; + char *port; + const char *err; + + while (vstring_fgets_nonl(in_buf, VSTREAM_IN)) { + vstream_printf(">> %s\n", STR(in_buf)); + vstream_fflush(VSTREAM_OUT); + if (*STR(in_buf) == '#') + continue; + vstring_strcpy(parse_buf, STR(in_buf)); + if ((err = host_port(STR(parse_buf), &host, (char *) 0, &port, "default-service")) != 0) { + msg_warn("%s in %s", err, STR(in_buf)); + } else { + vstream_printf("host %s port %s\n", host, port); + vstream_fflush(VSTREAM_OUT); + } + } + vstring_free(in_buf); + vstring_free(parse_buf); + return (0); +} + +#endif diff --git a/src/util/host_port.h b/src/util/host_port.h new file mode 100644 index 0000000..f2fecbb --- /dev/null +++ b/src/util/host_port.h @@ -0,0 +1,35 @@ +#ifndef _HOST_PORT_H_INCLUDED_ +#define _HOST_PORT_H_INCLUDED_ + +/*++ +/* NAME +/* host_port 3h +/* SUMMARY +/* split string into host and port, destroy string +/* SYNOPSIS +/* #include <host_port.h> +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern const char *WARN_UNUSED_RESULT host_port(char *, char **, char *, + char **, char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/host_port.in b/src/util/host_port.in new file mode 100644 index 0000000..1608222 --- /dev/null +++ b/src/util/host_port.in @@ -0,0 +1,16 @@ +hhh:ppp +hhh: +hhh +[hhh]:ppp +[hhh]: +[hhh] +#[hhh:ppp] +#[hhh:] +hhh:1pp +[hh.] +hh. +999 +[::1] +[ipv6:::1] +[ipv6:127.0.0.1] +[ipv6:example.com] diff --git a/src/util/host_port.ref b/src/util/host_port.ref new file mode 100644 index 0000000..1d79745 --- /dev/null +++ b/src/util/host_port.ref @@ -0,0 +1,30 @@ +>> hhh:ppp +host hhh port ppp +>> hhh: +host hhh port default-service +>> hhh +host hhh port default-service +>> [hhh]:ppp +host hhh port ppp +>> [hhh]: +host hhh port default-service +>> [hhh] +host hhh port default-service +>> #[hhh:ppp] +>> #[hhh:] +>> hhh:1pp +unknown: warning: garbage after numerical service in hhh:1pp +>> [hh.] +unknown: warning: valid hostname or network address required in [hh.] +>> hh. +unknown: warning: valid hostname or network address required in hh. +>> 999 +unknown: warning: valid hostname or network address required in 999 +>> [::1] +host ::1 port default-service +>> [ipv6:::1] +host ::1 port default-service +>> [ipv6:127.0.0.1] +unknown: warning: malformed IPv6 address in [ipv6:127.0.0.1] +>> [ipv6:example.com] +unknown: warning: malformed IPv6 address in [ipv6:example.com] diff --git a/src/util/htable.c b/src/util/htable.c new file mode 100644 index 0000000..1c08e97 --- /dev/null +++ b/src/util/htable.c @@ -0,0 +1,439 @@ +/*++ +/* NAME +/* htable 3 +/* SUMMARY +/* hash table manager +/* SYNOPSIS +/* #include <htable.h> +/* +/* typedef struct { +/* .in +4 +/* char *key; +/* void *value; +/* /* private fields... */ +/* .in -4 +/* } HTABLE_INFO; +/* +/* HTABLE *htable_create(size) +/* int size; +/* +/* HTABLE_INFO *htable_enter(table, key, value) +/* HTABLE *table; +/* const char *key; +/* void *value; +/* +/* char *htable_find(table, key) +/* HTABLE *table; +/* const char *key; +/* +/* HTABLE_INFO *htable_locate(table, key) +/* HTABLE *table; +/* const char *key; +/* +/* void htable_delete(table, key, free_fn) +/* HTABLE *table; +/* const char *key; +/* void (*free_fn)(void *); +/* +/* void htable_free(table, free_fn) +/* HTABLE *table; +/* void (*free_fn)(void *); +/* +/* void htable_walk(table, action, ptr) +/* HTABLE *table; +/* void (*action)(HTABLE_INFO *, void *ptr); +/* void *ptr; +/* +/* HTABLE_INFO **htable_list(table) +/* HTABLE *table; +/* +/* HTABLE_INFO *htable_sequence(table, how) +/* HTABLE *table; +/* int how; +/* DESCRIPTION +/* This module maintains one or more hash tables. Each table entry +/* consists of a unique string-valued lookup key and a generic +/* character-pointer value. +/* The tables are automatically resized when they fill up. When the +/* values to be remembered are not character pointers, proper casts +/* should be used or the code will not be portable. +/* +/* htable_create() creates a table of the specified size and returns a +/* pointer to the result. The lookup keys are saved with mystrdup(). +/* htable_enter() stores a (key, value) pair into the specified table +/* and returns a pointer to the resulting entry. The code does not +/* check if an entry with that key already exists: use htable_locate() +/* for updating an existing entry. +/* +/* htable_find() returns the value that was stored under the given key, +/* or a null pointer if it was not found. In order to distinguish +/* a null value from a non-existent value, use htable_locate(). +/* +/* htable_locate() returns a pointer to the entry that was stored +/* for the given key, or a null pointer if it was not found. +/* +/* htable_delete() removes one entry that was stored under the given key. +/* If the free_fn argument is not a null pointer, the corresponding +/* function is called with as argument the non-zero value stored under +/* the key. +/* +/* htable_free() destroys a hash table, including contents. If the free_fn +/* argument is not a null pointer, the corresponding function is called +/* for each table entry, with as argument the non-zero value stored +/* with the entry. +/* +/* htable_walk() invokes the action function for each table entry, with +/* a pointer to the entry as its argument. The ptr argument is passed +/* on to the action function. +/* +/* htable_list() returns a null-terminated list of pointers to +/* all elements in the named table. The list should be passed to +/* myfree(). +/* +/* htable_sequence() returns the first or next element depending +/* on the value of the "how" argument. Specify HTABLE_SEQ_FIRST +/* to start a new sequence, HTABLE_SEQ_NEXT to continue, and +/* HTABLE_SEQ_STOP to terminate a sequence early. The caller +/* must not delete an element before it is visited. +/* RESTRICTIONS +/* A callback function should not modify the hash table that is +/* specified to its caller. +/* DIAGNOSTICS +/* The following conditions are reported and cause the program to +/* terminate immediately: memory allocation failure; an attempt +/* to delete a non-existent entry. +/* SEE ALSO +/* mymalloc(3) memory management wrapper +/* hash_fnv(3) Fowler/Noll/Vo hash function +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* C library */ + +#include <sys_defs.h> +#include <string.h> + +/* Local stuff */ + +#include "mymalloc.h" +#include "msg.h" +#include "htable.h" + +/* htable_hash - hash a string */ + +#ifndef NO_HASH_FNV +#include "hash_fnv.h" + +#define htable_hash(s, size) (hash_fnv((s), strlen(s)) % (size)) + +#else + +static size_t htable_hash(const char *s, size_t size) +{ + size_t h = 0; + size_t g; + + /* + * From the "Dragon" book by Aho, Sethi and Ullman. + */ + + while (*s) { + h = (h << 4U) + *(unsigned const char *) s++; + if ((g = (h & 0xf0000000)) != 0) { + h ^= (g >> 24U); + h ^= g; + } + } + return (h % size); +} + +#endif + +/* htable_link - insert element into table */ + +#define htable_link(table, element) { \ + HTABLE_INFO **_h = table->data + htable_hash(element->key, table->size);\ + element->prev = 0; \ + if ((element->next = *_h) != 0) \ + (*_h)->prev = element; \ + *_h = element; \ + table->used++; \ +} + +/* htable_size - allocate and initialize hash table */ + +static void htable_size(HTABLE *table, size_t size) +{ + HTABLE_INFO **h; + + size |= 1; + + table->data = h = (HTABLE_INFO **) mymalloc(size * sizeof(HTABLE_INFO *)); + table->size = size; + table->used = 0; + + while (size-- > 0) + *h++ = 0; +} + +/* htable_create - create initial hash table */ + +HTABLE *htable_create(ssize_t size) +{ + HTABLE *table; + + table = (HTABLE *) mymalloc(sizeof(HTABLE)); + htable_size(table, size < 13 ? 13 : size); + table->seq_bucket = table->seq_element = 0; + return (table); +} + +/* htable_grow - extend existing table */ + +static void htable_grow(HTABLE *table) +{ + HTABLE_INFO *ht; + HTABLE_INFO *next; + size_t old_size = table->size; + HTABLE_INFO **h = table->data; + HTABLE_INFO **old_entries = h; + + htable_size(table, 2 * old_size); + + while (old_size-- > 0) { + for (ht = *h++; ht; ht = next) { + next = ht->next; + htable_link(table, ht); + } + } + myfree((void *) old_entries); +} + +/* htable_enter - enter (key, value) pair */ + +HTABLE_INFO *htable_enter(HTABLE *table, const char *key, void *value) +{ + HTABLE_INFO *ht; + + if (table->used >= table->size) + htable_grow(table); + ht = (HTABLE_INFO *) mymalloc(sizeof(HTABLE_INFO)); + ht->key = mystrdup(key); + ht->value = value; + htable_link(table, ht); + return (ht); +} + +/* htable_find - lookup value */ + +void *htable_find(HTABLE *table, const char *key) +{ + HTABLE_INFO *ht; + +#define STREQ(x,y) (x == y || (x[0] == y[0] && strcmp(x,y) == 0)) + + if (table) + for (ht = table->data[htable_hash(key, table->size)]; ht; ht = ht->next) + if (STREQ(key, ht->key)) + return (ht->value); + return (0); +} + +/* htable_locate - lookup entry */ + +HTABLE_INFO *htable_locate(HTABLE *table, const char *key) +{ + HTABLE_INFO *ht; + +#define STREQ(x,y) (x == y || (x[0] == y[0] && strcmp(x,y) == 0)) + + if (table) + for (ht = table->data[htable_hash(key, table->size)]; ht; ht = ht->next) + if (STREQ(key, ht->key)) + return (ht); + return (0); +} + +/* htable_delete - delete one entry */ + +void htable_delete(HTABLE *table, const char *key, void (*free_fn) (void *)) +{ + if (table) { + HTABLE_INFO *ht; + HTABLE_INFO **h = table->data + htable_hash(key, table->size); + +#define STREQ(x,y) (x == y || (x[0] == y[0] && strcmp(x,y) == 0)) + + for (ht = *h; ht; ht = ht->next) { + if (STREQ(key, ht->key)) { + if (ht->next) + ht->next->prev = ht->prev; + if (ht->prev) + ht->prev->next = ht->next; + else + *h = ht->next; + table->used--; + myfree(ht->key); + if (free_fn && ht->value) + (*free_fn) (ht->value); + myfree((void *) ht); + return; + } + } + msg_panic("htable_delete: unknown_key: \"%s\"", key); + } +} + +/* htable_free - destroy hash table */ + +void htable_free(HTABLE *table, void (*free_fn) (void *)) +{ + if (table) { + ssize_t i = table->size; + HTABLE_INFO *ht; + HTABLE_INFO *next; + HTABLE_INFO **h = table->data; + + while (i-- > 0) { + for (ht = *h++; ht; ht = next) { + next = ht->next; + myfree(ht->key); + if (free_fn && ht->value) + (*free_fn) (ht->value); + myfree((void *) ht); + } + } + myfree((void *) table->data); + table->data = 0; + if (table->seq_bucket) + myfree((void *) table->seq_bucket); + table->seq_bucket = 0; + myfree((void *) table); + } +} + +/* htable_walk - iterate over hash table */ + +void htable_walk(HTABLE *table, void (*action) (HTABLE_INFO *, void *), + void *ptr) { + if (table) { + ssize_t i = table->size; + HTABLE_INFO **h = table->data; + HTABLE_INFO *ht; + + while (i-- > 0) + for (ht = *h++; ht; ht = ht->next) + (*action) (ht, ptr); + } +} + +/* htable_list - list all table members */ + +HTABLE_INFO **htable_list(HTABLE *table) +{ + HTABLE_INFO **list; + HTABLE_INFO *member; + ssize_t count = 0; + ssize_t i; + + if (table != 0) { + list = (HTABLE_INFO **) mymalloc(sizeof(*list) * (table->used + 1)); + for (i = 0; i < table->size; i++) + for (member = table->data[i]; member != 0; member = member->next) + list[count++] = member; + } else { + list = (HTABLE_INFO **) mymalloc(sizeof(*list)); + } + list[count] = 0; + return (list); +} + +/* htable_sequence - dict(3) compatibility iterator */ + +HTABLE_INFO *htable_sequence(HTABLE *table, int how) +{ + if (table == 0) + return (0); + + switch (how) { + case HTABLE_SEQ_FIRST: /* start new sequence */ + if (table->seq_bucket) + myfree((void *) table->seq_bucket); + table->seq_bucket = htable_list(table); + table->seq_element = table->seq_bucket; + return (*(table->seq_element)++); + case HTABLE_SEQ_NEXT: /* next element */ + if (table->seq_element && *table->seq_element) + return (*(table->seq_element)++); + /* FALLTHROUGH */ + default: /* terminate sequence */ + if (table->seq_bucket) { + myfree((void *) table->seq_bucket); + table->seq_bucket = table->seq_element = 0; + } + return (0); + } +} + +#ifdef TEST +#include <vstring_vstream.h> +#include <myrand.h> + +int main(int unused_argc, char **unused_argv) +{ + VSTRING *buf = vstring_alloc(10); + ssize_t count = 0; + HTABLE *hash; + HTABLE_INFO **ht_info; + HTABLE_INFO **ht; + HTABLE_INFO *info; + ssize_t i; + ssize_t r; + int op; + + /* + * Load a large number of strings and delete them in a random order. + */ + hash = htable_create(10); + while (vstring_get(buf, VSTREAM_IN) != VSTREAM_EOF) + htable_enter(hash, vstring_str(buf), CAST_INT_TO_VOID_PTR(count++)); + if (count != hash->used) + msg_panic("%ld entries stored, but %lu entries exist", + (long) count, (unsigned long) hash->used); + for (i = 0, op = HTABLE_SEQ_FIRST; htable_sequence(hash, op) != 0; + i++, op = HTABLE_SEQ_NEXT) + /* void */ ; + if (i != hash->used) + msg_panic("%ld entries found, but %lu entries exist", + (long) i, (unsigned long) hash->used); + ht_info = htable_list(hash); + for (i = 0; i < hash->used; i++) { + r = myrand() % hash->used; + info = ht_info[i]; + ht_info[i] = ht_info[r]; + ht_info[r] = info; + } + for (ht = ht_info; *ht; ht++) + htable_delete(hash, ht[0]->key, (void (*) (void *)) 0); + if (hash->used > 0) + msg_panic("%ld entries not deleted", (long) hash->used); + myfree((void *) ht_info); + htable_free(hash, (void (*) (void *)) 0); + vstring_free(buf); + return (0); +} + +#endif diff --git a/src/util/htable.h b/src/util/htable.h new file mode 100644 index 0000000..bba43e8 --- /dev/null +++ b/src/util/htable.h @@ -0,0 +1,70 @@ +#ifndef _HTABLE_H_INCLUDED_ +#define _HTABLE_H_INCLUDED_ + +/*++ +/* NAME +/* htable 3h +/* SUMMARY +/* hash table manager +/* SYNOPSIS +/* #include <htable.h> +/* DESCRIPTION +/* .nf + + /* Structure of one hash table entry. */ + +typedef struct HTABLE_INFO { + char *key; /* lookup key */ + void *value; /* associated value */ + struct HTABLE_INFO *next; /* colliding entry */ + struct HTABLE_INFO *prev; /* colliding entry */ +} HTABLE_INFO; + + /* Structure of one hash table. */ + +typedef struct HTABLE { + ssize_t size; /* length of entries array */ + ssize_t used; /* number of entries in table */ + HTABLE_INFO **data; /* entries array, auto-resized */ + HTABLE_INFO **seq_bucket; /* current sequence hash bucket */ + HTABLE_INFO **seq_element; /* current sequence element */ +} HTABLE; + +extern HTABLE *htable_create(ssize_t); +extern HTABLE_INFO *htable_enter(HTABLE *, const char *, void *); +extern HTABLE_INFO *htable_locate(HTABLE *, const char *); +extern void *htable_find(HTABLE *, const char *); +extern void htable_delete(HTABLE *, const char *, void (*) (void *)); +extern void htable_free(HTABLE *, void (*) (void *)); +extern void htable_walk(HTABLE *, void (*) (HTABLE_INFO *, void *), void *); +extern HTABLE_INFO **htable_list(HTABLE *); +extern HTABLE_INFO *htable_sequence(HTABLE *, int); + +#define HTABLE_SEQ_FIRST 0 +#define HTABLE_SEQ_NEXT 1 +#define HTABLE_SEQ_STOP (-1) + + /* + * Correct only when casting (char *) to (void *). + */ +#define HTABLE_ACTION_FN_CAST(f) ((void *)(HTABLE_INFO *, void *)) (f) +#define HTABLE_FREE_FN_CAST(f) ((void *)(void *)) (f) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* CREATION DATE +/* Fri Feb 14 13:43:19 EST 1997 +/* LAST MODIFICATION +/* %E% %U% +/* VERSION/RELEASE +/* %I% +/*--*/ + +#endif diff --git a/src/util/inet_addr_host.c b/src/util/inet_addr_host.c new file mode 100644 index 0000000..d2c9d84 --- /dev/null +++ b/src/util/inet_addr_host.c @@ -0,0 +1,173 @@ +/*++ +/* NAME +/* inet_addr_host 3 +/* SUMMARY +/* determine all host internet interface addresses +/* SYNOPSIS +/* #include <inet_addr_host.h> +/* +/* int inet_addr_host(addr_list, hostname) +/* INET_ADDR_LIST *addr_list; +/* const char *hostname; +/* DESCRIPTION +/* inet_addr_host() determines all interface addresses of the +/* named host. The host may be specified as a symbolic name, +/* or as a numerical address. An empty host expands as the +/* wild-card address. Address results are appended to +/* the specified address list. The result value is the number +/* of addresses appended to the list. +/* DIAGNOSTICS +/* Fatal errors: out of memory. +/* BUGS +/* This code uses the name service, so it talks to the network, +/* and that may not be desirable. +/* SEE ALSO +/* inet_addr_list(3) address list management +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/socket.h> +#include <netdb.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +/* Utility library. */ + +#include <mymalloc.h> +#include <inet_addr_list.h> +#include <inet_addr_host.h> +#include <myaddrinfo.h> +#include <sock_addr.h> +#include <inet_proto.h> +#include <msg.h> + +/* inet_addr_host - look up address list for host */ + +int inet_addr_host(INET_ADDR_LIST *addr_list, const char *hostname) +{ + const char *myname = "inet_addr_host"; + int sock; + struct addrinfo *res0; + struct addrinfo *res; + int aierr; + ssize_t hostnamelen; + const char *hname; + const char *serv; + int initial_count = addr_list->used; + const INET_PROTO_INFO *proto_info; + + /* + * The use of square brackets around an IPv6 addresses is required, even + * though we don't enforce it as it'd make the code unnecessarily + * complicated. + * + * XXX AIX 5.1 getaddrinfo() does not allow "0" as service, regardless of + * whether or not a host is specified. + */ + if (*hostname == 0) { + hname = 0; + serv = "1"; + } else if (*hostname == '[' + && hostname[(hostnamelen = strlen(hostname)) - 1] == ']') { + hname = mystrndup(hostname + 1, hostnamelen - 2); + serv = 0; + } else { + hname = hostname; + serv = 0; + } + + proto_info = inet_proto_info(); + if ((aierr = hostname_to_sockaddr(hname, serv, SOCK_STREAM, &res0)) == 0) { + for (res = res0; res; res = res->ai_next) { + + /* + * Safety net. + */ + if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) { + msg_info("%s: skipping address family %d for host \"%s\"", + myname, res->ai_family, hostname); + continue; + } + + /* + * On Linux systems it is not unusual for user-land to be out of + * sync with kernel-land. When this is the case we try to be + * helpful and filter out address families that the library + * claims to understand but that are not supported by the kernel. + */ + if ((sock = socket(res->ai_family, SOCK_STREAM, 0)) < 0) { + msg_warn("%s: skipping address family %d: %m", + myname, res->ai_family); + continue; + } + if (close(sock)) + msg_warn("%s: close socket: %m", myname); + + inet_addr_list_append(addr_list, res->ai_addr); + } + freeaddrinfo(res0); + } + if (hname && hname != hostname) + myfree((void *) hname); + + return (addr_list->used - initial_count); +} + +#ifdef TEST + +#include <msg.h> +#include <vstream.h> +#include <msg_vstream.h> +#include <sock_addr.h> + +int main(int argc, char **argv) +{ + INET_ADDR_LIST list; + struct sockaddr_storage *sa; + MAI_HOSTADDR_STR hostaddr; + INET_PROTO_INFO *proto_info; + + msg_vstream_init(argv[0], VSTREAM_ERR); + + if (argc < 3) + msg_fatal("usage: %s protocols hostname...", argv[0]); + + proto_info = inet_proto_init(argv[0], argv[1]); + argv += 1; + + while (--argc && *++argv) { + inet_addr_list_init(&list); + if (inet_addr_host(&list, *argv) == 0) + msg_fatal("not found: %s", *argv); + + for (sa = list.addrs; sa < list.addrs + list.used; sa++) { + SOCKADDR_TO_HOSTADDR(SOCK_ADDR_PTR(sa), SOCK_ADDR_LEN(sa), + &hostaddr, (MAI_SERVPORT_STR *) 0, 0); + vstream_printf("%s\t%s\n", *argv, hostaddr.buf); + } + vstream_fflush(VSTREAM_OUT); + inet_addr_list_free(&list); + } + return (0); +} + +#endif diff --git a/src/util/inet_addr_host.h b/src/util/inet_addr_host.h new file mode 100644 index 0000000..39d150a --- /dev/null +++ b/src/util/inet_addr_host.h @@ -0,0 +1,35 @@ +#ifndef INET_ADDR_HOST_H_INCLUDED_ +#define INET_ADDR_HOST_H_INCLUDED_ + +/*++ +/* NAME +/* inet_addr_host 3h +/* SUMMARY +/* determine all host internet interface addresses +/* SYNOPSIS +/* #include <inet_addr_host.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <inet_addr_list.h> + + /* + * External interface. + */ +extern int inet_addr_host(INET_ADDR_LIST *, const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/inet_addr_list.c b/src/util/inet_addr_list.c new file mode 100644 index 0000000..e579b17 --- /dev/null +++ b/src/util/inet_addr_list.c @@ -0,0 +1,186 @@ +/*++ +/* NAME +/* inet_addr_list 3 +/* SUMMARY +/* internet address list manager +/* SYNOPSIS +/* #include <inet_addr_list.h> +/* +/* void inet_addr_list_init(list) +/* INET_ADDR_LIST *list; +/* +/* void inet_addr_list_append(list,addr) +/* INET_ADDR_LIST *list; +/* struct sockaddr *addr; +/* +/* void inet_addr_list_uniq(list) +/* INET_ADDR_LIST *list; +/* +/* void inet_addr_list_free(list) +/* INET_ADDR_LIST *list; +/* DESCRIPTION +/* This module maintains simple lists of internet addresses. +/* +/* inet_addr_list_init() initializes a user-provided structure +/* so that it can be used by inet_addr_list_append() and by +/* inet_addr_list_free(). +/* +/* inet_addr_list_append() appends the specified address to +/* the specified list, extending the list on the fly. +/* +/* inet_addr_list_uniq() sorts the specified address list and +/* eliminates duplicates. +/* +/* inet_addr_list_free() reclaims memory used for the +/* specified address list. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdlib.h> +#include <netdb.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <myaddrinfo.h> +#include <sock_addr.h> +#include <inet_addr_list.h> + +/* inet_addr_list_init - initialize internet address list */ + +void inet_addr_list_init(INET_ADDR_LIST *list) +{ + int init_size; + + list->used = 0; + list->size = 0; + init_size = 2; + list->addrs = (struct sockaddr_storage *) + mymalloc(sizeof(*list->addrs) * init_size); + list->size = init_size; +} + +/* inet_addr_list_append - append address to internet address list */ + +void inet_addr_list_append(INET_ADDR_LIST *list, + struct sockaddr *addr) +{ + const char *myname = "inet_addr_list_append"; + MAI_HOSTADDR_STR hostaddr; + int new_size; + + if (msg_verbose > 1) { + SOCKADDR_TO_HOSTADDR(addr, SOCK_ADDR_LEN(addr), + &hostaddr, (MAI_SERVPORT_STR *) 0, 0); + msg_info("%s: %s", myname, hostaddr.buf); + } + if (list->used >= list->size) { + new_size = list->size * 2; + list->addrs = (struct sockaddr_storage *) + myrealloc((void *) list->addrs, sizeof(*list->addrs) * new_size); + list->size = new_size; + } + memcpy(list->addrs + list->used++, addr, SOCK_ADDR_LEN(addr)); +} + +/* inet_addr_list_comp - compare addresses */ + +static int inet_addr_list_comp(const void *a, const void *b) +{ + + /* + * In case (struct *) != (void *). + */ + return (sock_addr_cmp_addr(SOCK_ADDR_PTR(a), SOCK_ADDR_PTR(b))); +} + +/* inet_addr_list_uniq - weed out duplicates */ + +void inet_addr_list_uniq(INET_ADDR_LIST *list) +{ + int n; + int m; + + /* + * Put the identical members right next to each other. + */ + qsort((void *) list->addrs, list->used, + sizeof(list->addrs[0]), inet_addr_list_comp); + + /* + * Nuke the duplicates. Postcondition after while loop: m is the largest + * index for which list->addrs[n] == list->addrs[m]. + */ + for (m = n = 0; m < list->used; m++, n++) { + if (m != n) + list->addrs[n] = list->addrs[m]; + while (m + 1 < list->used + && inet_addr_list_comp((void *) &(list->addrs[n]), + (void *) &(list->addrs[m + 1])) == 0) + m += 1; + } + list->used = n; +} + +/* inet_addr_list_free - destroy internet address list */ + +void inet_addr_list_free(INET_ADDR_LIST *list) +{ + myfree((void *) list->addrs); +} + +#ifdef TEST +#include <inet_proto.h> + + /* + * Duplicate elimination needs to be tested. + */ +#include <inet_addr_host.h> + +static void inet_addr_list_print(INET_ADDR_LIST *list) +{ + MAI_HOSTADDR_STR hostaddr; + struct sockaddr_storage *sa; + + for (sa = list->addrs; sa < list->addrs + list->used; sa++) { + SOCKADDR_TO_HOSTADDR(SOCK_ADDR_PTR(sa), SOCK_ADDR_LEN(sa), + &hostaddr, (MAI_SERVPORT_STR *) 0, 0); + msg_info("%s", hostaddr.buf); + } +} + +int main(int argc, char **argv) +{ + INET_ADDR_LIST list; + INET_PROTO_INFO *proto_info; + + proto_info = inet_proto_init(argv[0], INET_PROTO_NAME_ALL); + inet_addr_list_init(&list); + while (--argc && *++argv) + if (inet_addr_host(&list, *argv) == 0) + msg_fatal("host not found: %s", *argv); + msg_info("list before sort/uniq"); + inet_addr_list_print(&list); + inet_addr_list_uniq(&list); + msg_info("list after sort/uniq"); + inet_addr_list_print(&list); + inet_addr_list_free(&list); + return (0); +} + +#endif diff --git a/src/util/inet_addr_list.h b/src/util/inet_addr_list.h new file mode 100644 index 0000000..8a109c1 --- /dev/null +++ b/src/util/inet_addr_list.h @@ -0,0 +1,44 @@ +#ifndef _INET_ADDR_LIST_H_INCLUDED_ +#define _INET_ADDR_LIST_H_INCLUDED_ + +/*++ +/* NAME +/* inet_addr_list 3h +/* SUMMARY +/* internet address list manager +/* SYNOPSIS +/* #include <inet_addr_list.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <myaddrinfo.h> /* generic name/addr API */ + + /* + * External interface. + */ +typedef struct INET_ADDR_LIST { + int used; /* nr of elements in use */ + int size; /* actual list size */ + struct sockaddr_storage *addrs; /* payload */ +} INET_ADDR_LIST; + +extern void inet_addr_list_init(INET_ADDR_LIST *); +extern void inet_addr_list_free(INET_ADDR_LIST *); +extern void inet_addr_list_uniq(INET_ADDR_LIST *); +extern void inet_addr_list_append(INET_ADDR_LIST *, struct sockaddr *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/inet_addr_list.in b/src/util/inet_addr_list.in new file mode 100644 index 0000000..5c72a1b --- /dev/null +++ b/src/util/inet_addr_list.in @@ -0,0 +1,9 @@ +168.100.3.2 +168.100.3.2 +168.100.3.1 +168.100.3.3 +168.100.3.3 +168.100.3.3 +168.100.3.4 +168.100.3.1 +168.100.3.4 diff --git a/src/util/inet_addr_list.ref b/src/util/inet_addr_list.ref new file mode 100644 index 0000000..9fbf48e --- /dev/null +++ b/src/util/inet_addr_list.ref @@ -0,0 +1,15 @@ +unknown: list before sort/uniq +unknown: 168.100.3.2 +unknown: 168.100.3.2 +unknown: 168.100.3.1 +unknown: 168.100.3.3 +unknown: 168.100.3.3 +unknown: 168.100.3.3 +unknown: 168.100.3.4 +unknown: 168.100.3.1 +unknown: 168.100.3.4 +unknown: list after sort/uniq +unknown: 168.100.3.1 +unknown: 168.100.3.2 +unknown: 168.100.3.3 +unknown: 168.100.3.4 diff --git a/src/util/inet_addr_local.c b/src/util/inet_addr_local.c new file mode 100644 index 0000000..e48803a --- /dev/null +++ b/src/util/inet_addr_local.c @@ -0,0 +1,621 @@ +/*++ +/* NAME +/* inet_addr_local 3 +/* SUMMARY +/* determine if IP address is local +/* SYNOPSIS +/* #include <inet_addr_local.h> +/* +/* int inet_addr_local(addr_list, mask_list, addr_family_list) +/* INET_ADDR_LIST *addr_list; +/* INET_ADDR_LIST *mask_list; +/* unsigned *addr_family; +/* DESCRIPTION +/* inet_addr_local() determines all active IP interface addresses +/* of the local system. Any address found is appended to the +/* specified address list. The result value is the number of +/* active interfaces found. +/* +/* The mask_list is either a null pointer, or it is a list that +/* receives the netmasks of the interface addresses that were found. +/* +/* The addr_family_list specifies one or more of AF_INET or AF_INET6. +/* DIAGNOSTICS +/* Fatal errors: out of memory. +/* SEE ALSO +/* inet_addr_list(3) address list management +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Dean C. Strik +/* Department ICT +/* Eindhoven University of Technology +/* P.O. Box 513 +/* 5600 MB Eindhoven, Netherlands +/* E-mail: <dean@ipnet6.org> +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <netinet/in.h> +#include <net/if.h> +#include <sys/ioctl.h> +#include <arpa/inet.h> +#include <unistd.h> +#ifdef USE_SYS_SOCKIO_H +#include <sys/sockio.h> +#endif +#include <errno.h> +#include <string.h> +#ifdef HAS_IPV6 /* Linux only? */ +#include <netdb.h> +#include <stdio.h> +#endif +#ifdef HAVE_GETIFADDRS +#include <ifaddrs.h> +#endif + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstring.h> +#include <inet_addr_list.h> +#include <inet_addr_local.h> +#include <myaddrinfo.h> +#include <sock_addr.h> +#include <mask_addr.h> +#include <hex_code.h> + + /* + * Postfix needs its own interface address information to determine whether + * or not it is an MX host for some destination; without this information, + * mail would loop between MX hosts. Postfix also needs its interface + * addresses to figure out whether or not it is final destination for + * addresses of the form username@[ipaddress]. + * + * Postfix needs its own interface netmask information when no explicit + * mynetworks setting is given in main.cf, and "mynetworks_style = subnet". + * The mynetworks parameter controls, among others, what mail clients are + * allowed to relay mail through Postfix. + * + * Different systems have different ways to find out this information. We will + * therefore use OS dependent methods. An overview: + * + * - Use getifaddrs() when available. This supports both IPv4/IPv6 addresses. + * The implementation however is not present in all major operating systems. + * + * - Use SIOCGLIFCONF when available. This supports both IPv4/IPv6 addresses. + * With SIOCGLIFNETMASK we can obtain the netmask for either address family. + * Again, this is not present in all major operating systems. + * + * - On Linux, glibc's getifaddrs(3) has returned IPv4 information for some + * time, but IPv6 information was not returned until 2.3.3. With older Linux + * versions we get IPv4 interface information with SIOCGIFCONF, and read + * IPv6 address/prefix information from a file in the /proc filesystem. + * + * - On other systems we expect SIOCGIFCONF to return IPv6 addresses. Since + * SIOCGIFNETMASK does not work reliably for IPv6 addresses, we always set + * the prefix length to /128 (host), and expect the user to configure a more + * appropriate mynetworks setting if needed. + * + * XXX: Each lookup method is implemented by its own function, so we duplicate + * some code. In this case, I think this is better than really drowning in + * the #ifdefs... + * + * -- Dean Strik (dcs) + */ + +#ifndef HAVE_GETIFADDRS + +/* ial_socket - make socket for ioctl() operations */ + +static int ial_socket(int af) +{ + const char *myname = "inet_addr_local[socket]"; + int sock; + + /* + * The host may not be actually configured with IPv6. When IPv6 support + * is not actually in the kernel, don't consider failure to create an + * IPv6 socket as fatal. This could be tuned better though. For other + * families, the error is fatal. + * + * XXX Now that Postfix controls protocol support centrally with the + * inet_proto(3) module, this workaround should no longer be needed. + */ + if ((sock = socket(af, SOCK_DGRAM, 0)) < 0) { +#ifdef HAS_IPV6 + if (af == AF_INET6) { + if (msg_verbose) + msg_warn("%s: socket: %m", myname); + return (-1); + } +#endif + msg_fatal("%s: socket: %m", myname); + } + return (sock); +} + +#endif + +#ifdef HAVE_GETIFADDRS + +/* + * The getifaddrs(3) function, introduced by BSD/OS, provides a + * platform-independent way of requesting interface addresses, + * including IPv6 addresses. The implementation however is not + * present in all major operating systems. + */ + +/* ial_getifaddrs - determine IP addresses using getifaddrs(3) */ + +static int ial_getifaddrs(INET_ADDR_LIST *addr_list, + INET_ADDR_LIST *mask_list, + int af) +{ + const char *myname = "inet_addr_local[getifaddrs]"; + struct ifaddrs *ifap, *ifa; + struct sockaddr *sa, *sam; + + if (getifaddrs(&ifap) < 0) + msg_fatal("%s: getifaddrs: %m", myname); + + /* + * Get the address of each IP network interface. According to BIND we + * must include interfaces that are down because the machine may still + * receive packets for that address (yes, via some other interface). + * Having no way to verify this claim on every machine, I will give them + * the benefit of the doubt. + * + * FIX 200501: The IPv6 patch did not report NetBSD loopback interfaces; + * fixed by replacing IFF_RUNNING by IFF_UP. + * + * FIX 200501: The IPV6 patch did not skip wild-card interface addresses + * (tested on FreeBSD). + */ + for (ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (!(ifa->ifa_flags & IFF_UP) || ifa->ifa_addr == 0) + continue; + sa = ifa->ifa_addr; + if (af != AF_UNSPEC && sa->sa_family != af) + continue; + sam = ifa->ifa_netmask; + if (sam == 0) { + /* XXX In mynetworks, a null netmask would match everyone. */ + msg_warn("ignoring interface with null netmask, address family %d", + sa->sa_family); + continue; + } + switch (sa->sa_family) { + case AF_INET: + if (SOCK_ADDR_IN_ADDR(sa).s_addr == INADDR_ANY) + continue; + break; +#ifdef HAS_IPV6 + case AF_INET6: + if (IN6_IS_ADDR_UNSPECIFIED(&SOCK_ADDR_IN6_ADDR(sa))) + continue; + break; +#endif + default: + continue; + } + + inet_addr_list_append(addr_list, sa); + if (mask_list != 0) { + + /* + * Unfortunately, sa_len/sa_family may be broken in the netmask + * sockaddr structure. We must fix this manually to have correct + * addresses. --dcs + */ +#ifdef HAS_SA_LEN + sam->sa_len = sa->sa_family == AF_INET6 ? + sizeof(struct sockaddr_in6) : + sizeof(struct sockaddr_in); +#endif + sam->sa_family = sa->sa_family; + inet_addr_list_append(mask_list, sam); + } + } + freeifaddrs(ifap); + return (0); +} + +#elif defined(HAS_SIOCGLIF) /* HAVE_GETIFADDRS */ + +/* + * The SIOCLIF* ioctls are the successors of SIOCGIF* on the Solaris + * and HP/UX operating systems. The data is stored in sockaddr_storage + * structure. Both IPv4 and IPv6 addresses are returned though these + * calls. + */ +#define NEXT_INTERFACE(lifr) (lifr + 1) +#define LIFREQ_SIZE(lifr) sizeof(lifr[0]) + +/* ial_siocglif - determine IP addresses using ioctl(SIOCGLIF*) */ + +static int ial_siocglif(INET_ADDR_LIST *addr_list, + INET_ADDR_LIST *mask_list, + int af) +{ + const char *myname = "inet_addr_local[siocglif]"; + struct lifconf lifc; + struct lifreq *lifr; + struct lifreq *lifr_mask; + struct lifreq *the_end; + struct sockaddr *sa; + int sock; + VSTRING *buf; + + /* + * See also comments in ial_siocgif() + */ + if (af != AF_INET && af != AF_INET6) + msg_fatal("%s: address family was %d, must be AF_INET (%d) or " + "AF_INET6 (%d)", myname, af, AF_INET, AF_INET6); + sock = ial_socket(af); + if (sock < 0) + return (0); + buf = vstring_alloc(1024); + for (;;) { + memset(&lifc, 0, sizeof(lifc)); + lifc.lifc_family = AF_UNSPEC; /* XXX Why??? */ + lifc.lifc_len = vstring_avail(buf); + lifc.lifc_buf = vstring_str(buf); + if (ioctl(sock, SIOCGLIFCONF, (char *) &lifc) < 0) { + if (errno != EINVAL) + msg_fatal("%s: ioctl SIOCGLIFCONF: %m", myname); + } else if (lifc.lifc_len < vstring_avail(buf) / 2) + break; + VSTRING_SPACE(buf, vstring_avail(buf) * 2); + } + + the_end = (struct lifreq *) (lifc.lifc_buf + lifc.lifc_len); + for (lifr = lifc.lifc_req; lifr < the_end;) { + sa = (struct sockaddr *) &lifr->lifr_addr; + if (sa->sa_family != af) { + lifr = NEXT_INTERFACE(lifr); + continue; + } + if (af == AF_INET) { + if (SOCK_ADDR_IN_ADDR(sa).s_addr == INADDR_ANY) { + lifr = NEXT_INTERFACE(lifr); + continue; + } +#ifdef HAS_IPV6 + } else if (af == AF_INET6) { + if (IN6_IS_ADDR_UNSPECIFIED(&SOCK_ADDR_IN6_ADDR(sa))) { + lifr = NEXT_INTERFACE(lifr); + continue; + } + } +#endif + inet_addr_list_append(addr_list, sa); + if (mask_list) { + lifr_mask = (struct lifreq *) mymalloc(sizeof(struct lifreq)); + memcpy((void *) lifr_mask, (void *) lifr, sizeof(struct lifreq)); + if (ioctl(sock, SIOCGLIFNETMASK, lifr_mask) < 0) + msg_fatal("%s: ioctl(SIOCGLIFNETMASK): %m", myname); + /* XXX: Check whether sa_len/family are honoured --dcs */ + inet_addr_list_append(mask_list, + (struct sockaddr *) &lifr_mask->lifr_addr); + myfree((void *) lifr_mask); + } + lifr = NEXT_INTERFACE(lifr); + } + vstring_free(buf); + (void) close(sock); + return (0); +} + +#else /* HAVE_SIOCGLIF */ + +/* + * The classic SIOCGIF* ioctls. Modern BSD operating systems will + * also return IPv6 addresses through these structure. Note however + * that recent versions of these operating systems have getifaddrs. + */ +#if defined(_SIZEOF_ADDR_IFREQ) +#define NEXT_INTERFACE(ifr) ((struct ifreq *) \ + ((char *) ifr + _SIZEOF_ADDR_IFREQ(*ifr))) +#define IFREQ_SIZE(ifr) _SIZEOF_ADDR_IFREQ(*ifr) +#elif defined(HAS_SA_LEN) +#define NEXT_INTERFACE(ifr) ((struct ifreq *) \ + ((char *) ifr + sizeof(ifr->ifr_name) + ifr->ifr_addr.sa_len)) +#define IFREQ_SIZE(ifr) (sizeof(ifr->ifr_name) + ifr->ifr_addr.sa_len) +#else +#define NEXT_INTERFACE(ifr) (ifr + 1) +#define IFREQ_SIZE(ifr) sizeof(ifr[0]) +#endif + +/* ial_siocgif - determine IP addresses using ioctl(SIOCGIF*) */ + +static int ial_siocgif(INET_ADDR_LIST *addr_list, + INET_ADDR_LIST *mask_list, + int af) +{ + const char *myname = "inet_addr_local[siocgif]"; + struct in_addr addr; + struct ifconf ifc; + struct ifreq *ifr; + struct ifreq *ifr_mask; + struct ifreq *the_end; + int sock; + VSTRING *buf; + + /* + * Get the network interface list. XXX The socket API appears to have no + * function that returns the number of network interfaces, so we have to + * guess how much space is needed to store the result. + * + * On BSD-derived systems, ioctl SIOCGIFCONF returns as much information as + * possible, leaving it up to the application to repeat the request with + * a larger buffer if the result caused a tight fit. + * + * Other systems, such as Solaris 2.5, generate an EINVAL error when the + * buffer is too small for the entire result. Workaround: ignore EINVAL + * errors and repeat the request with a larger buffer. The downside is + * that the program can run out of memory due to a non-memory problem, + * making it more difficult than necessary to diagnose the real problem. + */ + sock = ial_socket(af); + if (sock < 0) + return (0); + buf = vstring_alloc(1024); + for (;;) { + ifc.ifc_len = vstring_avail(buf); + ifc.ifc_buf = vstring_str(buf); + if (ioctl(sock, SIOCGIFCONF, (char *) &ifc) < 0) { + if (errno != EINVAL) + msg_fatal("%s: ioctl SIOCGIFCONF: %m", myname); + } else if (ifc.ifc_len < vstring_avail(buf) / 2) + break; + VSTRING_SPACE(buf, vstring_avail(buf) * 2); + } + + the_end = (struct ifreq *) (ifc.ifc_buf + ifc.ifc_len); + for (ifr = ifc.ifc_req; ifr < the_end;) { + if (ifr->ifr_addr.sa_family != af) { + ifr = NEXT_INTERFACE(ifr); + continue; + } + if (af == AF_INET) { + addr = ((struct sockaddr_in *) & ifr->ifr_addr)->sin_addr; + if (addr.s_addr != INADDR_ANY) { + inet_addr_list_append(addr_list, &ifr->ifr_addr); + if (mask_list) { + ifr_mask = (struct ifreq *) mymalloc(IFREQ_SIZE(ifr)); + memcpy((void *) ifr_mask, (void *) ifr, IFREQ_SIZE(ifr)); + if (ioctl(sock, SIOCGIFNETMASK, ifr_mask) < 0) + msg_fatal("%s: ioctl SIOCGIFNETMASK: %m", myname); + + /* + * Note that this SIOCGIFNETMASK has truly screwed up the + * contents of sa_len/sa_family. We must fix this + * manually to have correct addresses. --dcs + */ + ifr_mask->ifr_addr.sa_family = af; +#ifdef HAS_SA_LEN + ifr_mask->ifr_addr.sa_len = sizeof(struct sockaddr_in); +#endif + inet_addr_list_append(mask_list, &ifr_mask->ifr_addr); + myfree((void *) ifr_mask); + } + } + } +#ifdef HAS_IPV6 + else if (af == AF_INET6) { + struct sockaddr *sa; + + sa = SOCK_ADDR_PTR(&ifr->ifr_addr); + if (!(IN6_IS_ADDR_UNSPECIFIED(&SOCK_ADDR_IN6_ADDR(sa)))) { + inet_addr_list_append(addr_list, sa); + if (mask_list) { + /* XXX Assume /128 for everything */ + struct sockaddr_in6 mask6; + + mask6 = *SOCK_ADDR_IN6_PTR(sa); + memset((void *) &mask6.sin6_addr, ~0, + sizeof(mask6.sin6_addr)); + inet_addr_list_append(mask_list, SOCK_ADDR_PTR(&mask6)); + } + } + } +#endif + ifr = NEXT_INTERFACE(ifr); + } + vstring_free(buf); + (void) close(sock); + return (0); +} + +#endif /* HAVE_SIOCGLIF */ + +#ifdef HAS_PROCNET_IFINET6 + +/* + * Older Linux versions lack proper calls to retrieve IPv6 interface + * addresses. Instead, the addresses can be read from a file in the + * /proc tree. The most important issue with this approach however + * is that the /proc tree may not always be available, for example + * in a chrooted environment or in "hardened" (sic) installations. + */ + +/* ial_procnet_ifinet6 - determine IPv6 addresses using /proc/net/if_inet6 */ + +static int ial_procnet_ifinet6(INET_ADDR_LIST *addr_list, + INET_ADDR_LIST *mask_list) +{ + const char *myname = "inet_addr_local[procnet_ifinet6]"; + FILE *fp; + char buf[BUFSIZ]; + unsigned plen; + VSTRING *addrbuf; + struct sockaddr_in6 addr; + struct sockaddr_in6 mask; + + /* + * Example: 00000000000000000000000000000001 01 80 10 80 lo + * + * Fields: address, interface index, prefix length, scope value + * (net/ipv6.h), interface flags (linux/rtnetlink.h), device name. + * + * FIX 200501 The IPv6 patch used fscanf(), which will hang on unexpected + * input. Use fgets() + sscanf() instead. + */ + if ((fp = fopen(_PATH_PROCNET_IFINET6, "r")) != 0) { + addrbuf = vstring_alloc(MAI_V6ADDR_BYTES + 1); + memset((void *) &addr, 0, sizeof(addr)); + addr.sin6_family = AF_INET6; +#ifdef HAS_SA_LEN + addr.sin6_len = sizeof(addr); +#endif + mask = addr; + while (fgets(buf, sizeof(buf), fp) != 0) { + /* 200501 hex_decode() is light-weight compared to getaddrinfo(). */ + if (hex_decode(addrbuf, buf, MAI_V6ADDR_BYTES * 2) == 0 + || sscanf(buf + MAI_V6ADDR_BYTES * 2, " %*x %x", &plen) != 1 + || plen > MAI_V6ADDR_BITS) { + msg_warn("unexpected data in %s - skipping IPv6 configuration", + _PATH_PROCNET_IFINET6); + break; + } + /* vstring_str(addrbuf) has worst-case alignment. */ + addr.sin6_addr = *(struct in6_addr *) vstring_str(addrbuf); + inet_addr_list_append(addr_list, SOCK_ADDR_PTR(&addr)); + + memset((void *) &mask.sin6_addr, ~0, sizeof(mask.sin6_addr)); + mask_addr((unsigned char *) &mask.sin6_addr, + sizeof(mask.sin6_addr), plen); + inet_addr_list_append(mask_list, SOCK_ADDR_PTR(&mask)); + } + vstring_free(addrbuf); + fclose(fp); /* FIX 200501 */ + } else { + msg_warn("can't open %s (%m) - skipping IPv6 configuration", + _PATH_PROCNET_IFINET6); + } + return (0); +} + +#endif /* HAS_PROCNET_IFINET6 */ + +/* inet_addr_local - find all IP addresses for this host */ + +int inet_addr_local(INET_ADDR_LIST *addr_list, INET_ADDR_LIST *mask_list, + unsigned *addr_family_list) +{ + const char *myname = "inet_addr_local"; + int initial_count = addr_list->used; + unsigned family; + int count; + + while ((family = *addr_family_list++) != 0) { + + /* + * IP Version 4 + */ + if (family == AF_INET) { + count = addr_list->used; +#if defined(HAVE_GETIFADDRS) + ial_getifaddrs(addr_list, mask_list, AF_INET); +#elif defined (HAS_SIOCGLIF) + ial_siocglif(addr_list, mask_list, AF_INET); +#else + ial_siocgif(addr_list, mask_list, AF_INET); +#endif + if (msg_verbose) + msg_info("%s: configured %d IPv4 addresses", + myname, addr_list->used - count); + } + + /* + * IP Version 6 + */ +#ifdef HAS_IPV6 + else if (family == AF_INET6) { + count = addr_list->used; +#if defined(HAVE_GETIFADDRS) + ial_getifaddrs(addr_list, mask_list, AF_INET6); +#elif defined(HAS_PROCNET_IFINET6) + ial_procnet_ifinet6(addr_list, mask_list); +#elif defined(HAS_SIOCGLIF) + ial_siocglif(addr_list, mask_list, AF_INET6); +#else + ial_siocgif(addr_list, mask_list, AF_INET6); +#endif + if (msg_verbose) + msg_info("%s: configured %d IPv6 addresses", myname, + addr_list->used - count); + } +#endif + + /* + * Something's not right. + */ + else + msg_panic("%s: unknown address family %d", myname, family); + } + return (addr_list->used - initial_count); +} + +#ifdef TEST + +#include <string.h> +#include <vstream.h> +#include <msg_vstream.h> +#include <inet_proto.h> + +int main(int unused_argc, char **argv) +{ + INET_ADDR_LIST addr_list; + INET_ADDR_LIST mask_list; + MAI_HOSTADDR_STR hostaddr; + MAI_HOSTADDR_STR hostmask; + struct sockaddr *sa; + int i; + INET_PROTO_INFO *proto_info; + + msg_vstream_init(argv[0], VSTREAM_ERR); + msg_verbose = 1; + + proto_info = inet_proto_init(argv[0], + argv[1] ? argv[1] : INET_PROTO_NAME_ALL); + inet_addr_list_init(&addr_list); + inet_addr_list_init(&mask_list); + inet_addr_local(&addr_list, &mask_list, proto_info->ai_family_list); + + if (addr_list.used == 0) + msg_fatal("cannot find any active network interfaces"); + + if (addr_list.used == 1) + msg_warn("found only one active network interface"); + + for (i = 0; i < addr_list.used; i++) { + sa = SOCK_ADDR_PTR(addr_list.addrs + i); + SOCKADDR_TO_HOSTADDR(SOCK_ADDR_PTR(sa), SOCK_ADDR_LEN(sa), + &hostaddr, (MAI_SERVPORT_STR *) 0, 0); + sa = SOCK_ADDR_PTR(mask_list.addrs + i); + SOCKADDR_TO_HOSTADDR(SOCK_ADDR_PTR(sa), SOCK_ADDR_LEN(sa), + &hostmask, (MAI_SERVPORT_STR *) 0, 0); + vstream_printf("%s/%s\n", hostaddr.buf, hostmask.buf); + vstream_fflush(VSTREAM_OUT); + } + inet_addr_list_free(&addr_list); + inet_addr_list_free(&mask_list); + return (0); +} + +#endif diff --git a/src/util/inet_addr_local.h b/src/util/inet_addr_local.h new file mode 100644 index 0000000..cb6b3f2 --- /dev/null +++ b/src/util/inet_addr_local.h @@ -0,0 +1,35 @@ +#ifndef _INET_ADDR_LOCAL_H_INCLUDED_ +#define _INET_ADDR_LOCAL_H_INCLUDED_ + +/*++ +/* NAME +/* inet_addr_local 3h +/* SUMMARY +/* determine if IP address is local +/* SYNOPSIS +/* #include <inet_addr_local.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <inet_addr_list.h> + + /* + * External interface. + */ +extern int inet_addr_local(INET_ADDR_LIST *, INET_ADDR_LIST *, unsigned *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/inet_connect.c b/src/util/inet_connect.c new file mode 100644 index 0000000..0f5542e --- /dev/null +++ b/src/util/inet_connect.c @@ -0,0 +1,189 @@ +/*++ +/* NAME +/* inet_connect 3 +/* SUMMARY +/* connect to TCP listener +/* SYNOPSIS +/* #include <connect.h> +/* +/* int inet_windowsize; +/* +/* int inet_connect(addr, block_mode, timeout) +/* const char *addr; +/* int block_mode; +/* int timeout; +/* DESCRIPTION +/* inet_connect connects to a TCP listener at +/* the specified address, and returns the resulting file descriptor. +/* +/* Specify an inet_windowsize value > 0 to override the TCP +/* window size that the client advertises to the server. +/* +/* Arguments: +/* .IP addr +/* The destination to connect to. The format is host:port. If no +/* host is specified, a port on the local host is assumed. +/* Host and port information may be given in numerical form +/* or as symbolical names. +/* .IP block_mode +/* Either NON_BLOCKING for a non-blocking socket, or BLOCKING for +/* blocking mode. +/* .IP timeout +/* Bounds the number of seconds that the operation may take. Specify +/* a value <= 0 to disable the time limit. +/* DIAGNOSTICS +/* The result is -1 when the connection could not be made. +/* The nature of the error is available via the global \fIerrno\fR +/* variable. +/* Fatal errors: other system call failures. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System interfaces. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <netdb.h> + +/* Utility library. */ + +#include "mymalloc.h" +#include "msg.h" +#include "iostuff.h" +#include "host_port.h" +#include "sane_connect.h" +#include "connect.h" +#include "timed_connect.h" +#include "myaddrinfo.h" +#include "sock_addr.h" +#include "inet_proto.h" + +static int inet_connect_one(struct addrinfo *, int, int); + +/* inet_connect - connect to TCP listener */ + +int inet_connect(const char *addr, int block_mode, int timeout) +{ + char *buf; + char *host; + char *port; + const char *parse_err; + struct addrinfo *res; + struct addrinfo *res0; + int aierr; + int sock; + MAI_HOSTADDR_STR hostaddr; + const INET_PROTO_INFO *proto_info; + int found; + + /* + * Translate address information to internal form. No host defaults to + * the local host. + */ + buf = mystrdup(addr); + if ((parse_err = host_port(buf, &host, "localhost", &port, (char *) 0)) != 0) + msg_fatal("%s: %s", addr, parse_err); + if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res0)) != 0) + msg_warn("host or service %s not found: %s", + addr, MAI_STRERROR(aierr)); + myfree(buf); + if (aierr) { + errno = EADDRNOTAVAIL; /* for up-stream "%m" */ + return (-1); + } + proto_info = inet_proto_info(); + for (sock = -1, found = 0, res = res0; res != 0; res = res->ai_next) { + + /* + * Safety net. + */ + if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) { + msg_info("skipping address family %d for host %s", + res->ai_family, host); + continue; + } + found++; + + /* + * In case of multiple addresses, show what address we're trying now. + */ + if (msg_verbose) { + SOCKADDR_TO_HOSTADDR(res->ai_addr, res->ai_addrlen, + &hostaddr, (MAI_SERVPORT_STR *) 0, 0); + msg_info("trying... [%s]", hostaddr.buf); + } + if ((sock = inet_connect_one(res, block_mode, timeout)) < 0) { + if (msg_verbose) + msg_info("%m"); + } else + break; + } + if (found == 0) + msg_fatal("host not found: %s", addr); + freeaddrinfo(res0); + return (sock); +} + +/* inet_connect_one - try to connect to one address */ + +static int inet_connect_one(struct addrinfo * res, int block_mode, int timeout) +{ + int sock; + + /* + * Create a client socket. + */ + sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (sock < 0) + return (-1); + + /* + * Window scaling workaround. + */ + if (inet_windowsize > 0) + set_inet_windowsize(sock, inet_windowsize); + + /* + * Timed connect. + */ + if (timeout > 0) { + non_blocking(sock, NON_BLOCKING); + if (timed_connect(sock, res->ai_addr, res->ai_addrlen, timeout) < 0) { + close(sock); + return (-1); + } + if (block_mode != NON_BLOCKING) + non_blocking(sock, block_mode); + return (sock); + } + + /* + * Maybe block until connected. + */ + else { + non_blocking(sock, block_mode); + if (sane_connect(sock, res->ai_addr, res->ai_addrlen) < 0 + && errno != EINPROGRESS) { + close(sock); + return (-1); + } + return (sock); + } +} diff --git a/src/util/inet_listen.c b/src/util/inet_listen.c new file mode 100644 index 0000000..31800cd --- /dev/null +++ b/src/util/inet_listen.c @@ -0,0 +1,189 @@ +/*++ +/* NAME +/* inet_listen 3 +/* SUMMARY +/* start TCP listener +/* SYNOPSIS +/* #include <listen.h> +/* +/* int inet_windowsize; +/* +/* int inet_listen(addr, backlog, block_mode) +/* const char *addr; +/* int backlog; +/* int block_mode; +/* +/* int inet_accept(fd) +/* int fd; +/* DESCRIPTION +/* The \fBinet_listen\fR routine starts a TCP listener +/* on the specified address, with the specified backlog, and returns +/* the resulting file descriptor. +/* +/* inet_accept() accepts a connection and sanitizes error results. +/* +/* Specify an inet_windowsize value > 0 to override the TCP +/* window size that the server advertises to the client. +/* +/* Arguments: +/* .IP addr +/* The communication endpoint to listen on. The syntax is "host:port". +/* Host and port may be specified in symbolic form or numerically. +/* A null host field means listen on all network interfaces. +/* .IP backlog +/* This argument is passed on to the \fIlisten(2)\fR routine. +/* .IP block_mode +/* Either NON_BLOCKING for a non-blocking socket, or BLOCKING for +/* blocking mode. +/* .IP fd +/* File descriptor returned by inet_listen(). +/* DIAGNOSTICS +/* Fatal errors: inet_listen() aborts upon any system call failure. +/* inet_accept() leaves all error handling up to the caller. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System libraries. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#ifndef MAXHOSTNAMELEN +#include <sys/param.h> +#endif +#include <errno.h> +#include <string.h> +#include <unistd.h> + +/* Utility library. */ + +#include "mymalloc.h" +#include "msg.h" +#include "host_port.h" +#include "iostuff.h" +#include "listen.h" +#include "sane_accept.h" +#include "myaddrinfo.h" +#include "sock_addr.h" +#include "inet_proto.h" + +/* inet_listen - create TCP listener */ + +int inet_listen(const char *addr, int backlog, int block_mode) +{ + struct addrinfo *res; + struct addrinfo *res0; + int aierr; + int sock; + int on = 1; + char *buf; + char *host; + char *port; + const char *parse_err; + MAI_HOSTADDR_STR hostaddr; + MAI_SERVPORT_STR portnum; + const INET_PROTO_INFO *proto_info; + + /* + * Translate address information to internal form. + */ + buf = mystrdup(addr); + if ((parse_err = host_port(buf, &host, "", &port, (char *) 0)) != 0) + msg_fatal("%s: %s", addr, parse_err); + if (*host == 0) + host = 0; + if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res0)) != 0) + msg_fatal("%s: %s", addr, MAI_STRERROR(aierr)); + myfree(buf); + /* No early returns or res0 leaks. */ + + proto_info = inet_proto_info(); + for (res = res0; /* see below */ ; res = res->ai_next) { + + /* + * No usable address found. + */ + if (res == 0) + msg_fatal("%s: host found but no usable address", addr); + + /* + * Safety net. + */ + if (strchr((char *) proto_info->sa_family_list, res->ai_family) != 0) + break; + + msg_info("skipping address family %d for %s", res->ai_family, addr); + } + + /* + * Show what address we're trying. + */ + if (msg_verbose) { + SOCKADDR_TO_HOSTADDR(res->ai_addr, res->ai_addrlen, + &hostaddr, &portnum, 0); + msg_info("trying... [%s]:%s", hostaddr.buf, portnum.buf); + } + + /* + * Create a listener socket. + */ + if ((sock = socket(res->ai_family, res->ai_socktype, 0)) < 0) + msg_fatal("socket: %m"); +#ifdef HAS_IPV6 +#if defined(IPV6_V6ONLY) && !defined(BROKEN_AI_PASSIVE_NULL_HOST) + if (res->ai_family == AF_INET6 + && setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, + (void *) &on, sizeof(on)) < 0) + msg_fatal("setsockopt(IPV6_V6ONLY): %m"); +#endif +#endif + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + (void *) &on, sizeof(on)) < 0) + msg_fatal("setsockopt(SO_REUSEADDR): %m"); +#if defined(SO_REUSEPORT_LB) + if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT_LB, + (void *) &on, sizeof(on)) < 0) + msg_fatal("setsockopt(SO_REUSEPORT_LB): %m"); +#elif defined(SO_REUSEPORT) + if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, + (void *) &on, sizeof(on)) < 0) + msg_fatal("setsockopt(SO_REUSEPORT): %m"); +#endif + if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) { + SOCKADDR_TO_HOSTADDR(res->ai_addr, res->ai_addrlen, + &hostaddr, &portnum, 0); + msg_fatal("bind %s port %s: %m", hostaddr.buf, portnum.buf); + } + freeaddrinfo(res0); + non_blocking(sock, block_mode); + if (inet_windowsize > 0) + set_inet_windowsize(sock, inet_windowsize); + if (listen(sock, backlog) < 0) + msg_fatal("listen: %m"); + return (sock); +} + +/* inet_accept - accept connection */ + +int inet_accept(int fd) +{ + struct sockaddr_storage ss; + SOCKADDR_SIZE ss_len = sizeof(ss); + + return (sane_accept(fd, (struct sockaddr *) &ss, &ss_len)); +} diff --git a/src/util/inet_proto.c b/src/util/inet_proto.c new file mode 100644 index 0000000..fedf761 --- /dev/null +++ b/src/util/inet_proto.c @@ -0,0 +1,328 @@ +/*++ +/* NAME +/* inet_proto 3 +/* SUMMARY +/* convert protocol names to assorted constants +/* SYNOPSIS +/* #include <inet_proto.h> +/* +/* typedef struct { +/* .in +4 +/* unsigned ai_family; /* PF_UNSPEC, PF_INET, or PF_INET6 */ +/* unsigned *ai_family_list; /* PF_INET and/or PF_INET6 */ +/* unsigned *dns_atype_list;/* TAAAA and/or TA */ +/* unsigned char *sa_family_list;/* AF_INET6 and/or AF_INET */ +/* .in -4 +/* } INET_PROTO_INFO; +/* +/* const INET_PROTO_INFO *inet_proto_init(context, protocols) +/* +/* const INET_PROTO_INFO *inet_proto_info() +/* DESCRIPTION +/* inet_proto_init() converts a string with protocol names +/* into null-terminated lists of appropriate constants used +/* by Postfix library routines. The idea is that one should +/* be able to configure an MTA for IPv4 only, without having +/* to recompile code (what a concept). +/* +/* Unfortunately, some compilers won't link initialized data +/* without a function call into the same source module, so +/* we invoke inet_proto_info() in order to access the result +/* from inet_proto_init() from within library routines. +/* inet_proto_info() also conveniently initializes the data +/* to built-in defaults. +/* +/* Arguments: +/* .IP context +/* Typically, a configuration parameter name. +/* .IP protocols +/* Null-terminated string with protocol names separated by +/* whitespace and/or commas: +/* .RS +/* .IP INET_PROTO_NAME_ALL +/* Enable all available IP protocols. +/* .IP INET_PROTO_NAME_IPV4 +/* Enable IP version 4 support. +/* .IP INET_PROTO_NAME_IPV6 +/* Enable IP version 6 support. +/* .RS +/* .PP +/* Results: +/* .IP ai_family +/* Only one of PF_UNSPEC, PF_INET, or PF_INET6. This can be +/* used as input for the getaddrinfo() and getnameinfo() +/* routines. +/* .IP ai_family_list +/* One or more of PF_INET or PF_INET6. This can be used as +/* input for the inet_addr_local() routine. +/* .IP dns_atype_list +/* One or more of T_AAAA or T_A. This can be used as input for +/* the dns_lookup_v() and dns_lookup_l() routines. +/* .IP sa_family_list +/* One or more of AF_INET6 or AF_INET. This can be used as an +/* output filter for the results from the getaddrinfo() and +/* getnameinfo() routines. +/* SEE ALSO +/* msg(3) diagnostics interface +/* DIAGNOSTICS +/* This module will warn and turn off support for any protocol +/* that is requested but unavailable. +/* +/* Fatal errors: memory allocation problem. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <netinet/in.h> +#include <arpa/nameser.h> +#ifdef RESOLVE_H_NEEDS_STDIO_H +#include <stdio.h> +#endif +#include <resolv.h> +#include <stdarg.h> +#include <unistd.h> + +/* Utility library. */ + +#include <mymalloc.h> +#include <msg.h> +#include <myaddrinfo.h> +#include <name_mask.h> +#include <inet_proto.h> + + /* + * Application-specific. + */ + + /* + * Run-time initialization, so we can work around LINUX where IPv6 falls + * flat on its face because it is not turned on in the kernel. + */ +INET_PROTO_INFO *inet_proto_table = 0; + + /* + * Infrastructure: lookup table with the protocol names that we support. + */ +#define INET_PROTO_MASK_IPV4 (1<<0) +#define INET_PROTO_MASK_IPV6 (1<<1) + +static const NAME_MASK proto_table[] = { +#ifdef HAS_IPV6 + INET_PROTO_NAME_ALL, INET_PROTO_MASK_IPV4 | INET_PROTO_MASK_IPV6, + INET_PROTO_NAME_IPV6, INET_PROTO_MASK_IPV6, +#else + INET_PROTO_NAME_ALL, INET_PROTO_MASK_IPV4, +#endif + INET_PROTO_NAME_IPV4, INET_PROTO_MASK_IPV4, + 0, +}; + +/* make_uchar_vector - create and initialize uchar vector */ + +static unsigned char *make_uchar_vector(int len,...) +{ + const char *myname = "make_uchar_vector"; + va_list ap; + int count; + unsigned char *vp; + + va_start(ap, len); + if (len <= 0) + msg_panic("%s: bad vector length: %d", myname, len); + vp = (unsigned char *) mymalloc(sizeof(*vp) * len); + for (count = 0; count < len; count++) + vp[count] = va_arg(ap, unsigned); + va_end(ap); + return (vp); +} + +/* make_unsigned_vector - create and initialize integer vector */ + +static unsigned *make_unsigned_vector(int len,...) +{ + const char *myname = "make_unsigned_vector"; + va_list ap; + int count; + unsigned *vp; + + va_start(ap, len); + if (len <= 0) + msg_panic("%s: bad vector length: %d", myname, len); + vp = (unsigned *) mymalloc(sizeof(*vp) * len); + for (count = 0; count < len; count++) + vp[count] = va_arg(ap, unsigned); + va_end(ap); + return (vp); +} + +/* inet_proto_free - destroy data */ + +static void inet_proto_free(INET_PROTO_INFO *pf) +{ + myfree((void *) pf->ai_family_list); + myfree((void *) pf->dns_atype_list); + myfree((void *) pf->sa_family_list); + myfree((void *) pf); +} + +/* inet_proto_init - convert protocol names to library inputs */ + +const INET_PROTO_INFO *inet_proto_init(const char *context, const char *protocols) +{ + const char *myname = "inet_proto"; + INET_PROTO_INFO *pf; + int inet_proto_mask; + int sock; + + /* + * Avoid run-time errors when all network protocols are disabled. We + * can't look up interface information, and we can't convert explicit + * names or addresses. + */ + inet_proto_mask = name_mask(context, proto_table, protocols); +#ifdef HAS_IPV6 + if (inet_proto_mask & INET_PROTO_MASK_IPV6) { + if ((sock = socket(PF_INET6, SOCK_STREAM, 0)) >= 0) { + close(sock); + } else if (errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT) { + msg_warn("%s: disabling IPv6 name/address support: %m", context); + inet_proto_mask &= ~INET_PROTO_MASK_IPV6; + } else { + msg_fatal("socket: %m"); + } + } +#endif + if (inet_proto_mask & INET_PROTO_MASK_IPV4) { + if ((sock = socket(PF_INET, SOCK_STREAM, 0)) >= 0) { + close(sock); + } else if (errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT) { + msg_warn("%s: disabling IPv4 name/address support: %m", context); + inet_proto_mask &= ~INET_PROTO_MASK_IPV4; + } else { + msg_fatal("socket: %m"); + } + } + + /* + * Store address family etc. info as null-terminated vectors. If that + * breaks because we must be able to store nulls, we'll deal with the + * additional complexity. + * + * XXX Use compile-time initialized data templates instead of building the + * reply on the fly. + */ + switch (inet_proto_mask) { +#ifdef HAS_IPV6 + case INET_PROTO_MASK_IPV6: + pf = (INET_PROTO_INFO *) mymalloc(sizeof(*pf)); + pf->ai_family = PF_INET6; + pf->ai_family_list = make_unsigned_vector(2, PF_INET6, 0); + pf->dns_atype_list = make_unsigned_vector(2, T_AAAA, 0); + pf->sa_family_list = make_uchar_vector(2, AF_INET6, 0); + break; + case (INET_PROTO_MASK_IPV6 | INET_PROTO_MASK_IPV4): + pf = (INET_PROTO_INFO *) mymalloc(sizeof(*pf)); + pf->ai_family = PF_UNSPEC; + pf->ai_family_list = make_unsigned_vector(3, PF_INET, PF_INET6, 0); + pf->dns_atype_list = make_unsigned_vector(3, T_A, T_AAAA, 0); + pf->sa_family_list = make_uchar_vector(3, AF_INET, AF_INET6, 0); + break; +#endif + case INET_PROTO_MASK_IPV4: + pf = (INET_PROTO_INFO *) mymalloc(sizeof(*pf)); + pf->ai_family = PF_INET; + pf->ai_family_list = make_unsigned_vector(2, PF_INET, 0); + pf->dns_atype_list = make_unsigned_vector(2, T_A, 0); + pf->sa_family_list = make_uchar_vector(2, AF_INET, 0); + break; + case 0: + pf = (INET_PROTO_INFO *) mymalloc(sizeof(*pf)); + pf->ai_family = PF_UNSPEC; + pf->ai_family_list = make_unsigned_vector(1, 0); + pf->dns_atype_list = make_unsigned_vector(1, 0); + pf->sa_family_list = make_uchar_vector(1, 0); + break; + default: + msg_panic("%s: bad inet_proto_mask 0x%x", myname, inet_proto_mask); + } + if (inet_proto_table) + inet_proto_free(inet_proto_table); + return (inet_proto_table = pf); +} + +#ifdef TEST + + /* + * Small driver for unit tests. + */ + +static char *print_unsigned_vector(VSTRING *buf, unsigned *vector) +{ + unsigned *p; + + VSTRING_RESET(buf); + for (p = vector; *p; p++) { + vstring_sprintf_append(buf, "%u", *p); + if (p[1]) + VSTRING_ADDCH(buf, ' '); + } + VSTRING_TERMINATE(buf); + return (vstring_str(buf)); +} + +static char *print_uchar_vector(VSTRING *buf, unsigned char *vector) +{ + unsigned char *p; + + VSTRING_RESET(buf); + for (p = vector; *p; p++) { + vstring_sprintf_append(buf, "%u", *p); + if (p[1]) + VSTRING_ADDCH(buf, ' '); + } + VSTRING_TERMINATE(buf); + return (vstring_str(buf)); +} + +int main(int argc, char **argv) +{ + const char *myname = argv[0]; + INET_PROTO_INFO *pf; + VSTRING *buf; + + if (argc < 2) + msg_fatal("usage: %s protocol(s)...", myname); + + buf = vstring_alloc(10); + while (*++argv) { + msg_info("=== %s ===", *argv); + inet_proto_init(myname, *argv); + pf = inet_proto_table; + msg_info("ai_family = %u", pf->ai_family); + msg_info("ai_family_list = %s", + print_unsigned_vector(buf, pf->ai_family_list)); + msg_info("dns_atype_list = %s", + print_unsigned_vector(buf, pf->dns_atype_list)); + msg_info("sa_family_list = %s", + print_uchar_vector(buf, pf->sa_family_list)); + } + vstring_free(buf); + return (0); +} + +#endif diff --git a/src/util/inet_proto.h b/src/util/inet_proto.h new file mode 100644 index 0000000..9175eae --- /dev/null +++ b/src/util/inet_proto.h @@ -0,0 +1,56 @@ +#ifndef _INET_PROTO_INFO_H_INCLUDED_ +#define _INET_PROTO_INFO_H_INCLUDED_ + +/*++ +/* NAME +/* inet_proto_info 3h +/* SUMMARY +/* convert protocol names to assorted constants +/* SYNOPSIS +/* #include <inet_proto_info.h> + DESCRIPTION + .nf + + /* + * External interface. + */ +typedef struct { + unsigned int ai_family; /* PF_UNSPEC, PF_INET, or PF_INET6 */ + unsigned int *ai_family_list; /* PF_INET and/or PF_INET6 */ + unsigned int *dns_atype_list; /* TAAAA and/or TA */ + unsigned char *sa_family_list; /* AF_INET6 and/or AF_INET */ +} INET_PROTO_INFO; + + /* + * Some compilers won't link initialized data unless we call a function in + * the same source file. Therefore, inet_proto_info() is a function instead + * of a global variable. + */ +#define inet_proto_info() \ + (inet_proto_table ? (const INET_PROTO_INFO*) inet_proto_table : \ + inet_proto_init("default protocol setting", DEF_INET_PROTOCOLS)) + +extern const INET_PROTO_INFO *inet_proto_init(const char *, const char *); +extern INET_PROTO_INFO *inet_proto_table; + +#define INET_PROTO_NAME_IPV6 "ipv6" +#define INET_PROTO_NAME_IPV4 "ipv4" +#define INET_PROTO_NAME_ALL "all" + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/inet_trigger.c b/src/util/inet_trigger.c new file mode 100644 index 0000000..b1734ee --- /dev/null +++ b/src/util/inet_trigger.c @@ -0,0 +1,130 @@ +/*++ +/* NAME +/* inet_trigger 3 +/* SUMMARY +/* wakeup INET-domain server +/* SYNOPSIS +/* #include <trigger.h> +/* +/* int inet_trigger(service, buf, len, timeout) +/* char *service; +/* const char *buf; +/* ssize_t len; +/* int timeout; +/* DESCRIPTION +/* inet_trigger() wakes up the named INET-domain server by making +/* a brief connection to it and by writing the contents of the +/* named buffer. +/* +/* The connection is closed by a background thread. Some kernels +/* cannot handle client-side disconnect before the server has +/* received the message. +/* +/* Arguments: +/* .IP service +/* Name of the communication endpoint. +/* .IP buf +/* Address of data to be written. +/* .IP len +/* Amount of data to be written. +/* .IP timeout +/* Deadline in seconds. Specify a value <= 0 to disable +/* the time limit. +/* DIAGNOSTICS +/* The result is zero in case of success, -1 in case of problems. +/* BUGS +/* SEE ALSO +/* inet_connect(3), INET-domain client +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <unistd.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <connect.h> +#include <iostuff.h> +#include <mymalloc.h> +#include <events.h> +#include <trigger.h> + +struct inet_trigger { + int fd; + char *service; +}; + +/* inet_trigger_event - disconnect from peer */ + +static void inet_trigger_event(int event, void *context) +{ + struct inet_trigger *ip = (struct inet_trigger *) context; + static const char *myname = "inet_trigger_event"; + + /* + * Disconnect. + */ + if (event == EVENT_TIME) + msg_warn("%s: read timeout for service %s", myname, ip->service); + event_disable_readwrite(ip->fd); + event_cancel_timer(inet_trigger_event, context); + if (close(ip->fd) < 0) + msg_warn("%s: close %s: %m", myname, ip->service); + myfree(ip->service); + myfree((void *) ip); +} + + +/* inet_trigger - wakeup INET-domain server */ + +int inet_trigger(const char *service, const char *buf, ssize_t len, int timeout) +{ + const char *myname = "inet_trigger"; + struct inet_trigger *ip; + int fd; + + if (msg_verbose > 1) + msg_info("%s: service %s", myname, service); + + /* + * Connect... + */ + if ((fd = inet_connect(service, BLOCKING, timeout)) < 0) { + if (msg_verbose) + msg_warn("%s: connect to %s: %m", myname, service); + return (-1); + } + close_on_exec(fd, CLOSE_ON_EXEC); + ip = (struct inet_trigger *) mymalloc(sizeof(*ip)); + ip->fd = fd; + ip->service = mystrdup(service); + + /* + * Write the request... + */ + if (write_buf(fd, buf, len, timeout) < 0 + || write_buf(fd, "", 1, timeout) < 0) + if (msg_verbose) + msg_warn("%s: write to %s: %m", myname, service); + + /* + * Wakeup when the peer disconnects, or when we lose patience. + */ + if (timeout > 0) + event_request_timer(inet_trigger_event, (void *) ip, timeout + 100); + event_enable_read(fd, inet_trigger_event, (void *) ip); + return (0); +} diff --git a/src/util/inet_windowsize.c b/src/util/inet_windowsize.c new file mode 100644 index 0000000..e982149 --- /dev/null +++ b/src/util/inet_windowsize.c @@ -0,0 +1,82 @@ +/*++ +/* NAME +/* inet_windowsize 3 +/* SUMMARY +/* TCP window scaling control +/* SYNOPSIS +/* #include <iostuff.h> +/* +/* int inet_windowsize; +/* +/* void set_inet_windowsize(sock, windowsize) +/* int sock; +/* int windowsize; +/* DESCRIPTION +/* set_inet_windowsize() overrides the default TCP window size +/* with the specified value. When called before listen() or +/* accept(), this works around broken infrastructure that +/* mis-handles TCP window scaling options. +/* +/* The global inet_windowsize variable is available for other +/* routines to remember that they wish to override the default +/* TCP window size. The variable is not accessed by the +/* set_inet_windowsize() function itself. +/* +/* Arguments: +/* .IP sock +/* TCP communication endpoint, before the connect(2) or listen(2) call. +/* .IP windowsize +/* The preferred TCP window size. This must be > 0. +/* DIAGNOSTICS +/* Panic: interface violation. +/* Warnings: some error return from setsockopt(). +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include <sys_defs.h> +#include <sys/socket.h> + +/* Utility library. */ + +#include <msg.h> +#include <iostuff.h> + +/* Application storage. */ + + /* + * Tunable to work around broken routers. + */ +int inet_windowsize = 0; + +/* set_inet_windowsize - set TCP send/receive window size */ + +void set_inet_windowsize(int sock, int windowsize) +{ + + /* + * Sanity check. + */ + if (windowsize <= 0) + msg_panic("inet_windowsize: bad window size %d", windowsize); + + /* + * Generic implementation: set the send and receive buffer size before + * listen() or connect(). + */ + if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void *) &windowsize, + sizeof(windowsize)) < 0) + msg_warn("setsockopt SO_SNDBUF %d: %m", windowsize); + if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void *) &windowsize, + sizeof(windowsize)) < 0) + msg_warn("setsockopt SO_RCVBUF %d: %m", windowsize); +} diff --git a/src/util/iostuff.h b/src/util/iostuff.h new file mode 100644 index 0000000..0c54d03 --- /dev/null +++ b/src/util/iostuff.h @@ -0,0 +1,73 @@ +#ifndef _IOSTUFF_H_INCLUDED_ +#define _IOSTUFF_H_INCLUDED_ + +/*++ +/* NAME +/* iostuff 3h +/* SUMMARY +/* miscellaneous I/O primitives +/* SYNOPSIS +/* #include <iostuff.h> +/* DESCRIPTION + + /* + * External interface. + */ +extern int non_blocking(int, int); +extern int close_on_exec(int, int); +extern int open_limit(int); +extern int poll_fd(int, int, int, int, int); +extern off_t get_file_limit(void); +extern void set_file_limit(off_t); +extern ssize_t peekfd(int); +extern ssize_t write_buf(int, const char *, ssize_t, int); +extern ssize_t timed_read(int, void *, size_t, int, void *); +extern ssize_t timed_write(int, const void *, size_t, int, void *); +extern void doze(unsigned); +extern void rand_sleep(unsigned, unsigned); +extern int duplex_pipe(int *); +extern int stream_recv_fd(int); +extern int stream_send_fd(int, int); +extern int unix_recv_fd(int); +extern int unix_send_fd(int, int); +extern ssize_t dummy_read(int, void *, size_t, int, void *); +extern ssize_t dummy_write(int, void *, size_t, int, void *); + +#define readable(fd) poll_fd((fd), POLL_FD_READ, 0, 1, 0) +#define writable(fd) poll_fd((fd), POLL_FD_WRITE, 0, 1, 0) + +#define read_wait(fd, timeout) poll_fd((fd), POLL_FD_READ, (timeout), 0, -1) +#define write_wait(fd, timeout) poll_fd((fd), POLL_FD_WRITE, (timeout), 0, -1) + +extern int inet_windowsize; +extern void set_inet_windowsize(int, int); + +#define POLL_FD_READ 0 +#define POLL_FD_WRITE 1 + +#define BLOCKING 0 +#define NON_BLOCKING 1 + +#define CLOSE_ON_EXEC 1 +#define PASS_ON_EXEC 0 + +extern int unix_pass_fd_fix; +extern void set_unix_pass_fd_fix(const char *); + +#define UNIX_PASS_FD_FIX_NONE (0) +#define UNIX_PASS_FD_FIX_CMSG_LEN (1<<0) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* CREATION DATE +/* Sat Jan 25 16:54:13 EST 1997 +/*--*/ + +#endif diff --git a/src/util/ip_match.c b/src/util/ip_match.c new file mode 100644 index 0000000..aeea799 --- /dev/null +++ b/src/util/ip_match.c @@ -0,0 +1,676 @@ +/*++ +/* NAME +/* ip_match 3 +/* SUMMARY +/* IP address pattern matching +/* SYNOPSIS +/* #include <ip_match.h> +/* +/* char *ip_match_parse(byte_codes, pattern) +/* VSTRING *byte_codes; +/* char *pattern; +/* +/* char *ip_match_save(byte_codes) +/* const VSTRING *byte_codes; +/* +/* int ip_match_execute(byte_codes, addr_bytes) +/* cost char *byte_codes; +/* const char *addr_bytes; +/* +/* char *ip_match_dump(printable, byte_codes) +/* VSTRING *printable; +/* const char *byte_codes; +/* DESCRIPTION +/* This module supports IP address pattern matching. See below +/* for a description of the supported address pattern syntax. +/* +/* This implementation aims to minimize the cost of encoding +/* the pattern in internal form, while still providing good +/* matching performance in the typical case. The first byte +/* of an encoded pattern specifies the expected address family +/* (for example, AF_INET); other details of the encoding are +/* private and are subject to change. +/* +/* ip_match_parse() converts the user-specified pattern to +/* internal form. The result value is a null pointer in case +/* of success, or a pointer into the byte_codes buffer with a +/* detailed problem description. +/* +/* ip_match_save() saves the result from ip_match_parse() for +/* longer-term usage. The result should be passed to myfree(). +/* +/* ip_match_execute() matches a binary network in addr_bytes +/* against a byte-code array in byte_codes. It is an error to +/* use different address families for the byte_codes and addr_bytes +/* arguments (the first byte-code value contains the expected +/* address family). The result is non-zero in case of success. +/* +/* ip_match_dump() produces an ASCII dump of a byte-code array. +/* The dump is supposed to be identical to the input pattern +/* modulo upper/lower case or leading nulls with IPv6). This +/* function is primarily a debugging aid. +/* +/* Arguments +/* .IP addr_bytes +/* Binary network address in network-byte order. +/* .IP byte_codes +/* Byte-code array produced by ip_match_parse(). +/* .IP pattern +/* Human-readable address pattern. +/* .IP printable +/* storage for ASCII dump of a byte-code array. +/* IPV4 PATTERN SYNTAX +/* .ad +/* .fi +/* An IPv4 address pattern has four fields separated by ".". +/* Each field is either a decimal number, or a sequence inside +/* "[]" that contains one or more ";"-separated decimal +/* numbers or number..number ranges. +/* +/* Examples of patterns are 1.2.3.4 (matches itself, as one +/* would expect) and 1.2.3.[2,4,6..8] (matches 1.2.3.2, 1.2.3.4, +/* 1.2.3.6, 1.2.3.7, 1.2.3.8). +/* +/* Thus, any pattern field can be a sequence inside "[]", but +/* a "[]" sequence cannot span multiple address fields, and +/* a pattern field cannot contain both a number and a "[]" +/* sequence at the same time. +/* +/* This means that the pattern 1.2.[3.4] is not valid (the +/* sequence [3.4] cannot span two address fields) and the +/* pattern 1.2.3.3[6..9] is also not valid (the last field +/* cannot be both number 3 and sequence [6..9] at the same +/* time). +/* +/* The syntax for IPv4 patterns is as follows: +/* +/* .in +5 +/* v4pattern = v4field "." v4field "." v4field "." v4field +/* .br +/* v4field = v4octet | "[" v4sequence "]" +/* .br +/* v4octet = any decimal number in the range 0 through 255 +/* .br +/* v4sequence = v4seq_member | v4sequence ";" v4seq_member +/* .br +/* v4seq_member = v4octet | v4octet ".." v4octet +/* .in +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this +/* software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <ctype.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstring.h> +#include <ip_match.h> + + /* + * Token values. The in-band values are also used as byte-code values. + */ +#define IP_MATCH_CODE_OPEN '[' /* in-band */ +#define IP_MATCH_CODE_CLOSE ']' /* in-band */ +#define IP_MATCH_CODE_OVAL 'N' /* in-band */ +#define IP_MATCH_CODE_RANGE 'R' /* in-band */ +#define IP_MATCH_CODE_EOF '\0' /* in-band */ +#define IP_MATCH_CODE_ERR 256 /* out-of-band */ + + /* + * SLMs. + */ +#define STR vstring_str +#define LEN VSTRING_LEN + +/* ip_match_save - make longer-term copy of byte code */ + +char *ip_match_save(const VSTRING *byte_codes) +{ + char *dst; + + dst = mymalloc(LEN(byte_codes)); + return (memcpy(dst, STR(byte_codes), LEN(byte_codes))); +} + +/* ip_match_dump - byte-code pretty printer */ + +char *ip_match_dump(VSTRING *printable, const char *byte_codes) +{ + const char *myname = "ip_match_dump"; + const unsigned char *bp; + int octet_count = 0; + int ch; + + /* + * Sanity check. Use different dumping loops for AF_INET and AF_INET6. + */ + if (*byte_codes != AF_INET) + msg_panic("%s: malformed byte-code header", myname); + + /* + * Pretty-print and sanity-check the byte codes. Note: the loops in this + * code have no auto-increment at the end of the iteration. Instead, each + * byte-code handler bumps the byte-code pointer appropriately. + */ + VSTRING_RESET(printable); + bp = (const unsigned char *) byte_codes + 1; + for (;;) { + + /* + * Simple numeric field. + */ + if ((ch = *bp++) == IP_MATCH_CODE_OVAL) { + vstring_sprintf_append(printable, "%d", *bp); + bp += 1; + } + + /* + * Wild-card numeric field. + */ + else if (ch == IP_MATCH_CODE_OPEN) { + vstring_sprintf_append(printable, "["); + for (;;) { + /* Numeric range. */ + if ((ch = *bp++) == IP_MATCH_CODE_RANGE) { + vstring_sprintf_append(printable, "%d..%d", bp[0], bp[1]); + bp += 2; + } + /* Number. */ + else if (ch == IP_MATCH_CODE_OVAL) { + vstring_sprintf_append(printable, "%d", *bp); + bp += 1; + } + /* End-of-wildcard. */ + else if (ch == IP_MATCH_CODE_CLOSE) { + break; + } + /* Corruption. */ + else { + msg_panic("%s: unexpected byte code (decimal %d) " + "after \"%s\"", myname, ch, STR(printable)); + } + /* Output the wild-card field separator and repeat the loop. */ + if (*bp != IP_MATCH_CODE_CLOSE) + vstring_sprintf_append(printable, ";"); + } + vstring_sprintf_append(printable, "]"); + } + + /* + * Corruption. + */ + else { + msg_panic("%s: unexpected byte code (decimal %d) after \"%s\"", + myname, ch, STR(printable)); + } + + /* + * Require four octets, not one more, not one less. + */ + if (++octet_count == 4) { + if (*bp != 0) + msg_panic("%s: unexpected byte code (decimal %d) after \"%s\"", + myname, ch, STR(printable)); + return (STR(printable)); + } + if (*bp == 0) + msg_panic("%s: truncated byte code after \"%s\"", + myname, STR(printable)); + + /* + * Output the address field separator and repeat the loop. + */ + vstring_sprintf_append(printable, "."); + } +} + +/* ip_match_print_code_prefix - printable byte-code prefix */ + +static char *ip_match_print_code_prefix(const char *byte_codes, size_t len) +{ + static VSTRING *printable = 0; + const char *fmt; + const char *bp; + + /* + * This is primarily for emergency debugging so we don't care about + * non-reentrancy. + */ + if (printable == 0) + printable = vstring_alloc(100); + else + VSTRING_RESET(printable); + + /* + * Use decimal for IPv4 and hexadecimal otherwise, so that address octet + * values are easy to recognize. + */ + fmt = (*byte_codes == AF_INET ? "%d " : "%02x "); + for (bp = byte_codes; bp < byte_codes + len; bp++) + vstring_sprintf_append(printable, fmt, *(const unsigned char *) bp); + + return (STR(printable)); +} + +/* ip_match_execute - byte-code matching engine */ + +int ip_match_execute(const char *byte_codes, const char *addr_bytes) +{ + const char *myname = "ip_match_execute"; + const unsigned char *bp; + const unsigned char *ap; + int octet_count = 0; + int ch; + int matched; + + /* + * Sanity check. Use different execute loops for AF_INET and AF_INET6. + */ + if (*byte_codes != AF_INET) + msg_panic("%s: malformed byte-code header (decimal %d)", + myname, *(const unsigned char *) byte_codes); + + /* + * Match the address bytes against the byte codes. Avoid problems with + * (char -> int) sign extension on architectures with signed characters. + */ + bp = (const unsigned char *) byte_codes + 1; + ap = (const unsigned char *) addr_bytes; + + for (octet_count = 0; octet_count < 4; octet_count++, ap++) { + + /* + * Simple numeric field. + */ + if ((ch = *bp++) == IP_MATCH_CODE_OVAL) { + if (*ap == *bp) + bp += 1; + else + return (0); + } + + /* + * Wild-card numeric field. + */ + else if (ch == IP_MATCH_CODE_OPEN) { + matched = 0; + for (;;) { + /* Numeric range. */ + if ((ch = *bp++) == IP_MATCH_CODE_RANGE) { + if (!matched) + matched = (*ap >= bp[0] && *ap <= bp[1]); + bp += 2; + } + /* Number. */ + else if (ch == IP_MATCH_CODE_OVAL) { + if (!matched) + matched = (*ap == *bp); + bp += 1; + } + /* End-of-wildcard. */ + else if (ch == IP_MATCH_CODE_CLOSE) { + break; + } + /* Corruption. */ + else { + size_t len = (const char *) bp - byte_codes - 1; + + msg_panic("%s: unexpected byte code (decimal %d) " + "after \"%s\"", myname, ch, + ip_match_print_code_prefix(byte_codes, len)); + } + } + if (matched == 0) + return (0); + } + + /* + * Corruption. + */ + else { + size_t len = (const char *) bp - byte_codes - 1; + + msg_panic("%s: unexpected byte code (decimal %d) after \"%s\"", + myname, ch, ip_match_print_code_prefix(byte_codes, len)); + } + } + return (1); +} + +/* ip_match_next_token - carve out the next token from input pattern */ + +static int ip_match_next_token(char **pstart, char **psaved_start, int *poval) +{ + unsigned char *cp; + int oval; /* octet value */ + int type; /* token value */ + + /* + * Return a literal, error, or EOF token. Update the read pointer to the + * start of the next token or leave it at the string terminator. + */ +#define IP_MATCH_RETURN_TOK(next, type) \ + do { *pstart = (char *) (next); return (type); } while (0) + + /* + * Return a token that contains an IPv4 address octet value. + */ +#define IP_MATCH_RETURN_TOK_VAL(next, type, oval) do { \ + *poval = (oval); IP_MATCH_RETURN_TOK((next), type); \ + } while (0) + + /* + * Light-weight tokenizer. Each result is an IPv4 address octet value, a + * literal character value, error, or EOF. + */ + *psaved_start = *pstart; + cp = (unsigned char *) *pstart; + if (ISDIGIT(*cp)) { + oval = *cp - '0'; + type = IP_MATCH_CODE_OVAL; + for (cp += 1; ISDIGIT(*cp); cp++) { + oval *= 10; + oval += *cp - '0'; + if (oval > 255) + type = IP_MATCH_CODE_ERR; + } + IP_MATCH_RETURN_TOK_VAL(cp, type, oval); + } else { + IP_MATCH_RETURN_TOK(*cp ? cp + 1 : cp, *cp); + } +} + +/* ipmatch_print_parse_error - formatted parsing error, with context */ + +static void PRINTFLIKE(5, 6) ipmatch_print_parse_error(VSTRING *reply, + char *start, + char *here, + char *next, + const char *fmt,...) +{ + va_list ap; + int start_width; + int here_width; + + /* + * Format the error type. + */ + va_start(ap, fmt); + vstring_vsprintf(reply, fmt, ap); + va_end(ap); + + /* + * Format the error context. The syntax is complex enough that it is + * worth the effort to precisely indicate what input is in error. + * + * XXX Workaround for %.*s to avoid output when a zero width is specified. + */ + if (start != 0) { + start_width = here - start; + here_width = next - here; + vstring_sprintf_append(reply, " at \"%.*s>%.*s<%s\"", + start_width, start_width == 0 ? "" : start, + here_width, here_width == 0 ? "" : here, next); + } +} + +/* ip_match_parse - parse an entire wild-card address pattern */ + +char *ip_match_parse(VSTRING *byte_codes, char *pattern) +{ + int octet_count; + char *saved_cp; + char *cp; + int token_type; + int look_ahead; + int oval; + int saved_oval; + + /* + * Simplify this if we change to {} for wildcard notation. + */ +#define FIND_TERMINATOR(start, cp) do { \ + int _level = 0; \ + for (cp = (start) ; *cp; cp++) { \ + if (*cp == '[') _level++; \ + if (*cp != ']') continue; \ + if (--_level == 0) break; \ + } \ + } while (0) + + /* + * Strip [] around the entire pattern. + */ + if (*pattern == '[') { + FIND_TERMINATOR(pattern, cp); + if (cp[0] == 0) { + vstring_sprintf(byte_codes, "missing \"]\" character"); + return (STR(byte_codes)); + } + if (cp[1] == 0) { + *cp = 0; + pattern += 1; + } + } + + /* + * Sanity check. In this case we can't show any error context. + */ + if (*pattern == 0) { + vstring_sprintf(byte_codes, "empty address pattern"); + return (STR(byte_codes)); + } + + /* + * Simple parser with on-the-fly encoding. For now, IPv4 support only. + * Use different parser loops for IPv4 and IPv6. + */ + VSTRING_RESET(byte_codes); + VSTRING_ADDCH(byte_codes, AF_INET); + octet_count = 0; + cp = pattern; + + /* + * Require four address fields separated by ".", each field containing a + * numeric octet value or a sequence inside []. The loop head has no test + * and does not step the loop variable. The tokenizer advances the loop + * variable, and the loop termination logic is inside the loop. + */ + for (;;) { + switch (token_type = ip_match_next_token(&cp, &saved_cp, &oval)) { + + /* + * Numeric address field. + */ + case IP_MATCH_CODE_OVAL: + VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_OVAL); + VSTRING_ADDCH(byte_codes, oval); + break; + + /* + * Wild-card address field. + */ + case IP_MATCH_CODE_OPEN: + VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_OPEN); + /* Require ";"-separated numbers or numeric ranges. */ + for (;;) { + token_type = ip_match_next_token(&cp, &saved_cp, &oval); + if (token_type == IP_MATCH_CODE_OVAL) { + saved_oval = oval; + look_ahead = ip_match_next_token(&cp, &saved_cp, &oval); + /* Numeric range. */ + if (look_ahead == '.') { + /* Brute-force parsing. */ + if (ip_match_next_token(&cp, &saved_cp, &oval) == '.' + && ip_match_next_token(&cp, &saved_cp, &oval) + == IP_MATCH_CODE_OVAL + && saved_oval <= oval) { + VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_RANGE); + VSTRING_ADDCH(byte_codes, saved_oval); + VSTRING_ADDCH(byte_codes, oval); + look_ahead = + ip_match_next_token(&cp, &saved_cp, &oval); + } else { + ipmatch_print_parse_error(byte_codes, pattern, + saved_cp, cp, + "numeric range error"); + return (STR(byte_codes)); + } + } + /* Single number. */ + else { + VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_OVAL); + VSTRING_ADDCH(byte_codes, saved_oval); + } + /* Require ";" or end-of-wildcard. */ + token_type = look_ahead; + if (token_type == ';') { + continue; + } else if (token_type == IP_MATCH_CODE_CLOSE) { + break; + } else { + ipmatch_print_parse_error(byte_codes, pattern, + saved_cp, cp, + "need \";\" or \"%c\"", + IP_MATCH_CODE_CLOSE); + return (STR(byte_codes)); + } + } else { + ipmatch_print_parse_error(byte_codes, pattern, saved_cp, cp, + "need decimal number 0..255"); + return (STR(byte_codes)); + } + } + VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_CLOSE); + break; + + /* + * Invalid field. + */ + default: + ipmatch_print_parse_error(byte_codes, pattern, saved_cp, cp, + "need decimal number 0..255 or \"%c\"", + IP_MATCH_CODE_OPEN); + return (STR(byte_codes)); + } + octet_count += 1; + + /* + * Require four address fields. Not one more, not one less. + */ + if (octet_count == 4) { + if (*cp != 0) { + (void) ip_match_next_token(&cp, &saved_cp, &oval); + ipmatch_print_parse_error(byte_codes, pattern, saved_cp, cp, + "garbage after pattern"); + return (STR(byte_codes)); + } + VSTRING_ADDCH(byte_codes, 0); + return (0); + } + + /* + * Require "." before the next address field. + */ + if (ip_match_next_token(&cp, &saved_cp, &oval) != '.') { + ipmatch_print_parse_error(byte_codes, pattern, saved_cp, cp, + "need \".\""); + return (STR(byte_codes)); + } + } +} + +#ifdef TEST + + /* + * Dummy main program for regression tests. + */ +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdlib.h> +#include <unistd.h> +#include <vstream.h> +#include <vstring_vstream.h> +#include <stringops.h> + +int main(int argc, char **argv) +{ + VSTRING *byte_codes = vstring_alloc(100); + VSTRING *line_buf = vstring_alloc(100); + char *bufp; + char *err; + char *user_pattern; + char *user_address; + int echo_input = !isatty(0); + + /* + * Iterate over the input stream. The input format is a pattern, followed + * by optional addresses to match against. + */ + while (vstring_fgets_nonl(line_buf, VSTREAM_IN)) { + bufp = STR(line_buf); + if (echo_input) { + vstream_printf("> %s\n", bufp); + vstream_fflush(VSTREAM_OUT); + } + if (*bufp == '#') + continue; + if ((user_pattern = mystrtok(&bufp, " \t")) == 0) + continue; + + /* + * Parse and dump the pattern. + */ + if ((err = ip_match_parse(byte_codes, user_pattern)) != 0) { + vstream_printf("Error: %s\n", err); + } else { + vstream_printf("Code: %s\n", + ip_match_dump(line_buf, STR(byte_codes))); + } + vstream_fflush(VSTREAM_OUT); + + /* + * Match the optional patterns. + */ + while ((user_address = mystrtok(&bufp, " \t")) != 0) { + struct in_addr netw_addr; + + switch (inet_pton(AF_INET, user_address, &netw_addr)) { + case 1: + vstream_printf("Match %s: %s\n", user_address, + ip_match_execute(STR(byte_codes), + (char *) &netw_addr.s_addr) ? + "yes" : "no"); + break; + case 0: + vstream_printf("bad address syntax: %s\n", user_address); + break; + case -1: + vstream_printf("%s: %m\n", user_address); + break; + } + vstream_fflush(VSTREAM_OUT); + } + } + vstring_free(line_buf); + vstring_free(byte_codes); + exit(0); +} + +#endif diff --git a/src/util/ip_match.h b/src/util/ip_match.h new file mode 100644 index 0000000..781a04a --- /dev/null +++ b/src/util/ip_match.h @@ -0,0 +1,38 @@ +#ifndef _IP_MATCH_H_INCLUDED_ +#define _IP_MATCH_H_INCLUDED_ + +/*++ +/* NAME +/* ip_match 3h +/* SUMMARY +/* IP address pattern matching +/* SYNOPSIS +/* #include <ip_match.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <vstring.h> + + /* + * External interface. + */ +extern char *ip_match_parse(VSTRING *, char *); +extern char *ip_match_save(const VSTRING *); +extern char *ip_match_dump(VSTRING *, const char *); +extern int ip_match_execute(const char *, const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/ip_match.in b/src/util/ip_match.in new file mode 100644 index 0000000..eec13e3 --- /dev/null +++ b/src/util/ip_match.in @@ -0,0 +1,26 @@ +1.2.3.4 +1.2.300.4 +1.2.3000.4 +1.2.3. +1.2.3 +a +1.2.3;4 +1.2.[3].4 +1.2.[].4 +1.2.[.4 +1.2.].4 +1.2.[1..127;128..255].5 +1.2.[1-255].5 +1.2.[1..127.128..255].5 +1.2.3.[4] +1.2.3.[4..1] +1.2.3.[4.1] +1.2.3.[4.x] +1.2.3.[x] +1.2.3.4x +1.2.[3..11].5 1.2.3.5 1.2.2.5 1.2.11.5 1.2.12.5 1.2.11.6 +1.2.[3;5;7;9;11].5 1.2.3.5 1.2.2.5 1.2.4.5 1.2.11.5 1.2.12.5 1.2.11.6 +[1;2].3.4.5 1.3.4.5 2.3.4.5 3.3.4.5 +[[1;2].3.4.5] 1.3.4.5 2.3.4.5 3.3.4.5 +[[1;2].3.4.5 +1;2].3.4.5 diff --git a/src/util/ip_match.ref b/src/util/ip_match.ref new file mode 100644 index 0000000..04b291f --- /dev/null +++ b/src/util/ip_match.ref @@ -0,0 +1,69 @@ +> 1.2.3.4 +Code: 1.2.3.4 +> 1.2.300.4 +Error: need decimal number 0..255 or "[" at "1.2.>300<.4" +> 1.2.3000.4 +Error: need decimal number 0..255 or "[" at "1.2.>3000<.4" +> 1.2.3. +Error: need decimal number 0..255 or "[" at "1.2.3.><" +> 1.2.3 +Error: need "." at "1.2.3><" +> a +Error: need decimal number 0..255 or "[" at ">a<" +> 1.2.3;4 +Error: need "." at "1.2.3>;<4" +> 1.2.[3].4 +Code: 1.2.[3].4 +> 1.2.[].4 +Error: need decimal number 0..255 at "1.2.[>]<.4" +> 1.2.[.4 +Error: need decimal number 0..255 at "1.2.[>.<4" +> 1.2.].4 +Error: need decimal number 0..255 or "[" at "1.2.>]<.4" +> 1.2.[1..127;128..255].5 +Code: 1.2.[1..127;128..255].5 +> 1.2.[1-255].5 +Error: need ";" or "]" at "1.2.[1>-<255].5" +> 1.2.[1..127.128..255].5 +Error: need ";" or "]" at "1.2.[1..127>.<128..255].5" +> 1.2.3.[4] +Code: 1.2.3.[4] +> 1.2.3.[4..1] +Error: numeric range error at "1.2.3.[4..>1<]" +> 1.2.3.[4.1] +Error: numeric range error at "1.2.3.[4.>1<]" +> 1.2.3.[4.x] +Error: numeric range error at "1.2.3.[4.>x<]" +> 1.2.3.[x] +Error: need decimal number 0..255 at "1.2.3.[>x<]" +> 1.2.3.4x +Error: garbage after pattern at "1.2.3.4>x<" +> 1.2.[3..11].5 1.2.3.5 1.2.2.5 1.2.11.5 1.2.12.5 1.2.11.6 +Code: 1.2.[3..11].5 +Match 1.2.3.5: yes +Match 1.2.2.5: no +Match 1.2.11.5: yes +Match 1.2.12.5: no +Match 1.2.11.6: no +> 1.2.[3;5;7;9;11].5 1.2.3.5 1.2.2.5 1.2.4.5 1.2.11.5 1.2.12.5 1.2.11.6 +Code: 1.2.[3;5;7;9;11].5 +Match 1.2.3.5: yes +Match 1.2.2.5: no +Match 1.2.4.5: no +Match 1.2.11.5: yes +Match 1.2.12.5: no +Match 1.2.11.6: no +> [1;2].3.4.5 1.3.4.5 2.3.4.5 3.3.4.5 +Code: [1;2].3.4.5 +Match 1.3.4.5: yes +Match 2.3.4.5: yes +Match 3.3.4.5: no +> [[1;2].3.4.5] 1.3.4.5 2.3.4.5 3.3.4.5 +Code: [1;2].3.4.5 +Match 1.3.4.5: yes +Match 2.3.4.5: yes +Match 3.3.4.5: no +> [[1;2].3.4.5 +Error: missing "]" character +> 1;2].3.4.5 +Error: need "." at "1>;<2].3.4.5" diff --git a/src/util/killme_after.c b/src/util/killme_after.c new file mode 100644 index 0000000..34e0434 --- /dev/null +++ b/src/util/killme_after.c @@ -0,0 +1,69 @@ +/*++ +/* NAME +/* killme_after 3 +/* SUMMARY +/* programmed death +/* SYNOPSIS +/* #include <killme_after.h> +/* +/* void killme_after(seconds) +/* unsigned int seconds; +/* DESCRIPTION +/* The killme_after() function does a best effort to terminate +/* the process after the specified time, should it still exist. +/* It is meant to be used in a signal handler, as an insurance +/* against getting stuck somewhere while preparing for exit. +/* DIAGNOSTICS +/* None. This routine does a best effort, damn the torpedoes. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <signal.h> +#include <unistd.h> + +/* Utility library. */ + +#include <killme_after.h> + +/* killme_after - self-assured death */ + +void killme_after(unsigned int seconds) +{ + struct sigaction sig_action; + + /* + * Schedule an ALARM signal, and make sure the signal will be delivered + * even if we are being called from a signal handler and SIGALRM delivery + * is blocked. + * + * Undocumented: when a process runs with PID 1, Linux won't deliver a + * signal unless the process specifies a handler (i.e. SIG_DFL is treated + * as SIG_IGN). Conveniently, _exit() can be used directly as a signal + * handler. This changes the wait status that a parent would see, but in + * the case of "init" mode on Linux, no-one would care. + */ + alarm(0); + sigemptyset(&sig_action.sa_mask); + sig_action.sa_flags = 0; + sig_action.sa_handler = (getpid() == 1 ? _exit : SIG_DFL); + sigaction(SIGALRM, &sig_action, (struct sigaction *) 0); + alarm(seconds); + sigaddset(&sig_action.sa_mask, SIGALRM); + sigprocmask(SIG_UNBLOCK, &sig_action.sa_mask, (sigset_t *) 0); +} diff --git a/src/util/killme_after.h b/src/util/killme_after.h new file mode 100644 index 0000000..9505b2d --- /dev/null +++ b/src/util/killme_after.h @@ -0,0 +1,30 @@ +#ifndef _KILLME_AFTER_H_INCLUDED_ +#define _KILLME_AFTER_H_INCLUDED_ + +/*++ +/* NAME +/* killme_after 3h +/* SUMMARY +/* programmed death +/* SYNOPSIS +/* #include "killme_after.h" +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern void killme_after(unsigned int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/known_tcp_ports.c b/src/util/known_tcp_ports.c new file mode 100644 index 0000000..1d524d4 --- /dev/null +++ b/src/util/known_tcp_ports.c @@ -0,0 +1,253 @@ +/*++ +/* NAME +/* known_tcp_ports 3 +/* SUMMARY +/* reduce dependency on the services(5) database +/* SYNOPSIS +/* #include <known_tcp_ports.h> +/* +/* const char *add_known_tcp_port( +/* const char *name) +/* const char *port) +/* +/* const char *filter_known_tcp_port( +/* const char *name_or_port) +/* +/* void clear_known_tcp_ports(void) +/* AUXILIARY FUNCTIONS +/* char *export_known_tcp_ports( +/* VSTRING *result) +/* DESCRIPTION +/* This module reduces dependency on the services(5) database. +/* +/* add_known_tcp_port() associates a symbolic name with a numerical +/* port. The function returns a pointer to error text if the +/* arguments are malformed or if the symbolic name already has +/* an association. +/* +/* filter_known_tcp_port() returns the argument if it does not +/* specify a symbolic name, or if the argument specifies a symbolic +/* name that is not associated with a numerical port. Otherwise, +/* it returns the associated numerical port. +/* +/* clear_known_tcp_ports() destroys all name-number associations. +/* string. +/* +/* export_known_tcp_ports() overwrites a VSTRING with all known +/* name=port associations, sorted by service name, and separated +/* by whitespace. The result is pointer to the VSTRING payload. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library + */ +#include <sys_defs.h> +#include <stdlib.h> +#include <string.h> + + /* + * Utility library + */ +#include <htable.h> +#include <mymalloc.h> +#include <stringops.h> + + /* + * Application-specific. + */ +#include <known_tcp_ports.h> + +#define STR(x) vstring_str(x) + +static HTABLE *known_tcp_ports; + +/* add_known_tcp_port - associate symbolic name with numerical port */ + +const char *add_known_tcp_port(const char *name, const char *port) +{ + if (alldig(name)) + return ("numerical service name"); + if (!alldig(port)) + return ("non-numerical service port"); + if (known_tcp_ports == 0) + known_tcp_ports = htable_create(10); + if (htable_locate(known_tcp_ports, name) != 0) + return ("duplicate service name"); + (void) htable_enter(known_tcp_ports, name, mystrdup(port)); + return (0); +} + +/* filter_known_tcp_port - replace argument if associated with known port */ + +const char *filter_known_tcp_port(const char *name_or_port) +{ + HTABLE_INFO *ht; + + if (name_or_port == 0 || known_tcp_ports == 0 || alldig(name_or_port)) { + return (name_or_port); + } else if ((ht = htable_locate(known_tcp_ports, name_or_port)) != 0) { + return (ht->value); + } else { + return (name_or_port); + } +} + +/* clear_known_tcp_ports - destroy all name-port associations */ + +void clear_known_tcp_ports(void) +{ + htable_free(known_tcp_ports, myfree); + known_tcp_ports = 0; +} + +/* compare_ht_keys - compare table keys */ + +static int compare_ht_keys(const void *a, const void *b) +{ + HTABLE_INFO **ap = (HTABLE_INFO **) a; + HTABLE_INFO **bp = (HTABLE_INFO **) b; + + return (strcmp((const char *) ap[0]->key, (const char *) bp[0]->key)); +} + +/* export_known_tcp_ports - sorted dump */ + +char *export_known_tcp_ports(VSTRING *out) +{ + HTABLE_INFO **list; + HTABLE_INFO **ht; + + VSTRING_RESET(out); + if (known_tcp_ports) { + list = htable_list(known_tcp_ports); + qsort((void *) list, known_tcp_ports->used, sizeof(*list), + compare_ht_keys); + for (ht = list; *ht; ht++) + vstring_sprintf_append(out, "%s%s=%s", ht > list ? " " : "", + ht[0]->key, (const char *) ht[0]->value); + myfree((void *) list); + } + VSTRING_TERMINATE(out); + return (STR(out)); +} + +#ifdef TEST + +#include <msg.h> + +struct association { + const char *lhs; /* service name */ + const char *rhs; /* service port */ +}; + +struct probe { + const char *query; /* query */ + const char *exp_reply; /* expected reply */ +}; + +struct test_case { + const char *label; /* identifies test case */ + struct association associations[10]; + const char *exp_err; /* expected error */ + const char *exp_export; /* expected export output */ + struct probe probes[10]; +}; + +struct test_case test_cases[] = { + {"good", + /* association */ {{"smtp", "25"}, {"lmtp", "24"}, 0}, + /* error */ 0, + /* export */ "lmtp=24 smtp=25", + /* probe */ {{"smtp", "25"}, {"1", "1"}, {"x", "x"}, {"lmtp", "24"}, 0} + }, + {"duplicate lhs", + /* association */ {{"smtp", "25"}, {"smtp", "100"}, 0}, + /* error */ "duplicate service name" + }, + {"numerical lhs", + /* association */ {{"100", "100"}, 0}, + /* error */ "numerical service name" + }, + {"symbolic rhs", + /* association */ {{"smtp", "lmtp"}, 0}, + /* error */ "non-numerical service port" + }, + {"uninitialized", + /* association */ {0}, + /* error */ 0, + /* export */ "", + /* probe */ {{"smtp", "smtp"}, {"1", "1"}, {"x", "x"}, 0} + }, + 0, +}; + +int main(int argc, char **argv) +{ + VSTRING *export_buf; + struct test_case *tp; + struct association *ap; + struct probe *pp; + int pass = 0; + int fail = 0; + const char *err; + int test_failed; + const char *reply; + const char *export; + +#define STRING_OR_NULL(s) ((s) ? (s) : "(null)") + + export_buf = vstring_alloc(100); + for (tp = test_cases; tp->label != 0; tp++) { + test_failed = 0; + for (err = 0, ap = tp->associations; err == 0 && ap->lhs != 0; ap++) + err = add_known_tcp_port(ap->lhs, ap->rhs); + if (!err != !tp->exp_err) { + msg_warn("test case %s: got error: \"%s\", want: \"%s\"", + tp->label, STRING_OR_NULL(err), STRING_OR_NULL(tp->exp_err)); + test_failed = 1; + } else if (err != 0) { + if (strcmp(err, tp->exp_err) != 0) { + msg_warn("test case %s: got err: \"%s\", want: \"%s\"", + tp->label, err, tp->exp_err); + test_failed = 1; + } + } else { + export = export_known_tcp_ports(export_buf); + if (strcmp(export, tp->exp_export) != 0) { + msg_warn("test case %s: got export: \"%s\", want: \"%s\"", + tp->label, export, tp->exp_export); + test_failed = 1; + } + for (pp = tp->probes; test_failed == 0 && pp->query != 0; pp++) { + reply = filter_known_tcp_port(pp->query); + if (strcmp(reply, pp->exp_reply) != 0) { + msg_warn("test case %s: got reply: \"%s\", want: \"%s\"", + tp->label, reply, pp->exp_reply); + test_failed = 1; + } + } + } + clear_known_tcp_ports(); + if (test_failed) { + msg_info("%s: FAIL", tp->label); + fail++; + } else { + msg_info("%s: PASS", tp->label); + pass++; + } + } + msg_info("PASS=%d FAIL=%d", pass, fail); + vstring_free(export_buf); + exit(fail != 0); +} + +#endif diff --git a/src/util/known_tcp_ports.h b/src/util/known_tcp_ports.h new file mode 100644 index 0000000..bc254f2 --- /dev/null +++ b/src/util/known_tcp_ports.h @@ -0,0 +1,38 @@ +#ifndef _KNOWN_TCP_PORTS_H_INCLUDED_ +#define _KNOWN_TCP_PORTS_H_INCLUDED_ + +/*++ +/* NAME +/* known_tcp_port 3h +/* SUMMARY +/* reduce dependency on the services(5) database +/* SYNOPSIS +/* #include <known_tcp_ports.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <vstring.h> + + /* + * External interface. + */ +extern const char *add_known_tcp_port(const char *name, const char *port); +extern const char *filter_known_tcp_port(const char *name_or_port); +extern void clear_known_tcp_ports(void); +extern char *export_known_tcp_ports(VSTRING *out); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/known_tcp_ports.ref b/src/util/known_tcp_ports.ref new file mode 100644 index 0000000..adcf182 --- /dev/null +++ b/src/util/known_tcp_ports.ref @@ -0,0 +1,6 @@ +unknown: good: PASS +unknown: duplicate lhs: PASS +unknown: numerical lhs: PASS +unknown: symbolic rhs: PASS +unknown: uninitialized: PASS +unknown: PASS=5 FAIL=0 diff --git a/src/util/ldseed.c b/src/util/ldseed.c new file mode 100644 index 0000000..c231152 --- /dev/null +++ b/src/util/ldseed.c @@ -0,0 +1,138 @@ +/*++ +/* NAME +/* ldseed 3 +/* SUMMARY +/* seed for non-cryptographic applications +/* SYNOPSIS +/* #include <ldseed.h> +/* +/* void ldseed( +/* void *dst, +/* size_t len) +/* DESCRIPTION +/* ldseed() preferably extracts pseudo-random bits from +/* /dev/urandom, a non-blocking device that is available on +/* modern systems. +/* +/* On systems where /dev/urandom is unavailable or does not +/* immediately return the requested amount of randomness, +/* ldseed() falls back to a combination of wallclock time, +/* the time since boot, and the process ID. +/* BUGS +/* With Linux "the O_NONBLOCK flag has no effect when opening +/* /dev/urandom", but reads "can incur an appreciable delay +/* when requesting large amounts of data". Apparently, "large" +/* means more than 256 bytes. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library + */ +#include <sys_defs.h> +#include <string.h> +#include <sys/time.h> +#include <time.h> +#include <stdlib.h> +#include <fcntl.h> +#include <unistd.h> +#include <limits.h> /* CHAR_BIT */ + + /* + * Utility library. + */ +#include <iostuff.h> +#include <msg.h> +#include <ldseed.h> + + /* + * Different systems have different names for non-wallclock time. + */ +#ifdef CLOCK_UPTIME +#define NON_WALLTIME_CLOCK CLOCK_UPTIME +#elif defined(CLOCK_BOOTTIME) +#define NON_WALLTIME_CLOCK CLOCK_BOOTTIME +#elif defined(CLOCK_MONOTONIC) +#define NON_WALLTIME_CLOCK CLOCK_MONOTONIC +#elif defined(CLOCK_HIGHRES) +#define NON_WALLTIME_CLOCK CLOCK_HIGHRES +#endif + +/* ldseed - best-effort, low-dependency seed */ + +void ldseed(void *dst, size_t len) +{ + int count; + int fd; + int n; + time_t fallback = 0; + + /* + * Medium-quality seed. + */ + if ((fd = open("/dev/urandom", O_RDONLY)) > 0) { + non_blocking(fd, NON_BLOCKING); + count = read(fd, dst, len); + (void) close(fd); + if (count == len) + return; + } + + /* + * Low-quality seed. Based on 1) the time since boot (good when an + * attacker knows the program start time but not the system boot time), + * and 2) absolute time (good when an attacker does not know the program + * start time). Assumes a system with better than microsecond resolution, + * and a network stack that does not leak the time since boot, for + * example, through TCP or ICMP timestamps. With those caveats, this seed + * is good for 20-30 bits of randomness. + */ +#ifdef NON_WALLTIME_CLOCK + { + struct timespec ts; + + if (clock_gettime(NON_WALLTIME_CLOCK, &ts) != 0) + msg_fatal("clock_gettime() failed: %m"); + fallback += ts.tv_sec ^ ts.tv_nsec; + } +#elif defined(USE_GETHRTIME) + fallback += gethrtime(); +#endif + +#ifdef CLOCK_REALTIME + { + struct timespec ts; + + if (clock_gettime(CLOCK_REALTIME, &ts) != 0) + msg_fatal("clock_gettime() failed: %m"); + fallback += ts.tv_sec ^ ts.tv_nsec; + } +#else + { + struct timeval tv; + + if (GETTIMEOFDAY(&tv) != 0) + msg_fatal("gettimeofday() failed: %m"); + fallback += tv.tv_sec + tv.tv_usec; + } +#endif + fallback += getpid(); + + /* + * Copy the least significant bytes first, because those are the most + * volatile. + */ + for (n = 0; n < sizeof(fallback) && n < len; n++) { + *(char *) dst++ ^= (fallback & 0xff); + fallback >>= CHAR_BIT; + } + return; +} diff --git a/src/util/ldseed.h b/src/util/ldseed.h new file mode 100644 index 0000000..891986e --- /dev/null +++ b/src/util/ldseed.h @@ -0,0 +1,30 @@ +#ifndef _LDSEED_H_INCLUDED_ +#define _LDSEED_H_INCLUDED_ + +/*++ +/* NAME +/* ldseed 3h +/* SUMMARY +/* seed for non-cryptographic applications +/* SYNOPSIS +/* #include <ldseed.h> +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern void ldseed(void *, size_t); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/line_number.c b/src/util/line_number.c new file mode 100644 index 0000000..557e053 --- /dev/null +++ b/src/util/line_number.c @@ -0,0 +1,71 @@ +/*++ +/* NAME +/* line_number 3 +/* SUMMARY +/* line number utilities +/* SYNOPSIS +/* #include <line_number.h> +/* +/* char *format_line_number(result, first, last) +/* VSTRING *buffer; +/* ssize_t first; +/* ssize_t lastl +/* DESCRIPTION +/* format_line_number() formats a line number or number range. +/* The output is <first-number>-<last-number> when the numbers +/* differ, <first-number> when the numbers are identical. +/* .IP result +/* Result buffer, or null-pointer. In the latter case the +/* result is stored in a static buffer that is overwritten +/* with subsequent calls. The function result value is a +/* pointer into the result buffer. +/* .IP first +/* First line number. +/* .IP last +/* Last line number. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + + /* + * System library. + */ +#include <sys_defs.h> + + /* + * Utility library. + */ +#include <vstring.h> +#include <line_number.h> + +/* format_line_number - pretty-print line number or number range */ + +char *format_line_number(VSTRING *result, ssize_t first, ssize_t last) +{ + static VSTRING *buf; + + /* + * Your buffer or mine? + */ + if (result == 0) { + if (buf == 0) + buf = vstring_alloc(10); + result = buf; + } + + /* + * Print a range only when the numbers differ. + */ + vstring_sprintf(result, "%ld", (long) first); + if (first != last) + vstring_sprintf_append(result, "-%ld", (long) last); + + return (vstring_str(result)); +} diff --git a/src/util/line_number.h b/src/util/line_number.h new file mode 100644 index 0000000..0a53d15 --- /dev/null +++ b/src/util/line_number.h @@ -0,0 +1,35 @@ +#ifndef _LINE_NUMBER_H_INCLUDED_ +#define _LINE_NUMBER_H_INCLUDED_ + +/*++ +/* NAME +/* line_number 3h +/* SUMMARY +/* line number utilities +/* SYNOPSIS +/* #include <line_number.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <vstring.h> + + /* + * External interface. + */ +extern char *format_line_number(VSTRING *, ssize_t, ssize_t); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/line_wrap.c b/src/util/line_wrap.c new file mode 100644 index 0000000..0f399e8 --- /dev/null +++ b/src/util/line_wrap.c @@ -0,0 +1,122 @@ +/*++ +/* NAME +/* line_wrap 3 +/* SUMMARY +/* wrap long lines upon output +/* SYNOPSIS +/* #include <line_wrap.h> +/* +/* void line_wrap(string, len, indent, output_fn, context) +/* const char *buf; +/* int len; +/* int indent; +/* void (*output_fn)(const char *str, int len, int indent, void *context); +/* void *context; +/* DESCRIPTION +/* The \fBline_wrap\fR routine outputs the specified string via +/* the specified output function, and attempts to keep output lines +/* shorter than the specified length. The routine does not attempt to +/* break long words that do not fit on a single line. Upon output, +/* trailing whitespace is stripped. +/* +/* Arguments +/* .IP string +/* The input, which cannot contain any newline characters. +/* .IP len +/* The desired maximal output line length. +/* .IP indent +/* The desired amount of indentation of the second etc. output lines +/* with respect to the first output line. A negative indent causes +/* only the first line to be indented; a positive indent causes all +/* but the first line to be indented. A zero count causes no indentation. +/* .IP output_fn +/* The output function that is called with as arguments a string +/* pointer, a string length, a non-negative indentation count, and +/* application context. A typical implementation looks like this: +/* .sp +/* .nf +/* .na +void print(const char *str, int len, int indent, void *context) +{ + VSTREAM *fp = (VSTREAM *) context; + + vstream_fprintf(fp, "%*s%.*s", indent, "", len, str); +} +/* .fi +/* .ad +/* .IP context +/* Application context that is passed on to the output function. +/* For example, a VSTREAM pointer, or a structure that contains +/* a VSTREAM pointer. +/* BUGS +/* No tab expansion and no backspace processing. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> +#include <ctype.h> + +/* Utility library. */ + +#include <line_wrap.h> + +/* line_wrap - wrap long lines upon output */ + +void line_wrap(const char *str, int len, int indent, LINE_WRAP_FN output_fn, + void *context) +{ + const char *start_line; + const char *word; + const char *next_word; + const char *next_space; + int line_len; + int curr_len; + int curr_indent; + + if (indent < 0) { + curr_indent = -indent; + curr_len = len + indent; + } else { + curr_indent = 0; + curr_len = len; + } + + /* + * At strategic positions, output what we have seen, after stripping off + * trailing blanks. + */ + for (start_line = word = str; word != 0; word = next_word) { + next_space = word + strcspn(word, " \t"); + if (word > start_line) { + if (next_space - start_line > curr_len) { + line_len = word - start_line; + while (line_len > 0 && ISSPACE(start_line[line_len - 1])) + line_len--; + output_fn(start_line, line_len, curr_indent, context); + while (*word && ISSPACE(*word)) + word++; + if (start_line == str) { + curr_indent += indent; + curr_len -= indent; + } + start_line = word; + } + } + next_word = *next_space ? next_space + 1 : 0; + } + line_len = strlen(start_line); + while (line_len > 0 && ISSPACE(start_line[line_len - 1])) + line_len--; + output_fn(start_line, line_len, curr_indent, context); +} diff --git a/src/util/line_wrap.h b/src/util/line_wrap.h new file mode 100644 index 0000000..32c3548 --- /dev/null +++ b/src/util/line_wrap.h @@ -0,0 +1,31 @@ +#ifndef _LINE_WRAP_H_INCLUDED_ +#define _LINE_WRAP_H_INCLUDED_ + +/*++ +/* NAME +/* line_wrap 3h +/* SUMMARY +/* wrap long lines upon output +/* SYNOPSIS +/* #include <line_wrap.h> +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +typedef void (*LINE_WRAP_FN) (const char *, int, int, void *); +extern void line_wrap(const char *, int, int, LINE_WRAP_FN, void *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/listen.h b/src/util/listen.h new file mode 100644 index 0000000..131755c --- /dev/null +++ b/src/util/listen.h @@ -0,0 +1,53 @@ +#ifndef _LISTEN_H_INCLUDED_ +#define _LISTEN_H_INCLUDED_ + +/*++ +/* NAME +/* listen 3h +/* SUMMARY +/* listener interface file +/* SYNOPSIS +/* #include <listen.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <iostuff.h> +#include <htable.h> + + /* + * Listener external interface. + */ +extern int unix_listen(const char *, int, int); +extern int inet_listen(const char *, int, int); +extern int fifo_listen(const char *, int, int); +extern int stream_listen(const char *, int, int); +extern int unix_dgram_listen(const char *, int); + +extern int inet_accept(int); +extern int unix_accept(int); +extern int stream_accept(int); + +extern int WARN_UNUSED_RESULT recv_pass_attr(int, HTABLE **, int, ssize_t); +extern int pass_accept(int); +extern int pass_accept_attr(int, HTABLE **); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/lmdb_cache_test_1.sh b/src/util/lmdb_cache_test_1.sh new file mode 100644 index 0000000..f5d4767 --- /dev/null +++ b/src/util/lmdb_cache_test_1.sh @@ -0,0 +1,55 @@ +#!/bin/sh + +# Torture test: run update and purge operations in parallel processes. +# This will result in some purge operations not finding all entries, +# but the final sequential purge should eliminate all. + +set -e + +rm -f foo.lmdb +./dict_cache <<EOF +lmdb_map_size 20000 +cache lmdb:foo +EOF + +(./dict_cache <<EOF +cache lmdb:foo +update x ${1-10000} +run +update y ${1-10000} +purge x +run +purge y +run +EOF +) & + +(./dict_cache <<EOF +cache lmdb:foo +update a ${1-10000} +run +update b ${1-10000} +purge a +run +purge b +run +EOF +) & + +wait + +./dict_cache <<EOF +cache lmdb:foo +purge a +run +purge b +run +purge x +run +purge y +run +EOF + +../../bin/postmap -s lmdb:foo | diff /dev/null - + +rm -f foo.lmdb diff --git a/src/util/lmdb_cache_test_2.sh b/src/util/lmdb_cache_test_2.sh new file mode 100644 index 0000000..a54e7bd --- /dev/null +++ b/src/util/lmdb_cache_test_2.sh @@ -0,0 +1,42 @@ +#!/bin/sh + +# Torture test: run update and delete ${1-10000}perations in +# parallel processes. None should remain. + +set -e + +rm -f foo.lmdb +./dict_cache <<EOF +lmdb_map_size 20000 +cache lmdb:foo +EOF + +(./dict_cache <<EOF +cache lmdb:foo +update x ${1-10000} +run +update y ${1-10000} +delete x ${1-10000} +run +delete y ${1-10000} +run +EOF +) & + +(./dict_cache <<EOF +cache lmdb:foo +update a ${1-10000} +run +update b ${1-10000} +delete a ${1-10000} +run +delete b ${1-10000} +run +EOF +) & + +wait + +../../bin/postmap -s lmdb:foo | diff /dev/null - + +rm -f foo.lmdb diff --git a/src/util/load_file.c b/src/util/load_file.c new file mode 100644 index 0000000..4e575d1 --- /dev/null +++ b/src/util/load_file.c @@ -0,0 +1,80 @@ +/*++ +/* NAME +/* load_file 3 +/* SUMMARY +/* load file with some prejudice +/* SYNOPSIS +/* #include <load_file.h> +/* +/* void load_file(path, action, context) +/* const char *path; +/* void (*action)(VSTREAM, void *); +/* void *context; +/* DESCRIPTION +/* This routine reads a file and reads it again when the +/* file changed recently. +/* +/* Arguments: +/* .IP path +/* The file to be opened, read-only. +/* .IP action +/* The function that presumably reads the file. +/* .IP context +/* Application-specific context for the action routine. +/* DIAGNOSTICS +/* Fatal errors: out of memory, cannot open file. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/stat.h> +#include <time.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstream.h> +#include <iostuff.h> +#include <load_file.h> +#include <warn_stat.h> + +/* load_file - load file with some prejudice */ + +void load_file(const char *path, LOAD_FILE_FN action, void *context) +{ + VSTREAM *fp; + struct stat st; + time_t before; + time_t after; + + /* + * Read the file again if it is hot. This may result in reading a partial + * parameter name or missing end marker when a file changes in the middle + * of a read. + */ + for (before = time((time_t *) 0); /* see below */ ; before = after) { + if ((fp = vstream_fopen(path, O_RDONLY, 0)) == 0) + msg_fatal("open %s: %m", path); + action(fp, context); + if (fstat(vstream_fileno(fp), &st) < 0) + msg_fatal("fstat %s: %m", path); + if (vstream_ferror(fp) || vstream_fclose(fp)) + msg_fatal("read %s: %m", path); + after = time((time_t *) 0); + if (st.st_mtime < before - 1 || st.st_mtime > after) + break; + if (msg_verbose) + msg_info("pausing to let %s cool down", path); + doze(300000); + } +} diff --git a/src/util/load_file.h b/src/util/load_file.h new file mode 100644 index 0000000..3e635f3 --- /dev/null +++ b/src/util/load_file.h @@ -0,0 +1,32 @@ +#ifndef LOAD_FILE_H_INCLUDED_ +#define LOAD_FILE_H_INCLUDED_ + +/*++ +/* NAME +/* load_file 3h +/* SUMMARY +/* load file with some prejudice +/* SYNOPSIS +/* #include <load_file.h> +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +typedef void (*LOAD_FILE_FN)(VSTREAM *, void *); + +extern void load_file(const char *, LOAD_FILE_FN, void *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/load_lib.c b/src/util/load_lib.c new file mode 100644 index 0000000..44e540d --- /dev/null +++ b/src/util/load_lib.c @@ -0,0 +1,146 @@ +/*++ +/* NAME +/* load_lib 3 +/* SUMMARY +/* library loading wrappers +/* SYNOPSIS +/* #include <load_lib.h> +/* +/* void load_library_symbols(const char *, LIB_FN *, LIB_DP *); +/* const char *libname; +/* LIB_FN *libfuncs; +/* LIB_DP *libdata; +/* DESCRIPTION +/* load_library_symbols() loads the specified shared object +/* and looks up the function or data pointers for the specified +/* symbols. All errors are fatal. +/* +/* Arguments: +/* .IP libname +/* shared-library pathname. +/* .IP libfuncs +/* Array of LIB_FN structures. The last name member must be null. +/* .IP libdata +/* Array of LIB_DP structures. The last name member must be null. +/* SEE ALSO +/* msg(3) diagnostics interface +/* DIAGNOSTICS +/* Problems are reported via the msg(3) diagnostics routines: +/* library not found, symbols not found, other fatal errors. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* LaMont Jones +/* Hewlett-Packard Company +/* 3404 Harmony Road +/* Fort Collins, CO 80528, USA +/* +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + + /* + * System libraries. + */ +#include "sys_defs.h" +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#ifdef USE_DYNAMIC_MAPS +#if defined(HAS_DLOPEN) +#include <dlfcn.h> +#elif defined(HAS_SHL_LOAD) +#include <dl.h> +#else +#error "USE_DYNAMIC_LIBS requires HAS_DLOPEN or HAS_SHL_LOAD" +#endif + + /* + * Utility library. + */ +#include <msg.h> +#include <load_lib.h> + +/* load_library_symbols - load shared library and look up symbols */ + +void load_library_symbols(const char *libname, LIB_FN *libfuncs, + LIB_DP *libdata) +{ + static const char myname[] = "load_library_symbols"; + LIB_FN *fn; + LIB_DP *dp; + +#if defined(HAS_DLOPEN) + void *handle; + char *emsg; + + /* + * XXX This is basically how FreeBSD dlfunc() silences a compiler warning + * about a data/function pointer conversion. The solution below is non- + * portable: it assumes that both data and function pointers are the same + * in size, and that both have the same representation. + */ + union { + void *dptr; /* data pointer */ + void (*fptr) (void); /* function pointer */ + } non_portable_union; + + if ((handle = dlopen(libname, RTLD_NOW)) == 0) { + emsg = dlerror(); + msg_fatal("%s: dlopen failure loading %s: %s", myname, libname, + emsg ? emsg : "don't know why"); + } + if (libfuncs) { + for (fn = libfuncs; fn->name; fn++) { + if ((non_portable_union.dptr = dlsym(handle, fn->name)) == 0) { + emsg = dlerror(); + msg_fatal("%s: dlsym failure looking up %s in %s: %s", myname, + fn->name, libname, emsg ? emsg : "don't know why"); + } + fn->fptr = non_portable_union.fptr; + if (msg_verbose > 1) + msg_info("loaded %s = %p", fn->name, non_portable_union.dptr); + } + } + if (libdata) { + for (dp = libdata; dp->name; dp++) { + if ((dp->dptr = dlsym(handle, dp->name)) == 0) { + emsg = dlerror(); + msg_fatal("%s: dlsym failure looking up %s in %s: %s", myname, + dp->name, libname, emsg ? emsg : "don't know why"); + } + if (msg_verbose > 1) + msg_info("loaded %s = %p", dp->name, dp->dptr); + } + } +#elif defined(HAS_SHL_LOAD) + shl_t handle; + + handle = shl_load(libname, BIND_IMMEDIATE, 0); + + if (libfuncs) { + for (fn = libfuncs; fn->name; fn++) { + if (shl_findsym(&handle, fn->name, TYPE_PROCEDURE, &fn->fptr) != 0) + msg_fatal("%s: shl_findsym failure looking up %s in %s: %m", + myname, fn->name, libname); + if (msg_verbose > 1) + msg_info("loaded %s = %p", fn->name, (void *) fn->fptr); + } + } + if (libdata) { + for (dp = libdata; dp->name; dp++) { + if (shl_findsym(&handle, dp->name, TYPE_DATA, &dp->dptr) != 0) + msg_fatal("%s: shl_findsym failure looking up %s in %s: %m", + myname, dp->name, libname); + if (msg_verbose > 1) + msg_info("loaded %s = %p", dp->name, dp->dptr); + } + } +#endif +} + +#endif diff --git a/src/util/load_lib.h b/src/util/load_lib.h new file mode 100644 index 0000000..1c999c5 --- /dev/null +++ b/src/util/load_lib.h @@ -0,0 +1,46 @@ +#ifndef _LOAD_LIB_H_INCLUDED_ +#define _LOAD_LIB_H_INCLUDED_ + +/*++ +/* NAME +/* load_lib 3h +/* SUMMARY +/* library loading wrappers +/* SYNOPSIS +/* #include "load_lib.h" +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +/* NULL name terminates list */ +typedef struct LIB_FN { + const char *name; + void (*fptr)(void); +} LIB_FN; + +typedef struct LIB_DP { + const char *name; + void *dptr; +} LIB_DP; + +extern void load_library_symbols(const char *, LIB_FN *, LIB_DP *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* LaMont Jones +/* Hewlett-Packard Company +/* 3404 Harmony Road +/* Fort Collins, CO 80528, USA +/* +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/logwriter.c b/src/util/logwriter.c new file mode 100644 index 0000000..aea2767 --- /dev/null +++ b/src/util/logwriter.c @@ -0,0 +1,124 @@ +/*++ +/* NAME +/* logwriter 3 +/* SUMMARY +/* logfile writer +/* SYNOPSIS +/* #include <logwriter.h> +/* +/* VSTREAM *logwriter_open_or_die( +/* const char *path) +/* +/* int logwriter_write( +/* VSTREAM *file, +/* const char *buffer. +/* ssize_t buflen) +/* +/* int logwriter_close( +/* VSTREAM *file) +/* +/* int logwriter_one_shot( +/* const char *path, +/* const char *buffer, +/* ssize_t buflen) +/* DESCRIPTION +/* This module manages a logfile writer. +/* +/* logwriter_open_or_die() safely opens the specified file in +/* write+append mode. File open/create errors are fatal. +/* +/* logwriter_write() writes the buffer plus newline to the +/* open logfile. The result is zero if successful, VSTREAM_EOF +/* if the operation failed. +/* +/* logwriter_close() closes the logfile and destroys the VSTREAM +/* instance. The result is zero if there were no errors writing +/* the file, VSTREAM_EOF otherwise. +/* +/* logwriter_one_shot() combines all the above operations. The +/* result is zero if successful, VSTREAM_EOF if any operation +/* failed. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include <sys_defs.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + + /* + * Utility library. + */ +#include <iostuff.h> +#include <logwriter.h> +#include <msg.h> +#include <mymalloc.h> +#include <safe_open.h> +#include <vstream.h> + + /* + * Application-specific. + */ + +/* logwriter_open_or_die - open logfile */ + +VSTREAM *logwriter_open_or_die(const char *path) +{ + VSTREAM *fp; + VSTRING *why = vstring_alloc(100); + +#define NO_STATP ((struct stat *) 0) +#define NO_CHOWN (-1) +#define NO_CHGRP (-1) + + fp = safe_open(path, O_CREAT | O_WRONLY | O_APPEND, 0644, + NO_STATP, NO_CHOWN, NO_CHGRP, why); + if (fp == 0) + msg_fatal("open logfile '%s': %s", path, vstring_str(why)); + close_on_exec(vstream_fileno(fp), CLOSE_ON_EXEC); + vstring_free(why); + return (fp); +} + +/* logwriter_write - append to logfile */ + +int logwriter_write(VSTREAM *fp, const char *buf, ssize_t len) +{ + if (len < 0) + msg_panic("logwriter_write: negative length %ld", (long) len); + if (vstream_fwrite(fp, buf, len) != len) + return (VSTREAM_EOF); + VSTREAM_PUTC('\n', fp); + return (vstream_fflush(fp)); +} + +/* logwriter_close - close logfile */ + +int logwriter_close(VSTREAM *fp) +{ + return (vstream_fclose(fp)); +} + +/* logwriter_one_shot - one-shot logwriter */ + +int logwriter_one_shot(const char *path, const char *buf, ssize_t len) +{ + VSTREAM *fp; + int err; + + fp = logwriter_open_or_die(path); + err = logwriter_write(fp, buf, len); + err |= logwriter_close(fp); + return (err ? VSTREAM_EOF : 0); +} diff --git a/src/util/logwriter.h b/src/util/logwriter.h new file mode 100644 index 0000000..f5266e4 --- /dev/null +++ b/src/util/logwriter.h @@ -0,0 +1,38 @@ +#ifndef _LOGWRITER_H_INCLUDED_ +#define _LOGWRITER_H_INCLUDED_ + +/*++ +/* NAME +/* logwriter 3h +/* SUMMARY +/* logfile writer +/* SYNOPSIS +/* #include <logwriter.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <vstream.h> + + /* + * External interface. + */ +extern VSTREAM *logwriter_open_or_die(const char *); +extern int logwriter_write(VSTREAM *, const char *, ssize_t); +extern int logwriter_close(VSTREAM *); +extern int logwriter_one_shot(const char *, const char *, ssize_t); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/lowercase.c b/src/util/lowercase.c new file mode 100644 index 0000000..f7388b4 --- /dev/null +++ b/src/util/lowercase.c @@ -0,0 +1,43 @@ +/*++ +/* NAME +/* lowercase 3 +/* SUMMARY +/* map uppercase characters to lowercase +/* SYNOPSIS +/* #include <stringops.h> +/* +/* char *lowercase(buf) +/* char *buf; +/* DESCRIPTION +/* lowercase() replaces uppercase characters in its null-terminated +/* input by their lowercase equivalent. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <ctype.h> + +/* Utility library. */ + +#include "stringops.h" + +char *lowercase(char *string) +{ + char *cp; + int ch; + + for (cp = string; (ch = *cp) != 0; cp++) + if (ISUPPER(ch)) + *cp = TOLOWER(ch); + return (string); +} diff --git a/src/util/lstat_as.c b/src/util/lstat_as.c new file mode 100644 index 0000000..18e0f9f --- /dev/null +++ b/src/util/lstat_as.c @@ -0,0 +1,73 @@ +/*++ +/* NAME +/* lstat_as 3 +/* SUMMARY +/* lstat file as user +/* SYNOPSIS +/* #include <sys/stat.h> +/* #include <lstat_as.h> +/* +/* int lstat_as(path, st, euid, egid) +/* const char *path; +/* struct stat *st; +/* uid_t euid; +/* gid_t egid; +/* DESCRIPTION +/* lstat_as() looks up the file status of the named \fIpath\fR, +/* using the effective rights specified by \fIeuid\fR +/* and \fIegid\fR, and stores the result into the structure pointed +/* to by \fIst\fR. A -1 result means the lookup failed. +/* This call does not follow symbolic links. +/* DIAGNOSTICS +/* Fatal error: no permission to change privilege level. +/* SEE ALSO +/* set_eugid(3) switch effective rights +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/stat.h> +#include <unistd.h> + +/* Utility library. */ + +#include "msg.h" +#include "set_eugid.h" +#include "lstat_as.h" +#include "warn_stat.h" + +/* lstat_as - lstat file as user */ + +int lstat_as(const char *path, struct stat * st, uid_t euid, gid_t egid) +{ + uid_t saved_euid = geteuid(); + gid_t saved_egid = getegid(); + int status; + + /* + * Switch to the target user privileges. + */ + set_eugid(euid, egid); + + /* + * Lstat that file. + */ + status = lstat(path, st); + + /* + * Restore saved privileges. + */ + set_eugid(saved_euid, saved_egid); + + return (status); +} diff --git a/src/util/lstat_as.h b/src/util/lstat_as.h new file mode 100644 index 0000000..2195a1e --- /dev/null +++ b/src/util/lstat_as.h @@ -0,0 +1,35 @@ +#ifndef _LSTAT_AS_H_INCLUDED_ +#define _LSTAT_AS_H_INCLUDED_ + +/*++ +/* NAME +/* lstat_as 3h +/* SUMMARY +/* lstat file as user +/* SYNOPSIS +/* #include <sys/stat.h> +/* #include <lstat_as.h> +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern int WARN_UNUSED_RESULT lstat_as(const char *, struct stat *, uid_t, gid_t); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/mac_expand.c b/src/util/mac_expand.c new file mode 100644 index 0000000..f40819e --- /dev/null +++ b/src/util/mac_expand.c @@ -0,0 +1,848 @@ +/*++ +/* NAME +/* mac_expand 3 +/* SUMMARY +/* attribute expansion +/* SYNOPSIS +/* #include <mac_expand.h> +/* +/* int mac_expand(result, pattern, flags, filter, lookup, context) +/* VSTRING *result; +/* const char *pattern; +/* int flags; +/* const char *filter; +/* const char *lookup(const char *key, int mode, void *context) +/* void *context; +/* AUXILIARY FUNCTIONS +/* typedef MAC_EXP_OP_RES (*MAC_EXPAND_RELOP_FN) ( +/* const char *left, +/* int tok_val, +/* const char *rite) +/* +/* void mac_expand_add_relop( +/* int *tok_list, +/* const char *suffix, +/* MAC_EXPAND_RELOP_FN relop_eval) +/* +/* MAC_EXP_OP_RES mac_exp_op_res_bool[2]; +/* DESCRIPTION +/* This module implements parameter-less named attribute +/* expansions, both conditional and unconditional. As of Postfix +/* 3.0 this code supports relational expression evaluation. +/* +/* In this text, an attribute is considered "undefined" when its value +/* is a null pointer. Otherwise, the attribute is considered "defined" +/* and is expected to have as value a null-terminated string. +/* +/* In the text below, the legacy form $(...) is equivalent to +/* ${...}. The legacy form $(...) may eventually disappear +/* from documentation. In the text below, the name in $name +/* and ${name...} must contain only characters from the set +/* [a-zA-Z0-9_]. +/* +/* The following substitutions are supported: +/* .IP "$name, ${name}" +/* Unconditional attribute-based substitution. The result is the +/* named attribute value (empty if the attribute is not defined) +/* after optional further named attribute substitution. +/* .IP "${name?text}, ${name?{text}}" +/* Conditional attribute-based substitution. If the named attribute +/* value is non-empty, the result is the given text, after +/* named attribute expansion and relational expression evaluation. +/* Otherwise, the result is empty. Whitespace before or after +/* {text} is ignored. +/* .IP "${name:text}, ${name:{text}}" +/* Conditional attribute-based substitution. If the attribute +/* value is empty or undefined, the expansion is the given +/* text, after named attribute expansion and relational expression +/* evaluation. Otherwise, the result is empty. Whitespace +/* before or after {text} is ignored. +/* .IP "${name?{text1}:{text2}}, ${name?{text1}:text2}" +/* Conditional attribute-based substitution. If the named attribute +/* value is non-empty, the result is text1. Otherwise, the +/* result is text2. In both cases the result is subject to +/* named attribute expansion and relational expression evaluation. +/* Whitespace before or after {text1} or {text2} is ignored. +/* .IP "${{text1} == ${text2} ? {text3} : {text4}}" +/* Relational expression-based substitution. First, the content +/* of {text1} and ${text2} is subjected to named attribute and +/* relational expression-based substitution. Next, the relational +/* expression is evaluated. If it evaluates to "true", the +/* result is the content of {text3}, otherwise it is the content +/* of {text4}, after named attribute and relational expression-based +/* substitution. In addition to ==, this supports !=, <, <=, +/* >=, and >. Comparisons are numerical when both operands are +/* all digits, otherwise the comparisons are lexicographical. +/* +/* Arguments: +/* .IP result +/* Storage for the result of expansion. By default, the result +/* is truncated upon entry. +/* .IP pattern +/* The string to be expanded. +/* .IP flags +/* Bit-wise OR of zero or more of the following: +/* .RS +/* .IP MAC_EXP_FLAG_RECURSE +/* Expand attributes in lookup results. This should never be +/* done with data whose origin is untrusted. +/* .IP MAC_EXP_FLAG_APPEND +/* Append text to the result buffer without truncating it. +/* .IP MAC_EXP_FLAG_SCAN +/* Scan the input for named attributes, including named +/* attributes in all conditional result values. Do not expand +/* named attributes, and do not truncate or write to the result +/* argument. +/* .IP MAC_EXP_FLAG_PRINTABLE +/* Use the printable() function instead of \fIfilter\fR. +/* .PP +/* The constant MAC_EXP_FLAG_NONE specifies a manifest null value. +/* .RE +/* .IP filter +/* A null pointer, or a null-terminated array of characters that +/* are allowed to appear in an expansion. Illegal characters are +/* replaced by underscores. +/* .IP lookup +/* The attribute lookup routine. Arguments are: the attribute name, +/* MAC_EXP_MODE_TEST to test the existence of the named attribute +/* or MAC_EXP_MODE_USE to use the value of the named attribute, +/* and the caller context that was given to mac_expand(). A null +/* result value means that the requested attribute was not defined. +/* .IP context +/* Caller context that is passed on to the attribute lookup routine. +/* .PP +/* mac_expand_add_relop() registers a function that implements +/* support for custom relational operators. Custom operator names +/* such as "==xxx" have two parts: a prefix that is identical to +/* a built-in operator such as "==", and an application-specified +/* suffix such as "xxx". +/* +/* Arguments: +/* .IP tok_list +/* A null-terminated list of MAC_EXP_OP_TOK_* values that support +/* the custom operator suffix. +/* .IP suffix +/* A null-terminated alphanumeric string that specifies the custom +/* operator suffix. +/* .IP relop_eval +/* A function that compares two strings according to the +/* MAC_EXP_OP_TOK_* value specified with the tok_val argument, +/* and that returns non-zero if the custom operator evaluates to +/* true, zero otherwise. +/* +/* mac_exp_op_res_bool provides an array that converts a boolean +/* value (0 or 1) to the corresponding MAX_EXP_OP_RES_TRUE or +/* MAX_EXP_OP_RES_FALSE value. +/* DIAGNOSTICS +/* Fatal errors: out of memory. Warnings: syntax errors, unreasonable +/* recursion depth. +/* +/* The result value is the binary OR of zero or more of the following: +/* .IP MAC_PARSE_ERROR +/* A syntax error was found in \fBpattern\fR, or some attribute had +/* an unreasonable nesting depth. +/* .IP MAC_PARSE_UNDEF +/* An attribute was expanded but its value was not defined. +/* SEE ALSO +/* mac_parse(3) locate macro references in string. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <ctype.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> + +/* Utility library. */ + +#include <msg.h> +#include <htable.h> +#include <vstring.h> +#include <mymalloc.h> +#include <stringops.h> +#include <name_code.h> +#include <sane_strtol.h> +#include <mac_parse.h> +#include <mac_expand.h> + + /* + * Simplifies the return of common relational operator results. + */ +MAC_EXP_OP_RES mac_exp_op_res_bool[2] = { + MAC_EXP_OP_RES_FALSE, + MAC_EXP_OP_RES_TRUE +}; + + /* + * Little helper structure. + */ +typedef struct { + VSTRING *result; /* result buffer */ + int flags; /* features */ + const char *filter; /* character filter */ + MAC_EXP_LOOKUP_FN lookup; /* lookup routine */ + void *context; /* caller context */ + int status; /* findings */ + int level; /* nesting level */ +} MAC_EXP_CONTEXT; + + /* + * Support for relational expressions. + * + * As of Postfix 2.2, ${attr-name?result} or ${attr-name:result} return the + * result respectively when the parameter value is non-empty, or when the + * parameter value is undefined or empty; support for the ternary ?: + * operator was anticipated, but not implemented for 10 years. + * + * To make ${relational-expr?result} and ${relational-expr:result} work as + * expected without breaking the way that ? and : work, relational + * expressions evaluate to a non-empty or empty value. It does not matter + * what non-empty value we use for TRUE. However we must not use the + * undefined (null pointer) value for FALSE - that would raise the + * MAC_PARSE_UNDEF flag. + * + * The value of a relational expression can be exposed with ${relational-expr}, + * i.e. a relational expression that is not followed by ? or : conditional + * expansion. + */ +#define MAC_EXP_BVAL_TRUE "true" +#define MAC_EXP_BVAL_FALSE "" + + /* + * Relational operators. The MAC_EXP_OP_TOK_* are defined in the header + * file. + */ +#define MAC_EXP_OP_STR_EQ "==" +#define MAC_EXP_OP_STR_NE "!=" +#define MAC_EXP_OP_STR_LT "<" +#define MAC_EXP_OP_STR_LE "<=" +#define MAC_EXP_OP_STR_GE ">=" +#define MAC_EXP_OP_STR_GT ">" +#define MAC_EXP_OP_STR_ANY "\"" MAC_EXP_OP_STR_EQ \ + "\" or \"" MAC_EXP_OP_STR_NE "\"" \ + "\" or \"" MAC_EXP_OP_STR_LT "\"" \ + "\" or \"" MAC_EXP_OP_STR_LE "\"" \ + "\" or \"" MAC_EXP_OP_STR_GE "\"" \ + "\" or \"" MAC_EXP_OP_STR_GT "\"" + +static const NAME_CODE mac_exp_op_table[] = +{ + MAC_EXP_OP_STR_EQ, MAC_EXP_OP_TOK_EQ, + MAC_EXP_OP_STR_NE, MAC_EXP_OP_TOK_NE, + MAC_EXP_OP_STR_LT, MAC_EXP_OP_TOK_LT, + MAC_EXP_OP_STR_LE, MAC_EXP_OP_TOK_LE, + MAC_EXP_OP_STR_GE, MAC_EXP_OP_TOK_GE, + MAC_EXP_OP_STR_GT, MAC_EXP_OP_TOK_GT, + 0, MAC_EXP_OP_TOK_NONE, +}; + + /* + * The whitespace separator set. + */ +#define MAC_EXP_WHITESPACE CHARS_SPACE + + /* + * Support for operator extensions. + */ +static HTABLE *mac_exp_ext_table; +static VSTRING *mac_exp_ext_key; + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) + +/* atol_or_die - convert or die */ + +static long atol_or_die(const char *strval) +{ + long result; + char *remainder; + + result = sane_strtol(strval, &remainder, 10); + if (*strval == 0 /* can't happen */ || *remainder != 0 || errno == ERANGE) + msg_fatal("mac_exp_eval: bad conversion: %s", strval); + return (result); +} + +/* mac_exp_eval - evaluate binary expression */ + +static MAC_EXP_OP_RES mac_exp_eval(const char *left, int tok_val, + const char *rite) +{ + static const char myname[] = "mac_exp_eval"; + long delta; + + /* + * Numerical or string comparison. + */ + if (alldig(left) && alldig(rite)) { + delta = atol_or_die(left) - atol_or_die(rite); + } else { + delta = strcmp(left, rite); + } + switch (tok_val) { + case MAC_EXP_OP_TOK_EQ: + return (mac_exp_op_res_bool[delta == 0]); + case MAC_EXP_OP_TOK_NE: + return (mac_exp_op_res_bool[delta != 0]); + case MAC_EXP_OP_TOK_LT: + return (mac_exp_op_res_bool[delta < 0]); + case MAC_EXP_OP_TOK_LE: + return (mac_exp_op_res_bool[delta <= 0]); + case MAC_EXP_OP_TOK_GE: + return (mac_exp_op_res_bool[delta >= 0]); + case MAC_EXP_OP_TOK_GT: + return (mac_exp_op_res_bool[delta > 0]); + default: + msg_panic("%s: unknown operator: %d", + myname, tok_val); + } +} + +/* mac_exp_parse_error - report parse error, set error flag, return status */ + +static int PRINTFLIKE(2, 3) mac_exp_parse_error(MAC_EXP_CONTEXT *mc, + const char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + vmsg_warn(fmt, ap); + va_end(ap); + return (mc->status |= MAC_PARSE_ERROR); +}; + +/* MAC_EXP_ERR_RETURN - report parse error, set error flag, return status */ + +#define MAC_EXP_ERR_RETURN(mc, fmt, ...) do { \ + return (mac_exp_parse_error(mc, fmt, __VA_ARGS__)); \ + } while (0) + + /* + * Postfix 3.0 introduces support for {text} operands. Only with these do we + * support the ternary ?: operator and relational operators. + * + * We cannot support operators in random text, because that would break Postfix + * 2.11 compatibility. For example, with the expression "${name?value}", the + * value is random text that may contain ':', '?', '{' and '}' characters. + * In particular, with Postfix 2.2 .. 2.11, "${name??foo:{b}ar}" evaluates + * to "?foo:{b}ar" or empty. There are explicit tests in this directory and + * the postconf directory to ensure that Postfix 2.11 compatibility is + * maintained. + * + * Ideally, future Postfix configurations enclose random text operands inside + * {} braces. These allow whitespace around operands, which improves + * readability. + */ + +/* MAC_EXP_FIND_LEFT_CURLY - skip over whitespace to '{', advance read ptr */ + +#define MAC_EXP_FIND_LEFT_CURLY(len, cp) \ + ((cp[len = strspn(cp, MAC_EXP_WHITESPACE)] == '{') ? \ + (cp += len) : 0) + +/* mac_exp_extract_curly_payload - balance {}, skip whitespace, return payload */ + +static char *mac_exp_extract_curly_payload(MAC_EXP_CONTEXT *mc, char **bp) +{ + char *payload; + char *cp; + int level; + int ch; + + /* + * Extract the payload and balance the {}. The caller is expected to skip + * leading whitespace before the {. See MAC_EXP_FIND_LEFT_CURLY(). + */ + for (level = 1, cp = *bp, payload = ++cp; /* see below */ ; cp++) { + if ((ch = *cp) == 0) { + mac_exp_parse_error(mc, "unbalanced {} in attribute expression: " + "\"%s\"", + *bp); + return (0); + } else if (ch == '{') { + level++; + } else if (ch == '}') { + if (--level <= 0) + break; + } + } + *cp++ = 0; + + /* + * Skip trailing whitespace after }. + */ + *bp = cp + strspn(cp, MAC_EXP_WHITESPACE); + return (payload); +} + +/* mac_exp_parse_relational - parse relational expression, advance read ptr */ + +static int mac_exp_parse_relational(MAC_EXP_CONTEXT *mc, const char **lookup, + char **bp) +{ + char *cp = *bp; + VSTRING *left_op_buf; + VSTRING *rite_op_buf; + const char *left_op_strval; + const char *rite_op_strval; + char *op_pos; + char *op_strval; + size_t op_len; + int op_tokval; + int op_result; + size_t tmp_len; + char *type_pos; + size_t type_len; + MAC_EXPAND_RELOP_FN relop_eval; + + /* + * Left operand. The caller is expected to skip leading whitespace before + * the {. See MAC_EXP_FIND_LEFT_CURLY(). + */ + if ((left_op_strval = mac_exp_extract_curly_payload(mc, &cp)) == 0) + return (mc->status); + + /* + * Operator. Todo: regexp operator. + */ + op_pos = cp; + op_len = strspn(cp, "<>!=?+-*/~&|%"); /* for better diagnostics. */ + op_strval = mystrndup(cp, op_len); + op_tokval = name_code(mac_exp_op_table, NAME_CODE_FLAG_NONE, op_strval); + myfree(op_strval); + if (op_tokval == MAC_EXP_OP_TOK_NONE) + MAC_EXP_ERR_RETURN(mc, "%s expected at: \"...%s}>>>%.20s\"", + MAC_EXP_OP_STR_ANY, left_op_strval, cp); + cp += op_len; + + /* + * Custom operator suffix. + */ + if (mac_exp_ext_table && ISALNUM(*cp)) { + type_pos = cp; + for (type_len = 1; ISALNUM(cp[type_len]); type_len++) + /* void */ ; + cp += type_len; + vstring_sprintf(mac_exp_ext_key, "%.*s", + (int) (op_len + type_len), op_pos); + if ((relop_eval = (MAC_EXPAND_RELOP_FN) htable_find(mac_exp_ext_table, + STR(mac_exp_ext_key))) == 0) + MAC_EXP_ERR_RETURN(mc, "bad operator suffix at: \"...%.*s>>>%.*s\"", + (int) op_len, op_pos, (int) type_len, type_pos); + } else { + relop_eval = mac_exp_eval; + } + + /* + * Right operand. Todo: syntax may depend on operator. + */ + if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp) == 0) + MAC_EXP_ERR_RETURN(mc, "\"{expression}\" expected at: " + "\"...{%s} %.*s>>>%.20s\"", + left_op_strval, (int) op_len, op_pos, cp); + if ((rite_op_strval = mac_exp_extract_curly_payload(mc, &cp)) == 0) + return (mc->status); + + /* + * Evaluate the relational expression. Todo: regexp support. + */ + mc->status |= + mac_expand(left_op_buf = vstring_alloc(100), left_op_strval, + mc->flags, mc->filter, mc->lookup, mc->context); + mc->status |= + mac_expand(rite_op_buf = vstring_alloc(100), rite_op_strval, + mc->flags, mc->filter, mc->lookup, mc->context); + if ((mc->flags & MAC_EXP_FLAG_SCAN) == 0 + && (op_result = relop_eval(vstring_str(left_op_buf), op_tokval, + vstring_str(rite_op_buf))) == MAC_EXP_OP_RES_ERROR) + mc->status |= MAC_PARSE_ERROR; + vstring_free(left_op_buf); + vstring_free(rite_op_buf); + if (mc->status & MAC_PARSE_ERROR) + return (mc->status); + + /* + * Here, we fake up a non-empty or empty parameter value lookup result, + * for compatibility with the historical code that looks named parameter + * values. + */ + if (mc->flags & MAC_EXP_FLAG_SCAN) { + *lookup = 0; + } else { + switch (op_result) { + case MAC_EXP_OP_RES_TRUE: + *lookup = MAC_EXP_BVAL_TRUE; + break; + case MAC_EXP_OP_RES_FALSE: + *lookup = MAC_EXP_BVAL_FALSE; + break; + default: + msg_panic("mac_expand: unexpected operator result: %d", op_result); + } + } + *bp = cp; + return (0); +} + +/* mac_expand_add_relop - register operator extensions */ + +void mac_expand_add_relop(int *tok_list, const char *suffix, + MAC_EXPAND_RELOP_FN relop_eval) +{ + const char myname[] = "mac_expand_add_relop"; + const char *tok_name; + int *tp; + + /* + * Sanity checks. + */ + if (!allalnum(suffix)) + msg_panic("%s: bad operator suffix: %s", myname, suffix); + + /* + * One-time initialization. + */ + if (mac_exp_ext_table == 0) { + mac_exp_ext_table = htable_create(10); + mac_exp_ext_key = vstring_alloc(10); + } + for (tp = tok_list; *tp; tp++) { + if ((tok_name = str_name_code(mac_exp_op_table, *tp)) == 0) + msg_panic("%s: unknown token code: %d", myname, *tp); + vstring_sprintf(mac_exp_ext_key, "%s%s", tok_name, suffix); + if (htable_locate(mac_exp_ext_table, STR(mac_exp_ext_key)) != 0) + msg_panic("%s: duplicate key: %s", myname, STR(mac_exp_ext_key)); + (void) htable_enter(mac_exp_ext_table, + STR(mac_exp_ext_key), (void *) relop_eval); + } +} + +/* mac_expand_callback - callback for mac_parse */ + +static int mac_expand_callback(int type, VSTRING *buf, void *ptr) +{ + static const char myname[] = "mac_expand_callback"; + MAC_EXP_CONTEXT *mc = (MAC_EXP_CONTEXT *) ptr; + int lookup_mode; + const char *lookup; + char *cp; + int ch; + ssize_t res_len; + ssize_t tmp_len; + const char *res_iftrue; + const char *res_iffalse; + + /* + * Sanity check. + */ + if (mc->level++ > 100) + mac_exp_parse_error(mc, "unreasonable macro call nesting: \"%s\"", + vstring_str(buf)); + if (mc->status & MAC_PARSE_ERROR) + return (mc->status); + + /* + * Named parameter or relational expression. In case of a syntax error, + * return without doing damage, and issue a warning instead. + */ + if (type == MAC_PARSE_EXPR) { + + cp = vstring_str(buf); + + /* + * Relational expression. If recursion is disabled, perform only one + * level of $name expansion. + */ + if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) { + if (mac_exp_parse_relational(mc, &lookup, &cp) != 0) + return (mc->status); + + /* + * Look for the ? or : operator. + */ + if ((ch = *cp) != 0) { + if (ch != '?' && ch != ':') + MAC_EXP_ERR_RETURN(mc, "\"?\" or \":\" expected at: " + "\"...}>>>%.20s\"", cp); + cp++; + } + } + + /* + * Named parameter. + */ + else { + char *start; + + /* + * Look for the ? or : operator. In case of a syntax error, + * return without doing damage, and issue a warning instead. + */ + start = (cp += strspn(cp, MAC_EXP_WHITESPACE)); + for ( /* void */ ; /* void */ ; cp++) { + if ((ch = cp[tmp_len = strspn(cp, MAC_EXP_WHITESPACE)]) == 0) { + *cp = 0; + lookup_mode = MAC_EXP_MODE_USE; + break; + } + if (ch == '?' || ch == ':') { + *cp++ = 0; + cp += tmp_len; + lookup_mode = MAC_EXP_MODE_TEST; + break; + } + ch = *cp; + if (!ISALNUM(ch) && ch != '_') { + MAC_EXP_ERR_RETURN(mc, "attribute name syntax error at: " + "\"...%.*s>>>%.20s\"", + (int) (cp - vstring_str(buf)), + vstring_str(buf), cp); + } + } + + /* + * Look up the named parameter. Todo: allow the lookup function + * to specify if the result is safe for $name expansion. + */ + lookup = mc->lookup(start, lookup_mode, mc->context); + } + + /* + * Return the requested result. After parsing the result operand + * following ?, we fall through to parse the result operand following + * :. This is necessary with the ternary ?: operator: first, with + * MAC_EXP_FLAG_SCAN to parse both result operands with mac_parse(), + * and second, to find garbage after any result operand. Without + * MAC_EXP_FLAG_SCAN the content of only one of the ?: result + * operands will be parsed with mac_parse(); syntax errors in the + * other operand will be missed. + */ + switch (ch) { + case '?': + if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) { + if ((res_iftrue = mac_exp_extract_curly_payload(mc, &cp)) == 0) + return (mc->status); + } else { + res_iftrue = cp; + cp = ""; /* no left-over text */ + } + if ((lookup != 0 && *lookup != 0) || (mc->flags & MAC_EXP_FLAG_SCAN)) + mc->status |= mac_parse(res_iftrue, mac_expand_callback, + (void *) mc); + if (*cp == 0) /* end of input, OK */ + break; + if (*cp != ':') /* garbage */ + MAC_EXP_ERR_RETURN(mc, "\":\" expected at: " + "\"...%s}>>>%.20s\"", res_iftrue, cp); + cp += 1; + /* FALLTHROUGH: do not remove, see comment above. */ + case ':': + if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) { + if ((res_iffalse = mac_exp_extract_curly_payload(mc, &cp)) == 0) + return (mc->status); + } else { + res_iffalse = cp; + cp = ""; /* no left-over text */ + } + if (lookup == 0 || *lookup == 0 || (mc->flags & MAC_EXP_FLAG_SCAN)) + mc->status |= mac_parse(res_iffalse, mac_expand_callback, + (void *) mc); + if (*cp != 0) /* garbage */ + MAC_EXP_ERR_RETURN(mc, "unexpected input at: " + "\"...%s}>>>%.20s\"", res_iffalse, cp); + break; + case 0: + if (lookup == 0) { + mc->status |= MAC_PARSE_UNDEF; + } else if (*lookup == 0 || (mc->flags & MAC_EXP_FLAG_SCAN)) { + /* void */ ; + } else if (mc->flags & MAC_EXP_FLAG_RECURSE) { + vstring_strcpy(buf, lookup); + mc->status |= mac_parse(vstring_str(buf), mac_expand_callback, + (void *) mc); + } else { + res_len = VSTRING_LEN(mc->result); + vstring_strcat(mc->result, lookup); + if (mc->flags & MAC_EXP_FLAG_PRINTABLE) { + printable(vstring_str(mc->result) + res_len, '_'); + } else if (mc->filter) { + cp = vstring_str(mc->result) + res_len; + while (*(cp += strspn(cp, mc->filter))) + *cp++ = '_'; + } + } + break; + default: + msg_panic("%s: unknown operator code %d", myname, ch); + } + } + + /* + * Literal text. + */ + else if ((mc->flags & MAC_EXP_FLAG_SCAN) == 0) { + vstring_strcat(mc->result, vstring_str(buf)); + } + mc->level--; + + return (mc->status); +} + +/* mac_expand - expand $name instances */ + +int mac_expand(VSTRING *result, const char *pattern, int flags, + const char *filter, + MAC_EXP_LOOKUP_FN lookup, void *context) +{ + MAC_EXP_CONTEXT mc; + int status; + + /* + * Bundle up the request and do the substitutions. + */ + mc.result = result; + mc.flags = flags; + mc.filter = filter; + mc.lookup = lookup; + mc.context = context; + mc.status = 0; + mc.level = 0; + if ((flags & (MAC_EXP_FLAG_APPEND | MAC_EXP_FLAG_SCAN)) == 0) + VSTRING_RESET(result); + status = mac_parse(pattern, mac_expand_callback, (void *) &mc); + if ((flags & MAC_EXP_FLAG_SCAN) == 0) + VSTRING_TERMINATE(result); + + return (status); +} + +#ifdef TEST + + /* + * This code certainly deserves a stand-alone test program. + */ +#include <stringops.h> +#include <htable.h> +#include <vstream.h> +#include <vstring_vstream.h> + +static const char *lookup(const char *name, int unused_mode, void *context) +{ + HTABLE *table = (HTABLE *) context; + + return (htable_find(table, name)); +} + +static MAC_EXP_OP_RES length_relop_eval(const char *left, int relop, + const char *rite) +{ + const char myname[] = "length_relop_eval"; + ssize_t delta = strlen(left) - strlen(rite); + + switch (relop) { + case MAC_EXP_OP_TOK_EQ: + return (mac_exp_op_res_bool[delta == 0]); + case MAC_EXP_OP_TOK_NE: + return (mac_exp_op_res_bool[delta != 0]); + case MAC_EXP_OP_TOK_LT: + return (mac_exp_op_res_bool[delta < 0]); + case MAC_EXP_OP_TOK_LE: + return (mac_exp_op_res_bool[delta <= 0]); + case MAC_EXP_OP_TOK_GE: + return (mac_exp_op_res_bool[delta >= 0]); + case MAC_EXP_OP_TOK_GT: + return (mac_exp_op_res_bool[delta > 0]); + default: + msg_panic("%s: unknown operator: %d", + myname, relop); + } +} + +int main(int unused_argc, char **argv) +{ + VSTRING *buf = vstring_alloc(100); + VSTRING *result = vstring_alloc(100); + char *cp; + char *name; + char *value; + HTABLE *table; + int stat; + int length_relops[] = { + MAC_EXP_OP_TOK_EQ, MAC_EXP_OP_TOK_NE, + MAC_EXP_OP_TOK_GT, MAC_EXP_OP_TOK_GE, + MAC_EXP_OP_TOK_LT, MAC_EXP_OP_TOK_LE, + 0, + }; + + /* + * Add relops that compare string lengths instead of content. + */ + mac_expand_add_relop(length_relops, "length", length_relop_eval); + + /* + * Loop over the inputs. + */ + while (!vstream_feof(VSTREAM_IN)) { + + table = htable_create(0); + + /* + * Read a block of definitions, terminated with an empty line. + */ + while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) { + vstream_printf("<< %s\n", vstring_str(buf)); + vstream_fflush(VSTREAM_OUT); + if (VSTRING_LEN(buf) == 0) + break; + cp = vstring_str(buf); + name = mystrtok(&cp, CHARS_SPACE "="); + value = mystrtok(&cp, CHARS_SPACE "="); + htable_enter(table, name, value ? mystrdup(value) : 0); + } + + /* + * Read a block of patterns, terminated with an empty line or EOF. + */ + while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) { + vstream_printf("<< %s\n", vstring_str(buf)); + vstream_fflush(VSTREAM_OUT); + if (VSTRING_LEN(buf) == 0) + break; + cp = vstring_str(buf); + VSTRING_RESET(result); + stat = mac_expand(result, vstring_str(buf), MAC_EXP_FLAG_NONE, + (char *) 0, lookup, (void *) table); + vstream_printf("stat=%d result=%s\n", stat, vstring_str(result)); + vstream_fflush(VSTREAM_OUT); + } + htable_free(table, myfree); + vstream_printf("\n"); + } + + /* + * Clean up. + */ + vstring_free(buf); + vstring_free(result); + exit(0); +} + +#endif diff --git a/src/util/mac_expand.h b/src/util/mac_expand.h new file mode 100644 index 0000000..fbe6347 --- /dev/null +++ b/src/util/mac_expand.h @@ -0,0 +1,81 @@ +#ifndef _MAC_EXPAND_H_INCLUDED_ +#define _MAC_EXPAND_H_INCLUDED_ + +/*++ +/* NAME +/* mac_expand 3h +/* SUMMARY +/* expand macro references in string +/* SYNOPSIS +/* #include <mac_expand.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <vstring.h> +#include <mac_parse.h> + + /* + * Features. + */ +#define MAC_EXP_FLAG_NONE (0) +#define MAC_EXP_FLAG_RECURSE (1<<0) +#define MAC_EXP_FLAG_APPEND (1<<1) +#define MAC_EXP_FLAG_SCAN (1<<2) +#define MAC_EXP_FLAG_PRINTABLE (1<<3) + + /* + * Token codes, public so tha they are available to mac_expand_add_relop() + */ +#define MAC_EXP_OP_TOK_NONE 0 /* Sentinel */ +#define MAC_EXP_OP_TOK_EQ 1 /* == */ +#define MAC_EXP_OP_TOK_NE 2 /* != */ +#define MAC_EXP_OP_TOK_LT 3 /* < */ +#define MAC_EXP_OP_TOK_LE 4 /* <= */ +#define MAC_EXP_OP_TOK_GE 5 /* >= */ +#define MAC_EXP_OP_TOK_GT 6 /* > */ + + /* + * Relational operator results. An enum to discourage assuming that 0 is + * false, !0 is true. + */ +typedef enum MAC_EXP_OP_RES { + MAC_EXP_OP_RES_TRUE, + MAC_EXP_OP_RES_FALSE, + MAC_EXP_OP_RES_ERROR, +} MAC_EXP_OP_RES; + + +extern MAC_EXP_OP_RES mac_exp_op_res_bool[2]; + + /* + * Real lookup or just a test? + */ +#define MAC_EXP_MODE_TEST (0) +#define MAC_EXP_MODE_USE (1) + +typedef const char *(*MAC_EXP_LOOKUP_FN) (const char *, int, void *); +typedef MAC_EXP_OP_RES (*MAC_EXPAND_RELOP_FN) (const char *, int, const char *); + +extern int mac_expand(VSTRING *, const char *, int, const char *, MAC_EXP_LOOKUP_FN, void *); +void mac_expand_add_relop(int *, const char *, MAC_EXPAND_RELOP_FN); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/mac_expand.in b/src/util/mac_expand.in new file mode 100644 index 0000000..1d61906 --- /dev/null +++ b/src/util/mac_expand.in @@ -0,0 +1,105 @@ +name1 = name1-value + +$name1 +$(name1 +$(name1) +$( name1) +$(name1 ) +$(na me1) +${na me1} +${${name1} != {}?name 1 defined, |$name1|$name2|} +${ ${name1} != {}?name 1 defined, |$name1|$name2|} +${ ${name1} ?name 1 defined, |$name1|$name2|} +${{$name1} ? {name 1 defined, |$name1|$name2|} : {name 1 undefined, |$name1|$name2|} } +${x{$name1} != {}?{name 1 defined, |$name1|$name2|}} +${{$name1}x?{name 1 defined, |$name1|$name2|}} +${{$name1} != {}x{name 1 defined, |$name1|$name2|}} +${{$name1} != {}?x{name 1 defined, |$name1|$name2|}} +${{$name2} != {}?x{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}} +${{$name1} != {}?{name 1 defined, |$name1|$name2|}x} +${{$name1} != {}?{name 1 defined, |$name1|$name2|}x:{name 1 undefined, |$name1|$name2|}} +${{$name1} != {}?{name 1 defined, |$name1|$name2|}:x{name 1 undefined, |$name1|$name2|}} +${{$name2} != {}?{name 2 defined, |$name1|$name2|}:x{name 2 undefined, |$name1|$name2|}} +${{text}} +${{text}?{non-empty}:{empty}} +${{text} = {}} +${{${ name1}} == {}} +${name1?{${ name1}}:{${name2}}} +${name2?{${ name1}}:{${name2}}} +${name2?{${name1}}:{${ name2}}} +${name2:{${name1}}:{${name2}}} +${name2?{${name1}}?{${name2}}} +${{${name1?bug:test}} != {bug:test}?{Error: NOT}:{Good:}} Postfix 2.11 compatible +${{${name1??bug}} != {?bug}?{Error: NOT}:{Good:}} Postfix 2.11 compatible +${{${name2::bug}} != {:bug}?{Error: NOT}:{Good:}} Postfix 2.11 compatible +${{xx}==(yy)?{oops}:{phew}} + +name1 = name1-value + +${name1?name 1 defined, |$name1|$name2|} +${name1:name 1 undefined, |$name1|$name2|} +${name2?name 2 defined, |$name1|$name2|} +${name2:name 2 undefined, |$name1|$name2|} +|$name1|$name2| +${{$name1} != {}?{name 1 defined, |$name1|$name2|}} +${{$name1} != {}:{name 1 undefined, |$name1|$name2|}} +${{$name1} == {}?{name 1 undefined, |$name1|$name2|}} +${{$name1} == {}:{name 1 defined, |$name1|$name2|}} +${name1?{name 1 defined, |$name1|$name2|}:{name 1 undefined, |$name1|$name2|}} +${{$name1} != {}?{name 1 defined, |$name1|$name2|}:{name 1 undefined, |$name1|$name2|}} +${{$name1} != {} ? {name 1 defined, |$name1|$name2|} : {name 1 undefined, |$name1|$name2|}} +${{$name1} != {}?{name 1 defined, |$name1|$name2|}:name 1 undefined, |$name1|$name2|} +${{$name1} != {} ? {name 1 defined, |$name1|$name2|} : name 1 undefined, |$name1|$name2|} +${{$name1} != {}} +${{$name1} == {}} +${{$name2} != {}?{name 2 defined, |$name1|$name2|}} +${{$name2} != {}:{name 2 undefined, |$name1|$name2|}} +${{$name2} == {}?{name 2 undefined, |$name1|$name2|}} +${{$name2} == {}:{name 2 defined, |$name1|$name2|}} +${name2?{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}} +${{$name2} != {}?{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}} +${{$name2} != {} ? {name 2 defined, |$name1|$name2|} : {name 2 undefined, |$name1|$name2|}} +${{$name2} != {}?{name 2 defined, |$name1|$name2|}:name 2 undefined, |$name1|$name2|} +${{$name2} != {} ? {name 2 defined, |$name1|$name2|} : name 2 undefined, |$name1|$name2|} +${{$name2} != {}} +${{$name2} == {}} + + +${{1} == {1}} +${{1} < {1}} +${{1} <= {1}} +${{1} >= {1}} +${{1} > {1}} +${{1} == {2}} +${{1} < {2}} +${{1} <= {2}} +${{1} >= {2}} +${{1} > {2}} +${{a} == {a}} +${{a} < {a}} +${{a} <= {a}} +${{a} >= {a}} +${{a} > {a}} +${{a} == {b}} +${{a} < {b}} +${{a} <= {b}} +${{a} >= {b}} +${{a} > {b}} + +name1 = foo + +${{$name1} >=blah {bar}} +${{aaa} == {bbb}} +${{aaa} ==length {bbb}} +${{aaa} <=length {bbb}} +${{aaa} >=length {bbb}} +${{aaa} != {bbb}} +${{aaa} !=length {bbb}} +${{aaa} > {bb}} +${{aaa} >length {bb}} +${{aaa} >= {bb}} +${{aaa} >=length {bb}} +${{aaa} < {bb}} +${{aaa} <length {bb}} +${{aaa} <= {bb}} +${{aaa} <=length {bb}} diff --git a/src/util/mac_expand.ref b/src/util/mac_expand.ref new file mode 100644 index 0000000..854c4f9 --- /dev/null +++ b/src/util/mac_expand.ref @@ -0,0 +1,222 @@ +<< name1 = name1-value +<< +<< $name1 +stat=0 result=name1-value +<< $(name1 +unknown: warning: truncated macro reference: "$(name1" +stat=1 result= +<< $(name1) +stat=0 result=name1-value +<< $( name1) +stat=0 result=name1-value +<< $(name1 ) +stat=0 result=name1-value +<< $(na me1) +unknown: warning: attribute name syntax error at: "...na>>> me1" +stat=1 result= +<< ${na me1} +unknown: warning: attribute name syntax error at: "...na>>> me1" +stat=1 result= +<< ${${name1} != {}?name 1 defined, |$name1|$name2|} +unknown: warning: attribute name syntax error at: "...>>>${name1} != {}?name " +stat=1 result= +<< ${ ${name1} != {}?name 1 defined, |$name1|$name2|} +unknown: warning: attribute name syntax error at: "... >>>${name1} != {}?name " +stat=1 result= +<< ${ ${name1} ?name 1 defined, |$name1|$name2|} +unknown: warning: attribute name syntax error at: "... >>>${name1} ?name 1 def" +stat=1 result= +<< ${{$name1} ? {name 1 defined, |$name1|$name2|} : {name 1 undefined, |$name1|$name2|} } +unknown: warning: "==" or "!="" or "<"" or "<="" or ">="" or ">" expected at: "...$name1}>>>? {name 1 defined, |" +stat=1 result= +<< ${x{$name1} != {}?{name 1 defined, |$name1|$name2|}} +unknown: warning: attribute name syntax error at: "...x>>>{$name1} != {}?{name" +stat=1 result= +<< ${{$name1}x?{name 1 defined, |$name1|$name2|}} +unknown: warning: "==" or "!="" or "<"" or "<="" or ">="" or ">" expected at: "...$name1}>>>x?{name 1 defined, |" +stat=1 result= +<< ${{$name1} != {}x{name 1 defined, |$name1|$name2|}} +unknown: warning: "?" or ":" expected at: "...}>>>x{name 1 defined, |$" +stat=1 result= +<< ${{$name1} != {}?x{name 1 defined, |$name1|$name2|}} +stat=2 result=x{name 1 defined, |name1-value||} +<< ${{$name2} != {}?x{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}} +stat=2 result= +<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}x} +unknown: warning: ":" expected at: "...name 1 defined, |$name1|$name2|}>>>x" +stat=3 result=name 1 defined, |name1-value|| +<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}x:{name 1 undefined, |$name1|$name2|}} +unknown: warning: ":" expected at: "...name 1 defined, |$name1|$name2|}>>>x:{name 1 undefined," +stat=3 result=name 1 defined, |name1-value|| +<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}:x{name 1 undefined, |$name1|$name2|}} +stat=2 result=name 1 defined, |name1-value|| +<< ${{$name2} != {}?{name 2 defined, |$name1|$name2|}:x{name 2 undefined, |$name1|$name2|}} +stat=2 result=x{name 2 undefined, |name1-value||} +<< ${{text}} +unknown: warning: "==" or "!="" or "<"" or "<="" or ">="" or ">" expected at: "...text}>>>" +stat=1 result= +<< ${{text}?{non-empty}:{empty}} +unknown: warning: "==" or "!="" or "<"" or "<="" or ">="" or ">" expected at: "...text}>>>?{non-empty}:{empty}" +stat=1 result= +<< ${{text} = {}} +unknown: warning: "==" or "!="" or "<"" or "<="" or ">="" or ">" expected at: "...text}>>>= {}" +stat=1 result= +<< ${{${ name1}} == {}} +stat=0 result= +<< ${name1?{${ name1}}:{${name2}}} +stat=0 result=name1-value +<< ${name2?{${ name1}}:{${name2}}} +stat=2 result= +<< ${name2?{${name1}}:{${ name2}}} +stat=2 result= +<< ${name2:{${name1}}:{${name2}}} +unknown: warning: unexpected input at: "...${name1}}>>>:{${name2}}" +stat=1 result=name1-value +<< ${name2?{${name1}}?{${name2}}} +unknown: warning: ":" expected at: "...${name1}}>>>?{${name2}}" +stat=1 result= +<< ${{${name1?bug:test}} != {bug:test}?{Error: NOT}:{Good:}} Postfix 2.11 compatible +stat=0 result=Good: Postfix 2.11 compatible +<< ${{${name1??bug}} != {?bug}?{Error: NOT}:{Good:}} Postfix 2.11 compatible +stat=0 result=Good: Postfix 2.11 compatible +<< ${{${name2::bug}} != {:bug}?{Error: NOT}:{Good:}} Postfix 2.11 compatible +stat=0 result=Good: Postfix 2.11 compatible +<< ${{xx}==(yy)?{oops}:{phew}} +unknown: warning: "{expression}" expected at: "...{xx} ==>>>(yy)?{oops}:{phew}" +stat=1 result= +<< + +<< name1 = name1-value +<< +<< ${name1?name 1 defined, |$name1|$name2|} +stat=2 result=name 1 defined, |name1-value|| +<< ${name1:name 1 undefined, |$name1|$name2|} +stat=0 result= +<< ${name2?name 2 defined, |$name1|$name2|} +stat=0 result= +<< ${name2:name 2 undefined, |$name1|$name2|} +stat=2 result=name 2 undefined, |name1-value|| +<< |$name1|$name2| +stat=2 result=|name1-value|| +<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}} +stat=2 result=name 1 defined, |name1-value|| +<< ${{$name1} != {}:{name 1 undefined, |$name1|$name2|}} +stat=0 result= +<< ${{$name1} == {}?{name 1 undefined, |$name1|$name2|}} +stat=0 result= +<< ${{$name1} == {}:{name 1 defined, |$name1|$name2|}} +stat=2 result=name 1 defined, |name1-value|| +<< ${name1?{name 1 defined, |$name1|$name2|}:{name 1 undefined, |$name1|$name2|}} +stat=2 result=name 1 defined, |name1-value|| +<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}:{name 1 undefined, |$name1|$name2|}} +stat=2 result=name 1 defined, |name1-value|| +<< ${{$name1} != {} ? {name 1 defined, |$name1|$name2|} : {name 1 undefined, |$name1|$name2|}} +stat=2 result=name 1 defined, |name1-value|| +<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}:name 1 undefined, |$name1|$name2|} +stat=2 result=name 1 defined, |name1-value|| +<< ${{$name1} != {} ? {name 1 defined, |$name1|$name2|} : name 1 undefined, |$name1|$name2|} +stat=2 result=name 1 defined, |name1-value|| +<< ${{$name1} != {}} +stat=0 result=true +<< ${{$name1} == {}} +stat=0 result= +<< ${{$name2} != {}?{name 2 defined, |$name1|$name2|}} +stat=2 result= +<< ${{$name2} != {}:{name 2 undefined, |$name1|$name2|}} +stat=2 result=name 2 undefined, |name1-value|| +<< ${{$name2} == {}?{name 2 undefined, |$name1|$name2|}} +stat=2 result=name 2 undefined, |name1-value|| +<< ${{$name2} == {}:{name 2 defined, |$name1|$name2|}} +stat=2 result= +<< ${name2?{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}} +stat=2 result=name 2 undefined, |name1-value|| +<< ${{$name2} != {}?{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}} +stat=2 result=name 2 undefined, |name1-value|| +<< ${{$name2} != {} ? {name 2 defined, |$name1|$name2|} : {name 2 undefined, |$name1|$name2|}} +stat=2 result=name 2 undefined, |name1-value|| +<< ${{$name2} != {}?{name 2 defined, |$name1|$name2|}:name 2 undefined, |$name1|$name2|} +stat=2 result=name 2 undefined, |name1-value|| +<< ${{$name2} != {} ? {name 2 defined, |$name1|$name2|} : name 2 undefined, |$name1|$name2|} +stat=2 result= name 2 undefined, |name1-value|| +<< ${{$name2} != {}} +stat=2 result= +<< ${{$name2} == {}} +stat=2 result=true +<< + +<< +<< ${{1} == {1}} +stat=0 result=true +<< ${{1} < {1}} +stat=0 result= +<< ${{1} <= {1}} +stat=0 result=true +<< ${{1} >= {1}} +stat=0 result=true +<< ${{1} > {1}} +stat=0 result= +<< ${{1} == {2}} +stat=0 result= +<< ${{1} < {2}} +stat=0 result=true +<< ${{1} <= {2}} +stat=0 result=true +<< ${{1} >= {2}} +stat=0 result= +<< ${{1} > {2}} +stat=0 result= +<< ${{a} == {a}} +stat=0 result=true +<< ${{a} < {a}} +stat=0 result= +<< ${{a} <= {a}} +stat=0 result=true +<< ${{a} >= {a}} +stat=0 result=true +<< ${{a} > {a}} +stat=0 result= +<< ${{a} == {b}} +stat=0 result= +<< ${{a} < {b}} +stat=0 result=true +<< ${{a} <= {b}} +stat=0 result=true +<< ${{a} >= {b}} +stat=0 result= +<< ${{a} > {b}} +stat=0 result= +<< + +<< name1 = foo +<< +<< ${{$name1} >=blah {bar}} +unknown: warning: bad operator suffix at: "...>=>>>blah" +stat=1 result= +<< ${{aaa} == {bbb}} +stat=0 result= +<< ${{aaa} ==length {bbb}} +stat=0 result=true +<< ${{aaa} <=length {bbb}} +stat=0 result=true +<< ${{aaa} >=length {bbb}} +stat=0 result=true +<< ${{aaa} != {bbb}} +stat=0 result=true +<< ${{aaa} !=length {bbb}} +stat=0 result= +<< ${{aaa} > {bb}} +stat=0 result= +<< ${{aaa} >length {bb}} +stat=0 result=true +<< ${{aaa} >= {bb}} +stat=0 result= +<< ${{aaa} >=length {bb}} +stat=0 result=true +<< ${{aaa} < {bb}} +stat=0 result=true +<< ${{aaa} <length {bb}} +stat=0 result= +<< ${{aaa} <= {bb}} +stat=0 result=true +<< ${{aaa} <=length {bb}} +stat=0 result= diff --git a/src/util/mac_parse.c b/src/util/mac_parse.c new file mode 100644 index 0000000..45b717a --- /dev/null +++ b/src/util/mac_parse.c @@ -0,0 +1,199 @@ +/*++ +/* NAME +/* mac_parse 3 +/* SUMMARY +/* locate macro references in string +/* SYNOPSIS +/* #include <mac_parse.h> +/* +/* int mac_parse(string, action, context) +/* const char *string; +/* int (*action)(int type, VSTRING *buf, void *context); +/* DESCRIPTION +/* This module recognizes macro expressions in null-terminated +/* strings. Macro expressions have the form $name, $(text) or +/* ${text}. A macro name consists of alphanumerics and/or +/* underscore. Text other than macro expressions is treated +/* as literal text. +/* +/* mac_parse() breaks up its string argument into macro references +/* and other text, and invokes the \fIaction\fR routine for each item +/* found. With each action routine call, the \fItype\fR argument +/* indicates what was found, \fIbuf\fR contains a copy of the text +/* found, and \fIcontext\fR is passed on unmodified from the caller. +/* The application is at liberty to clobber \fIbuf\fR. +/* .IP MAC_PARSE_LITERAL +/* The content of \fIbuf\fR is literal text. +/* .IP MAC_PARSE_EXPR +/* The content of \fIbuf\fR is a macro expression: either a +/* bare macro name without the preceding "$", or all the text +/* inside $() or ${}. +/* .PP +/* The action routine result value is the bit-wise OR of zero or more +/* of the following: +/* .IP MAC_PARSE_ERROR +/* A parsing error was detected. +/* .IP MAC_PARSE_UNDEF +/* A macro was expanded but not defined. +/* .PP +/* Use the constant MAC_PARSE_OK when no error was detected. +/* SEE ALSO +/* dict(3) dictionary interface. +/* DIAGNOSTICS +/* Fatal errors: out of memory. malformed macro name. +/* +/* The result value is the bit-wise OR of zero or more of the +/* following: +/* .IP MAC_PARSE_ERROR +/* A parsing error was detected. +/* .IP MAC_PARSE_UNDEF +/* A macro was expanded but not defined. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <ctype.h> + +/* Utility library. */ + +#include <msg.h> +#include <mac_parse.h> + + /* + * Helper macro for consistency. Null-terminate the temporary buffer, + * execute the action, and reset the temporary buffer for re-use. + */ +#define MAC_PARSE_ACTION(status, type, buf, context) \ + do { \ + VSTRING_TERMINATE(buf); \ + status |= action((type), (buf), (context)); \ + VSTRING_RESET(buf); \ + } while(0) + +/* mac_parse - split string into literal text and macro references */ + +int mac_parse(const char *value, MAC_PARSE_FN action, void *context) +{ + const char *myname = "mac_parse"; + VSTRING *buf = vstring_alloc(1); /* result buffer */ + const char *vp; /* value pointer */ + const char *pp; /* open_paren pointer */ + const char *ep; /* string end pointer */ + static char open_paren[] = "({"; + static char close_paren[] = ")}"; + int level; + int status = 0; + +#define SKIP(start, var, cond) do { \ + for (var = start; *var && (cond); var++) \ + /* void */; \ + } while (0) + + if (msg_verbose > 1) + msg_info("%s: %s", myname, value); + + for (vp = value; *vp;) { + if (*vp != '$') { /* ordinary character */ + VSTRING_ADDCH(buf, *vp); + vp += 1; + } else if (vp[1] == '$') { /* $$ becomes $ */ + VSTRING_ADDCH(buf, *vp); + vp += 2; + } else { /* found bare $ */ + if (VSTRING_LEN(buf) > 0) + MAC_PARSE_ACTION(status, MAC_PARSE_LITERAL, buf, context); + vp += 1; + pp = open_paren; + if (*vp == *pp || *vp == *++pp) { /* ${x} or $(x) */ + level = 1; + vp += 1; + for (ep = vp; level > 0; ep++) { + if (*ep == 0) { + msg_warn("truncated macro reference: \"%s\"", value); + status |= MAC_PARSE_ERROR; + break; + } + if (*ep == *pp) + level++; + if (*ep == close_paren[pp - open_paren]) + level--; + } + if (status & MAC_PARSE_ERROR) + break; + vstring_strncat(buf, vp, level > 0 ? ep - vp : ep - vp - 1); + vp = ep; + } else { /* plain $x */ + SKIP(vp, ep, ISALNUM(*ep) || *ep == '_'); + vstring_strncat(buf, vp, ep - vp); + vp = ep; + } + if (VSTRING_LEN(buf) == 0) { + status |= MAC_PARSE_ERROR; + msg_warn("empty macro name: \"%s\"", value); + break; + } + MAC_PARSE_ACTION(status, MAC_PARSE_EXPR, buf, context); + } + } + if (VSTRING_LEN(buf) > 0 && (status & MAC_PARSE_ERROR) == 0) + MAC_PARSE_ACTION(status, MAC_PARSE_LITERAL, buf, context); + + /* + * Cleanup. + */ + vstring_free(buf); + + return (status); +} + +#ifdef TEST + + /* + * Proof-of-concept test program. Read strings from stdin, print parsed + * result to stdout. + */ +#include <vstring_vstream.h> + +/* mac_parse_print - print parse tree */ + +static int mac_parse_print(int type, VSTRING *buf, void *unused_context) +{ + char *type_name; + + switch (type) { + case MAC_PARSE_EXPR: + type_name = "MAC_PARSE_EXPR"; + break; + case MAC_PARSE_LITERAL: + type_name = "MAC_PARSE_LITERAL"; + break; + default: + msg_panic("unknown token type %d", type); + } + vstream_printf("%s \"%s\"\n", type_name, vstring_str(buf)); + return (0); +} + +int main(int unused_argc, char **unused_argv) +{ + VSTRING *buf = vstring_alloc(1); + + while (vstring_fgets_nonl(buf, VSTREAM_IN)) { + mac_parse(vstring_str(buf), mac_parse_print, (void *) 0); + vstream_fflush(VSTREAM_OUT); + } + vstring_free(buf); + return (0); +} + +#endif diff --git a/src/util/mac_parse.h b/src/util/mac_parse.h new file mode 100644 index 0000000..5ed0dc1 --- /dev/null +++ b/src/util/mac_parse.h @@ -0,0 +1,51 @@ +#ifndef _MAC_PARSE_H_INCLUDED_ +#define _MAC_PARSE_H_INCLUDED_ + +/*++ +/* NAME +/* mac_parse 3h +/* SUMMARY +/* locate macro references in string +/* SYNOPSIS +/* #include <mac_parse.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <vstring.h> + + /* + * External interface. + */ +#define MAC_PARSE_LITERAL 1 +#define MAC_PARSE_EXPR 2 +#define MAC_PARSE_VARNAME MAC_PARSE_EXPR /* 2.1 compatibility */ + +#define MAC_PARSE_OK 0 +#define MAC_PARSE_ERROR (1<<0) +#define MAC_PARSE_UNDEF (1<<1) +#define MAC_PARSE_USER 2 /* start user definitions */ + +typedef int (*MAC_PARSE_FN) (int, VSTRING *, void *); + +extern int WARN_UNUSED_RESULT mac_parse(const char *, MAC_PARSE_FN, void *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/make_dirs.c b/src/util/make_dirs.c new file mode 100644 index 0000000..2e37f8f --- /dev/null +++ b/src/util/make_dirs.c @@ -0,0 +1,173 @@ +/*++ +/* NAME +/* make_dirs 3 +/* SUMMARY +/* create directory hierarchy +/* SYNOPSIS +/* #include <make_dirs.h> +/* +/* int make_dirs(path, perms) +/* const char *path; +/* int perms; +/* DESCRIPTION +/* make_dirs() creates the directory specified in \fIpath\fR, and +/* creates any missing intermediate directories as well. Directories +/* are created with the permissions specified in \fIperms\fR, as +/* modified by the process umask. +/* DIAGNOSTICS: +/* Fatal: out of memory. make_dirs() returns 0 in case of success. +/* In case of problems. make_dirs() returns -1 and \fIerrno\fR +/* reflects the nature of the problem. +/* SEE ALSO +/* mkdir(2) +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/stat.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> + +/* Utility library. */ + +#include "msg.h" +#include "mymalloc.h" +#include "stringops.h" +#include "make_dirs.h" +#include "warn_stat.h" + +/* make_dirs - create directory hierarchy */ + +int make_dirs(const char *path, int perms) +{ + const char *myname = "make_dirs"; + char *saved_path; + unsigned char *cp; + int saved_ch; + struct stat st; + int ret; + mode_t saved_mode = 0; + gid_t egid = -1; + + /* + * Initialize. Make a copy of the path that we can safely clobber. + */ + cp = (unsigned char *) (saved_path = mystrdup(path)); + + /* + * I didn't like the 4.4BSD "mkdir -p" implementation, but coming up with + * my own took a day, spread out over several days. + */ +#define SKIP_WHILE(cond, ptr) { while(*ptr && (cond)) ptr++; } + + SKIP_WHILE(*cp == '/', cp); + + for (;;) { + SKIP_WHILE(*cp != '/', cp); + if ((saved_ch = *cp) != 0) + *cp = 0; + if ((ret = stat(saved_path, &st)) >= 0) { + if (!S_ISDIR(st.st_mode)) { + errno = ENOTDIR; + ret = -1; + break; + } + saved_mode = st.st_mode; + } else { + if (errno != ENOENT) + break; + + /* + * mkdir(foo) fails with EEXIST if foo is a symlink. + */ +#if 0 + + /* + * Create a new directory. Unfortunately, mkdir(2) has no + * equivalent of open(2)'s O_CREAT|O_EXCL safety net, so we must + * require that the parent directory is not world writable. + * Detecting a lost race condition after the fact is not + * sufficient, as an attacker could repeat the attack and add one + * directory level at a time. + */ + if (saved_mode & S_IWOTH) { + msg_warn("refusing to mkdir %s: parent directory is writable by everyone", + saved_path); + errno = EPERM; + ret = -1; + break; + } +#endif + if ((ret = mkdir(saved_path, perms)) < 0) { + if (errno != EEXIST) + break; + /* Race condition? */ + if ((ret = stat(saved_path, &st)) < 0) + break; + if (!S_ISDIR(st.st_mode)) { + errno = ENOTDIR; + ret = -1; + break; + } + } + + /* + * Fix directory ownership when mkdir() ignores the effective + * GID. Don't change the effective UID for doing this. + */ + if ((ret = stat(saved_path, &st)) < 0) { + msg_warn("%s: stat %s: %m", myname, saved_path); + break; + } + if (egid == -1) + egid = getegid(); + if (st.st_gid != egid && (ret = chown(saved_path, -1, egid)) < 0) { + msg_warn("%s: chgrp %s: %m", myname, saved_path); + break; + } + } + if (saved_ch != 0) + *cp = saved_ch; + SKIP_WHILE(*cp == '/', cp); + if (*cp == 0) + break; + } + + /* + * Cleanup. + */ + myfree(saved_path); + return (ret); +} + +#ifdef TEST + + /* + * Test program. Usage: make_dirs path... + */ +#include <stdlib.h> +#include <msg_vstream.h> + +int main(int argc, char **argv) +{ + msg_vstream_init(argv[0], VSTREAM_ERR); + if (argc < 2) + msg_fatal("usage: %s path...", argv[0]); + while (--argc > 0 && *++argv != 0) + if (make_dirs(*argv, 0755)) + msg_fatal("%s: %m", *argv); + exit(0); +} + +#endif diff --git a/src/util/make_dirs.h b/src/util/make_dirs.h new file mode 100644 index 0000000..0df6117 --- /dev/null +++ b/src/util/make_dirs.h @@ -0,0 +1,30 @@ +#ifndef MAKE_DIRS_H_INCLUDED_ +#define MAKE_DIRS_H_INCLUDED_ + +/*++ +/* NAME +/* make_dirs 3h +/* SUMMARY +/* create directory hierarchy +/* SYNOPSIS +/* #include <make_dirs.h> +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern int make_dirs(const char *, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/mask_addr.c b/src/util/mask_addr.c new file mode 100644 index 0000000..5ddd0ca --- /dev/null +++ b/src/util/mask_addr.c @@ -0,0 +1,68 @@ +/*++ +/* NAME +/* mask_addr 3 +/* SUMMARY +/* address bit banging +/* SYNOPSIS +/* #include <mask_addr.h> +/* +/* void mask_addr(addr_bytes, addr_byte_count, network_bits) +/* unsigned char *addr_bytes; +/* unsigned addr_byte_count; +/* unsigned network_bits; +/* DESCRIPTION +/* mask_addr() clears all the host bits in the specified +/* address. The code can handle addresses of any length, +/* and bytes of any width. +/* +/* Arguments: +/* .IP addr_bytes +/* The network address in network byte order. +/* .IP addr_byte_count +/* The network address length in bytes. +/* .IP network_bits +/* The number of initial bits that will not be cleared. +/* DIAGNOSTICS +/* Fatal errors: the number of network bits exceeds the address size. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <limits.h> /* CHAR_BIT */ + +/* Utility library. */ + +#include <msg.h> +#include <mask_addr.h> + +/* mask_addr - mask off a variable-length address */ + +void mask_addr(unsigned char *addr_bytes, + unsigned addr_byte_count, + unsigned network_bits) +{ + unsigned char *p; + + if (network_bits > addr_byte_count * CHAR_BIT) + msg_panic("mask_addr: address byte count %d too small for bit count %d", + addr_byte_count, network_bits); + + p = addr_bytes + network_bits / CHAR_BIT; + network_bits %= CHAR_BIT; + + if (network_bits != 0) + *p++ &= ~0U << (CHAR_BIT - network_bits); + + while (p < addr_bytes + addr_byte_count) + *p++ = 0; +} diff --git a/src/util/mask_addr.h b/src/util/mask_addr.h new file mode 100644 index 0000000..0e70a41 --- /dev/null +++ b/src/util/mask_addr.h @@ -0,0 +1,30 @@ +#ifndef _MASK_ADDR_H_INCLUDED_ +#define _MASK_ADDR_H_INCLUDED_ + +/*++ +/* NAME +/* mask_addr 3h +/* SUMMARY +/* address bit banging +/* SYNOPSIS +/* #include <mask_addr.h> +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern void mask_addr(unsigned char *, unsigned, unsigned); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/match_list.c b/src/util/match_list.c new file mode 100644 index 0000000..520485d --- /dev/null +++ b/src/util/match_list.c @@ -0,0 +1,281 @@ +/*++ +/* NAME +/* match_list 3 +/* SUMMARY +/* generic list-based pattern matching +/* SYNOPSIS +/* #include <match_list.h> +/* +/* MATCH_LIST *match_list_init(pname, flags, pattern_list, count, func,...) +/* const char *pname; +/* int flags; +/* const char *pattern_list; +/* int count; +/* int (*func)(int flags, const char *string, const char *pattern); +/* +/* int match_list_match(list, string,...) +/* MATCH_LIST *list; +/* const char *string; +/* +/* void match_list_free(list) +/* MATCH_LIST *list; +/* DESCRIPTION +/* This module implements a framework for tests for list +/* membership. The actual tests are done by user-supplied +/* functions. +/* +/* Patterns are separated by whitespace and/or commas. A pattern +/* is either a string, a file name (in which case the contents +/* of the file are substituted for the file name) or a type:name +/* lookup table specification. In order to reverse the result +/* of a pattern match, precede a pattern with an exclamation +/* point (!). +/* +/* match_list_init() performs initializations. When the global +/* util_utf8_enable variable is non-zero, and when the code +/* is compiled with EAI support, string comparison will use +/* caseless UTF-8 mode. Otherwise, only ASCII characters will +/* be casefolded. +/* +/* match_list_match() matches strings against the specified +/* pattern list, passing the first string to the first function +/* given to match_list_init(), the second string to the second +/* function, and so on. +/* +/* match_list_free() releases storage allocated by match_list_init(). +/* +/* Arguments: +/* .IP pname +/* Parameter name or other identifying information that is +/* prepended to error messages. +/* .IP flags +/* Specifies the bit-wise OR of zero or more of the following: +/* .RS +/* .IP MATCH_FLAG_PARENT +/* The hostname pattern foo.com matches any name within the +/* domain foo.com. If this flag is cleared, foo.com matches +/* itself only, and .foo.com matches any name below the domain +/* foo.com. +/* .IP MATCH_FLAG_RETURN +/* Request that match_list_match() logs a warning and returns +/* zero (with list->error set to a non-zero dictionary error +/* code) instead of raising a fatal run-time error. +/* .RE +/* Specify MATCH_FLAG_NONE to request none of the above. +/* .IP pattern_list +/* A list of patterns. +/* .IP count +/* Specifies how many match functions follow. +/* .IP list +/* Pattern list produced by match_list_init(). +/* .IP string +/* Search string. +/* DIAGNOSTICS +/* Fatal error: unable to open or read a match_list file; invalid +/* match_list pattern; casefold error (UTF-8 mode only). +/* SEE ALSO +/* host_match(3) match hosts by name or by address +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stdarg.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstring.h> +#include <vstream.h> +#include <vstring_vstream.h> +#include <stringops.h> +#include <argv.h> +#include <dict.h> +#include <match_list.h> + +/* Application-specific */ + +#define MATCH_DICTIONARY(pattern) \ + ((pattern)[0] != '[' && strchr((pattern), ':') != 0) + +/* match_list_parse - parse buffer, destroy buffer */ + +static ARGV *match_list_parse(MATCH_LIST *match_list, ARGV *pat_list, + char *string, int init_match) +{ + const char *myname = "match_list_parse"; + VSTRING *buf = vstring_alloc(10); + VSTREAM *fp; + const char *delim = CHARS_COMMA_SP; + char *bp = string; + char *start; + char *item; + char *map_type_name_flags; + int match; + + /* + * We do not use DICT_FLAG_FOLD_FIX, because we casefold the search + * string at the beginning of a search, and we use strcmp() for string + * comparison. This works because string patterns are casefolded during + * match_list initialization, and databases are supposed to fold case + * upon creation. + */ +#define OPEN_FLAGS O_RDONLY +#define DICT_FLAGS (DICT_FLAG_LOCK | DICT_FLAG_UTF8_REQUEST) +#define STR(x) vstring_str(x) + + /* + * /filename contents are expanded in-line. To support !/filename we + * prepend the negation operator to each item from the file. + * + * If there is an error, implement graceful degradation by inserting a + * pseudo table whose lookups fail with a warning message. + */ + while ((start = mystrtokq(&bp, delim, CHARS_BRACE)) != 0) { + if (*start == '#') { + msg_warn("%s: comment at end of line is not supported: %s %s", + match_list->pname, start, bp); + break; + } + for (match = init_match, item = start; *item == '!'; item++) + match = !match; + if (*item == 0) + /* No graceful degradation for this... */ + msg_fatal("%s: no pattern after '!'", match_list->pname); + if (*item == '/') { /* /file/name */ + if ((fp = vstream_fopen(item, O_RDONLY, 0)) == 0) { + /* Replace unusable pattern with pseudo table. */ + vstring_sprintf(buf, "%s:%s", DICT_TYPE_NOFILE, item); + if (dict_handle(STR(buf)) == 0) + dict_register(STR(buf), + dict_surrogate(DICT_TYPE_NOFILE, item, + OPEN_FLAGS, DICT_FLAGS, + "open file %s: %m", item)); + argv_add(pat_list, STR(buf), (char *) 0); + } else { + while (vstring_fgets(buf, fp)) + if (vstring_str(buf)[0] != '#') + pat_list = match_list_parse(match_list, pat_list, + vstring_str(buf), match); + if (vstream_fclose(fp)) + msg_fatal("%s: read file %s: %m", myname, item); + } + } else if (MATCH_DICTIONARY(item)) { /* type:table */ + vstring_sprintf(buf, "%s%s(%o,%s)", match ? "" : "!", + item, OPEN_FLAGS, dict_flags_str(DICT_FLAGS)); + map_type_name_flags = STR(buf) + (match == 0); + if (dict_handle(map_type_name_flags) == 0) + dict_register(map_type_name_flags, + dict_open(item, OPEN_FLAGS, DICT_FLAGS)); + argv_add(pat_list, STR(buf), (char *) 0); + } else { /* other pattern */ + casefold(match_list->fold_buf, match ? + item : STR(vstring_sprintf(buf, "!%s", item))); + argv_add(pat_list, STR(match_list->fold_buf), (char *) 0); + } + } + vstring_free(buf); + return (pat_list); +} + +/* match_list_init - initialize pattern list */ + +MATCH_LIST *match_list_init(const char *pname, int flags, + const char *patterns, int match_count,...) +{ + MATCH_LIST *list; + char *saved_patterns; + va_list ap; + int i; + + if (flags & ~MATCH_FLAG_ALL) + msg_panic("match_list_init: bad flags 0x%x", flags); + + list = (MATCH_LIST *) mymalloc(sizeof(*list)); + list->pname = mystrdup(pname); + list->flags = flags; + list->match_count = match_count; + list->match_func = + (MATCH_LIST_FN *) mymalloc(match_count * sizeof(MATCH_LIST_FN)); + list->match_args = + (const char **) mymalloc(match_count * sizeof(const char *)); + va_start(ap, match_count); + for (i = 0; i < match_count; i++) + list->match_func[i] = va_arg(ap, MATCH_LIST_FN); + va_end(ap); + list->error = 0; + list->fold_buf = vstring_alloc(20); + +#define DO_MATCH 1 + + saved_patterns = mystrdup(patterns); + list->patterns = match_list_parse(list, argv_alloc(1), saved_patterns, + DO_MATCH); + argv_terminate(list->patterns); + myfree(saved_patterns); + return (list); +} + +/* match_list_match - match strings against pattern list */ + +int match_list_match(MATCH_LIST *list,...) +{ + const char *myname = "match_list_match"; + char **cpp; + char *pat; + int match; + int i; + va_list ap; + + /* + * Iterate over all patterns in the list, stop at the first match. + */ + va_start(ap, list); + for (i = 0; i < list->match_count; i++) + list->match_args[i] = va_arg(ap, const char *); + va_end(ap); + + list->error = 0; + for (cpp = list->patterns->argv; (pat = *cpp) != 0; cpp++) { + for (match = 1; *pat == '!'; pat++) + match = !match; + for (i = 0; i < list->match_count; i++) { + casefold(list->fold_buf, list->match_args[i]); + if (list->match_func[i] (list, STR(list->fold_buf), pat)) + return (match); + else if (list->error != 0) + return (0); + } + } + if (msg_verbose) + for (i = 0; i < list->match_count; i++) + msg_info("%s: %s: no match", myname, list->match_args[i]); + return (0); +} + +/* match_list_free - release storage */ + +void match_list_free(MATCH_LIST *list) +{ + /* XXX Should decrement map refcounts. */ + myfree(list->pname); + argv_free(list->patterns); + myfree((void *) list->match_func); + myfree((void *) list->match_args); + vstring_free(list->fold_buf); + myfree((void *) list); +} diff --git a/src/util/match_list.h b/src/util/match_list.h new file mode 100644 index 0000000..d8b7794 --- /dev/null +++ b/src/util/match_list.h @@ -0,0 +1,66 @@ +#ifndef _MATCH_LIST_H_INCLUDED_ +#define _MATCH_LIST_H_INCLUDED_ + +/*++ +/* NAME +/* match_list 3h +/* SUMMARY +/* generic list-based pattern matching +/* SYNOPSIS +/* #include <match_list.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <argv.h> +#include <vstring.h> + + /* + * External interface. + */ +typedef struct MATCH_LIST MATCH_LIST; + +typedef int (*MATCH_LIST_FN) (MATCH_LIST *, const char *, const char *); + +struct MATCH_LIST { + char *pname; /* used in error messages */ + int flags; /* processing options */ + ARGV *patterns; /* one pattern each */ + int match_count; /* match function/argument count */ + MATCH_LIST_FN *match_func; /* match functions */ + const char **match_args; /* match arguments */ + VSTRING *fold_buf; /* case-folded pattern string */ + int error; /* last operation */ +}; + +#define MATCH_FLAG_NONE 0 +#define MATCH_FLAG_PARENT (1<<0) +#define MATCH_FLAG_RETURN (1<<1) +#define MATCH_FLAG_ALL (MATCH_FLAG_PARENT | MATCH_FLAG_RETURN) + +extern MATCH_LIST *match_list_init(const char *, int, const char *, int,...); +extern int match_list_match(MATCH_LIST *,...); +extern void match_list_free(MATCH_LIST *); + + /* + * The following functions are not part of the public interface. These + * functions may be called only through match_list_match(). + */ +extern int match_string(MATCH_LIST *, const char *, const char *); +extern int match_hostname(MATCH_LIST *, const char *, const char *); +extern int match_hostaddr(MATCH_LIST *, const char *, const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/match_ops.c b/src/util/match_ops.c new file mode 100644 index 0000000..e0b7779 --- /dev/null +++ b/src/util/match_ops.c @@ -0,0 +1,312 @@ +/*++ +/* NAME +/* match_ops 3 +/* SUMMARY +/* simple string or host pattern matching +/* SYNOPSIS +/* #include <match_list.h> +/* +/* int match_string(list, string, pattern) +/* MATCH_LIST *list; +/* const char *string; +/* const char *pattern; +/* +/* int match_hostname(list, name, pattern) +/* MATCH_LIST *list; +/* const char *name; +/* const char *pattern; +/* +/* int match_hostaddr(list, addr, pattern) +/* MATCH_LIST *list; +/* const char *addr; +/* const char *pattern; +/* DESCRIPTION +/* This module implements simple string and host name or address +/* matching. The matching process is case insensitive. If a pattern +/* has the form type:name, table lookup is used instead of string +/* or address comparison. +/* +/* match_string() matches the string against the pattern, requiring +/* an exact (case-insensitive) match. The flags argument is not used. +/* +/* match_hostname() matches the host name when the hostname matches +/* the pattern exactly, or when the pattern matches a parent domain +/* of the named host. The flags argument specifies the bit-wise OR +/* of zero or more of the following: +/* .IP MATCH_FLAG_PARENT +/* The hostname pattern foo.com matches itself and any name below +/* the domain foo.com. If this flag is cleared, foo.com matches itself +/* only, and .foo.com matches any name below the domain foo.com. +/* .IP MATCH_FLAG_RETURN +/* Log a warning, return "not found", and set list->error to +/* a non-zero dictionary error code, instead of raising a fatal +/* run-time error. +/* .RE +/* Specify MATCH_FLAG_NONE to request none of the above. +/* +/* match_hostaddr() matches a host address when the pattern is +/* identical to the host address, or when the pattern is a net/mask +/* that contains the address. The mask specifies the number of +/* bits in the network part of the pattern. The flags argument is +/* not used. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <string.h> +#include <stdlib.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <split_at.h> +#include <dict.h> +#include <match_list.h> +#include <stringops.h> +#include <cidr_match.h> + +#define MATCH_DICTIONARY(pattern) \ + ((pattern)[0] != '[' && strchr((pattern), ':') != 0) + +/* match_error - return or raise fatal error */ + +static int match_error(MATCH_LIST *list, const char *fmt,...) +{ + VSTRING *buf = vstring_alloc(100); + va_list ap; + + /* + * Report, and maybe return. + */ + va_start(ap, fmt); + vstring_vsprintf(buf, fmt, ap); + va_end(ap); + if (list->flags & MATCH_FLAG_RETURN) { + msg_warn("%s: %s", list->pname, vstring_str(buf)); + } else { + msg_fatal("%s: %s", list->pname, vstring_str(buf)); + } + vstring_free(buf); + return (0); +} + +/* match_string - match a string literal */ + +int match_string(MATCH_LIST *list, const char *string, const char *pattern) +{ + const char *myname = "match_string"; + DICT *dict; + + if (msg_verbose) + msg_info("%s: %s: %s ~? %s", myname, list->pname, string, pattern); + + /* + * Try dictionary lookup: exact match. + */ + if (MATCH_DICTIONARY(pattern)) { + if ((dict = dict_handle(pattern)) == 0) + msg_panic("%s: unknown dictionary: %s", myname, pattern); + if (dict_get(dict, string) != 0) + return (1); + if ((list->error = dict->error) != 0) + return (match_error(list, "%s:%s: table lookup problem", + dict->type, dict->name)); + return (0); + } + + /* + * Try an exact string match. Note that the string and pattern are + * already casefolded. + */ + if (strcmp(string, pattern) == 0) { + return (1); + } + + /* + * No match found. + */ + return (0); +} + +/* match_hostname - match a host by name */ + +int match_hostname(MATCH_LIST *list, const char *name, const char *pattern) +{ + const char *myname = "match_hostname"; + const char *pd; + const char *entry; + const char *next; + int match; + DICT *dict; + + if (msg_verbose) + msg_info("%s: %s: %s ~? %s", myname, list->pname, name, pattern); + + /* + * Try dictionary lookup: exact match and parent domains. + * + * Don't look up parent domain substrings with regexp maps etc. + */ + if (MATCH_DICTIONARY(pattern)) { + if ((dict = dict_handle(pattern)) == 0) + msg_panic("%s: unknown dictionary: %s", myname, pattern); + match = 0; + for (entry = name; *entry != 0; entry = next) { + if (entry == name || (dict->flags & DICT_FLAG_FIXED)) { + match = (dict_get(dict, entry) != 0); + if (msg_verbose > 1) + msg_info("%s: %s: lookup %s:%s %s: %s", + myname, list->pname, dict->type, dict->name, + entry, match ? "found" : "notfound"); + if (match != 0) + break; + if ((list->error = dict->error) != 0) + return (match_error(list, "%s:%s: table lookup problem", + dict->type, dict->name)); + } + if ((next = strchr(entry + 1, '.')) == 0) + break; + if (list->flags & MATCH_FLAG_PARENT) + next += 1; + } + return (match); + } + + /* + * Try an exact match with the host name. Note that the name and the + * pattern are already casefolded. + */ + if (strcmp(name, pattern) == 0) { + return (1); + } + + /* + * See if the pattern is a parent domain of the hostname. Note that the + * name and the pattern are already casefolded. + */ + else { + if (list->flags & MATCH_FLAG_PARENT) { + pd = name + strlen(name) - strlen(pattern); + if (pd > name && pd[-1] == '.' && strcmp(pd, pattern) == 0) + return (1); + } else if (pattern[0] == '.') { + pd = name + strlen(name) - strlen(pattern); + if (pd > name && strcmp(pd, pattern) == 0) + return (1); + } + } + return (0); +} + +/* match_hostaddr - match host by address */ + +int match_hostaddr(MATCH_LIST *list, const char *addr, const char *pattern) +{ + const char *myname = "match_hostaddr"; + char *saved_patt; + CIDR_MATCH match_info; + DICT *dict; + VSTRING *err; + int rc; + + if (msg_verbose) + msg_info("%s: %s: %s ~? %s", myname, list->pname, addr, pattern); + +#define V4_ADDR_STRING_CHARS "01234567890." +#define V6_ADDR_STRING_CHARS V4_ADDR_STRING_CHARS "abcdefABCDEF:" + + if (addr[strspn(addr, V6_ADDR_STRING_CHARS)] != 0) + return (0); + + /* + * Try dictionary lookup. This can be case insensitive. + */ + if (MATCH_DICTIONARY(pattern)) { + if ((dict = dict_handle(pattern)) == 0) + msg_panic("%s: unknown dictionary: %s", myname, pattern); + if (dict_get(dict, addr) != 0) + return (1); + if ((list->error = dict->error) != 0) + return (match_error(list, "%s:%s: table lookup problem", + dict->type, dict->name)); + return (0); + } + + /* + * Try an exact match with the host address. Note that the address and + * pattern are already casefolded. + */ + if (pattern[0] != '[') { + if (strcmp(addr, pattern) == 0) + return (1); + } else { + size_t addr_len = strlen(addr); + + if (strncmp(addr, pattern + 1, addr_len) == 0 + && strcmp(pattern + 1 + addr_len, "]") == 0) + return (1); + } + + /* + * Light-weight tests before we get into expensive operations. + * + * - Don't bother matching IPv4 against IPv6. Postfix transforms + * IPv4-in-IPv6 to native IPv4 form when IPv4 support is enabled in + * Postfix; if not, then Postfix has no business dealing with IPv4 + * addresses anyway. + * + * - Don't bother unless the pattern is either an IPv6 address or net/mask. + * + * We can safely skip IPv4 address patterns because their form is + * unambiguous and they did not match in the strcmp() calls above. + * + * XXX We MUST skip (parent) domain names, which may appear in NAMADR_LIST + * input, to avoid triggering false cidr_match_parse() errors. + * + * The last two conditions below are for backwards compatibility with + * earlier Postfix versions: don't abort with fatal errors on junk that + * was silently ignored (principle of least astonishment). + */ + if (!strchr(addr, ':') != !strchr(pattern, ':') + || pattern[strcspn(pattern, ":/")] == 0 + || pattern[strspn(pattern, V4_ADDR_STRING_CHARS)] == 0 + || pattern[strspn(pattern, V6_ADDR_STRING_CHARS "[]/")] != 0) + return (0); + + /* + * No escape from expensive operations: either we have a net/mask + * pattern, or we have an address that can have multiple valid + * representations (e.g., 0:0:0:0:0:0:0:1 versus ::1, etc.). The only way + * to find out if the address matches the pattern is to transform + * everything into to binary form, and to do the comparison there. + */ + saved_patt = mystrdup(pattern); + err = cidr_match_parse(&match_info, saved_patt, CIDR_MATCH_TRUE, + (VSTRING *) 0); + myfree(saved_patt); + if (err != 0) { + list->error = DICT_ERR_RETRY; + rc = match_error(list, "%s", vstring_str(err)); + vstring_free(err); + return (rc); + } + return (cidr_match_execute(&match_info, addr) != 0); +} diff --git a/src/util/midna_domain.c b/src/util/midna_domain.c new file mode 100644 index 0000000..333a5c9 --- /dev/null +++ b/src/util/midna_domain.c @@ -0,0 +1,419 @@ +/*++ +/* NAME +/* midna_domain 3 +/* SUMMARY +/* ASCII/UTF-8 domain name conversion +/* SYNOPSIS +/* #include <midna_domain.h> +/* +/* int midna_domain_cache_size; +/* int midna_domain_transitional; +/* +/* const char *midna_domain_to_ascii( +/* const char *name) +/* +/* const char *midna_domain_to_utf8( +/* const char *name) +/* +/* const char *midna_domain_suffix_to_ascii( +/* const char *name) +/* +/* const char *midna_domain_suffix_to_utf8( +/* const char *name) +/* AUXILIARY FUNCTIONS +/* void midna_domain_pre_chroot(void) +/* DESCRIPTION +/* The functions in this module transform domain names from/to +/* ASCII and UTF-8 form. The result is cached to avoid repeated +/* conversion. +/* +/* This module builds on the ICU library implementation of the +/* UTS #46 specification, using default ICU library options +/* because those are likely best tested: with transitional +/* processing, with case mapping, with normalization, with +/* limited IDNA2003 compatibility, without STD3 ASCII rules. +/* +/* midna_domain_to_ascii() converts an UTF-8 or ASCII domain +/* name to ASCII. The result is a null pointer in case of +/* error. This function verifies that the result passes +/* valid_hostname(). +/* +/* midna_domain_to_utf8() converts an UTF-8 or ASCII domain +/* name to UTF-8. The result is a null pointer in case of +/* error. This function verifies that the result, after +/* conversion to ASCII, passes valid_hostname(). +/* +/* midna_domain_suffix_to_ascii() and midna_domain_suffix_to_utf8() +/* take a name that starts with '.' and otherwise perform the +/* same operations as midna_domain_to_ascii() and +/* midna_domain_to_utf8(). +/* +/* midna_domain_cache_size specifies the size of the conversion +/* result cache. This value is used only once, upon the first +/* lookup request. +/* +/* midna_domain_transitional enables transitional conversion +/* between UTF8 and ASCII labels. +/* +/* midna_domain_pre_chroot() does some pre-chroot initialization. +/* SEE ALSO +/* http://unicode.org/reports/tr46/ Unicode IDNA Compatibility processing +/* msg(3) diagnostics interface +/* DIAGNOSTICS +/* Fatal errors: memory allocation problem. +/* Warnings: conversion error or result validation error. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Arnt Gulbrandsen +/* +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include <sys_defs.h> +#include <string.h> +#include <ctype.h> + +#ifndef NO_EAI +#include <unicode/uidna.h> + + /* + * Utility library. + */ +#include <mymalloc.h> +#include <msg.h> +#include <ctable.h> +#include <stringops.h> +#include <valid_hostname.h> +#include <name_mask.h> +#include <midna_domain.h> + + /* + * Application-specific. + */ +#define DEF_MIDNA_CACHE_SIZE 256 + +int midna_domain_cache_size = DEF_MIDNA_CACHE_SIZE; +int midna_domain_transitional = 0; +static VSTRING *midna_domain_buf; /* x.suffix */ + +#define STR(x) vstring_str(x) + +/* midna_domain_strerror - pick one for error reporting */ + +static const char *midna_domain_strerror(UErrorCode error, int info_errors) +{ + + /* + * XXX The UIDNA_ERROR_EMPTY_LABEL etc. names are defined in an ENUM, so + * we can't use #ifdef to dynamically determine which names exist. + */ + static LONG_NAME_MASK uidna_errors[] = { + "UIDNA_ERROR_EMPTY_LABEL", UIDNA_ERROR_EMPTY_LABEL, + "UIDNA_ERROR_LABEL_TOO_LONG", UIDNA_ERROR_LABEL_TOO_LONG, + "UIDNA_ERROR_DOMAIN_NAME_TOO_LONG", UIDNA_ERROR_DOMAIN_NAME_TOO_LONG, + "UIDNA_ERROR_LEADING_HYPHEN", UIDNA_ERROR_LEADING_HYPHEN, + "UIDNA_ERROR_TRAILING_HYPHEN", UIDNA_ERROR_TRAILING_HYPHEN, + "UIDNA_ERROR_HYPHEN_3_4", UIDNA_ERROR_HYPHEN_3_4, + "UIDNA_ERROR_LEADING_COMBINING_MARK", UIDNA_ERROR_LEADING_COMBINING_MARK, + "UIDNA_ERROR_DISALLOWED", UIDNA_ERROR_DISALLOWED, + "UIDNA_ERROR_PUNYCODE", UIDNA_ERROR_PUNYCODE, + "UIDNA_ERROR_LABEL_HAS_DOT", UIDNA_ERROR_LABEL_HAS_DOT, + "UIDNA_ERROR_INVALID_ACE_LABEL", UIDNA_ERROR_INVALID_ACE_LABEL, + "UIDNA_ERROR_BIDI", UIDNA_ERROR_BIDI, + "UIDNA_ERROR_CONTEXTJ", UIDNA_ERROR_CONTEXTJ, + /* The above errors are defined with ICU 46 and later. */ + 0, + }; + + if (info_errors) { + return (str_long_name_mask_opt((VSTRING *) 0, "idna error", + uidna_errors, info_errors, + NAME_MASK_NUMBER | NAME_MASK_COMMA)); + } else { + return u_errorName(error); + } +} + +/* midna_domain_pre_chroot - pre-chroot initialization */ + +void midna_domain_pre_chroot(void) +{ + UErrorCode error = U_ZERO_ERROR; + UIDNAInfo info = UIDNA_INFO_INITIALIZER; + UIDNA *idna; + + idna = uidna_openUTS46(midna_domain_transitional ? UIDNA_DEFAULT + : UIDNA_NONTRANSITIONAL_TO_ASCII, &error); + if (U_FAILURE(error)) + msg_warn("ICU library initialization failed: %s", + midna_domain_strerror(error, info.errors)); + uidna_close(idna); +} + +/* midna_domain_to_ascii_create - convert domain to ASCII */ + +static void *midna_domain_to_ascii_create(const char *name, void *unused_context) +{ + static const char myname[] = "midna_domain_to_ascii_create"; + char buf[1024]; /* XXX */ + UErrorCode error = U_ZERO_ERROR; + UIDNAInfo info = UIDNA_INFO_INITIALIZER; + UIDNA *idna; + int anl; + + /* + * Paranoia: do not expose uidna_*() to unfiltered network data. + */ + if (allascii(name) == 0 && valid_utf8_string(name, strlen(name)) == 0) { + msg_warn("%s: Problem translating domain \"%.100s\" to ASCII form: %s", + myname, name, "malformed UTF-8"); + return (0); + } + + /* + * Perform the requested conversion. + */ + idna = uidna_openUTS46(midna_domain_transitional ? UIDNA_DEFAULT + : UIDNA_NONTRANSITIONAL_TO_ASCII, &error); + anl = uidna_nameToASCII_UTF8(idna, + name, strlen(name), + buf, sizeof(buf) - 1, + &info, + &error); + uidna_close(idna); + + /* + * Paranoia: verify that the result passes valid_hostname(). A quick + * check shows that UTS46 ToASCII by default rejects inputs with labels + * that start or end in '-', with names or labels that are over-long, or + * "fake" A-labels, as required by UTS 46 section 4.1, but we rely on + * valid_hostname() on the output side just to be sure. + */ + if (U_SUCCESS(error) && info.errors == 0 && anl > 0) { + buf[anl] = 0; /* XXX */ + if (!valid_hostname(buf, DONT_GRIPE)) { + msg_warn("%s: Problem translating domain \"%.100s\" to ASCII form: %s", + myname, name, "malformed ASCII label(s)"); + return (0); + } + return (mystrndup(buf, anl)); + } else { + msg_warn("%s: Problem translating domain \"%.100s\" to ASCII form: %s", + myname, name, midna_domain_strerror(error, info.errors)); + return (0); + } +} + +/* midna_domain_to_utf8_create - convert domain to UTF8 */ + +static void *midna_domain_to_utf8_create(const char *name, void *unused_context) +{ + static const char myname[] = "midna_domain_to_utf8_create"; + char buf[1024]; /* XXX */ + UErrorCode error = U_ZERO_ERROR; + UIDNAInfo info = UIDNA_INFO_INITIALIZER; + UIDNA *idna; + int anl; + + /* + * Paranoia: do not expose uidna_*() to unfiltered network data. + */ + if (allascii(name) == 0 && valid_utf8_string(name, strlen(name)) == 0) { + msg_warn("%s: Problem translating domain \"%.100s\" to UTF-8 form: %s", + myname, name, "malformed UTF-8"); + return (0); + } + + /* + * Perform the requested conversion. + */ + idna = uidna_openUTS46(midna_domain_transitional ? UIDNA_DEFAULT + : UIDNA_NONTRANSITIONAL_TO_UNICODE, &error); + anl = uidna_nameToUnicodeUTF8(idna, + name, strlen(name), + buf, sizeof(buf) - 1, + &info, + &error); + uidna_close(idna); + + /* + * Paranoia: UTS46 toUTF8 by default accepts and produces an over-long + * name or a name that contains an over-long NR-LDH label (and perhaps + * other invalid forms that are not covered in UTS 46, section 4.1). We + * rely on midna_domain_to_ascii() to validate the output. + */ + if (U_SUCCESS(error) && info.errors == 0 && anl > 0) { + buf[anl] = 0; /* XXX */ + if (midna_domain_to_ascii(buf) == 0) + return (0); + return (mystrndup(buf, anl)); + } else { + msg_warn("%s: Problem translating domain \"%.100s\" to UTF8 form: %s", + myname, name, midna_domain_strerror(error, info.errors)); + return (0); + } +} + +/* midna_domain_cache_free - cache element destructor */ + +static void midna_domain_cache_free(void *value, void *unused_context) +{ + if (value) + myfree(value); +} + +/* midna_domain_to_ascii - convert name to ASCII */ + +const char *midna_domain_to_ascii(const char *name) +{ + static CTABLE *midna_domain_to_ascii_cache = 0; + + if (midna_domain_to_ascii_cache == 0) + midna_domain_to_ascii_cache = ctable_create(midna_domain_cache_size, + midna_domain_to_ascii_create, + midna_domain_cache_free, + (void *) 0); + return (ctable_locate(midna_domain_to_ascii_cache, name)); +} + +/* midna_domain_to_utf8 - convert name to UTF8 */ + +const char *midna_domain_to_utf8(const char *name) +{ + static CTABLE *midna_domain_to_utf8_cache = 0; + + if (midna_domain_to_utf8_cache == 0) + midna_domain_to_utf8_cache = ctable_create(midna_domain_cache_size, + midna_domain_to_utf8_create, + midna_domain_cache_free, + (void *) 0); + return (ctable_locate(midna_domain_to_utf8_cache, name)); +} + +/* midna_domain_suffix_to_ascii - convert .name to ASCII */ + +const char *midna_domain_suffix_to_ascii(const char *suffix) +{ + const char *cache_res; + + /* + * If prepending x to .name causes the result to become too long, then + * the suffix is bad. + */ + if (midna_domain_buf == 0) + midna_domain_buf = vstring_alloc(100); + vstring_sprintf(midna_domain_buf, "x%s", suffix); + if ((cache_res = midna_domain_to_ascii(STR(midna_domain_buf))) == 0) + return (0); + else + return (cache_res + 1); +} + +/* midna_domain_suffix_to_utf8 - convert .name to UTF8 */ + +const char *midna_domain_suffix_to_utf8(const char *name) +{ + const char *cache_res; + + /* + * If prepending x to .name causes the result to become too long, then + * the suffix is bad. + */ + if (midna_domain_buf == 0) + midna_domain_buf = vstring_alloc(100); + vstring_sprintf(midna_domain_buf, "x%s", name); + if ((cache_res = midna_domain_to_utf8(STR(midna_domain_buf))) == 0) + return (0); + else + return (cache_res + 1); +} + +#ifdef TEST + + /* + * Test program - reads names from stdin, reports invalid names to stderr. + */ +#include <unistd.h> +#include <stdlib.h> +#include <locale.h> + +#include <stringops.h> /* XXX util_utf8_enable */ +#include <vstring.h> +#include <vstream.h> +#include <vstring_vstream.h> +#include <msg_vstream.h> + +int main(int argc, char **argv) +{ + VSTRING *buffer = vstring_alloc(1); + const char *bp; + const char *ascii; + const char *utf8; + + if (setlocale(LC_ALL, "C") == 0) + msg_fatal("setlocale(LC_ALL, C) failed: %m"); + + msg_vstream_init(argv[0], VSTREAM_ERR); + /* msg_verbose = 1; */ + util_utf8_enable = 1; + + if (geteuid() == 0) { + midna_domain_pre_chroot(); + if (chroot(".") != 0) + msg_fatal("chroot(\".\"): %m"); + } + while (vstring_fgets_nonl(buffer, VSTREAM_IN)) { + bp = STR(buffer); + msg_info("> %s", bp); + while (ISSPACE(*bp)) + bp++; + if (*bp == '#' || *bp == 0) + continue; + msg_info("unconditional conversions:"); + utf8 = midna_domain_to_utf8(bp); + msg_info("\"%s\" ->utf8 \"%s\"", bp, utf8 ? utf8 : "(error)"); + ascii = midna_domain_to_ascii(bp); + msg_info("\"%s\" ->ascii \"%s\"", bp, ascii ? ascii : "(error)"); + msg_info("conditional conversions:"); + if (!allascii(bp)) { + if (ascii != 0) { + utf8 = midna_domain_to_utf8(ascii); + msg_info("\"%s\" ->ascii \"%s\" ->utf8 \"%s\"", + bp, ascii, utf8 ? utf8 : "(error)"); + if (utf8 != 0) { + if (strcmp(utf8, bp) != 0) + msg_warn("\"%s\" != \"%s\"", bp, utf8); + } + } + } else { + if (utf8 != 0) { + ascii = midna_domain_to_ascii(utf8); + msg_info("\"%s\" ->utf8 \"%s\" ->ascii \"%s\"", + bp, utf8, ascii ? ascii : "(error)"); + if (ascii != 0) { + if (strcmp(ascii, bp) != 0) + msg_warn("\"%s\" != \"%s\"", bp, ascii); + } + } + } + } + exit(0); +} + +#endif /* TEST */ + +#endif /* NO_EAI */ diff --git a/src/util/midna_domain.h b/src/util/midna_domain.h new file mode 100644 index 0000000..1abe2a1 --- /dev/null +++ b/src/util/midna_domain.h @@ -0,0 +1,43 @@ +#ifndef _MIDNA_H_INCLUDED_ +#define _MIDNA_H_INCLUDED_ + +/*++ +/* NAME +/* midna_domain 3h +/* SUMMARY +/* ASCII/UTF-8 domain name conversion +/* SYNOPSIS +/* #include <midna_domain.h> +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern const char *midna_domain_to_ascii(const char *); +extern const char *midna_domain_to_utf8(const char *); +extern const char *midna_domain_suffix_to_ascii(const char *); +extern const char *midna_domain_suffix_to_utf8(const char *); +extern void midna_domain_pre_chroot(void); + +extern int midna_domain_cache_size; +extern int midna_domain_transitional; +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Arnt Gulbrandsen +/* +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/midna_domain_test.in b/src/util/midna_domain_test.in new file mode 100644 index 0000000..55491e4 --- /dev/null +++ b/src/util/midna_domain_test.in @@ -0,0 +1,21 @@ +# Upper-case greek -> lower-case greek. +Δημοσθένους.example.com +# Upper-case ASCII -> lower-case ASCII. +Hello.example.com +# Invalid LDH label('-' at begin or end). +bad-.example.com +-bad.example.com +# Invalid LDH (label > 63 bytes). +abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789.example.com +# Valid LDH label (label <= 63 bytes). +abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com +# Invalid name (length > 255 bytes). +abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.example.com +# Aliases for '.' -> '.'. +x。example.com +x.example.com +x。example.com +# Good a-label. +xn--mumble.example.com +# Bad a-label. +xn--123456.example.com diff --git a/src/util/midna_domain_test.ref b/src/util/midna_domain_test.ref new file mode 100644 index 0000000..c1db0bd --- /dev/null +++ b/src/util/midna_domain_test.ref @@ -0,0 +1,89 @@ +./midna_domain: > # Upper-case greek -> lower-case greek. +./midna_domain: > Δημοσθένους.example.com +./midna_domain: unconditional conversions: +./midna_domain: "Δημοσθένους.example.com" ->utf8 "δημοσθÎνους.example.com" +./midna_domain: "Δημοσθένους.example.com" ->ascii "xn--ixanjetild1af0a.example.com" +./midna_domain: conditional conversions: +./midna_domain: "Δημοσθένους.example.com" ->ascii "xn--ixanjetild1af0a.example.com" ->utf8 "δημοσθÎνους.example.com" +./midna_domain: warning: "Δημοσθένους.example.com" != "δημοσθÎνους.example.com" +./midna_domain: > # Upper-case ASCII -> lower-case ASCII. +./midna_domain: > Hello.example.com +./midna_domain: unconditional conversions: +./midna_domain: "Hello.example.com" ->utf8 "hello.example.com" +./midna_domain: "Hello.example.com" ->ascii "hello.example.com" +./midna_domain: conditional conversions: +./midna_domain: "Hello.example.com" ->utf8 "hello.example.com" ->ascii "hello.example.com" +./midna_domain: warning: "Hello.example.com" != "hello.example.com" +./midna_domain: > # Invalid LDH label('-' at begin or end). +./midna_domain: > bad-.example.com +./midna_domain: unconditional conversions: +./midna_domain: warning: midna_domain_to_utf8_create: Problem translating domain "bad-.example.com" to UTF8 form: UIDNA_ERROR_TRAILING_HYPHEN +./midna_domain: "bad-.example.com" ->utf8 "(error)" +./midna_domain: warning: midna_domain_to_ascii_create: Problem translating domain "bad-.example.com" to ASCII form: UIDNA_ERROR_TRAILING_HYPHEN +./midna_domain: "bad-.example.com" ->ascii "(error)" +./midna_domain: conditional conversions: +./midna_domain: > -bad.example.com +./midna_domain: unconditional conversions: +./midna_domain: warning: midna_domain_to_utf8_create: Problem translating domain "-bad.example.com" to UTF8 form: UIDNA_ERROR_LEADING_HYPHEN +./midna_domain: "-bad.example.com" ->utf8 "(error)" +./midna_domain: warning: midna_domain_to_ascii_create: Problem translating domain "-bad.example.com" to ASCII form: UIDNA_ERROR_LEADING_HYPHEN +./midna_domain: "-bad.example.com" ->ascii "(error)" +./midna_domain: conditional conversions: +./midna_domain: > # Invalid LDH (label > 63 bytes). +./midna_domain: > abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789.example.com +./midna_domain: unconditional conversions: +./midna_domain: warning: midna_domain_to_ascii_create: Problem translating domain "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789.example.com" to ASCII form: UIDNA_ERROR_LABEL_TOO_LONG +./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789.example.com" ->utf8 "(error)" +./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789.example.com" ->ascii "(error)" +./midna_domain: conditional conversions: +./midna_domain: > # Valid LDH label (label <= 63 bytes). +./midna_domain: > abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com +./midna_domain: unconditional conversions: +./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" ->utf8 "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" +./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" ->ascii "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" +./midna_domain: conditional conversions: +./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" ->utf8 "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" ->ascii "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" +./midna_domain: > # Invalid name (length > 255 bytes). +./midna_domain: > abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.example.com +./midna_domain: unconditional conversions: +./midna_domain: warning: midna_domain_to_ascii_create: Problem translating domain "abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcde" to ASCII form: UIDNA_ERROR_DOMAIN_NAME_TOO_LONG +./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.example.com" ->utf8 "(error)" +./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.example.com" ->ascii "(error)" +./midna_domain: conditional conversions: +./midna_domain: > # Aliases for '.' -> '.'. +./midna_domain: > x。example.com +./midna_domain: unconditional conversions: +./midna_domain: "x。example.com" ->utf8 "x.example.com" +./midna_domain: "x。example.com" ->ascii "x.example.com" +./midna_domain: conditional conversions: +./midna_domain: "x。example.com" ->ascii "x.example.com" ->utf8 "x.example.com" +./midna_domain: warning: "x。example.com" != "x.example.com" +./midna_domain: > x.example.com +./midna_domain: unconditional conversions: +./midna_domain: "x.example.com" ->utf8 "x.example.com" +./midna_domain: "x.example.com" ->ascii "x.example.com" +./midna_domain: conditional conversions: +./midna_domain: "x.example.com" ->ascii "x.example.com" ->utf8 "x.example.com" +./midna_domain: warning: "x.example.com" != "x.example.com" +./midna_domain: > x。example.com +./midna_domain: unconditional conversions: +./midna_domain: "x。example.com" ->utf8 "x.example.com" +./midna_domain: "x。example.com" ->ascii "x.example.com" +./midna_domain: conditional conversions: +./midna_domain: "x。example.com" ->ascii "x.example.com" ->utf8 "x.example.com" +./midna_domain: warning: "x。example.com" != "x.example.com" +./midna_domain: > # Good a-label. +./midna_domain: > xn--mumble.example.com +./midna_domain: unconditional conversions: +./midna_domain: "xn--mumble.example.com" ->utf8 "㲹㲺㲵㲴.example.com" +./midna_domain: "xn--mumble.example.com" ->ascii "xn--mumble.example.com" +./midna_domain: conditional conversions: +./midna_domain: "xn--mumble.example.com" ->utf8 "㲹㲺㲵㲴.example.com" ->ascii "xn--mumble.example.com" +./midna_domain: > # Bad a-label. +./midna_domain: > xn--123456.example.com +./midna_domain: unconditional conversions: +./midna_domain: warning: midna_domain_to_utf8_create: Problem translating domain "xn--123456.example.com" to UTF8 form: UIDNA_ERROR_PUNYCODE +./midna_domain: "xn--123456.example.com" ->utf8 "(error)" +./midna_domain: warning: midna_domain_to_ascii_create: Problem translating domain "xn--123456.example.com" to ASCII form: UIDNA_ERROR_PUNYCODE +./midna_domain: "xn--123456.example.com" ->ascii "(error)" +./midna_domain: conditional conversions: diff --git a/src/util/miss_endif_cidr.map b/src/util/miss_endif_cidr.map new file mode 100644 index 0000000..7c88208 --- /dev/null +++ b/src/util/miss_endif_cidr.map @@ -0,0 +1 @@ +if 1.2.3.4 diff --git a/src/util/miss_endif_cidr.ref b/src/util/miss_endif_cidr.ref new file mode 100644 index 0000000..df5dc6c --- /dev/null +++ b/src/util/miss_endif_cidr.ref @@ -0,0 +1,4 @@ +./dict_open: warning: cidr map miss_endif_cidr.map, line 1: IF has no matching ENDIF +owner=untrusted (uid=USER) +> get 1.2.3.5 +1.2.3.5: not found diff --git a/src/util/miss_endif_pcre.ref b/src/util/miss_endif_pcre.ref new file mode 100644 index 0000000..21d7402 --- /dev/null +++ b/src/util/miss_endif_pcre.ref @@ -0,0 +1,4 @@ +./dict_open: warning: pcre map miss_endif_re.map, line 1: IF has no matching ENDIF +owner=untrusted (uid=USER) +> get 1.2.3.5 +1.2.3.5: not found diff --git a/src/util/miss_endif_re.map b/src/util/miss_endif_re.map new file mode 100644 index 0000000..b085ecb --- /dev/null +++ b/src/util/miss_endif_re.map @@ -0,0 +1 @@ +if /foo/ diff --git a/src/util/miss_endif_regexp.ref b/src/util/miss_endif_regexp.ref new file mode 100644 index 0000000..f77f95a --- /dev/null +++ b/src/util/miss_endif_regexp.ref @@ -0,0 +1,4 @@ +./dict_open: warning: regexp map miss_endif_re.map, line 1: IF has no matching ENDIF +owner=untrusted (uid=USER) +> get 1.2.3.5 +1.2.3.5: not found diff --git a/src/util/msg.c b/src/util/msg.c new file mode 100644 index 0000000..70c6eab --- /dev/null +++ b/src/util/msg.c @@ -0,0 +1,340 @@ +/*++ +/* NAME +/* msg 3 +/* SUMMARY +/* diagnostic interface +/* SYNOPSIS +/* #include <msg.h> +/* +/* int msg_verbose; +/* +/* void msg_info(format, ...) +/* const char *format; +/* +/* void vmsg_info(format, ap) +/* const char *format; +/* va_list ap; +/* +/* void msg_warn(format, ...) +/* const char *format; +/* +/* void vmsg_warn(format, ap) +/* const char *format; +/* va_list ap; +/* +/* void msg_error(format, ...) +/* const char *format; +/* +/* void vmsg_error(format, ap) +/* const char *format; +/* va_list ap; +/* +/* NORETURN msg_fatal(format, ...) +/* const char *format; +/* +/* NORETURN vmsg_fatal(format, ap) +/* const char *format; +/* va_list ap; +/* +/* NORETURN msg_fatal_status(status, format, ...) +/* int status; +/* const char *format; +/* +/* NORETURN vmsg_fatal_status(status, format, ap) +/* int status; +/* const char *format; +/* va_list ap; +/* +/* NORETURN msg_panic(format, ...) +/* const char *format; +/* +/* NORETURN vmsg_panic(format, ap) +/* const char *format; +/* va_list ap; +/* +/* MSG_CLEANUP_FN msg_cleanup(cleanup) +/* void (*cleanup)(void); +/* AUXILIARY FUNCTIONS +/* int msg_error_limit(count) +/* int count; +/* +/* void msg_error_clear() +/* DESCRIPTION +/* This module reports diagnostics. By default, diagnostics are sent +/* to the standard error stream, but the disposition can be changed +/* by the user. See the hints below in the SEE ALSO section. +/* +/* msg_info(), msg_warn(), msg_error(), msg_fatal*() and msg_panic() +/* produce a one-line record with the program name, a severity code +/* (except for msg_info()), and an informative message. The program +/* name must have been set by calling one of the msg_XXX_init() +/* functions (see the SEE ALSO section). +/* +/* msg_error() reports a recoverable error and increments the error +/* counter. When the error count exceeds a pre-set limit (default: 13) +/* the program terminates by calling msg_fatal(). +/* +/* msg_fatal() reports an unrecoverable error and terminates the program +/* with a non-zero exit status. +/* +/* msg_fatal_status() reports an unrecoverable error and terminates the +/* program with the specified exit status. +/* +/* msg_panic() reports an internal inconsistency, terminates the +/* program immediately (i.e. without calling the optional user-specified +/* cleanup routine), and forces a core dump when possible. +/* +/* msg_cleanup() specifies a function that msg_fatal[_status]() should +/* invoke before terminating the program, and returns the +/* current function pointer. Specify a null argument to disable +/* this feature. +/* +/* msg_error_limit() sets the error message count limit, and returns. +/* the old limit. +/* +/* msg_error_clear() sets the error message count to zero. +/* +/* msg_verbose is a global flag that can be set to make software +/* more verbose about what it is doing. By default the flag is zero. +/* By convention, a larger value means more noise. +/* REENTRANCY +/* .ad +/* .fi +/* The msg_info() etc. output routines are protected against +/* ordinary recursive calls and against re-entry by signal +/* handlers. +/* +/* Protection against re-entry by signal handlers is subject +/* to the following limitations: +/* .IP \(bu +/* The signal handlers must never return. In other words, the +/* signal handlers must do one or more of the following: call +/* _exit(), kill the process with a signal, and permanently block +/* the process. +/* .IP \(bu +/* The signal handlers must invoke msg_info() etc. not until +/* after the msg_XXX_init() functions complete initialization, +/* and not until after the first formatted output to a VSTRING +/* or VSTREAM. +/* .IP \(bu +/* Each msg_cleanup() call-back function, and each Postfix or +/* system function invoked by that call-back function, either +/* protects itself against recursive calls and re-entry by a +/* terminating signal handler, or is called exclusively by the +/* msg(3) module. +/* .PP +/* When re-entrancy is detected, the requested output and +/* optional cleanup operations are skipped. Skipping the output +/* operations prevents memory corruption of VSTREAM_ERR data +/* structures, and prevents deadlock on Linux releases that +/* use mutexes within system library routines such as syslog(). +/* This protection exists under the condition that these +/* specific resources are accessed exclusively via the msg_info() +/* etc. functions. +/* SEE ALSO +/* msg_output(3) specify diagnostics disposition +/* msg_stdio(3) direct diagnostics to standard I/O stream +/* msg_vstream(3) direct diagnostics to VSTREAM. +/* msg_syslog(3) direct diagnostics to syslog daemon +/* BUGS +/* Some output functions may suffer from intentional or accidental +/* record length restrictions that are imposed by library routines +/* and/or by the runtime environment. +/* +/* Code that spawns a child process should almost always reset +/* the cleanup handler. The exception is when the parent exits +/* immediately and the child continues. +/* +/* msg_cleanup() may be unsafe in code that changes process +/* privileges, because the call-back routine may run with the +/* wrong privileges. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include <sys_defs.h> +#include <stdlib.h> +#include <stdarg.h> +#include <unistd.h> + +/* Application-specific. */ + +#include "msg.h" +#include "msg_output.h" + + /* + * Default is verbose logging off. + */ +int msg_verbose = 0; + + /* + * Private state. + */ +static MSG_CLEANUP_FN msg_cleanup_fn = 0; +static int msg_error_count = 0; +static int msg_error_bound = 13; + + /* + * The msg_exiting flag prevents us from recursively reporting an error with + * msg_fatal*() or msg_panic(), and provides a first-level safety net for + * optional cleanup actions against signal handler re-entry problems. Note + * that msg_vprintf() implements its own guard against re-entry. + * + * XXX We specify global scope, to discourage the compiler from doing smart + * things. + */ +volatile int msg_exiting = 0; + +/* msg_info - report informative message */ + +void msg_info(const char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + vmsg_info(fmt, ap); + va_end(ap); +} + +void vmsg_info(const char *fmt, va_list ap) +{ + msg_vprintf(MSG_INFO, fmt, ap); +} + +/* msg_warn - report warning message */ + +void msg_warn(const char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + vmsg_warn(fmt, ap); + va_end(ap); +} + +void vmsg_warn(const char *fmt, va_list ap) +{ + msg_vprintf(MSG_WARN, fmt, ap); +} + +/* msg_error - report recoverable error */ + +void msg_error(const char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + vmsg_error(fmt, ap); + va_end(ap); +} + +void vmsg_error(const char *fmt, va_list ap) +{ + msg_vprintf(MSG_ERROR, fmt, ap); + if (++msg_error_count >= msg_error_bound) + msg_fatal("too many errors - program terminated"); +} + +/* msg_fatal - report error and terminate gracefully */ + +NORETURN msg_fatal(const char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + vmsg_fatal(fmt, ap); + /* NOTREACHED */ +} + +NORETURN vmsg_fatal(const char *fmt, va_list ap) +{ + if (msg_exiting++ == 0) { + msg_vprintf(MSG_FATAL, fmt, ap); + if (msg_cleanup_fn) + msg_cleanup_fn(); + } + sleep(1); + /* In case we're running as a signal handler. */ + _exit(1); +} + +/* msg_fatal_status - report error and terminate gracefully */ + +NORETURN msg_fatal_status(int status, const char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + vmsg_fatal_status(status, fmt, ap); + /* NOTREACHED */ +} + +NORETURN vmsg_fatal_status(int status, const char *fmt, va_list ap) +{ + if (msg_exiting++ == 0) { + msg_vprintf(MSG_FATAL, fmt, ap); + if (msg_cleanup_fn) + msg_cleanup_fn(); + } + sleep(1); + /* In case we're running as a signal handler. */ + _exit(status); +} + +/* msg_panic - report error and dump core */ + +NORETURN msg_panic(const char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + vmsg_panic(fmt, ap); + /* NOTREACHED */ +} + +NORETURN vmsg_panic(const char *fmt, va_list ap) +{ + if (msg_exiting++ == 0) { + msg_vprintf(MSG_PANIC, fmt, ap); + } + sleep(1); + abort(); /* Die! */ + /* In case we're running as a signal handler. */ + _exit(1); /* DIE!! */ +} + +/* msg_cleanup - specify cleanup routine */ + +MSG_CLEANUP_FN msg_cleanup(MSG_CLEANUP_FN cleanup_fn) +{ + MSG_CLEANUP_FN old_fn = msg_cleanup_fn; + + msg_cleanup_fn = cleanup_fn; + return (old_fn); +} + +/* msg_error_limit - set error message counter limit */ + +int msg_error_limit(int limit) +{ + int old = msg_error_bound; + + msg_error_bound = limit; + return (old); +} + +/* msg_error_clear - reset error message counter */ + +void msg_error_clear(void) +{ + msg_error_count = 0; +} diff --git a/src/util/msg.h b/src/util/msg.h new file mode 100644 index 0000000..6c75baf --- /dev/null +++ b/src/util/msg.h @@ -0,0 +1,60 @@ +#ifndef _MSG_H_INCLUDED_ +#define _MSG_H_INCLUDED_ + +/*++ +/* NAME +/* msg 3h +/* SUMMARY +/* diagnostics interface +/* SYNOPSIS +/* #include "msg.h" +/* DESCRIPTION +/* .nf + +/* + * System library. + */ +#include <stdarg.h> +#include <time.h> + +/* + * External interface. + */ +typedef void (*MSG_CLEANUP_FN) (void); + +extern int msg_verbose; + +extern void PRINTFLIKE(1, 2) msg_info(const char *,...); +extern void PRINTFLIKE(1, 2) msg_warn(const char *,...); +extern void PRINTFLIKE(1, 2) msg_error(const char *,...); +extern NORETURN PRINTFLIKE(1, 2) msg_fatal(const char *,...); +extern NORETURN PRINTFLIKE(2, 3) msg_fatal_status(int, const char *,...); +extern NORETURN PRINTFLIKE(1, 2) msg_panic(const char *,...); + +extern void vmsg_info(const char *, va_list); +extern void vmsg_warn(const char *, va_list); +extern void vmsg_error(const char *, va_list); +extern NORETURN vmsg_fatal(const char *, va_list); +extern NORETURN vmsg_fatal_status(int, const char *, va_list); +extern NORETURN vmsg_panic(const char *, va_list); + +extern int msg_error_limit(int); +extern void msg_error_clear(void); +extern MSG_CLEANUP_FN msg_cleanup(MSG_CLEANUP_FN); + +extern void PRINTFLIKE(4, 5) msg_rate_delay(time_t *, int, + void PRINTFPTRLIKE(1, 2) (*log_fn) (const char *,...), + const char *,...); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/msg_logger.c b/src/util/msg_logger.c new file mode 100644 index 0000000..07c9e92 --- /dev/null +++ b/src/util/msg_logger.c @@ -0,0 +1,371 @@ +/*++ +/* NAME +/* msg_logger 3 +/* SUMMARY +/* direct diagnostics to logger service +/* SYNOPSIS +/* #include <msg_logger.h> +/* +/* void msg_logger_init( +/* const char *progname, +/* const char *hostname, +/* const char *unix_path, +/* void (*fallback)(const char *)) +/* +/* void msg_logger_control( +/* int key,...) +/* DESCRIPTION +/* This module implements support to report msg(3) diagnostics +/* through a logger daemon, with an optional fallback mechanism. +/* The log record format is like traditional syslog: +/* +/* .nf +/* Mmm dd host progname[pid]: text... +/* .fi +/* +/* msg_logger_init() arranges that subsequent msg(3) calls +/* will write to an internal logging service. This function +/* may also be used to update msg_logger settings. +/* +/* Arguments: +/* .IP progname +/* The program name that is prepended to a log record. +/* .IP hostname +/* The host name that is prepended to a log record. Only the +/* first hostname label will be used. +/* .IP unix_path +/* Pathname of a unix-domain datagram service endpoint. A +/* typical use case is the pathname of the postlog socket. +/* .IP fallback +/* Null pointer, or pointer to function that will be called +/* with a formatted message when the logger service is not +/* (yet) available. A typical use case is to pass the record +/* to the logwriter(3) module. +/* .PP +/* msg_logger_control() makes adjustments to the msg_logger +/* client. These adjustments remain in effect until the next +/* msg_logger_init() or msg_logger_control() call. The arguments +/* are a list of macros with zero or more arguments, terminated +/* with CA_MSG_LOGGER_CTL_END which has none. The following +/* lists the names and the types of the corresponding value +/* arguments. +/* +/* Arguments: +/* .IP CA_MSG_LOGGER_CTL_FALLBACK_ONLY +/* Disable the logging socket, and use the fallback function +/* only. This remains in effect until the next msg_logger_init() +/* call. +/* .IP CA_MSG_LOGGER_CTL_FALLBACK(void (*)(const char *)) +/* Override the fallback setting (see above) with the specified +/* function pointer. This remains in effect until the next +/* msg_logger_init() or msg_logger_control() call. +/* .IP CA_MSG_LOGGER_CTL_DISABLE +/* Disable the msg_logger. This remains in effect until the +/* next msg_logger_init() call. +/* .IP CA_MSG_LOGGER_CTL_CONNECT_NOW +/* Close the logging socket if it was already open, and open +/* the logging socket now, if permitted by current settings. +/* Otherwise, the open is delayed until a logging request. +/* SEE ALSO +/* msg(3) diagnostics module +/* BUGS +/* Output records are truncated to ~2000 characters, because +/* unlimited logging is a liability. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System libraries. + */ +#include <sys_defs.h> +#include <sys/socket.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + + /* + * Application-specific. + */ +#include <connect.h> +#include <logwriter.h> +#include <msg.h> +#include <msg_logger.h> +#include <msg_output.h> +#include <mymalloc.h> +#include <safe.h> +#include <vstream.h> +#include <vstring.h> + + /* + * Saved state from msg_logger_init(). + */ +static char *msg_logger_progname; +static char *msg_logger_hostname; +static char *msg_logger_unix_path; +static void (*msg_logger_fallback_fn) (const char *); +static int msg_logger_fallback_only_override = 0; +static int msg_logger_enable = 0; + +#define MSG_LOGGER_NEED_SOCKET() (msg_logger_fallback_only_override == 0) + + /* + * Other state. + */ +#define MSG_LOGGER_SOCK_NONE (-1) + +static VSTRING *msg_logger_buf; +static int msg_logger_sock = MSG_LOGGER_SOCK_NONE; + + /* + * Safety limit. + */ +#define MSG_LOGGER_RECLEN 2000 + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* msg_logger_connect - connect to logger service */ + +static void msg_logger_connect(void) +{ + if (msg_logger_sock == MSG_LOGGER_SOCK_NONE) { + msg_logger_sock = unix_dgram_connect(msg_logger_unix_path, BLOCKING); + if (msg_logger_sock >= 0) + close_on_exec(msg_logger_sock, CLOSE_ON_EXEC); + } +} + +/* msg_logger_disconnect - disconnect from logger service */ + +static void msg_logger_disconnect(void) +{ + if (msg_logger_sock != MSG_LOGGER_SOCK_NONE) { + (void) close(msg_logger_sock); + msg_logger_sock = MSG_LOGGER_SOCK_NONE; + } +} + +/* msg_logger_print - log info to service or file */ + +static void msg_logger_print(int level, const char *text) +{ + time_t now; + struct tm *lt; + ssize_t len; + + /* + * TODO: this should be a reusable NAME_CODE table plus lookup function. + */ + static int log_level[] = { + MSG_INFO, MSG_WARN, MSG_ERROR, MSG_FATAL, MSG_PANIC, + }; + static char *severity_name[] = { + "info", "warning", "error", "fatal", "panic", + }; + + /* + * This test is simple enough that we don't bother with unregistering the + * msg_logger_print() function. + */ + if (msg_logger_enable == 0) + return; + + /* + * Note: there is code in postlogd(8) that attempts to strip off + * information that is prepended here. If the formatting below is + * changed, then postlogd needs to be updated as well. + */ + + /* + * Format the time stamp. + */ + if (time(&now) < 0) + msg_fatal("no time: %m"); + lt = localtime(&now); + VSTRING_RESET(msg_logger_buf); + if ((len = strftime(vstring_str(msg_logger_buf), + vstring_avail(msg_logger_buf), + "%b %d %H:%M:%S ", lt)) == 0) + msg_fatal("strftime: %m"); + vstring_set_payload_size(msg_logger_buf, len); + + /* + * Format the host name (first name label only). + */ + vstring_sprintf_append(msg_logger_buf, "%.*s ", + (int) strcspn(msg_logger_hostname, "."), + msg_logger_hostname); + + /* + * Format the message. + */ + if (level < 0 || level >= (int) (sizeof(log_level) / sizeof(log_level[0]))) + msg_panic("msg_logger_print: invalid severity level: %d", level); + + if (level == MSG_INFO) { + vstring_sprintf_append(msg_logger_buf, "%s[%ld]: %.*s", + msg_logger_progname, (long) getpid(), + (int) MSG_LOGGER_RECLEN, text); + } else { + vstring_sprintf_append(msg_logger_buf, "%s[%ld]: %s: %.*s", + msg_logger_progname, (long) getpid(), + severity_name[level], (int) MSG_LOGGER_RECLEN, text); + } + + /* + * Connect to logging service, or fall back to direct log. Many systems + * will report ENOENT if the endpoint does not exist, ECONNREFUSED if no + * server has opened the endpoint. + */ + if (MSG_LOGGER_NEED_SOCKET()) + msg_logger_connect(); + if (msg_logger_sock != MSG_LOGGER_SOCK_NONE) { + send(msg_logger_sock, STR(msg_logger_buf), LEN(msg_logger_buf), 0); + } else if (msg_logger_fallback_fn) { + msg_logger_fallback_fn(STR(msg_logger_buf)); + } +} + +/* msg_logger_init - initialize */ + +void msg_logger_init(const char *progname, const char *hostname, + const char *unix_path, void (*fallback) (const char *)) +{ + static int first_call = 1; + extern char **environ; + + /* + * XXX If this program is set-gid, then TZ must not be trusted. This + * scrubbing code is in the wrong place. + */ + if (first_call) { + if (unsafe()) + while (getenv("TZ")) /* There may be multiple. */ + if (unsetenv("TZ") < 0) { /* Desperate measures. */ + environ[0] = 0; + msg_fatal("unsetenv: %m"); + } + tzset(); + } + + /* + * Save the request info. Use free-after-update because this data will be + * accessed when mystrdup() runs out of memory. + */ +#define UPDATE_AND_FREE(dst, src) do { \ + if ((dst) == 0 || strcmp((dst), (src)) != 0) { \ + char *_bak = (dst); \ + (dst) = mystrdup(src); \ + if ((_bak)) myfree(_bak); \ + } \ + } while (0) + + UPDATE_AND_FREE(msg_logger_progname, progname); + UPDATE_AND_FREE(msg_logger_hostname, hostname); + UPDATE_AND_FREE(msg_logger_unix_path, unix_path); + msg_logger_fallback_fn = fallback; + + /* + * One-time activity: register the output handler, and allocate a buffer. + */ + if (first_call) { + first_call = 0; + msg_output(msg_logger_print); + msg_logger_buf = vstring_alloc(2048); + } + + /* + * Always. + */ + msg_logger_enable = 1; + msg_logger_fallback_only_override = 0; +} + +/* msg_logger_control - tweak the client */ + +void msg_logger_control(int name,...) +{ + const char *myname = "msg_logger_control"; + va_list ap; + + /* + * Overrides remain in effect until the next msg_logger_init() or + * msg_logger_control() call, + */ + for (va_start(ap, name); name != MSG_LOGGER_CTL_END; name = va_arg(ap, int)) { + switch (name) { + case MSG_LOGGER_CTL_FALLBACK_ONLY: + msg_logger_fallback_only_override = 1; + msg_logger_disconnect(); + break; + case MSG_LOGGER_CTL_FALLBACK_FN: + msg_logger_fallback_fn = va_arg(ap, MSG_LOGGER_FALLBACK_FN); + break; + case MSG_LOGGER_CTL_DISABLE: + msg_logger_enable = 0; + break; + case MSG_LOGGER_CTL_CONNECT_NOW: + msg_logger_disconnect(); + if (MSG_LOGGER_NEED_SOCKET()) + msg_logger_connect(); + break; + default: + msg_panic("%s: bad name %d", myname, name); + } + } + va_end(ap); +} + +#ifdef TEST + + /* + * Proof-of-concept program to test the msg_logger module. + * + * Usage: msg_logger hostname unix_path fallback_path text... + */ +static char *fallback_path; + +static void fallback(const char *msg) +{ + if (logwriter_one_shot(fallback_path, msg) != 0) + msg_fatal("unable to fall back to directly write %s: %m", + fallback_path); +} + +int main(int argc, char **argv) +{ + VSTRING *vp = vstring_alloc(256); + + if (argc < 4) + msg_fatal("usage: %s host port path text to log", argv[0]); + msg_logger_init(argv[0], argv[1], argv[2], fallback); + fallback_path = argv[3]; + argc -= 3; + argv += 3; + while (--argc && *++argv) { + vstring_strcat(vp, *argv); + if (argv[1]) + vstring_strcat(vp, " "); + } + msg_warn("static text"); + msg_warn("dynamic text: >%s<", vstring_str(vp)); + msg_warn("dynamic numeric: >%d<", 42); + msg_warn("error text: >%m<"); + msg_warn("dynamic: >%s<: error: >%m<", vstring_str(vp)); + vstring_free(vp); + return (0); +} + +#endif diff --git a/src/util/msg_logger.h b/src/util/msg_logger.h new file mode 100644 index 0000000..4179f8b --- /dev/null +++ b/src/util/msg_logger.h @@ -0,0 +1,62 @@ +#ifndef _MSG_LOGGER_H_INCLUDED_ +#define _MSG_LOGGER_H_INCLUDED_ + +/*++ +/* NAME +/* msg_logger 3h +/* SUMMARY +/* direct diagnostics to logger service +/* SYNOPSIS +/* #include <msg_logger.h> +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include <stdarg.h> + + /* + * Utility library. + */ +#include <check_arg.h> + + /* + * External interface. + */ +typedef void (*MSG_LOGGER_FALLBACK_FN) (const char *); + +extern void msg_logger_init(const char *, const char *, const char *, + MSG_LOGGER_FALLBACK_FN); +extern void msg_logger_control(int,...); + +/* Internal-only API: type-unchecked arguments. */ +#define MSG_LOGGER_CTL_END 0 +#define MSG_LOGGER_CTL_FALLBACK_ONLY 1 +#define MSG_LOGGER_CTL_FALLBACK_FN 2 +#define MSG_LOGGER_CTL_DISABLE 3 +#define MSG_LOGGER_CTL_CONNECT_NOW 4 + +/* Safer API: type-checked arguments, external use. */ +#define CA_MSG_LOGGER_CTL_END MSG_LOGGER_CTL_END +#define CA_MSG_LOGGER_CTL_FALLBACK_ONLY MSG_LOGGER_CTL_FALLBACK_ONLY +#define CA_MSG_LOGGER_CTL_FALLBACK_FN(v) \ + MSG_LOGGER_CTL_FALLBACK_FN, CHECK_VAL(MSG_LOGGER_CTL, \ + MSG_LOGGER_FALLBACK_FN, (v)) +#define CA_MSG_LOGGER_CTL_DISABLE MSG_LOGGER_CTL_DISABLE +#define CA_MSG_LOGGER_CTL_CONNECT_NOW MSG_LOGGER_CTL_CONNECT_NOW + +CHECK_VAL_HELPER_DCL(MSG_LOGGER_CTL, MSG_LOGGER_FALLBACK_FN); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/msg_output.c b/src/util/msg_output.c new file mode 100644 index 0000000..6663877 --- /dev/null +++ b/src/util/msg_output.c @@ -0,0 +1,174 @@ +/*++ +/* NAME +/* msg_output 3 +/* SUMMARY +/* diagnostics output management +/* SYNOPSIS +/* #include <msg_output.h> +/* +/* typedef void (*MSG_OUTPUT_FN)(int level, char *text) +/* +/* void msg_output(output_fn) +/* MSG_OUTPUT_FN output_fn; +/* +/* void msg_printf(level, format, ...) +/* int level; +/* const char *format; +/* +/* void msg_vprintf(level, format, ap) +/* int level; +/* const char *format; +/* va_list ap; +/* DESCRIPTION +/* This module implements low-level output management for the +/* msg(3) diagnostics interface. +/* +/* msg_output() registers an output handler for the diagnostics +/* interface. An application can register multiple output handlers. +/* Output handlers are called in the specified order. +/* An output handler takes as arguments a severity level (MSG_INFO, +/* MSG_WARN, MSG_ERROR, MSG_FATAL, MSG_PANIC, monotonically increasing +/* integer values ranging from 0 to MSG_LAST) and pre-formatted, +/* sanitized, text in the form of a null-terminated string. +/* +/* msg_printf() and msg_vprintf() format their arguments, sanitize the +/* result, and call the output handlers registered with msg_output(). +/* +/* msg_text() copies a pre-formatted text, sanitizes the result, and +/* calls the output handlers registered with msg_output(). +/* REENTRANCY +/* .ad +/* .fi +/* The above output routines are protected against ordinary +/* recursive calls and against re-entry by signal +/* handlers, with the following limitations: +/* .IP \(bu +/* The signal handlers must never return. In other words, the +/* signal handlers must do one or more of the following: call +/* _exit(), kill the process with a signal, and permanently +/* block the process. +/* .IP \(bu +/* The signal handlers must call the above output routines not +/* until after msg_output() completes initialization, and not +/* until after the first formatted output to a VSTRING or +/* VSTREAM. +/* .IP \(bu +/* Each msg_output() call-back function, and each Postfix or +/* system function called by that call-back function, either +/* must protect itself against recursive calls and re-entry +/* by a terminating signal handler, or it must be called +/* exclusively by functions in the msg_output(3) module. +/* .PP +/* When re-entrancy is detected, the requested output operation +/* is skipped. This prevents memory corruption of VSTREAM_ERR +/* data structures, and prevents deadlock on Linux releases +/* that use mutexes within system library routines such as +/* syslog(). This protection exists under the condition that +/* these specific resources are accessed exclusively via +/* msg_output() call-back functions. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdarg.h> +#include <errno.h> + +/* Utility library. */ + +#include <mymalloc.h> +#include <vstring.h> +#include <vstream.h> +#include <msg_vstream.h> +#include <stringops.h> +#include <msg_output.h> + + /* + * Global scope, to discourage the compiler from doing smart things. + */ +volatile int msg_vprintf_level; + + /* + * Private state. Allow one nested call, so that one logging error can be + * reported to stderr before bailing out. + */ +#define MSG_OUT_NESTING_LIMIT 2 +static MSG_OUTPUT_FN *msg_output_fn = 0; +static int msg_output_fn_count = 0; +static VSTRING *msg_buffers[MSG_OUT_NESTING_LIMIT]; + +/* msg_output - specify output handler */ + +void msg_output(MSG_OUTPUT_FN output_fn) +{ + int i; + + /* + * Allocate all resources during initialization. This may result in a + * recursive call due to memory allocation error. + */ + if (msg_buffers[MSG_OUT_NESTING_LIMIT - 1] == 0) { + for (i = 0; i < MSG_OUT_NESTING_LIMIT; i++) + msg_buffers[i] = vstring_alloc(100); + } + + /* + * We're not doing this often, so avoid complexity and allocate memory + * for an exact fit. + */ + if (msg_output_fn_count == 0) + msg_output_fn = (MSG_OUTPUT_FN *) mymalloc(sizeof(*msg_output_fn)); + else + msg_output_fn = (MSG_OUTPUT_FN *) myrealloc((void *) msg_output_fn, + (msg_output_fn_count + 1) * sizeof(*msg_output_fn)); + msg_output_fn[msg_output_fn_count++] = output_fn; +} + +/* msg_printf - format text and log it */ + +void msg_printf(int level, const char *format,...) +{ + va_list ap; + + va_start(ap, format); + msg_vprintf(level, format, ap); + va_end(ap); +} + +/* msg_vprintf - format text and log it */ + +void msg_vprintf(int level, const char *format, va_list ap) +{ + int saved_errno = errno; + VSTRING *vp; + int i; + + if (msg_vprintf_level < MSG_OUT_NESTING_LIMIT) { + msg_vprintf_level += 1; + /* On-the-fly initialization for test programs and startup errors. */ + if (msg_output_fn_count == 0) + msg_vstream_init("unknown", VSTREAM_ERR); + vp = msg_buffers[msg_vprintf_level - 1]; + /* OK if terminating signal handler hijacks control before next stmt. */ + vstring_vsprintf(vp, format, ap); + printable(vstring_str(vp), '?'); + for (i = 0; i < msg_output_fn_count; i++) + msg_output_fn[i] (level, vstring_str(vp)); + msg_vprintf_level -= 1; + } + errno = saved_errno; +} diff --git a/src/util/msg_output.h b/src/util/msg_output.h new file mode 100644 index 0000000..bd84276 --- /dev/null +++ b/src/util/msg_output.h @@ -0,0 +1,51 @@ +#ifndef _MSG_OUTPUT_FN_ +#define _MSG_OUTPUT_FN_ + +/*++ +/* NAME +/* msg_output 3h +/* SUMMARY +/* diagnostics output management +/* SYNOPSIS +/* #include <msg_output.h> +/* DESCRIPTION + + /* + * System library. + */ +#include <stdarg.h> + + /* + * External interface. Severity levels are documented to be monotonically + * increasing from 0 up to MSG_LAST. + */ +typedef void (*MSG_OUTPUT_FN) (int, const char *); +extern void msg_output(MSG_OUTPUT_FN); +extern void PRINTFLIKE(2, 3) msg_printf(int, const char *,...); +extern void msg_vprintf(int, const char *, va_list); + +#define MSG_INFO 0 /* informative */ +#define MSG_WARN 1 /* warning (non-fatal) */ +#define MSG_ERROR 2 /* error (fatal) */ +#define MSG_FATAL 3 /* software error (fatal) */ +#define MSG_PANIC 4 /* software error (fatal) */ + +#define MSG_LAST 4 /* highest-numbered severity level */ + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/msg_rate_delay.c b/src/util/msg_rate_delay.c new file mode 100644 index 0000000..e21b021 --- /dev/null +++ b/src/util/msg_rate_delay.c @@ -0,0 +1,138 @@ +/*++ +/* NAME +/* msg_rate_delay 3 +/* SUMMARY +/* diagnostic interface +/* SYNOPSIS +/* #include <msg.h> +/* +/* void msg_rate_delay(stamp, delay, log_fn, fmt, ...) +/* time_t *stamp; +/* int delay; +/* void (*log_fn)(const char *fmt, ...); +/* const char *fmt; +/* DESCRIPTION +/* msg_rate_delay() produces log output at a reduced rate: no +/* more than one message per 'delay' seconds. It discards log +/* output that would violate the output rate policy. +/* +/* This is typically used to log errors accessing a cache with +/* high-frequency access but low-value information, to avoid +/* spamming the logfile with the same kind of message. +/* +/* Arguments: +/* .IP stamp +/* Time stamp of last log output; specify a zero time stamp +/* on the first call. This is an input-output parameter. +/* This parameter is ignored when verbose logging is enabled +/* or when the delay value is zero. +/* .IP delay +/* The minimum time between log outputs; specify zero to log +/* all output for debugging purposes. This parameter is ignored +/* when verbose logging is enabled. +/* .IP log_fn +/* The function that produces log output. Typically, this will +/* be msg_info() or msg_warn(). +/* .IP fmt +/* Format string as used with msg(3) routines. +/* SEE ALSO +/* msg(3) diagnostics interface +/* DIAGNOSTICS +/* Fatal errors: memory allocation problem. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + + +/* System library. */ + +#include <sys_defs.h> +#include <time.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstring.h> +#include <events.h> + +/* SLMs. */ + +#define STR(x) vstring_str(x) + +/* msg_rate_delay - rate-limit message logging */ + +void msg_rate_delay(time_t *stamp, int delay, + void (*log_fn) (const char *,...), + const char *fmt,...) +{ + const char *myname = "msg_rate_delay"; + static time_t saved_event_time; + time_t now; + VSTRING *buf; + va_list ap; + + /* + * Sanity check. + */ + if (delay < 0) + msg_panic("%s: bad message rate delay: %d", myname, delay); + + /* + * This function may be called frequently. Avoid an unnecessary syscall + * if possible. Deal with the possibility that a program does not use the + * events(3) engine, so that event_time() always produces the same + * result. + */ + if (msg_verbose == 0 && delay > 0) { + if (saved_event_time == 0) + now = saved_event_time = event_time(); + else if ((now = event_time()) == saved_event_time) + now = time((time_t *) 0); + + /* + * Don't log if time is too early. + */ + if (*stamp + delay > now) + return; + *stamp = now; + } + + /* + * OK to log. This is a low-rate event, so we can afford some overhead. + */ + buf = vstring_alloc(100); + va_start(ap, fmt); + vstring_vsprintf(buf, fmt, ap); + va_end(ap); + log_fn("%s", STR(buf)); + vstring_free(buf); +} + +#ifdef TEST + + /* + * Proof-of-concept test program: log messages but skip messages during a + * two-second gap. + */ +#include <unistd.h> + +int main(int argc, char **argv) +{ + int n; + time_t stamp = 0; + + for (n = 0; n < 6; n++) { + msg_rate_delay(&stamp, 2, msg_info, "text here %d", n); + sleep(1); + } + return (0); +} + +#endif diff --git a/src/util/msg_syslog.c b/src/util/msg_syslog.c new file mode 100644 index 0000000..7c979c6 --- /dev/null +++ b/src/util/msg_syslog.c @@ -0,0 +1,266 @@ +/*++ +/* NAME +/* msg_syslog 3 +/* SUMMARY +/* direct diagnostics to syslog daemon +/* SYNOPSIS +/* #include <msg_syslog.h> +/* +/* void msg_syslog_init(progname, log_opt, facility) +/* const char *progname; +/* int log_opt; +/* int facility; +/* +/* int msg_syslog_set_facility(facility_name) +/* const char *facility_name; +/* +/* void msg_syslog_disable(void) +/* DESCRIPTION +/* This module implements support to report msg(3) diagnostics +/* via the syslog daemon. +/* +/* msg_syslog_init() is a wrapper around the openlog(3) routine +/* that directs subsequent msg(3) output to the syslog daemon. +/* This function may also be called to update msg_syslog +/* settings. If the program name appears to contain a process ID +/* then msg_syslog_init will attempt to suppress its own PID. +/* +/* msg_syslog_set_facility() is a helper routine that overrides the +/* logging facility that is specified with msg_syslog_init(). +/* The result is zero in case of an unknown facility name. +/* +/* msg_syslog_disable() turns off the msg_syslog client, +/* until a subsequent msg_syslog_init() call. +/* SEE ALSO +/* syslog(3) syslog library +/* msg(3) diagnostics module +/* BUGS +/* Output records are truncated to 2000 characters. This is done in +/* order to defend against a buffer overflow problem in some +/* implementations of the syslog() library routine. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System libraries. */ + +#include <sys_defs.h> +#include <stdlib.h> /* 44BSD stdarg.h uses abort() */ +#include <stdarg.h> +#include <errno.h> +#include <syslog.h> +#include <string.h> +#include <time.h> + +/* Application-specific. */ + +#include "vstring.h" +#include "stringops.h" +#include "msg.h" +#include "msg_output.h" +#include "msg_syslog.h" +#include "safe.h" +#include <mymalloc.h> + + /* + * Stay a little below the 2048-byte limit of older syslog() + * implementations. + */ +#define MSG_SYSLOG_RECLEN 2000 + +struct facility_list { + const char *name; + int facility; +}; + +static struct facility_list facility_list[] = { +#ifdef LOG_AUTH + "auth", LOG_AUTH, +#endif +#ifdef LOG_AUTHPRIV + "authpriv", LOG_AUTHPRIV, +#endif +#ifdef LOG_CRON + "cron", LOG_CRON, +#endif +#ifdef LOG_DAEMON + "daemon", LOG_DAEMON, +#endif +#ifdef LOG_FTP + "ftp", LOG_FTP, +#endif +#ifdef LOG_KERN + "kern", LOG_KERN, +#endif +#ifdef LOG_LPR + "lpr", LOG_LPR, +#endif +#ifdef LOG_MAIL + "mail", LOG_MAIL, +#endif +#ifdef LOG_NEWS + "news", LOG_NEWS, +#endif +#ifdef LOG_SECURITY + "security", LOG_SECURITY, +#endif +#ifdef LOG_SYSLOG + "syslog", LOG_SYSLOG, +#endif +#ifdef LOG_USER + "user", LOG_USER, +#endif +#ifdef LOG_UUCP + "uucp", LOG_UUCP, +#endif +#ifdef LOG_LOCAL0 + "local0", LOG_LOCAL0, +#endif +#ifdef LOG_LOCAL1 + "local1", LOG_LOCAL1, +#endif +#ifdef LOG_LOCAL2 + "local2", LOG_LOCAL2, +#endif +#ifdef LOG_LOCAL3 + "local3", LOG_LOCAL3, +#endif +#ifdef LOG_LOCAL4 + "local4", LOG_LOCAL4, +#endif +#ifdef LOG_LOCAL5 + "local5", LOG_LOCAL5, +#endif +#ifdef LOG_LOCAL6 + "local6", LOG_LOCAL6, +#endif +#ifdef LOG_LOCAL7 + "local7", LOG_LOCAL7, +#endif + 0, +}; + +static int msg_syslog_facility; +static int msg_syslog_enable; + +/* msg_syslog_print - log info to syslog daemon */ + +static void msg_syslog_print(int level, const char *text) +{ + static int log_level[] = { + LOG_INFO, LOG_WARNING, LOG_ERR, LOG_CRIT, LOG_CRIT, + }; + static char *severity_name[] = { + "info", "warning", "error", "fatal", "panic", + }; + + if (msg_syslog_enable == 0) + return; + + if (level < 0 || level >= (int) (sizeof(log_level) / sizeof(log_level[0]))) + msg_panic("msg_syslog_print: invalid severity level: %d", level); + + if (level == MSG_INFO) { + syslog(msg_syslog_facility | log_level[level], "%.*s", + (int) MSG_SYSLOG_RECLEN, text); + } else { + syslog(msg_syslog_facility | log_level[level], "%s: %.*s", + severity_name[level], (int) MSG_SYSLOG_RECLEN, text); + } +} + +/* msg_syslog_init - initialize */ + +void msg_syslog_init(const char *name, int logopt, int facility) +{ + static int first_call = 1; + extern char **environ; + + /* + * XXX If this program is set-gid, then TZ must not be trusted. This + * scrubbing code is in the wrong place. + */ + if (first_call) { + if (unsafe()) + while (getenv("TZ")) /* There may be multiple. */ + if (unsetenv("TZ") < 0) { /* Desperate measures. */ + environ[0] = 0; + msg_fatal("unsetenv: %m"); + } + tzset(); + } + /* Hack for internal logging forwarding after config change. */ + if (strchr(name, '[') != 0) + logopt &= ~LOG_PID; + openlog(name, LOG_NDELAY | logopt, facility); + if (first_call) { + first_call = 0; + msg_output(msg_syslog_print); + } + msg_syslog_enable = 1; +} + +/* msg_syslog_set_facility - set logging facility by name */ + +int msg_syslog_set_facility(const char *facility_name) +{ + struct facility_list *fnp; + + for (fnp = facility_list; fnp->name; ++fnp) { + if (!strcmp(fnp->name, facility_name)) { + msg_syslog_facility = fnp->facility; + return (1); + } + } + return 0; +} + +/* msg_syslog_disable - disable the msg_syslog client */ + +void msg_syslog_disable(void) +{ + msg_syslog_enable = 0; +} + +#ifdef TEST + + /* + * Proof-of-concept program to test the syslogging diagnostics interface + * + * Usage: msg_syslog_test text... + */ + +int main(int argc, char **argv) +{ + VSTRING *vp = vstring_alloc(256); + + msg_syslog_init(argv[0], LOG_PID, LOG_MAIL); + if (argc < 2) + msg_error("usage: %s text to be logged", argv[0]); + while (--argc && *++argv) { + vstring_strcat(vp, *argv); + if (argv[1]) + vstring_strcat(vp, " "); + } + msg_warn("static text"); + msg_warn("dynamic text: >%s<", vstring_str(vp)); + msg_warn("dynamic numeric: >%d<", 42); + msg_warn("error text: >%m<"); + msg_warn("dynamic: >%s<: error: >%m<", vstring_str(vp)); + vstring_free(vp); + return (0); +} + +#endif diff --git a/src/util/msg_syslog.h b/src/util/msg_syslog.h new file mode 100644 index 0000000..d0441bb --- /dev/null +++ b/src/util/msg_syslog.h @@ -0,0 +1,41 @@ +#ifndef _MSG_SYSLOG_H_INCLUDED_ +#define _MSG_SYSLOG_H_INCLUDED_ + +/*++ +/* NAME +/* msg_syslog 3h +/* SUMMARY +/* direct diagnostics to syslog daemon +/* SYNOPSIS +/* #include <msg_syslog.h> +/* DESCRIPTION + + /* + * System library. + */ +#include <syslog.h> + + /* + * External interface. + */ +extern void msg_syslog_init(const char *, int, int); +extern int msg_syslog_set_facility(const char *); +extern void msg_syslog_disable(void); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/msg_vstream.c b/src/util/msg_vstream.c new file mode 100644 index 0000000..b6e24e6 --- /dev/null +++ b/src/util/msg_vstream.c @@ -0,0 +1,87 @@ +/*++ +/* NAME +/* msg_vstream 3 +/* SUMMARY +/* report diagnostics to VSTREAM +/* SYNOPSIS +/* #include <msg_vstream.h> +/* +/* void msg_vstream_init(progname, stream) +/* const char *progname; +/* VSTREAM *stream; +/* DESCRIPTION +/* This module implements support to report msg(3) diagnostics +/* to a VSTREAM. +/* +/* msg_vstream_init() sets the program name that appears in each output +/* record, and directs diagnostics (see msg(3)) to the specified +/* VSTREAM. The \fIprogname\fR argument is not copied. +/* SEE ALSO +/* msg(3) +/* BUGS +/* No guarantee that long records are written atomically. +/* Only the last msg_vstream_init() call takes effect. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include <sys_defs.h> +#include <errno.h> +#include <stdlib.h> /* 44BSD stdarg.h uses abort() */ +#include <stdarg.h> + +/* Utility library. */ + +#include "vstream.h" +#include "msg.h" +#include "msg_output.h" +#include "msg_vstream.h" + + /* + * Private state. + */ +static const char *msg_tag; +static VSTREAM *msg_stream; + +/* msg_vstream_print - log diagnostic to VSTREAM */ + +static void msg_vstream_print(int level, const char *text) +{ + static const char *level_text[] = { + "info", "warning", "error", "fatal", "panic", + }; + + if (level < 0 || level >= (int) (sizeof(level_text) / sizeof(level_text[0]))) + msg_panic("invalid severity level: %d", level); + if (level == MSG_INFO) { + vstream_fprintf(msg_stream, "%s: %s\n", + msg_tag, text); + } else { + vstream_fprintf(msg_stream, "%s: %s: %s\n", + msg_tag, level_text[level], text); + } + vstream_fflush(msg_stream); +} + +/* msg_vstream_init - initialize */ + +void msg_vstream_init(const char *name, VSTREAM *vp) +{ + static int first_call = 1; + + msg_tag = name; + msg_stream = vp; + if (first_call) { + first_call = 0; + msg_output(msg_vstream_print); + } +} diff --git a/src/util/msg_vstream.h b/src/util/msg_vstream.h new file mode 100644 index 0000000..d0679a0 --- /dev/null +++ b/src/util/msg_vstream.h @@ -0,0 +1,34 @@ +#ifndef _MSG_VSTREAM_H_INCLUDED_ +#define _MSG_VSTREAM_H_INCLUDED_ + +/*++ +/* NAME +/* msg_vstream 3h +/* SUMMARY +/* direct diagnostics to VSTREAM +/* SYNOPSIS +/* #include <msg_vstream.h> +/* DESCRIPTION + + /* + * Utility library. + */ +#include <vstream.h> + + /* + * External interface. + */ +extern void msg_vstream_init(const char *, VSTREAM *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/mvect.c b/src/util/mvect.c new file mode 100644 index 0000000..cf4b0d5 --- /dev/null +++ b/src/util/mvect.c @@ -0,0 +1,117 @@ +/*++ +/* NAME +/* mvect 3 +/* SUMMARY +/* memory vector management +/* SYNOPSIS +/* #include <mvect.h> +/* +/* char *mvect_alloc(vector, elsize, nelm, init_fn, wipe_fn) +/* MVECT *vector; +/* ssize_t elsize; +/* ssize_t nelm; +/* void (*init_fn)(char *ptr, ssize_t count); +/* void (*wipe_fn)(char *ptr, ssize_t count); +/* +/* char *mvect_realloc(vector, nelm) +/* MVECT *vector; +/* ssize_t nelm; +/* +/* char *mvect_free(vector) +/* MVECT *vector; +/* DESCRIPTION +/* This module supports memory management for arrays of arbitrary +/* objects. It is up to the application to provide specific code +/* that initializes and uses object memory. +/* +/* mvect_alloc() initializes memory for a vector with elements +/* of \fIelsize\fR bytes, and with at least \fInelm\fR elements. +/* \fIinit_fn\fR is a null pointer, or a pointer to a function +/* that initializes \fIcount\fR vector elements. +/* \fIwipe_fn\fR is a null pointer, or a pointer to a function +/* that is complementary to \fIinit_fn\fR. This routine is called +/* by mvect_free(). The result of mvect_alloc() is a pointer to +/* the allocated vector. +/* +/* mvect_realloc() guarantees that the specified vector has space +/* for at least \fInelm\fR elements. The result is a pointer to the +/* allocated vector, which may change across calls. +/* +/* mvect_free() releases storage for the named vector. The result +/* is a convenient null pointer. +/* SEE ALSO +/* mymalloc(3) memory management +/* DIAGNOSTICS +/* Problems are reported via the msg(3) diagnostics routines: +/* the requested amount of memory is not available; improper use +/* is detected; other fatal errors. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +/* Utility library. */ + +#include "mymalloc.h" +#include "mvect.h" + +/* mvect_alloc - allocate memory vector */ + +char *mvect_alloc(MVECT *vect, ssize_t elsize, ssize_t nelm, + void (*init_fn) (char *, ssize_t), void (*wipe_fn) (char *, ssize_t)) +{ + vect->init_fn = init_fn; + vect->wipe_fn = wipe_fn; + vect->nelm = 0; + vect->ptr = mymalloc(elsize * nelm); + vect->nelm = nelm; + vect->elsize = elsize; + if (vect->init_fn) + vect->init_fn(vect->ptr, vect->nelm); + return (vect->ptr); +} + +/* mvect_realloc - adjust memory vector allocation */ + +char *mvect_realloc(MVECT *vect, ssize_t nelm) +{ + ssize_t old_len = vect->nelm; + ssize_t incr = nelm - old_len; + ssize_t new_nelm; + + if (incr > 0) { + if (incr < old_len) + incr = old_len; + new_nelm = vect->nelm + incr; + vect->ptr = myrealloc(vect->ptr, vect->elsize * new_nelm); + vect->nelm = new_nelm; + if (vect->init_fn) + vect->init_fn(vect->ptr + old_len * vect->elsize, incr); + } + return (vect->ptr); +} + +/* mvect_free - release memory vector storage */ + +char *mvect_free(MVECT *vect) +{ + if (vect->wipe_fn) + vect->wipe_fn(vect->ptr, vect->nelm); + myfree(vect->ptr); + return (0); +} diff --git a/src/util/mvect.h b/src/util/mvect.h new file mode 100644 index 0000000..bdbb701 --- /dev/null +++ b/src/util/mvect.h @@ -0,0 +1,42 @@ +#ifndef _MVECT_H_INCLUDED_ +#define _MVECT_H_INCLUDED_ + +/*++ +/* NAME +/* mvect 3h +/* SUMMARY +/* memory vector management +/* SYNOPSIS +/* #include <mvect.h> +/* DESCRIPTION +/* .nf + + /* + * Generic memory vector interface. + */ +typedef void (*MVECT_FN) (char *, ssize_t); + +typedef struct { + char *ptr; + ssize_t elsize; + ssize_t nelm; + MVECT_FN init_fn; + MVECT_FN wipe_fn; +} MVECT; + +extern char *mvect_alloc(MVECT *, ssize_t, ssize_t, MVECT_FN, MVECT_FN); +extern char *mvect_realloc(MVECT *, ssize_t); +extern char *mvect_free(MVECT *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/myaddrinfo.c b/src/util/myaddrinfo.c new file mode 100644 index 0000000..5edafde --- /dev/null +++ b/src/util/myaddrinfo.c @@ -0,0 +1,905 @@ +/*++ +/* NAME +/* myaddrinfo 3 +/* SUMMARY +/* addrinfo encapsulation and emulation +/* SYNOPSIS +/* #include <myaddrinfo.h> +/* +/* #define MAI_V4ADDR_BITS ... +/* #define MAI_V6ADDR_BITS ... +/* #define MAI_V4ADDR_BYTES ... +/* #define MAI_V6ADDR_BYTES ... +/* +/* typedef struct { char buf[....]; } MAI_HOSTNAME_STR; +/* typedef struct { char buf[....]; } MAI_HOSTADDR_STR; +/* typedef struct { char buf[....]; } MAI_SERVNAME_STR; +/* typedef struct { char buf[....]; } MAI_SERVPORT_STR; +/* +/* int hostname_to_sockaddr(hostname, service, socktype, result) +/* const char *hostname; +/* const char *service; +/* int socktype; +/* struct addrinfo **result; +/* +/* int hostname_to_sockaddr_pf(hostname, pf, service, socktype, result) +/* const char *hostname; +/* int pf; +/* const char *service; +/* int socktype; +/* struct addrinfo **result; +/* +/* int hostaddr_to_sockaddr(hostaddr, service, socktype, result) +/* const char *hostaddr; +/* const char *service; +/* int socktype; +/* struct addrinfo **result; +/* +/* int sockaddr_to_hostaddr(sa, salen, hostaddr, portnum, socktype) +/* const struct sockaddr *sa; +/* SOCKADDR_SIZE salen; +/* MAI_HOSTADDR_STR *hostaddr; +/* MAI_SERVPORT_STR *portnum; +/* int socktype; +/* +/* int sockaddr_to_hostname(sa, salen, hostname, service, socktype) +/* const struct sockaddr *sa; +/* SOCKADDR_SIZE salen; +/* MAI_HOSTNAME_STR *hostname; +/* MAI_SERVNAME_STR *service; +/* int socktype; +/* +/* const char *MAI_STRERROR(error) +/* int error; +/* DESCRIPTION +/* This module provides a simplified user interface to the +/* getaddrinfo(3) and getnameinfo(3) routines (which provide +/* a unified interface to manipulate IPv4 and IPv6 socket +/* address structures). +/* +/* On systems without getaddrinfo(3) and getnameinfo(3) support, +/* emulation for IPv4 only can be enabled by defining +/* EMULATE_IPV4_ADDRINFO. +/* +/* hostname_to_sockaddr() looks up the binary addresses for +/* the specified symbolic hostname or numeric address. The +/* result should be destroyed with freeaddrinfo(). A null host +/* pointer converts to the null host address. +/* +/* hostname_to_sockaddr_pf() is an extended interface that +/* provides a protocol family override. +/* +/* hostaddr_to_sockaddr() converts a printable network address +/* into the corresponding binary form. The result should be +/* destroyed with freeaddrinfo(). A null host pointer converts +/* to the null host address. +/* +/* sockaddr_to_hostaddr() converts a binary network address +/* into printable form. The result buffers should be large +/* enough to hold the printable address or port including the +/* null terminator. +/* This function strips off the IPv6 datalink suffix. +/* +/* sockaddr_to_hostname() converts a binary network address +/* into a hostname or service. The result buffer should be +/* large enough to hold the hostname or service including the +/* null terminator. This routine rejects malformed hostnames +/* or numeric hostnames and pretends that the lookup failed. +/* +/* MAI_STRERROR() is an unsafe macro (it evaluates the argument +/* multiple times) that invokes strerror() or gai_strerror() +/* as appropriate. +/* +/* This module exports the following constants that should be +/* user for storage allocation of name or address information: +/* .IP MAI_V4ADDR_BITS +/* .IP MAI_V6ADDR_BITS +/* .IP MAI_V4ADDR_BYTES +/* .IP MAI_V6ADDR_BYTES +/* The number of bits or bytes needed to store a binary +/* IPv4 or IPv6 network address. +/* .PP +/* The types MAI_HOST{NAME,ADDR}_STR and MAI_SERV{NAME,PORT}_STR +/* implement buffers for the storage of the string representations +/* of symbolic or numerical hosts or services. Do not use +/* buffer types other than the ones that are expected here, +/* or things will blow up with buffer overflow problems. +/* +/* Arguments: +/* .IP hostname +/* On input to hostname_to_sockaddr(), a numeric or symbolic +/* hostname, or a null pointer (meaning the wild-card listen +/* address). On output from sockaddr_to_hostname(), storage +/* for the result hostname, or a null pointer. +/* .IP pf +/* Protocol type: PF_UNSPEC (meaning: use any protocol that is +/* available), PF_INET, or PF_INET6. This argument is ignored +/* in EMULATE_IPV4_ADDRINFO mode. +/* .IP hostaddr +/* On input to hostaddr_to_sockaddr(), a numeric hostname, +/* or a null pointer (meaning the wild-card listen address). +/* On output from sockaddr_to_hostaddr(), storage for the +/* result hostaddress, or a null pointer. +/* .IP service +/* On input to hostname/addr_to_sockaddr(), a numeric or +/* symbolic service name, or a null pointer in which case the +/* socktype argument is ignored. On output from +/* sockaddr_to_hostname/addr(), storage for the result service +/* name, or a null pointer. +/* .IP portnum +/* Storage for the result service port number, or a null pointer. +/* .IP socktype +/* Socket type: SOCK_STREAM, SOCK_DGRAM, etc. This argument is +/* ignored when no service or port are specified. +/* .IP sa +/* Protocol-independent socket address structure. +/* .IP salen +/* Protocol-dependent socket address structure size in bytes. +/* SEE ALSO +/* getaddrinfo(3), getnameinfo(3), freeaddrinfo(3), gai_strerror(3) +/* DIAGNOSTICS +/* All routines either return 0 upon success, or an error code +/* that is compatible with gai_strerror(). +/* +/* On systems where addrinfo support is emulated by Postfix, +/* some out-of-memory errors are not reported to the caller, +/* but are handled by mymalloc(). +/* BUGS +/* The IPv4-only emulation code does not support requests that +/* specify a service but no socket type. It returns an error +/* indication, instead of enumerating all the possible answers. +/* +/* The hostname/addr_to_sockaddr() routines should accept a +/* list of address families that the caller is interested in, +/* and they should return only information of those types. +/* +/* Unfortunately, it is not possible to remove unwanted address +/* family results from hostname_to_sockaddr(), because we +/* don't know how the system library routine getaddrinfo() +/* allocates memory. For example, getaddrinfo() could save +/* space by referencing the same string object from multiple +/* addrinfo structures; or it could allocate a string object +/* and the addrinfo structure as one memory block. +/* +/* We could get around this by copying getaddrinfo() results +/* to our own private data structures, but that would only +/* make an already expensive API even more expensive. +/* +/* A better workaround is to return a vector of addrinfo +/* pointers to the elements that contain only the elements +/* that the caller is interested in. The pointer to the +/* original getaddrinfo() result can be hidden at the end +/* after the null terminator, or before the first element. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> /* sprintf() */ + +/* Utility library. */ + +#include <mymalloc.h> +#include <valid_hostname.h> +#include <sock_addr.h> +#include <stringops.h> +#include <msg.h> +#include <inet_proto.h> +#include <myaddrinfo.h> +#include <split_at.h> +#include <known_tcp_ports.h> + +/* Application-specific. */ + + /* + * Use an old trick to save some space: allocate space for two objects in + * one. In Postfix we often use this trick for structures that have an array + * of things at the end. + */ +struct ipv4addrinfo { + struct addrinfo info; + struct sockaddr_in sin; +}; + + /* + * When we're not interested in service ports, we must pick a socket type + * otherwise getaddrinfo() will give us duplicate results: one set for TCP, + * and another set for UDP. For consistency, we'll use the same default + * socket type for the results from emulation mode. + */ +#define MAI_SOCKTYPE SOCK_STREAM /* getaddrinfo() query */ + +#ifdef EMULATE_IPV4_ADDRINFO + +/* clone_ipv4addrinfo - clone ipv4addrinfo structure */ + +static struct ipv4addrinfo *clone_ipv4addrinfo(struct ipv4addrinfo * tp) +{ + struct ipv4addrinfo *ip; + + ip = (struct ipv4addrinfo *) mymalloc(sizeof(*ip)); + *ip = *tp; + ip->info.ai_addr = (struct sockaddr *) &(ip->sin); + return (ip); +} + +/* init_ipv4addrinfo - initialize an ipv4addrinfo structure */ + +static void init_ipv4addrinfo(struct ipv4addrinfo * ip, int socktype) +{ + + /* + * Portability: null pointers aren't necessarily all-zero bits, so we + * make explicit assignments to all the pointers that we're aware of. + */ + memset((void *) ip, 0, sizeof(*ip)); + ip->info.ai_family = PF_INET; + ip->info.ai_socktype = socktype; + ip->info.ai_protocol = 0; /* XXX */ + ip->info.ai_addrlen = sizeof(ip->sin); + ip->info.ai_canonname = 0; + ip->info.ai_addr = (struct sockaddr *) &(ip->sin); + ip->info.ai_next = 0; + ip->sin.sin_family = AF_INET; +#ifdef HAS_SA_LEN + ip->sin.sin_len = sizeof(ip->sin); +#endif +} + +/* find_service - translate numeric or symbolic service name */ + +static int find_service(const char *service, int socktype) +{ + struct servent *sp; + const char *proto; + unsigned port; + + service = filter_known_tcp_port(service); + if (alldig(service)) { + port = atoi(service); + return (port < 65536 ? htons(port) : -1); + } + if (socktype == SOCK_STREAM) { + proto = "tcp"; + } else if (socktype == SOCK_DGRAM) { + proto = "udp"; + } else { + return (-1); + } + if ((sp = getservbyname(service, proto)) != 0) { + return (sp->s_port); + } else { + return (-1); + } +} + +#endif + +/* hostname_to_sockaddr_pf - hostname to binary address form */ + +int hostname_to_sockaddr_pf(const char *hostname, int pf, + const char *service, int socktype, + struct addrinfo ** res) +{ +#ifdef EMULATE_IPV4_ADDRINFO + + /* + * Emulated getaddrinfo(3) version. + */ + static struct ipv4addrinfo template; + struct ipv4addrinfo *ip; + struct ipv4addrinfo *prev; + struct in_addr addr; + struct hostent *hp; + char **name_list; + int port; + + /* + * Validate the service. + */ + if (service) { + if ((port = find_service(service, socktype)) < 0) + return (EAI_SERVICE); + } else { + port = 0; + socktype = MAI_SOCKTYPE; + } + + /* + * No host means INADDR_ANY. + */ + if (hostname == 0) { + ip = (struct ipv4addrinfo *) mymalloc(sizeof(*ip)); + init_ipv4addrinfo(ip, socktype); + ip->sin.sin_addr.s_addr = INADDR_ANY; + ip->sin.sin_port = port; + *res = &(ip->info); + return (0); + } + + /* + * Numeric host. + */ + if (inet_pton(AF_INET, hostname, (void *) &addr) == 1) { + ip = (struct ipv4addrinfo *) mymalloc(sizeof(*ip)); + init_ipv4addrinfo(ip, socktype); + ip->sin.sin_addr = addr; + ip->sin.sin_port = port; + *res = &(ip->info); + return (0); + } + + /* + * Look up the IPv4 address list. + */ + if ((hp = gethostbyname(hostname)) == 0) + return (h_errno == TRY_AGAIN ? EAI_AGAIN : EAI_NODATA); + if (hp->h_addrtype != AF_INET + || hp->h_length != sizeof(template.sin.sin_addr)) + return (EAI_NODATA); + + /* + * Initialize the result template. + */ + if (template.info.ai_addrlen == 0) + init_ipv4addrinfo(&template, socktype); + + /* + * Copy the address information into an addrinfo structure. + */ + prev = &template; + for (name_list = hp->h_addr_list; name_list[0]; name_list++) { + ip = clone_ipv4addrinfo(prev); + ip->sin.sin_addr = IN_ADDR(name_list[0]); + ip->sin.sin_port = port; + if (prev == &template) + *res = &(ip->info); + else + prev->info.ai_next = &(ip->info); + prev = ip; + } + return (0); +#else + + /* + * Native getaddrinfo(3) version. + * + * XXX Wild-card listener issues. + * + * With most IPv4 plus IPv6 systems, an IPv6 wild-card listener also listens + * on the IPv4 wild-card address. Connections from IPv4 clients appear as + * IPv4-in-IPv6 addresses; when Postfix support for IPv4 is turned on, + * Postfix automatically maps these embedded addresses to their original + * IPv4 form. So everything seems to be fine. + * + * However, some applications prefer to use separate listener sockets for + * IPv4 and IPv6. The Postfix IPv6 patch provided such an example. And + * this is where things become tricky. On many systems the IPv6 and IPv4 + * wild-card listeners cannot coexist. When one is already active, the + * other fails with EADDRINUSE. Solaris 9, however, will automagically + * "do the right thing" and allow both listeners to coexist. + * + * Recent systems have the IPV6_V6ONLY feature (RFC 3493), which tells the + * system that we really mean IPv6 when we say IPv6. This allows us to + * set up separate wild-card listener sockets for IPv4 and IPv6. So + * everything seems to be fine again. + * + * The following workaround disables the wild-card IPv4 listener when + * IPV6_V6ONLY is unavailable. This is necessary for some Linux versions, + * but is not needed for Solaris 9 (which allows IPv4 and IPv6 wild-card + * listeners to coexist). Solaris 10 beta already has IPV6_V6ONLY. + * + * XXX This workaround obviously breaks if we want to support protocols in + * addition to IPv6 and IPv4, but it is needed only until IPv6 + * implementations catch up with RFC 3493. A nicer fix is to filter the + * getaddrinfo() result, and to return a vector of addrinfo pointers to + * only those types of elements that the caller has expressed interested + * in. + * + * XXX Vanilla AIX 5.1 getaddrinfo() does not support a null hostname with + * AI_PASSIVE. And since we don't know how getaddrinfo() manages its + * memory we can't bypass it for this special case, or freeaddrinfo() + * might blow up. Instead we turn off IPV6_V6ONLY in inet_listen(), and + * supply a protocol-dependent hard-coded string value to getaddrinfo() + * below, so that it will convert into the appropriate wild-card address. + * + * XXX AIX 5.[1-3] getaddrinfo() may return a non-null port when a null + * service argument is specified. + */ + struct addrinfo hints; + int err; + + memset((void *) &hints, 0, sizeof(hints)); + hints.ai_family = (pf != PF_UNSPEC) ? pf : inet_proto_info()->ai_family; + hints.ai_socktype = service ? socktype : MAI_SOCKTYPE; + if (!hostname) { + hints.ai_flags = AI_PASSIVE; +#if !defined(IPV6_V6ONLY) || defined(BROKEN_AI_PASSIVE_NULL_HOST) + switch (hints.ai_family) { + case PF_UNSPEC: + hints.ai_family = PF_INET6; +#ifdef BROKEN_AI_PASSIVE_NULL_HOST + case PF_INET6: + hostname = "::"; + break; + case PF_INET: + hostname = "0.0.0.0"; + break; +#endif + } +#endif + } + if (service) { + service = filter_known_tcp_port(service); + if (alldig(service)) + hints.ai_flags |= AI_NUMERICSERV; + } + err = getaddrinfo(hostname, service, &hints, res); +#if defined(BROKEN_AI_NULL_SERVICE) + if (service == 0 && err == 0) { + struct addrinfo *r; + unsigned short *portp; + + for (r = *res; r != 0; r = r->ai_next) + if (*(portp = SOCK_ADDR_PORTP(r->ai_addr)) != 0) + *portp = 0; + } +#endif + return (err); +#endif +} + +/* hostaddr_to_sockaddr - printable address to binary address form */ + +int hostaddr_to_sockaddr(const char *hostaddr, const char *service, + int socktype, struct addrinfo ** res) +{ +#ifdef EMULATE_IPV4_ADDRINFO + + /* + * Emulated getaddrinfo(3) version. + */ + struct ipv4addrinfo *ip; + struct in_addr addr; + int port; + + /* + * Validate the service. + */ + if (service) { + if ((port = find_service(service, socktype)) < 0) + return (EAI_SERVICE); + } else { + port = 0; + socktype = MAI_SOCKTYPE; + } + + /* + * No host means INADDR_ANY. + */ + if (hostaddr == 0) { + ip = (struct ipv4addrinfo *) mymalloc(sizeof(*ip)); + init_ipv4addrinfo(ip, socktype); + ip->sin.sin_addr.s_addr = INADDR_ANY; + ip->sin.sin_port = port; + *res = &(ip->info); + return (0); + } + + /* + * Deal with bad address forms. + */ + switch (inet_pton(AF_INET, hostaddr, (void *) &addr)) { + case 1: /* Success */ + break; + default: /* Unparsable */ + return (EAI_NONAME); + case -1: /* See errno */ + return (EAI_SYSTEM); + } + + /* + * Initialize the result structure. + */ + ip = (struct ipv4addrinfo *) mymalloc(sizeof(*ip)); + init_ipv4addrinfo(ip, socktype); + + /* + * And copy the result. + */ + ip->sin.sin_addr = addr; + ip->sin.sin_port = port; + *res = &(ip->info); + + return (0); +#else + + /* + * Native getaddrinfo(3) version. See comments in hostname_to_sockaddr(). + * + * XXX Vanilla AIX 5.1 getaddrinfo() returns multiple results when + * converting a printable ipv4 or ipv6 address to socket address with + * ai_family=PF_UNSPEC, ai_flags=AI_NUMERICHOST, ai_socktype=SOCK_STREAM, + * ai_protocol=0 or IPPROTO_TCP, and service=0. The workaround is to + * ignore all but the first result. + * + * XXX AIX 5.[1-3] getaddrinfo() may return a non-null port when a null + * service argument is specified. + */ + struct addrinfo hints; + int err; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = inet_proto_info()->ai_family; + hints.ai_socktype = service ? socktype : MAI_SOCKTYPE; + hints.ai_flags = AI_NUMERICHOST; + if (!hostaddr) { + hints.ai_flags |= AI_PASSIVE; +#if !defined(IPV6_V6ONLY) || defined(BROKEN_AI_PASSIVE_NULL_HOST) + switch (hints.ai_family) { + case PF_UNSPEC: + hints.ai_family = PF_INET6; +#ifdef BROKEN_AI_PASSIVE_NULL_HOST + case PF_INET6: + hostaddr = "::"; + break; + case PF_INET: + hostaddr = "0.0.0.0"; + break; +#endif + } +#endif + } + if (service) { + service = filter_known_tcp_port(service); + if (alldig(service)) + hints.ai_flags |= AI_NUMERICSERV; + } + err = getaddrinfo(hostaddr, service, &hints, res); +#if defined(BROKEN_AI_NULL_SERVICE) + if (service == 0 && err == 0) { + struct addrinfo *r; + unsigned short *portp; + + for (r = *res; r != 0; r = r->ai_next) + if (*(portp = SOCK_ADDR_PORTP(r->ai_addr)) != 0) + *portp = 0; + } +#endif + return (err); +#endif +} + +/* sockaddr_to_hostaddr - binary address to printable address form */ + +int sockaddr_to_hostaddr(const struct sockaddr *sa, SOCKADDR_SIZE salen, + MAI_HOSTADDR_STR *hostaddr, + MAI_SERVPORT_STR *portnum, + int unused_socktype) +{ +#ifdef EMULATE_IPV4_ADDRINFO + char portbuf[sizeof("65535")]; + ssize_t len; + + /* + * Emulated getnameinfo(3) version. The buffer length includes the space + * for the null terminator. + */ + if (sa->sa_family != AF_INET) { + errno = EAFNOSUPPORT; + return (EAI_SYSTEM); + } + if (hostaddr != 0) { + if (inet_ntop(AF_INET, (void *) &(SOCK_ADDR_IN_ADDR(sa)), + hostaddr->buf, sizeof(hostaddr->buf)) == 0) + return (EAI_SYSTEM); + } + if (portnum != 0) { + sprintf(portbuf, "%d", ntohs(SOCK_ADDR_IN_PORT(sa)) & 0xffff); + if ((len = strlen(portbuf)) >= sizeof(portnum->buf)) { + errno = ENOSPC; + return (EAI_SYSTEM); + } + memcpy(portnum->buf, portbuf, len + 1); + } + return (0); +#else + int ret; + + /* + * Native getnameinfo(3) version. + */ + ret = getnameinfo(sa, salen, + hostaddr ? hostaddr->buf : (char *) 0, + hostaddr ? sizeof(hostaddr->buf) : 0, + portnum ? portnum->buf : (char *) 0, + portnum ? sizeof(portnum->buf) : 0, + NI_NUMERICHOST | NI_NUMERICSERV); + if (hostaddr != 0 && ret == 0 && sa->sa_family == AF_INET6) + (void) split_at(hostaddr->buf, '%'); + return (ret); +#endif +} + +/* sockaddr_to_hostname - binary address to printable hostname */ + +int sockaddr_to_hostname(const struct sockaddr *sa, SOCKADDR_SIZE salen, + MAI_HOSTNAME_STR *hostname, + MAI_SERVNAME_STR *service, + int socktype) +{ +#ifdef EMULATE_IPV4_ADDRINFO + + /* + * Emulated getnameinfo(3) version. + */ + struct hostent *hp; + struct servent *sp; + size_t len; + + /* + * Sanity check. + */ + if (sa->sa_family != AF_INET) + return (EAI_NODATA); + + /* + * Look up the host name. + */ + if (hostname != 0) { + if ((hp = gethostbyaddr((char *) &(SOCK_ADDR_IN_ADDR(sa)), + sizeof(SOCK_ADDR_IN_ADDR(sa)), + AF_INET)) == 0) + return (h_errno == TRY_AGAIN ? EAI_AGAIN : EAI_NONAME); + + /* + * Save the result. The buffer length includes the space for the null + * terminator. Hostname sanity checks are at the end of this + * function. + */ + if ((len = strlen(hp->h_name)) >= sizeof(hostname->buf)) { + errno = ENOSPC; + return (EAI_SYSTEM); + } + memcpy(hostname->buf, hp->h_name, len + 1); + } + + /* + * Look up the service. + */ + if (service != 0) { + if ((sp = getservbyport(ntohs(SOCK_ADDR_IN_PORT(sa)), + socktype == SOCK_DGRAM ? "udp" : "tcp")) == 0) + return (EAI_NONAME); + + /* + * Save the result. The buffer length includes the space for the null + * terminator. + */ + if ((len = strlen(sp->s_name)) >= sizeof(service->buf)) { + errno = ENOSPC; + return (EAI_SYSTEM); + } + memcpy(service->buf, sp->s_name, len + 1); + } +#else + + /* + * Native getnameinfo(3) version. + */ + int err; + + err = getnameinfo(sa, salen, + hostname ? hostname->buf : (char *) 0, + hostname ? sizeof(hostname->buf) : 0, + service ? service->buf : (char *) 0, + service ? sizeof(service->buf) : 0, + socktype == SOCK_DGRAM ? + NI_NAMEREQD | NI_DGRAM : NI_NAMEREQD); + if (err != 0) + return (err); +#endif + + /* + * Hostname sanity checks. + */ + if (hostname != 0) { + if (valid_hostaddr(hostname->buf, DONT_GRIPE)) { + msg_warn("numeric hostname: %s", hostname->buf); + return (EAI_NONAME); + } + if (!valid_hostname(hostname->buf, DO_GRIPE)) + return (EAI_NONAME); + } + return (0); +} + +/* myaddrinfo_control - fine control */ + +void myaddrinfo_control(int name,...) +{ + const char *myname = "myaddrinfo_control"; + va_list ap; + + for (va_start(ap, name); name != 0; name = va_arg(ap, int)) { + switch (name) { + default: + msg_panic("%s: bad name %d", myname, name); + } + } + va_end(ap); +} + +#ifdef EMULATE_IPV4_ADDRINFO + +/* freeaddrinfo - release storage */ + +void freeaddrinfo(struct addrinfo * ai) +{ + struct addrinfo *ap; + struct addrinfo *next; + + /* + * Artifact of implementation: tolerate a null pointer argument. + */ + for (ap = ai; ap != 0; ap = next) { + next = ap->ai_next; + if (ap->ai_canonname) + myfree(ap->ai_canonname); + /* ap->ai_addr is allocated within this memory block */ + myfree((void *) ap); + } +} + +static char *ai_errlist[] = { + "Success", + "Address family for hostname not supported", /* EAI_ADDRFAMILY */ + "Temporary failure in name resolution", /* EAI_AGAIN */ + "Invalid value for ai_flags", /* EAI_BADFLAGS */ + "Non-recoverable failure in name resolution", /* EAI_FAIL */ + "ai_family not supported", /* EAI_FAMILY */ + "Memory allocation failure", /* EAI_MEMORY */ + "No address associated with hostname", /* EAI_NODATA */ + "hostname nor servname provided, or not known", /* EAI_NONAME */ + "service name not supported for ai_socktype", /* EAI_SERVICE */ + "ai_socktype not supported", /* EAI_SOCKTYPE */ + "System error returned in errno", /* EAI_SYSTEM */ + "Invalid value for hints", /* EAI_BADHINTS */ + "Resolved protocol is unknown", /* EAI_PROTOCOL */ + "Unknown error", /* EAI_MAX */ +}; + +/* gai_strerror - error number to string */ + +char *gai_strerror(int ecode) +{ + + /* + * Note: EAI_SYSTEM errors are not automatically handed over to + * strerror(). The application decides. + */ + if (ecode < 0 || ecode > EAI_MAX) + ecode = EAI_MAX; + return (ai_errlist[ecode]); +} + +#endif + +#ifdef TEST + + /* + * A test program that takes some info from the command line and runs it + * forward and backward through the above conversion routines. + */ +#include <stdlib.h> +#include <msg.h> +#include <vstream.h> +#include <msg_vstream.h> + +static int compare_family(const void *a, const void *b) +{ + struct addrinfo *resa = *(struct addrinfo **) a; + struct addrinfo *resb = *(struct addrinfo **) b; + + return (resa->ai_family - resb->ai_family); +} + +int main(int argc, char **argv) +{ + struct addrinfo *info; + struct addrinfo *ip; + struct addrinfo **resv; + MAI_HOSTNAME_STR host; + MAI_HOSTADDR_STR addr; + size_t len, n; + int err; + + msg_vstream_init(argv[0], VSTREAM_ERR); + + if (argc != 4) + msg_fatal("usage: %s protocols hostname hostaddress", argv[0]); + + inet_proto_init(argv[0], argv[1]); + + msg_info("=== hostname %s ===", argv[2]); + + if ((err = hostname_to_sockaddr(argv[2], (char *) 0, 0, &info)) != 0) { + msg_info("hostname_to_sockaddr(%s): %s", + argv[2], err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err)); + } else { + for (len = 0, ip = info; ip != 0; ip = ip->ai_next) + len += 1; + resv = (struct addrinfo **) mymalloc(len * sizeof(*resv)); + for (len = 0, ip = info; ip != 0; ip = ip->ai_next) + resv[len++] = ip; + qsort((void *) resv, len, sizeof(*resv), compare_family); + for (n = 0; n < len; n++) { + ip = resv[n]; + if ((err = sockaddr_to_hostaddr(ip->ai_addr, ip->ai_addrlen, &addr, + (MAI_SERVPORT_STR *) 0, 0)) != 0) { + msg_info("sockaddr_to_hostaddr: %s", + err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err)); + continue; + } + msg_info("%s -> family=%d sock=%d proto=%d %s", argv[2], + ip->ai_family, ip->ai_socktype, ip->ai_protocol, addr.buf); + if ((err = sockaddr_to_hostname(ip->ai_addr, ip->ai_addrlen, &host, + (MAI_SERVNAME_STR *) 0, 0)) != 0) { + msg_info("sockaddr_to_hostname: %s", + err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err)); + continue; + } + msg_info("%s -> %s", addr.buf, host.buf); + } + freeaddrinfo(info); + myfree((void *) resv); + } + + msg_info("=== host address %s ===", argv[3]); + + if ((err = hostaddr_to_sockaddr(argv[3], (char *) 0, 0, &ip)) != 0) { + msg_info("hostaddr_to_sockaddr(%s): %s", + argv[3], err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err)); + } else { + if ((err = sockaddr_to_hostaddr(ip->ai_addr, ip->ai_addrlen, &addr, + (MAI_SERVPORT_STR *) 0, 0)) != 0) { + msg_info("sockaddr_to_hostaddr: %s", + err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err)); + } else { + msg_info("%s -> family=%d sock=%d proto=%d %s", argv[3], + ip->ai_family, ip->ai_socktype, ip->ai_protocol, addr.buf); + if ((err = sockaddr_to_hostname(ip->ai_addr, ip->ai_addrlen, &host, + (MAI_SERVNAME_STR *) 0, 0)) != 0) { + msg_info("sockaddr_to_hostname: %s", + err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err)); + } else + msg_info("%s -> %s", addr.buf, host.buf); + freeaddrinfo(ip); + } + } + exit(0); +} + +#endif diff --git a/src/util/myaddrinfo.h b/src/util/myaddrinfo.h new file mode 100644 index 0000000..94f1e9f --- /dev/null +++ b/src/util/myaddrinfo.h @@ -0,0 +1,229 @@ +#ifndef _MYADDRINFO_H_INCLUDED_ +#define _MYADDRINFO_H_INCLUDED_ + +/*++ +/* NAME +/* myaddrinfo 3h +/* SUMMARY +/* addrinfo encapsulation and emulation +/* SYNOPSIS +/* #include <myaddrinfo.h> +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <string.h> +#include <errno.h> /* MAI_STRERROR() */ +#include <limits.h> /* CHAR_BIT */ + + /* + * Backwards compatibility support for IPV4 systems without addrinfo API. + */ +#ifdef EMULATE_IPV4_ADDRINFO + + /* + * Avoid clashes with global symbols, just in case some third-party library + * provides its own addrinfo() implementation. This also allows us to test + * the IPV4 emulation code on an IPV6 enabled system. + */ +#undef freeaddrinfo +#define freeaddrinfo mai_freeaddrinfo +#undef gai_strerror +#define gai_strerror mai_strerror +#undef addrinfo +#define addrinfo mai_addrinfo +#undef sockaddr_storage +#define sockaddr_storage mai_sockaddr_storage + + /* + * Modern systems define this in <netdb.h>. + */ +struct addrinfo { + int ai_flags; /* AI_PASSIVE|CANONNAME|NUMERICHOST */ + int ai_family; /* PF_xxx */ + int ai_socktype; /* SOCK_xxx */ + int ai_protocol; /* 0 or IPPROTO_xxx */ + size_t ai_addrlen; /* length of ai_addr */ + char *ai_canonname; /* canonical name for nodename */ + struct sockaddr *ai_addr; /* binary address */ + struct addrinfo *ai_next; /* next structure in linked list */ +}; + + /* + * Modern systems define this in <sys/socket.h>. + */ +struct sockaddr_storage { + struct sockaddr_in dummy; /* alignment!! */ +}; + + /* + * Result codes. See gai_strerror() for text. Undefine already imported + * definitions so that we can test the IPv4-only emulation on a modern + * system without getting a ton of compiler warnings. + */ +#undef EAI_ADDRFAMILY +#define EAI_ADDRFAMILY 1 +#undef EAI_AGAIN +#define EAI_AGAIN 2 +#undef EAI_BADFLAGS +#define EAI_BADFLAGS 3 +#undef EAI_FAIL +#define EAI_FAIL 4 +#undef EAI_FAMILY +#define EAI_FAMILY 5 +#undef EAI_MEMORY +#define EAI_MEMORY 6 +#undef EAI_NODATA +#define EAI_NODATA 7 +#undef EAI_NONAME +#define EAI_NONAME 8 +#undef EAI_SERVICE +#define EAI_SERVICE 9 +#undef EAI_SOCKTYPE +#define EAI_SOCKTYPE 10 +#undef EAI_SYSTEM +#define EAI_SYSTEM 11 +#undef EAI_BADHINTS +#define EAI_BADHINTS 12 +#undef EAI_PROTOCOL +#define EAI_PROTOCOL 13 +#undef EAI_RESNULL +#define EAI_RESNULL 14 +#undef EAI_MAX +#define EAI_MAX 15 + +extern void freeaddrinfo(struct addrinfo *); +extern char *gai_strerror(int); + +#endif + + /* + * Bounds grow in leaps. These macros attempt to keep non-library code free + * from IPV6 #ifdef pollution. Avoid macro names that end in STRLEN because + * they suggest that space for the null terminator is not included. + */ +#ifdef HAS_IPV6 +# define MAI_HOSTADDR_STRSIZE INET6_ADDRSTRLEN +#else +# ifndef INET_ADDRSTRLEN +# define INET_ADDRSTRLEN 16 +# endif +# define MAI_HOSTADDR_STRSIZE INET_ADDRSTRLEN +#endif + +#define MAI_HOSTNAME_STRSIZE 1025 +#define MAI_SERVNAME_STRSIZE 32 +#define MAI_SERVPORT_STRSIZE sizeof("65535") + +#define MAI_V4ADDR_BITS 32 +#define MAI_V6ADDR_BITS 128 +#define MAI_V4ADDR_BYTES ((MAI_V4ADDR_BITS + (CHAR_BIT - 1))/CHAR_BIT) +#define MAI_V6ADDR_BYTES ((MAI_V6ADDR_BITS + (CHAR_BIT - 1))/CHAR_BIT) + + /* + * Routines and data structures to hide some of the complexity of the + * addrinfo API. They still don't hide that we may get results for address + * families that we aren't interested in. + * + * Note: the getnameinfo() and inet_ntop() system library functions use unsafe + * APIs with separate pointer and length arguments. To avoid buffer overflow + * problems with these functions, Postfix uses pointers to structures + * internally. This way the compiler can enforce that callers provide + * buffers with the appropriate length, instead of having to trust that + * callers will never mess up some length calculation. + */ +typedef struct { + char buf[MAI_HOSTNAME_STRSIZE]; +} MAI_HOSTNAME_STR; + +typedef struct { + char buf[MAI_HOSTADDR_STRSIZE]; +} MAI_HOSTADDR_STR; + +typedef struct { + char buf[MAI_SERVNAME_STRSIZE]; +} MAI_SERVNAME_STR; + +typedef struct { + char buf[MAI_SERVPORT_STRSIZE]; +} MAI_SERVPORT_STR; + +extern int WARN_UNUSED_RESULT hostname_to_sockaddr_pf(const char *, + int, const char *, int, struct addrinfo **); +extern int WARN_UNUSED_RESULT hostaddr_to_sockaddr(const char *, + const char *, int, struct addrinfo **); +extern int WARN_UNUSED_RESULT sockaddr_to_hostaddr(const struct sockaddr *, + SOCKADDR_SIZE, MAI_HOSTADDR_STR *, MAI_SERVPORT_STR *, int); +extern int WARN_UNUSED_RESULT sockaddr_to_hostname(const struct sockaddr *, + SOCKADDR_SIZE, MAI_HOSTNAME_STR *, MAI_SERVNAME_STR *, int); +extern void myaddrinfo_control(int,...); + +#define MAI_CTL_END 0 /* list terminator */ + +#define MAI_STRERROR(e) ((e) == EAI_SYSTEM ? strerror(errno) : gai_strerror(e)) + +#define hostname_to_sockaddr(host, serv, sock, res) \ + hostname_to_sockaddr_pf((host), PF_UNSPEC, (serv), (sock), (res)) + + /* + * Macros for the case where we really don't want to be bothered with things + * that may fail. + */ +#define HOSTNAME_TO_SOCKADDR_PF(host, pf, serv, sock, res) \ + do { \ + int _aierr; \ + _aierr = hostname_to_sockaddr_pf((host), (pf), (serv), (sock), (res)); \ + if (_aierr) \ + msg_fatal("hostname_to_sockaddr_pf: %s", MAI_STRERROR(_aierr)); \ + } while (0) + +#define HOSTNAME_TO_SOCKADDR(host, serv, sock, res) \ + HOSTNAME_TO_SOCKADDR_PF((host), PF_UNSPEC, (serv), (sock), (res)) + +#define HOSTADDR_TO_SOCKADDR(host, serv, sock, res) \ + do { \ + int _aierr; \ + _aierr = hostaddr_to_sockaddr((host), (serv), (sock), (res)); \ + if (_aierr) \ + msg_fatal("hostaddr_to_sockaddr: %s", MAI_STRERROR(_aierr)); \ + } while (0) + +#define SOCKADDR_TO_HOSTADDR(sa, salen, host, port, sock) \ + do { \ + int _aierr; \ + _aierr = sockaddr_to_hostaddr((sa), (salen), (host), (port), (sock)); \ + if (_aierr) \ + msg_fatal("sockaddr_to_hostaddr: %s", MAI_STRERROR(_aierr)); \ + } while (0) + +#define SOCKADDR_TO_HOSTNAME(sa, salen, host, service, sock) \ + do { \ + int _aierr; \ + _aierr = sockaddr_to_hostname((sa), (salen), (host), (service), (sock)); \ + if (_aierr) \ + msg_fatal("sockaddr_to_hostname: %s", MAI_STRERROR(_aierr)); \ + } while (0) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/myaddrinfo.ref b/src/util/myaddrinfo.ref new file mode 100644 index 0000000..7dccdb0 --- /dev/null +++ b/src/util/myaddrinfo.ref @@ -0,0 +1,8 @@ +./myaddrinfo: === hostname belly.porcupine.org === +./myaddrinfo: belly.porcupine.org -> family=2 sock=1 proto=6 168.100.3.6 +./myaddrinfo: 168.100.3.6 -> belly.porcupine.org +./myaddrinfo: belly.porcupine.org -> family=28 sock=1 proto=6 2604:8d00:189::6 +./myaddrinfo: 2604:8d00:189::6 -> belly.porcupine.org +./myaddrinfo: === host address 168.100.3.2 === +./myaddrinfo: 168.100.3.2 -> family=2 sock=1 proto=6 168.100.3.2 +./myaddrinfo: 168.100.3.2 -> spike.porcupine.org diff --git a/src/util/myaddrinfo.ref2 b/src/util/myaddrinfo.ref2 new file mode 100644 index 0000000..f2305dc --- /dev/null +++ b/src/util/myaddrinfo.ref2 @@ -0,0 +1,5 @@ +./myaddrinfo: === hostname null.porcupine.org === +./myaddrinfo: hostname_to_sockaddr(null.porcupine.org): hostname nor servname provided, or not known +./myaddrinfo: === host address 10.0.0.0 === +./myaddrinfo: 10.0.0.0 -> family=2 sock=1 proto=6 10.0.0.0 +./myaddrinfo: sockaddr_to_hostname: hostname nor servname provided, or not known diff --git a/src/util/myaddrinfo4.ref b/src/util/myaddrinfo4.ref new file mode 100644 index 0000000..33c1284 --- /dev/null +++ b/src/util/myaddrinfo4.ref @@ -0,0 +1,6 @@ +./myaddrinfo4: === hostname belly.porcupine.org === +./myaddrinfo4: belly.porcupine.org -> family=2 sock=1 proto=6 168.100.3.6 +./myaddrinfo4: 168.100.3.6 -> belly.porcupine.org +./myaddrinfo4: === host address 168.100.3.2 === +./myaddrinfo4: 168.100.3.2 -> family=2 sock=1 proto=6 168.100.3.2 +./myaddrinfo4: 168.100.3.2 -> spike.porcupine.org diff --git a/src/util/myaddrinfo4.ref2 b/src/util/myaddrinfo4.ref2 new file mode 100644 index 0000000..f73560b --- /dev/null +++ b/src/util/myaddrinfo4.ref2 @@ -0,0 +1,5 @@ +./myaddrinfo4: === hostname null.porcupine.org === +./myaddrinfo4: hostname2sockaddr(null.porcupine.org): No address associated with hostname +./myaddrinfo4: === host address 10.0.0.0 === +./myaddrinfo4: 10.0.0.0 -> family=2 sock=1 proto=6 10.0.0.0 +./myaddrinfo4: sockaddr2hostname: hostname nor servname provided, or not known diff --git a/src/util/myflock.c b/src/util/myflock.c new file mode 100644 index 0000000..bd903ee --- /dev/null +++ b/src/util/myflock.c @@ -0,0 +1,152 @@ +/*++ +/* NAME +/* myflock 3 +/* SUMMARY +/* lock open file +/* SYNOPSIS +/* #include <myflock.h> +/* +/* int myflock(fd, lock_style, operation) +/* int fd; +/* int lock_style; +/* int operation; +/* DESCRIPTION +/* myflock() locks or unlocks an entire open file. +/* +/* In the case of a blocking request, a call that fails due to +/* foreseeable transient problems is retried once per second. +/* +/* Arguments: +/* .IP fd +/* The open file to be locked/unlocked. +/* .IP lock_style +/* One of the following values: +/* .RS +/* .IP MYFLOCK_STYLE_FLOCK +/* Use BSD-style flock() locking. +/* .IP MYFLOCK_STYLE_FCNTL +/* Use POSIX-style fcntl() locking. +/* .RE +/* .IP operation +/* One of the following values: +/* .RS +/* .IP MYFLOCK_OP_NONE +/* Release any locks the process has on the specified open file. +/* .IP MYFLOCK_OP_SHARED +/* Attempt to acquire a shared lock on the specified open file. +/* This is appropriate for read-only access. +/* .IP MYFLOCK_OP_EXCLUSIVE +/* Attempt to acquire an exclusive lock on the specified open +/* file. This is appropriate for write access. +/* .PP +/* In addition, setting the MYFLOCK_OP_NOWAIT bit causes the +/* call to return immediately when the requested lock cannot +/* be acquired. +/* .RE +/* DIAGNOSTICS +/* myflock() returns 0 in case of success, -1 in case of failure. +/* A problem description is returned via the global \fIerrno\fR +/* variable. In the case of a non-blocking lock request the value +/* EAGAIN means that a lock is claimed by someone else. +/* +/* Panic: attempts to use an unsupported file locking method or +/* to implement an unsupported operation. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <errno.h> +#include <unistd.h> + +#ifdef HAS_FCNTL_LOCK +#include <fcntl.h> +#include <string.h> +#endif + +#ifdef HAS_FLOCK_LOCK +#include <sys/file.h> +#endif + +/* Utility library. */ + +#include "msg.h" +#include "myflock.h" + +/* myflock - lock/unlock entire open file */ + +int myflock(int fd, int lock_style, int operation) +{ + int status; + + /* + * Sanity check. + */ + if ((operation & (MYFLOCK_OP_BITS)) != operation) + msg_panic("myflock: improper operation type: 0x%x", operation); + + switch (lock_style) { + + /* + * flock() does exactly what we need. Too bad it is not standard. + */ +#ifdef HAS_FLOCK_LOCK + case MYFLOCK_STYLE_FLOCK: + { + static int lock_ops[] = { + LOCK_UN, LOCK_SH, LOCK_EX, -1, + -1, LOCK_SH | LOCK_NB, LOCK_EX | LOCK_NB, -1 + }; + + while ((status = flock(fd, lock_ops[operation])) < 0 + && errno == EINTR) + sleep(1); + break; + } +#endif + + /* + * fcntl() is standard and does more than we need, but we can handle + * it. + */ +#ifdef HAS_FCNTL_LOCK + case MYFLOCK_STYLE_FCNTL: + { + struct flock lock; + int request; + static int lock_ops[] = { + F_UNLCK, F_RDLCK, F_WRLCK + }; + + memset((void *) &lock, 0, sizeof(lock)); + lock.l_type = lock_ops[operation & ~MYFLOCK_OP_NOWAIT]; + request = (operation & MYFLOCK_OP_NOWAIT) ? F_SETLK : F_SETLKW; + while ((status = fcntl(fd, request, &lock)) < 0 + && errno == EINTR) + sleep(1); + break; + } +#endif + default: + msg_panic("myflock: unsupported lock style: 0x%x", lock_style); + } + + /* + * Return a consistent result. Some systems return EACCES when a lock is + * taken by someone else, and that would complicate error processing. + */ + if (status < 0 && (operation & MYFLOCK_OP_NOWAIT) != 0) + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EACCES) + errno = EAGAIN; + + return (status); +} diff --git a/src/util/myflock.h b/src/util/myflock.h new file mode 100644 index 0000000..ee18bdb --- /dev/null +++ b/src/util/myflock.h @@ -0,0 +1,52 @@ +#ifndef _MYFLOCK_H_INCLUDED_ +#define _MYFLOCK_H_INCLUDED_ + +/*++ +/* NAME +/* myflock 3h +/* SUMMARY +/* lock open file +/* SYNOPSIS +/* #include <myflock.h> +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern int WARN_UNUSED_RESULT myflock(int, int, int); + + /* + * Lock styles. + */ +#define MYFLOCK_STYLE_FLOCK 1 +#define MYFLOCK_STYLE_FCNTL 2 + + /* + * Lock request types. + */ +#define MYFLOCK_OP_NONE 0 +#define MYFLOCK_OP_SHARED 1 +#define MYFLOCK_OP_EXCLUSIVE 2 +#define MYFLOCK_OP_NOWAIT 4 + +#define MYFLOCK_OP_BITS \ + (MYFLOCK_OP_SHARED | MYFLOCK_OP_EXCLUSIVE | MYFLOCK_OP_NOWAIT) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/mymalloc.c b/src/util/mymalloc.c new file mode 100644 index 0000000..94f7bb3 --- /dev/null +++ b/src/util/mymalloc.c @@ -0,0 +1,269 @@ +/*++ +/* NAME +/* mymalloc 3 +/* SUMMARY +/* memory management wrappers +/* SYNOPSIS +/* #include <mymalloc.h> +/* +/* void *mymalloc(len) +/* ssize_t len; +/* +/* void *myrealloc(ptr, len) +/* void *ptr; +/* ssize_t len; +/* +/* void myfree(ptr) +/* void *ptr; +/* +/* char *mystrdup(str) +/* const char *str; +/* +/* char *mystrndup(str, len) +/* const char *str; +/* ssize_t len; +/* +/* void *mymemdup(ptr, len) +/* const void *ptr; +/* ssize_t len; +/* DESCRIPTION +/* This module performs low-level memory management with error +/* handling. A call of these functions either succeeds or it does +/* not return at all. +/* +/* To save memory, zero-length strings are shared and read-only. +/* The caller must not attempt to modify the null terminator. +/* This code is enabled unless NO_SHARED_EMPTY_STRINGS is +/* defined at compile time (for example, you have an sscanf() +/* routine that pushes characters back into its input). +/* +/* mymalloc() allocates the requested amount of memory. The memory +/* is not set to zero. +/* +/* myrealloc() resizes memory obtained from mymalloc() or myrealloc() +/* to the requested size. The result pointer value may differ from +/* that given via the \fIptr\fR argument. +/* +/* myfree() takes memory obtained from mymalloc() or myrealloc() +/* and makes it available for other use. +/* +/* mystrdup() returns a dynamic-memory copy of its null-terminated +/* argument. This routine uses mymalloc(). +/* +/* mystrndup() returns a dynamic-memory copy of at most \fIlen\fR +/* leading characters of its null-terminated +/* argument. The result is null-terminated. This routine uses mymalloc(). +/* +/* mymemdup() makes a copy of the memory pointed to by \fIptr\fR +/* with length \fIlen\fR. The result is NOT null-terminated. +/* This routine uses mymalloc(). +/* SEE ALSO +/* msg(3) diagnostics interface +/* DIAGNOSTICS +/* Problems are reported via the msg(3) diagnostics routines: +/* the requested amount of memory is not available; improper use +/* is detected; other fatal errors. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System libraries. */ + +#include "sys_defs.h" +#include <stdlib.h> +#include <stddef.h> +#include <string.h> + +/* Application-specific. */ + +#include "msg.h" +#include "mymalloc.h" + + /* + * Structure of an annotated memory block. In order to detect spurious + * free() calls we prepend a signature to memory given to the application. + * In order to detect access to free()d blocks, overwrite each block as soon + * as it is passed to myfree(). With the code below, the user data has + * integer alignment or better. + */ +typedef struct MBLOCK { + int signature; /* set when block is active */ + ssize_t length; /* user requested length */ + union { + ALIGN_TYPE align; + char payload[1]; /* actually a bunch of bytes */ + } u; +} MBLOCK; + +#define SIGNATURE 0xdead +#define FILLER 0xff + +#define CHECK_IN_PTR(ptr, real_ptr, len, fname) { \ + if (ptr == 0) \ + msg_panic("%s: null pointer input", fname); \ + real_ptr = (MBLOCK *) (ptr - offsetof(MBLOCK, u.payload[0])); \ + if (real_ptr->signature != SIGNATURE) \ + msg_panic("%s: corrupt or unallocated memory block", fname); \ + real_ptr->signature = 0; \ + if ((len = real_ptr->length) < 1) \ + msg_panic("%s: corrupt memory block length", fname); \ +} + +#define CHECK_OUT_PTR(ptr, real_ptr, len) { \ + real_ptr->signature = SIGNATURE; \ + real_ptr->length = len; \ + ptr = real_ptr->u.payload; \ +} + +#define SPACE_FOR(len) (offsetof(MBLOCK, u.payload[0]) + len) + + /* + * Optimization for short strings. We share one copy with multiple callers. + * This differs from normal heap memory in two ways, because the memory is + * shared: + * + * - It must be read-only to avoid horrible bugs. This is OK because there is + * no legitimate reason to modify the null terminator. + * + * - myfree() cannot overwrite the memory with a filler pattern like it can do + * with heap memory. Therefore, some dangling pointer bugs will be masked. + */ +#ifndef NO_SHARED_EMPTY_STRINGS +static const char empty_string[] = ""; + +#endif + +/* mymalloc - allocate memory or bust */ + +void *mymalloc(ssize_t len) +{ + void *ptr; + MBLOCK *real_ptr; + + /* + * Note: for safety reasons the request length is a signed type. This + * allows us to catch integer overflow problems that weren't already + * caught up-stream. + */ + if (len < 1) + msg_panic("mymalloc: requested length %ld", (long) len); +#ifdef MYMALLOC_FUZZ + len += MYMALLOC_FUZZ; +#endif + if ((real_ptr = (MBLOCK *) malloc(SPACE_FOR(len))) == 0) + msg_fatal("mymalloc: insufficient memory for %ld bytes: %m", + (long) len); + CHECK_OUT_PTR(ptr, real_ptr, len); + memset(ptr, FILLER, len); + return (ptr); +} + +/* myrealloc - reallocate memory or bust */ + +void *myrealloc(void *ptr, ssize_t len) +{ + MBLOCK *real_ptr; + ssize_t old_len; + +#ifndef NO_SHARED_EMPTY_STRINGS + if (ptr == empty_string) + return (mymalloc(len)); +#endif + + /* + * Note: for safety reasons the request length is a signed type. This + * allows us to catch integer overflow problems that weren't already + * caught up-stream. + */ + if (len < 1) + msg_panic("myrealloc: requested length %ld", (long) len); +#ifdef MYMALLOC_FUZZ + len += MYMALLOC_FUZZ; +#endif + CHECK_IN_PTR(ptr, real_ptr, old_len, "myrealloc"); + if ((real_ptr = (MBLOCK *) realloc((void *) real_ptr, SPACE_FOR(len))) == 0) + msg_fatal("myrealloc: insufficient memory for %ld bytes: %m", + (long) len); + CHECK_OUT_PTR(ptr, real_ptr, len); + if (len > old_len) + memset(ptr + old_len, FILLER, len - old_len); + return (ptr); +} + +/* myfree - release memory */ + +void myfree(void *ptr) +{ + MBLOCK *real_ptr; + ssize_t len; + +#ifndef NO_SHARED_EMPTY_STRINGS + if (ptr != empty_string) { +#endif + CHECK_IN_PTR(ptr, real_ptr, len, "myfree"); + memset((void *) real_ptr, FILLER, SPACE_FOR(len)); + free((void *) real_ptr); +#ifndef NO_SHARED_EMPTY_STRINGS + } +#endif +} + +/* mystrdup - save string to heap */ + +char *mystrdup(const char *str) +{ + size_t len; + + if (str == 0) + msg_panic("mystrdup: null pointer argument"); +#ifndef NO_SHARED_EMPTY_STRINGS + if (*str == 0) + return ((char *) empty_string); +#endif + if ((len = strlen(str) + 1) > SSIZE_T_MAX) + msg_panic("mystrdup: string length >= SSIZE_T_MAX"); + return (memcpy(mymalloc(len), str, len)); +} + +/* mystrndup - save substring to heap */ + +char *mystrndup(const char *str, ssize_t len) +{ + char *result; + char *cp; + + if (str == 0) + msg_panic("mystrndup: null pointer argument"); + if (len < 0) + msg_panic("mystrndup: requested length %ld", (long) len); +#ifndef NO_SHARED_EMPTY_STRINGS + if (*str == 0) + return ((char *) empty_string); +#endif + if ((cp = memchr(str, 0, len)) != 0) + len = cp - str; + result = memcpy(mymalloc(len + 1), str, len); + result[len] = 0; + return (result); +} + +/* mymemdup - copy memory */ + +void *mymemdup(const void *ptr, ssize_t len) +{ + if (ptr == 0) + msg_panic("mymemdup: null pointer argument"); + return (memcpy(mymalloc(len), ptr, len)); +} diff --git a/src/util/mymalloc.h b/src/util/mymalloc.h new file mode 100644 index 0000000..e2190f7 --- /dev/null +++ b/src/util/mymalloc.h @@ -0,0 +1,40 @@ +#ifndef _MALLOC_H_INCLUDED_ +#define _MALLOC_H_INCLUDED_ + +/*++ +/* NAME +/* mymalloc 3h +/* SUMMARY +/* memory management wrappers +/* SYNOPSIS +/* #include "mymalloc.h" +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern void *mymalloc(ssize_t); +extern void *myrealloc(void *, ssize_t); +extern void myfree(void *); +extern char *mystrdup(const char *); +extern char *mystrndup(const char *, ssize_t); +extern void *mymemdup(const void *, ssize_t); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/myrand.c b/src/util/myrand.c new file mode 100644 index 0000000..39fcbfd --- /dev/null +++ b/src/util/myrand.c @@ -0,0 +1,63 @@ +/*++ +/* NAME +/* myrand 3 +/* SUMMARY +/* rand wrapper +/* SYNOPSIS +/* #include <myrand.h> +/* +/* void mysrand(seed) +/* int seed; +/* +/* int myrand() +/* DESCRIPTION +/* This module implements a wrapper for the portable, pseudo-random +/* number generator. The wrapper adds automatic initialization. +/* +/* mysrand() performs initialization. This call may be skipped. +/* +/* myrand() returns a pseudo-random number in the range [0, RAND_MAX]. +/* If mysrand() was not called, it is invoked with the process ID +/* ex-or-ed with the time of day in seconds. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* WARNING +/* Do not use this code for generating unpredictable numbers. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdlib.h> +#include <unistd.h> +#include <time.h> + +/* Utility library. */ + +#include <myrand.h> + +static int myrand_initdone = 0; + +/* mysrand - initialize */ + +void mysrand(int seed) +{ + srand(seed); + myrand_initdone = 1; +} + +/* myrand - pseudo-random number */ + +int myrand(void) +{ + if (myrand_initdone == 0) + mysrand(getpid() ^ time((time_t *) 0)); + return (rand()); +} diff --git a/src/util/myrand.h b/src/util/myrand.h new file mode 100644 index 0000000..4cc88db --- /dev/null +++ b/src/util/myrand.h @@ -0,0 +1,35 @@ +#ifndef _MYRAND_H_INCLUDED_ +#define _MYRAND_H_INCLUDED_ + +/*++ +/* NAME +/* myrand 3h +/* SUMMARY +/* rand wrapper +/* SYNOPSIS +/* #include <myrand.h> +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +#ifndef RAND_MAX +#define RAND_MAX 0x7fffffff +#endif + +extern void mysrand(int); +extern int myrand(void); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/mystrtok.c b/src/util/mystrtok.c new file mode 100644 index 0000000..85b15f3 --- /dev/null +++ b/src/util/mystrtok.c @@ -0,0 +1,258 @@ +/*++ +/* NAME +/* mystrtok 3 +/* SUMMARY +/* safe tokenizer +/* SYNOPSIS +/* #include <stringops.h> +/* +/* char *mystrtok(bufp, delimiters) +/* char **bufp; +/* const char *delimiters; +/* +/* char *mystrtokq(bufp, delimiters, parens) +/* char **bufp; +/* const char *delimiters; +/* const char *parens; +/* +/* char *mystrtokdq(bufp, delimiters) +/* char **bufp; +/* const char *delimiters; +/* DESCRIPTION +/* mystrtok() splits a buffer on the specified \fIdelimiters\fR. +/* Tokens are delimited by runs of delimiters, so this routine +/* cannot return zero-length tokens. +/* +/* mystrtokq() is like mystrtok() but will not split text +/* between balanced parentheses. \fIparens\fR specifies the +/* opening and closing parenthesis (one of each). The set of +/* \fIparens\fR must be distinct from the set of \fIdelimiters\fR. +/* +/* mystrtokdq() is like mystrtok() but will not split text +/* between double quotes. The backslash character may be used +/* to escape characters. The double quote and backslash +/* character must not appear in the set of \fIdelimiters\fR. +/* +/* The \fIbufp\fR argument specifies the start of the search; it +/* is updated with each call. The input is destroyed. +/* +/* The result value is the next token, or a null pointer when the +/* end of the buffer was reached. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <string.h> + +/* Utility library. */ + +#include "stringops.h" + +/* mystrtok - safe tokenizer */ + +char *mystrtok(char **src, const char *sep) +{ + char *start = *src; + char *end; + + /* + * Skip over leading delimiters. + */ + start += strspn(start, sep); + if (*start == 0) { + *src = start; + return (0); + } + + /* + * Separate off one token. + */ + end = start + strcspn(start, sep); + if (*end != 0) + *end++ = 0; + *src = end; + return (start); +} + +/* mystrtokq - safe tokenizer with quoting support */ + +char *mystrtokq(char **src, const char *sep, const char *parens) +{ + char *start = *src; + static char *cp; + int ch; + int level; + + /* + * Skip over leading delimiters. + */ + start += strspn(start, sep); + if (*start == 0) { + *src = start; + return (0); + } + + /* + * Parse out the next token. + */ + for (level = 0, cp = start; (ch = *(unsigned char *) cp) != 0; cp++) { + if (ch == parens[0]) { + level++; + } else if (level > 0 && ch == parens[1]) { + level--; + } else if (level == 0 && strchr(sep, ch) != 0) { + *cp++ = 0; + break; + } + } + *src = cp; + return (start); +} + +/* mystrtokdq - safe tokenizer, double quote and backslash support */ + +char *mystrtokdq(char **src, const char *sep) +{ + char *cp = *src; + char *start; + + /* + * Skip leading delimiters. + */ + cp += strspn(cp, sep); + + /* + * Skip to next unquoted space or comma. + */ + if (*cp == 0) { + start = 0; + } else { + int in_quotes; + + for (in_quotes = 0, start = cp; *cp; cp++) { + if (*cp == '\\') { + if (*++cp == 0) + break; + } else if (*cp == '"') { + in_quotes = !in_quotes; + } else if (!in_quotes && strchr(sep, *(unsigned char *) cp) != 0) { + *cp++ = 0; + break; + } + } + } + *src = cp; + return (start); +} + +#ifdef TEST + + /* + * Test program. + */ +#include "msg.h" +#include "mymalloc.h" + + /* + * The following needs to be large enough to include a null terminator in + * every testcase.expected field. + */ +#define EXPECT_SIZE 5 + +struct testcase { + const char *action; + const char *input; + const char *expected[EXPECT_SIZE]; +}; +static const struct testcase testcases[] = { + {"mystrtok", ""}, + {"mystrtok", " foo ", {"foo"}}, + {"mystrtok", " foo bar ", {"foo", "bar"}}, + {"mystrtokq", ""}, + {"mystrtokq", "foo bar", {"foo", "bar"}}, + {"mystrtokq", "{ bar } ", {"{ bar }"}}, + {"mystrtokq", "foo { bar } baz", {"foo", "{ bar }", "baz"}}, + {"mystrtokq", "foo{ bar } baz", {"foo{ bar }", "baz"}}, + {"mystrtokq", "foo { bar }baz", {"foo", "{ bar }baz"}}, + {"mystrtokdq", ""}, + {"mystrtokdq", " foo ", {"foo"}}, + {"mystrtokdq", " foo bar ", {"foo", "bar"}}, + {"mystrtokdq", " foo\\ bar ", {"foo\\ bar"}}, + {"mystrtokdq", " foo \\\" bar", {"foo", "\\\"", "bar"}}, + {"mystrtokdq", " foo \" bar baz\" ", {"foo", "\" bar baz\""}}, +}; + +int main(void) +{ + const struct testcase *tp; + char *actual; + int pass; + int fail; + int match; + int n; + +#define NUM_TESTS sizeof(testcases)/sizeof(testcases[0]) +#define STR_OR_NULL(s) ((s) ? (s) : "null") + + for (pass = fail = 0, tp = testcases; tp < testcases + NUM_TESTS; tp++) { + char *saved_input = mystrdup(tp->input); + char *cp = saved_input; + + msg_info("RUN test case %ld %s >%s<", + (long) (tp - testcases), tp->action, tp->input); +#if 0 + msg_info("action=%s", tp->action); + msg_info("input=%s", tp->input); + for (n = 0; tp->expected[n]; tp++) + msg_info("expected[%d]=%s", n, tp->expected[n]); +#endif + + for (n = 0; n < EXPECT_SIZE; n++) { + if (strcmp(tp->action, "mystrtok") == 0) { + actual = mystrtok(&cp, CHARS_SPACE); + } else if (strcmp(tp->action, "mystrtokq") == 0) { + actual = mystrtokq(&cp, CHARS_SPACE, CHARS_BRACE); + } else if (strcmp(tp->action, "mystrtokdq") == 0) { + actual = mystrtokdq(&cp, CHARS_SPACE); + } else { + msg_panic("invalid command: %s", tp->action); + } + if ((match = (actual && tp->expected[n]) ? + (strcmp(actual, tp->expected[n]) == 0) : + (actual == tp->expected[n])) != 0) { + if (actual == 0) { + msg_info("PASS test %ld", (long) (tp - testcases)); + pass++; + break; + } + } else { + msg_warn("expected: >%s<, got: >%s<", + STR_OR_NULL(tp->expected[n]), STR_OR_NULL(actual)); + msg_info("FAIL test %ld", (long) (tp - testcases)); + fail++; + break; + } + } + if (n >= EXPECT_SIZE) + msg_panic("need to increase EXPECT_SIZE"); + myfree(saved_input); + } + return (fail > 0); +} + +#endif diff --git a/src/util/mystrtok.ref b/src/util/mystrtok.ref new file mode 100644 index 0000000..4f920f9 --- /dev/null +++ b/src/util/mystrtok.ref @@ -0,0 +1,30 @@ +unknown: RUN test case 0 mystrtok >< +unknown: PASS test 0 +unknown: RUN test case 1 mystrtok > foo < +unknown: PASS test 1 +unknown: RUN test case 2 mystrtok > foo bar < +unknown: PASS test 2 +unknown: RUN test case 3 mystrtokq >< +unknown: PASS test 3 +unknown: RUN test case 4 mystrtokq >foo bar< +unknown: PASS test 4 +unknown: RUN test case 5 mystrtokq >{ bar } < +unknown: PASS test 5 +unknown: RUN test case 6 mystrtokq >foo { bar } baz< +unknown: PASS test 6 +unknown: RUN test case 7 mystrtokq >foo{ bar } baz< +unknown: PASS test 7 +unknown: RUN test case 8 mystrtokq >foo { bar }baz< +unknown: PASS test 8 +unknown: RUN test case 9 mystrtokdq >< +unknown: PASS test 9 +unknown: RUN test case 10 mystrtokdq > foo < +unknown: PASS test 10 +unknown: RUN test case 11 mystrtokdq > foo bar < +unknown: PASS test 11 +unknown: RUN test case 12 mystrtokdq > foo\ bar < +unknown: PASS test 12 +unknown: RUN test case 13 mystrtokdq > foo \" bar< +unknown: PASS test 13 +unknown: RUN test case 14 mystrtokdq > foo " bar baz" < +unknown: PASS test 14 diff --git a/src/util/name_code.c b/src/util/name_code.c new file mode 100644 index 0000000..ca6d94a --- /dev/null +++ b/src/util/name_code.c @@ -0,0 +1,91 @@ +/*++ +/* NAME +/* name_code 3 +/* SUMMARY +/* name to number table mapping +/* SYNOPSIS +/* #include <name_code.h> +/* +/* typedef struct { +/* .in +4 +/* const char *name; +/* int code; +/* .in -4 +/* } NAME_CODE; +/* +/* int name_code(table, flags, name) +/* const NAME_CODE *table; +/* int flags; +/* const char *name; +/* +/* const char *str_name_code(table, code) +/* const NAME_CODE *table; +/* int code; +/* DESCRIPTION +/* This module does simple name<->number mapping. The process +/* is controlled by a table of (name, code) values. +/* The table is terminated with a null pointer and a code that +/* corresponds to "name not found". +/* +/* name_code() looks up the code that corresponds with the name. +/* The lookup is case insensitive. The flags argument specifies +/* zero or more of the following: +/* .IP NAME_CODE_FLAG_STRICT_CASE +/* String lookups are case sensitive. +/* .PP +/* For convenience the constant NAME_CODE_FLAG_NONE requests +/* no special processing. +/* +/* str_name_code() translates a number to its equivalent string. +/* DIAGNOSTICS +/* When the search fails, the result is the "name not found" code +/* or the null pointer, respectively. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> + +/* Utility library. */ + +#include <name_code.h> + +/* name_code - look up code by name */ + +int name_code(const NAME_CODE *table, int flags, const char *name) +{ + const NAME_CODE *np; + int (*lookup) (const char *, const char *); + + if (flags & NAME_CODE_FLAG_STRICT_CASE) + lookup = strcmp; + else + lookup = strcasecmp; + + for (np = table; np->name; np++) + if (lookup(name, np->name) == 0) + break; + return (np->code); +} + +/* str_name_code - look up name by code */ + +const char *str_name_code(const NAME_CODE *table, int code) +{ + const NAME_CODE *np; + + for (np = table; np->name; np++) + if (code == np->code) + break; + return (np->name); +} diff --git a/src/util/name_code.h b/src/util/name_code.h new file mode 100644 index 0000000..e08618d --- /dev/null +++ b/src/util/name_code.h @@ -0,0 +1,39 @@ +#ifndef _NAME_CODE_H_INCLUDED_ +#define _NAME_CODE_H_INCLUDED_ + +/*++ +/* NAME +/* name_mask 3h +/* SUMMARY +/* name to number table mapping +/* SYNOPSIS +/* #include <name_code.h> +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +typedef struct { + const char *name; + int code; +} NAME_CODE; + +#define NAME_CODE_FLAG_NONE 0 +#define NAME_CODE_FLAG_STRICT_CASE (1<<0) + +extern int name_code(const NAME_CODE *, int, const char *); +extern const char *str_name_code(const NAME_CODE *, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/name_mask.c b/src/util/name_mask.c new file mode 100644 index 0000000..284d4fa --- /dev/null +++ b/src/util/name_mask.c @@ -0,0 +1,511 @@ +/*++ +/* NAME +/* name_mask 3 +/* SUMMARY +/* map names to bit mask +/* SYNOPSIS +/* #include <name_mask.h> +/* +/* int name_mask(context, table, names) +/* const char *context; +/* const NAME_MASK *table; +/* const char *names; +/* +/* long long_name_mask(context, table, names) +/* const char *context; +/* const LONG_NAME_MASK *table; +/* const char *names; +/* +/* const char *str_name_mask(context, table, mask) +/* const char *context; +/* const NAME_MASK *table; +/* int mask; +/* +/* const char *str_long_name_mask(context, table, mask) +/* const char *context; +/* const LONG_NAME_MASK *table; +/* long mask; +/* +/* int name_mask_opt(context, table, names, flags) +/* const char *context; +/* const NAME_MASK *table; +/* const char *names; +/* int flags; +/* +/* long long_name_mask_opt(context, table, names, flags) +/* const char *context; +/* const LONG_NAME_MASK *table; +/* const char *names; +/* int flags; +/* +/* int name_mask_delim_opt(context, table, names, delim, flags) +/* const char *context; +/* const NAME_MASK *table; +/* const char *names; +/* const char *delim; +/* int flags; +/* +/* long long_name_mask_delim_opt(context, table, names, delim, flags) +/* const char *context; +/* const LONG_NAME_MASK *table; +/* const char *names; +/* const char *delim; +/* int flags; +/* +/* const char *str_name_mask_opt(buf, context, table, mask, flags) +/* VSTRING *buf; +/* const char *context; +/* const NAME_MASK *table; +/* int mask; +/* int flags; +/* +/* const char *str_long_name_mask_opt(buf, context, table, mask, flags) +/* VSTRING *buf; +/* const char *context; +/* const LONG_NAME_MASK *table; +/* long mask; +/* int flags; +/* DESCRIPTION +/* name_mask() takes a null-terminated \fItable\fR with (name, mask) +/* values and computes the bit-wise OR of the masks that correspond +/* to the names listed in the \fInames\fR argument, separated by +/* comma and/or whitespace characters. The "long_" version returns +/* a "long int" bitmask, rather than an "int" bitmask. +/* +/* str_name_mask() translates a mask into its equivalent names. +/* The result is written to a static buffer that is overwritten +/* upon each call. The "long_" version converts a "long int" +/* bitmask, rather than an "int" bitmask. +/* +/* name_mask_opt() and str_name_mask_opt() are extended versions +/* with additional fine control. name_mask_delim_opt() supports +/* non-default delimiter characters. +/* +/* Arguments: +/* .IP buf +/* Null pointer or pointer to buffer storage. +/* .IP context +/* What kind of names and +/* masks are being manipulated, in order to make error messages +/* more understandable. Typically, this would be the name of a +/* user-configurable parameter. +/* .IP table +/* Table with (name, bit mask) pairs. +/* .IP names +/* A list of names that is to be converted into a bit mask. +/* .IP mask +/* A bit mask. +/* .IP delim +/* Delimiter characters to use instead of whitespace and commas. +/* .IP flags +/* Bit-wise OR of one or more of the following. Where features +/* would have conflicting results (e.g., FATAL versus IGNORE), +/* the feature that takes precedence is described first. +/* +/* When converting from string to mask, at least one of the +/* following must be specified: NAME_MASK_FATAL, NAME_MASK_RETURN, +/* NAME_MASK_WARN or NAME_MASK_IGNORE. +/* +/* When converting from mask to string, at least one of the +/* following must be specified: NAME_MASK_NUMBER, NAME_MASK_FATAL, +/* NAME_MASK_RETURN, NAME_MASK_WARN or NAME_MASK_IGNORE. +/* .RS +/* .IP NAME_MASK_NUMBER +/* When converting from string to mask, accept hexadecimal +/* inputs starting with "0x" followed by hexadecimal digits. +/* Each hexadecimal input may specify multiple bits. This +/* feature is ignored for hexadecimal inputs that cannot be +/* converted (malformed, out of range, etc.). +/* +/* When converting from mask to string, represent bits not +/* defined in \fItable\fR as "0x" followed by hexadecimal +/* digits. This conversion always succeeds. +/* .IP NAME_MASK_FATAL +/* Require that all names listed in \fIname\fR exist in +/* \fItable\fR or that they can be parsed as a hexadecimal +/* string, and require that all bits listed in \fImask\fR exist +/* in \fItable\fR or that they can be converted to hexadecimal +/* string. Terminate with a fatal run-time error if this +/* condition is not met. This feature is enabled by default +/* when calling name_mask() or str_name_mask(). +/* .IP NAME_MASK_RETURN +/* Require that all names listed in \fIname\fR exist in +/* \fItable\fR or that they can be parsed as a hexadecimal +/* string, and require that all bits listed in \fImask\fR exist +/* in \fItable\fR or that they can be converted to hexadecimal +/* string. Log a warning, and return 0 (name_mask()) or a +/* null pointer (str_name_mask()) if this condition is not +/* met. This feature is not enabled by default when calling +/* name_mask() or str_name_mask(). +/* .IP NAME_MASK_WARN +/* Require that all names listed in \fIname\fR exist in +/* \fItable\fR or that they can be parsed as a hexadecimal +/* string, and require that all bits listed in \fImask\fR exist +/* in \fItable\fR or that they can be converted to hexadecimal +/* string. Log a warning if this condition is not met, continue +/* processing, and return all valid bits or names. This feature +/* is not enabled by default when calling name_mask() or +/* str_name_mask(). +/* .IP NAME_MASK_IGNORE +/* Silently ignore names listed in \fIname\fR that don't exist +/* in \fItable\fR and that can't be parsed as a hexadecimal +/* string, and silently ignore bits listed in \fImask\fR that +/* don't exist in \fItable\fR and that can't be converted to +/* hexadecimal string. +/* .IP NAME_MASK_ANY_CASE +/* Enable case-insensitive matching. +/* This feature is not enabled by default when calling name_mask(); +/* it has no effect with str_name_mask(). +/* .IP NAME_MASK_COMMA +/* Use comma instead of space when converting a mask to string. +/* .IP NAME_MASK_PIPE +/* Use "|" instead of space when converting a mask to string. +/* .RE +/* The value NAME_MASK_NONE explicitly requests no features, +/* and NAME_MASK_DEFAULT enables the default options. +/* DIAGNOSTICS +/* Fatal: the \fInames\fR argument specifies a name not found in +/* \fItable\fR, or the \fImask\fR specifies a bit not found in +/* \fItable\fR. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> + +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <stringops.h> +#include <name_mask.h> +#include <vstring.h> + +static int hex_to_ulong(char *, unsigned long, unsigned long *); + +#define STR(x) vstring_str(x) + +/* name_mask_delim_opt - compute mask corresponding to list of names */ + +int name_mask_delim_opt(const char *context, const NAME_MASK *table, + const char *names, const char *delim, int flags) +{ + const char *myname = "name_mask"; + char *saved_names = mystrdup(names); + char *bp = saved_names; + int result = 0; + const NAME_MASK *np; + char *name; + int (*lookup) (const char *, const char *); + unsigned long ulval; + + if ((flags & NAME_MASK_REQUIRED) == 0) + msg_panic("%s: missing NAME_MASK_FATAL/RETURN/WARN/IGNORE flag", + myname); + + if (flags & NAME_MASK_ANY_CASE) + lookup = strcasecmp; + else + lookup = strcmp; + + /* + * Break up the names string, and look up each component in the table. If + * the name is found, merge its mask with the result. + */ + while ((name = mystrtok(&bp, delim)) != 0) { + for (np = table; /* void */ ; np++) { + if (np->name == 0) { + if ((flags & NAME_MASK_NUMBER) + && hex_to_ulong(name, ~0U, &ulval)) { + result |= (unsigned int) ulval; + } else if (flags & NAME_MASK_FATAL) { + msg_fatal("unknown %s value \"%s\" in \"%s\"", + context, name, names); + } else if (flags & NAME_MASK_RETURN) { + msg_warn("unknown %s value \"%s\" in \"%s\"", + context, name, names); + myfree(saved_names); + return (0); + } else if (flags & NAME_MASK_WARN) { + msg_warn("unknown %s value \"%s\" in \"%s\"", + context, name, names); + } + break; + } + if (lookup(name, np->name) == 0) { + if (msg_verbose) + msg_info("%s: %s", myname, name); + result |= np->mask; + break; + } + } + } + myfree(saved_names); + return (result); +} + +/* str_name_mask_opt - mask to string */ + +const char *str_name_mask_opt(VSTRING *buf, const char *context, + const NAME_MASK *table, + int mask, int flags) +{ + const char *myname = "name_mask"; + const NAME_MASK *np; + ssize_t len; + static VSTRING *my_buf = 0; + int delim = (flags & NAME_MASK_COMMA ? ',' : + (flags & NAME_MASK_PIPE ? '|' : ' ')); + + if ((flags & STR_NAME_MASK_REQUIRED) == 0) + msg_panic("%s: missing NAME_MASK_NUMBER/FATAL/RETURN/WARN/IGNORE flag", + myname); + + if (buf == 0) { + if (my_buf == 0) + my_buf = vstring_alloc(1); + buf = my_buf; + } + VSTRING_RESET(buf); + + for (np = table; mask != 0; np++) { + if (np->name == 0) { + if (flags & NAME_MASK_NUMBER) { + vstring_sprintf_append(buf, "0x%x%c", mask, delim); + } else if (flags & NAME_MASK_FATAL) { + msg_fatal("%s: unknown %s bit in mask: 0x%x", + myname, context, mask); + } else if (flags & NAME_MASK_RETURN) { + msg_warn("%s: unknown %s bit in mask: 0x%x", + myname, context, mask); + return (0); + } else if (flags & NAME_MASK_WARN) { + msg_warn("%s: unknown %s bit in mask: 0x%x", + myname, context, mask); + } + break; + } + if (mask & np->mask) { + mask &= ~np->mask; + vstring_sprintf_append(buf, "%s%c", np->name, delim); + } + } + if ((len = VSTRING_LEN(buf)) > 0) + vstring_truncate(buf, len - 1); + VSTRING_TERMINATE(buf); + + return (STR(buf)); +} + +/* long_name_mask_delim_opt - compute mask corresponding to list of names */ + +long long_name_mask_delim_opt(const char *context, + const LONG_NAME_MASK * table, + const char *names, const char *delim, + int flags) +{ + const char *myname = "name_mask"; + char *saved_names = mystrdup(names); + char *bp = saved_names; + long result = 0; + const LONG_NAME_MASK *np; + char *name; + int (*lookup) (const char *, const char *); + unsigned long ulval; + + if ((flags & NAME_MASK_REQUIRED) == 0) + msg_panic("%s: missing NAME_MASK_FATAL/RETURN/WARN/IGNORE flag", + myname); + + if (flags & NAME_MASK_ANY_CASE) + lookup = strcasecmp; + else + lookup = strcmp; + + /* + * Break up the names string, and look up each component in the table. If + * the name is found, merge its mask with the result. + */ + while ((name = mystrtok(&bp, delim)) != 0) { + for (np = table; /* void */ ; np++) { + if (np->name == 0) { + if ((flags & NAME_MASK_NUMBER) + && hex_to_ulong(name, ~0UL, &ulval)) { + result |= ulval; + } else if (flags & NAME_MASK_FATAL) { + msg_fatal("unknown %s value \"%s\" in \"%s\"", + context, name, names); + } else if (flags & NAME_MASK_RETURN) { + msg_warn("unknown %s value \"%s\" in \"%s\"", + context, name, names); + myfree(saved_names); + return (0); + } else if (flags & NAME_MASK_WARN) { + msg_warn("unknown %s value \"%s\" in \"%s\"", + context, name, names); + } + break; + } + if (lookup(name, np->name) == 0) { + if (msg_verbose) + msg_info("%s: %s", myname, name); + result |= np->mask; + break; + } + } + } + + myfree(saved_names); + return (result); +} + +/* str_long_name_mask_opt - mask to string */ + +const char *str_long_name_mask_opt(VSTRING *buf, const char *context, + const LONG_NAME_MASK * table, + long mask, int flags) +{ + const char *myname = "name_mask"; + ssize_t len; + static VSTRING *my_buf = 0; + int delim = (flags & NAME_MASK_COMMA ? ',' : + (flags & NAME_MASK_PIPE ? '|' : ' ')); + const LONG_NAME_MASK *np; + + if ((flags & STR_NAME_MASK_REQUIRED) == 0) + msg_panic("%s: missing NAME_MASK_NUMBER/FATAL/RETURN/WARN/IGNORE flag", + myname); + + if (buf == 0) { + if (my_buf == 0) + my_buf = vstring_alloc(1); + buf = my_buf; + } + VSTRING_RESET(buf); + + for (np = table; mask != 0; np++) { + if (np->name == 0) { + if (flags & NAME_MASK_NUMBER) { + vstring_sprintf_append(buf, "0x%lx%c", mask, delim); + } else if (flags & NAME_MASK_FATAL) { + msg_fatal("%s: unknown %s bit in mask: 0x%lx", + myname, context, mask); + } else if (flags & NAME_MASK_RETURN) { + msg_warn("%s: unknown %s bit in mask: 0x%lx", + myname, context, mask); + return (0); + } else if (flags & NAME_MASK_WARN) { + msg_warn("%s: unknown %s bit in mask: 0x%lx", + myname, context, mask); + } + break; + } + if (mask & np->mask) { + mask &= ~np->mask; + vstring_sprintf_append(buf, "%s%c", np->name, delim); + } + } + if ((len = VSTRING_LEN(buf)) > 0) + vstring_truncate(buf, len - 1); + VSTRING_TERMINATE(buf); + + return (STR(buf)); +} + +/* hex_to_ulong - 0x... to unsigned long or smaller */ + +static int hex_to_ulong(char *value, unsigned long mask, unsigned long *ulp) +{ + unsigned long result; + char *cp; + + if (strncasecmp(value, "0x", 2) != 0) + return (0); + + /* + * Check for valid hex number. Since the value starts with 0x, strtoul() + * will not allow a negative sign before the first nibble. So we don't + * need to worry about explicit +/- signs. + */ + errno = 0; + result = strtoul(value, &cp, 16); + if (*cp != '\0' || errno == ERANGE) + return (0); + + *ulp = (result & mask); + return (*ulp == result); +} + +#ifdef TEST + + /* + * Stand-alone test program. + */ +#include <stdlib.h> +#include <vstream.h> +#include <vstring_vstream.h> + +int main(int argc, char **argv) +{ + static const NAME_MASK demo_table[] = { + "zero", 1 << 0, + "one", 1 << 1, + "two", 1 << 2, + "three", 1 << 3, + 0, 0, + }; + static const NAME_MASK feature_table[] = { + "DEFAULT", NAME_MASK_DEFAULT, + "FATAL", NAME_MASK_FATAL, + "ANY_CASE", NAME_MASK_ANY_CASE, + "RETURN", NAME_MASK_RETURN, + "COMMA", NAME_MASK_COMMA, + "PIPE", NAME_MASK_PIPE, + "NUMBER", NAME_MASK_NUMBER, + "WARN", NAME_MASK_WARN, + "IGNORE", NAME_MASK_IGNORE, + 0, + }; + int in_feature_mask; + int out_feature_mask; + int demo_mask; + const char *demo_str; + VSTRING *out_buf = vstring_alloc(1); + VSTRING *in_buf = vstring_alloc(1); + + if (argc != 3) + msg_fatal("usage: %s in-feature-mask out-feature-mask", argv[0]); + in_feature_mask = name_mask(argv[1], feature_table, argv[1]); + out_feature_mask = name_mask(argv[2], feature_table, argv[2]); + while (vstring_get_nonl(in_buf, VSTREAM_IN) != VSTREAM_EOF) { + demo_mask = name_mask_opt("name", demo_table, + STR(in_buf), in_feature_mask); + demo_str = str_name_mask_opt(out_buf, "mask", demo_table, + demo_mask, out_feature_mask); + vstream_printf("%s -> 0x%x -> %s\n", + STR(in_buf), demo_mask, + demo_str ? demo_str : "(null)"); + vstream_fflush(VSTREAM_OUT); + } + vstring_free(in_buf); + vstring_free(out_buf); + exit(0); +} + +#endif diff --git a/src/util/name_mask.h b/src/util/name_mask.h new file mode 100644 index 0000000..05e45ec --- /dev/null +++ b/src/util/name_mask.h @@ -0,0 +1,86 @@ +#ifndef _NAME_MASK_H_INCLUDED_ +#define _NAME_MASK_H_INCLUDED_ + +/*++ +/* NAME +/* name_mask 3h +/* SUMMARY +/* map names to bit mask +/* SYNOPSIS +/* #include <name_mask.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <vstring.h> + + /* + * External interface. + */ +typedef struct { + const char *name; + int mask; +} NAME_MASK; + +#define NAME_MASK_FATAL (1<<0) +#define NAME_MASK_ANY_CASE (1<<1) +#define NAME_MASK_RETURN (1<<2) +#define NAME_MASK_COMMA (1<<3) +#define NAME_MASK_PIPE (1<<4) +#define NAME_MASK_NUMBER (1<<5) +#define NAME_MASK_WARN (1<<6) +#define NAME_MASK_IGNORE (1<<7) + +#define NAME_MASK_REQUIRED \ + (NAME_MASK_FATAL | NAME_MASK_RETURN | NAME_MASK_WARN | NAME_MASK_IGNORE) +#define STR_NAME_MASK_REQUIRED (NAME_MASK_REQUIRED | NAME_MASK_NUMBER) + +#define NAME_MASK_MATCH_REQ NAME_MASK_FATAL + +#define NAME_MASK_NONE 0 +#define NAME_MASK_DEFAULT (NAME_MASK_FATAL) +#define NAME_MASK_DEFAULT_DELIM ", \t\r\n" + +#define name_mask_opt(tag, table, str, flags) \ + name_mask_delim_opt((tag), (table), (str), \ + NAME_MASK_DEFAULT_DELIM, (flags)) +#define name_mask(tag, table, str) \ + name_mask_opt((tag), (table), (str), NAME_MASK_DEFAULT) +#define str_name_mask(tag, table, mask) \ + str_name_mask_opt(((VSTRING *) 0), (tag), (table), (mask), NAME_MASK_DEFAULT) + +extern int name_mask_delim_opt(const char *, const NAME_MASK *, const char *, const char *, int); +extern const char *str_name_mask_opt(VSTRING *, const char *, const NAME_MASK *, int, int); + + /* + * "long" API + */ +typedef struct { + const char *name; + long mask; +} LONG_NAME_MASK; + +#define long_name_mask_opt(tag, table, str, flags) \ + long_name_mask_delim_opt((tag), (table), (str), NAME_MASK_DEFAULT_DELIM, (flags)) +#define long_name_mask(tag, table, str) \ + long_name_mask_opt((tag), (table), (str), NAME_MASK_DEFAULT) +#define str_long_name_mask(tag, table, mask) \ + str_long_name_mask_opt(((VSTRING *) 0), (tag), (table), (mask), NAME_MASK_DEFAULT) + +extern long long_name_mask_delim_opt(const char *, const LONG_NAME_MASK *, const char *, const char *, int); +extern const char *str_long_name_mask_opt(VSTRING *, const char *, const LONG_NAME_MASK *, long, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/name_mask.in b/src/util/name_mask.in new file mode 100644 index 0000000..d4c9b16 --- /dev/null +++ b/src/util/name_mask.in @@ -0,0 +1,9 @@ +zero +one +two +three +four +zero one two three four +0xff +0xffffffff +0xffffffffffffffff diff --git a/src/util/name_mask.ref0 b/src/util/name_mask.ref0 new file mode 100644 index 0000000..603dc26 --- /dev/null +++ b/src/util/name_mask.ref0 @@ -0,0 +1,9 @@ +zero -> 0x1 -> zero +one -> 0x2 -> one +two -> 0x4 -> two +three -> 0x8 -> three +four -> 0x0 -> +zero one two three four -> 0xf -> zero one two three +0xff -> 0x0 -> +0xffffffff -> 0x0 -> +0xffffffffffffffff -> 0x0 -> diff --git a/src/util/name_mask.ref1 b/src/util/name_mask.ref1 new file mode 100644 index 0000000..1ae0fca --- /dev/null +++ b/src/util/name_mask.ref1 @@ -0,0 +1,12 @@ +zero -> 0x1 -> zero +one -> 0x2 -> one +two -> 0x4 -> two +three -> 0x8 -> three +unknown: warning: unknown name value "four" in "four" +four -> 0x0 -> +unknown: warning: unknown name value "four" in "zero one two three four" +zero one two three four -> 0xf -> zero one two three +0xff -> 0xff -> zero one two three 0xf0 +0xffffffff -> 0xffffffff -> zero one two three 0xfffffff0 +unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff" +0xffffffffffffffff -> 0x0 -> diff --git a/src/util/name_mask.ref2 b/src/util/name_mask.ref2 new file mode 100644 index 0000000..1ac716a --- /dev/null +++ b/src/util/name_mask.ref2 @@ -0,0 +1,12 @@ +zero -> 0x1 -> zero +one -> 0x2 -> one +two -> 0x4 -> two +three -> 0x8 -> three +unknown: warning: unknown name value "four" in "four" +four -> 0x0 -> +unknown: warning: unknown name value "four" in "zero one two three four" +zero one two three four -> 0x0 -> +0xff -> 0xff -> zero one two three 0xf0 +0xffffffff -> 0xffffffff -> zero one two three 0xfffffff0 +unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff" +0xffffffffffffffff -> 0x0 -> diff --git a/src/util/name_mask.ref3 b/src/util/name_mask.ref3 new file mode 100644 index 0000000..82e8850 --- /dev/null +++ b/src/util/name_mask.ref3 @@ -0,0 +1,14 @@ +zero -> 0x1 -> zero +one -> 0x2 -> one +two -> 0x4 -> two +three -> 0x8 -> three +unknown: warning: unknown name value "four" in "four" +four -> 0x0 -> +unknown: warning: unknown name value "four" in "zero one two three four" +zero one two three four -> 0xf -> zero one two three +unknown: warning: unknown name value "0xff" in "0xff" +0xff -> 0x0 -> +unknown: warning: unknown name value "0xffffffff" in "0xffffffff" +0xffffffff -> 0x0 -> +unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff" +0xffffffffffffffff -> 0x0 -> diff --git a/src/util/name_mask.ref4 b/src/util/name_mask.ref4 new file mode 100644 index 0000000..4ec996b --- /dev/null +++ b/src/util/name_mask.ref4 @@ -0,0 +1,14 @@ +zero -> 0x1 -> zero +one -> 0x2 -> one +two -> 0x4 -> two +three -> 0x8 -> three +unknown: warning: unknown name value "four" in "four" +four -> 0x0 -> +unknown: warning: unknown name value "four" in "zero one two three four" +zero one two three four -> 0x0 -> +unknown: warning: unknown name value "0xff" in "0xff" +0xff -> 0x0 -> +unknown: warning: unknown name value "0xffffffff" in "0xffffffff" +0xffffffff -> 0x0 -> +unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff" +0xffffffffffffffff -> 0x0 -> diff --git a/src/util/name_mask.ref5 b/src/util/name_mask.ref5 new file mode 100644 index 0000000..68c1c30 --- /dev/null +++ b/src/util/name_mask.ref5 @@ -0,0 +1,14 @@ +zero -> 0x1 -> zero +one -> 0x2 -> one +two -> 0x4 -> two +three -> 0x8 -> three +unknown: warning: unknown name value "four" in "four" +four -> 0x0 -> +unknown: warning: unknown name value "four" in "zero one two three four" +zero one two three four -> 0xf -> zero one two three +unknown: warning: name_mask: unknown mask bit in mask: 0xf0 +0xff -> 0xff -> (null) +unknown: warning: name_mask: unknown mask bit in mask: 0xfffffff0 +0xffffffff -> 0xffffffff -> (null) +unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff" +0xffffffffffffffff -> 0x0 -> diff --git a/src/util/name_mask.ref6 b/src/util/name_mask.ref6 new file mode 100644 index 0000000..c86a532 --- /dev/null +++ b/src/util/name_mask.ref6 @@ -0,0 +1,14 @@ +zero -> 0x1 -> zero +one -> 0x2 -> one +two -> 0x4 -> two +three -> 0x8 -> three +unknown: warning: unknown name value "four" in "four" +four -> 0x0 -> +unknown: warning: unknown name value "four" in "zero one two three four" +zero one two three four -> 0xf -> zero one two three +unknown: warning: name_mask: unknown mask bit in mask: 0xf0 +0xff -> 0xff -> zero one two three +unknown: warning: name_mask: unknown mask bit in mask: 0xfffffff0 +0xffffffff -> 0xffffffff -> zero one two three +unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff" +0xffffffffffffffff -> 0x0 -> diff --git a/src/util/name_mask.ref7 b/src/util/name_mask.ref7 new file mode 100644 index 0000000..156d7aa --- /dev/null +++ b/src/util/name_mask.ref7 @@ -0,0 +1,12 @@ +zero -> 0x1 -> zero +one -> 0x2 -> one +two -> 0x4 -> two +three -> 0x8 -> three +unknown: warning: unknown name value "four" in "four" +four -> 0x0 -> +unknown: warning: unknown name value "four" in "zero one two three four" +zero one two three four -> 0xf -> zero one two three +0xff -> 0xff -> zero one two three +0xffffffff -> 0xffffffff -> zero one two three +unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff" +0xffffffffffffffff -> 0x0 -> diff --git a/src/util/name_mask.ref8 b/src/util/name_mask.ref8 new file mode 100644 index 0000000..7463fb7 --- /dev/null +++ b/src/util/name_mask.ref8 @@ -0,0 +1,12 @@ +zero -> 0x1 -> zero +one -> 0x2 -> one +two -> 0x4 -> two +three -> 0x8 -> three +unknown: warning: unknown name value "four" in "four" +four -> 0x0 -> +unknown: warning: unknown name value "four" in "zero one two three four" +zero one two three four -> 0xf -> zero,one,two,three +0xff -> 0xff -> zero,one,two,three,0xf0 +0xffffffff -> 0xffffffff -> zero,one,two,three,0xfffffff0 +unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff" +0xffffffffffffffff -> 0x0 -> diff --git a/src/util/name_mask.ref9 b/src/util/name_mask.ref9 new file mode 100644 index 0000000..e50a73a --- /dev/null +++ b/src/util/name_mask.ref9 @@ -0,0 +1,12 @@ +zero -> 0x1 -> zero +one -> 0x2 -> one +two -> 0x4 -> two +three -> 0x8 -> three +unknown: warning: unknown name value "four" in "four" +four -> 0x0 -> +unknown: warning: unknown name value "four" in "zero one two three four" +zero one two three four -> 0xf -> zero|one|two|three +0xff -> 0xff -> zero|one|two|three|0xf0 +0xffffffff -> 0xffffffff -> zero|one|two|three|0xfffffff0 +unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff" +0xffffffffffffffff -> 0x0 -> diff --git a/src/util/nbbio.c b/src/util/nbbio.c new file mode 100644 index 0000000..e9ccc38 --- /dev/null +++ b/src/util/nbbio.c @@ -0,0 +1,382 @@ +/*++ +/* NAME +/* nbbio 3 +/* SUMMARY +/* non-blocking buffered I/O +/* SYNOPSIS +/* #include <nbbio.h> +/* +/* NBBIO *nbbio_create(fd, bufsize, label, action, context) +/* int fd; +/* ssize_t bufsize; +/* const char *label; +/* void (*action)(int event, void *context); +/* char *context; +/* +/* void nbbio_free(np) +/* NBBIO *np; +/* +/* void nbbio_enable_read(np, timeout) +/* NBBIO *np; +/* int timeout; +/* +/* void nbbio_enable_write(np, timeout) +/* NBBIO *np; +/* int timeout; +/* +/* void nbbio_disable_readwrite(np) +/* NBBIO *np; +/* +/* void nbbio_slumber(np, timeout) +/* NBBIO *np; +/* int timeout; +/* +/* int NBBIO_ACTIVE_FLAGS(np) +/* NBBIO *np; +/* +/* int NBBIO_ERROR_FLAGS(np) +/* NBBIO *np; +/* +/* const ssize_t NBBIO_BUFSIZE(np) +/* NBBIO *np; +/* +/* ssize_t NBBIO_READ_PEND(np) +/* NBBIO *np; +/* +/* char *NBBIO_READ_BUF(np) +/* NBBIO *np; +/* +/* const ssize_t NBBIO_WRITE_PEND(np) +/* NBBIO *np; +/* +/* char *NBBIO_WRITE_BUF(np) +/* NBBIO *np; +/* DESCRIPTION +/* This module implements low-level support for event-driven +/* I/O on a full-duplex stream. Read/write events are handled +/* by pseudothreads that run under control by the events(5) +/* module. After each I/O operation, the application is +/* notified via a call-back routine. +/* +/* It is up to the call-back routine to turn on/off read/write +/* events as appropriate. It is an error to leave read events +/* enabled for a buffer that is full, or to leave write events +/* enabled for a buffer that is empty. +/* +/* nbbio_create() creates a pair of buffers of the named size +/* for the named stream. The label specifies the purpose of +/* the stream, and is used for diagnostic messages. The +/* nbbio(3) event handler invokes the application call-back +/* routine with the current event type (EVENT_READ etc.) and +/* with the application-specified context. +/* +/* nbbio_free() terminates any pseudothreads associated with +/* the named buffer pair, closes the stream, and destroys the +/* buffer pair. +/* +/* nbbio_enable_read() enables a read pseudothread (if one +/* does not already exist) for the named buffer pair, and +/* (re)starts the buffer pair's timer. It is an error to enable +/* a read pseudothread while the read buffer is full, or while +/* a write pseudothread is still enabled. +/* +/* nbbio_enable_write() enables a write pseudothread (if one +/* does not already exist) for the named buffer pair, and +/* (re)starts the buffer pair's timer. It is an error to enable +/* a write pseudothread while the write buffer is empty, or +/* while a read pseudothread is still enabled. +/* +/* nbbio_disable_readwrite() disables any read/write pseudothreads +/* for the named buffer pair, including timeouts. To ensure +/* buffer liveness, use nbbio_slumber() instead of +/* nbbio_disable_readwrite(). It is no error to call this +/* function while no read/write pseudothread is enabled. +/* +/* nbbio_slumber() disables any read/write pseudothreads for +/* the named buffer pair, but keeps the timer active to ensure +/* buffer liveness. It is no error to call this function while +/* no read/write pseudothread is enabled. +/* +/* NBBIO_ERROR_FLAGS() returns the error flags for the named buffer +/* pair: zero or more of NBBIO_FLAG_EOF (read EOF), NBBIO_FLAG_ERROR +/* (read/write error) or NBBIO_FLAG_TIMEOUT (time limit +/* exceeded). +/* +/* NBBIO_ACTIVE_FLAGS() returns the pseudothread flags for the +/* named buffer pair: NBBIO_FLAG_READ (read pseudothread is +/* active), NBBIO_FLAG_WRITE (write pseudothread is active), +/* or zero (no pseudothread is active). +/* +/* NBBIO_WRITE_PEND() and NBBIO_WRITE_BUF() evaluate to the +/* number of to-be-written bytes and the write buffer for the +/* named buffer pair. NBBIO_WRITE_PEND() must be updated by +/* the application code that fills the write buffer; no more +/* than NBBIO_BUFSIZE() bytes may be filled. +/* +/* NBBIO_READ_PEND() and NBBIO_READ_BUF() evaluate to the +/* number of unread bytes and the read buffer for the named +/* buffer pair. NBBIO_READ_PEND() and NBBIO_READ_BUF() must +/* be updated by the application code that drains the read +/* buffer. +/* SEE ALSO +/* events(3) event manager +/* DIAGNOSTICS +/* Panic: interface violation. +/* +/* Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include <sys_defs.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> /* memmove() */ + + /* + * Utility library. + */ +#include <mymalloc.h> +#include <msg.h> +#include <events.h> +#include <nbbio.h> + +/* nbbio_event - non-blocking event handler */ + +static void nbbio_event(int event, void *context) +{ + const char *myname = "nbbio_event"; + NBBIO *np = (NBBIO *) context; + ssize_t count; + + switch (event) { + + /* + * Read data into the read buffer. Leave it up to the application to + * drain the buffer until it is empty. + */ + case EVENT_READ: + if (np->read_pend == np->bufsize) + msg_panic("%s: socket fd=%d: read buffer is full", + myname, np->fd); + if (np->read_pend < 0 || np->read_pend > np->bufsize) + msg_panic("%s: socket fd=%d: bad pending read count %ld", + myname, np->fd, (long) np->read_pend); + count = read(np->fd, np->read_buf + np->read_pend, + np->bufsize - np->read_pend); + if (count > 0) { + np->read_pend += count; + if (msg_verbose) + msg_info("%s: read %ld on %s fd=%d", + myname, (long) count, np->label, np->fd); + } else if (count == 0) { + np->flags |= NBBIO_FLAG_EOF; + if (msg_verbose) + msg_info("%s: read EOF on %s fd=%d", + myname, np->label, np->fd); + } else { + if (errno == EAGAIN) + msg_warn("%s: read() returns EAGAIN on readable descriptor", + myname); + np->flags |= NBBIO_FLAG_ERROR; + if (msg_verbose) + msg_info("%s: read %s fd=%d: %m", myname, np->label, np->fd); + } + break; + + /* + * Drain data from the output buffer. Notify the application + * whenever some bytes are written. + * + * XXX Enforce a total time limit to ensure liveness when a hostile + * receiver sets a very small TCP window size. + */ + case EVENT_WRITE: + if (np->write_pend == 0) + msg_panic("%s: socket fd=%d: empty write buffer", myname, np->fd); + if (np->write_pend < 0 || np->write_pend > np->bufsize) + msg_panic("%s: socket fd=%d: bad pending write count %ld", + myname, np->fd, (long) np->write_pend); + count = write(np->fd, np->write_buf, np->write_pend); + if (count > 0) { + np->write_pend -= count; + if (np->write_pend > 0) + memmove(np->write_buf, np->write_buf + count, np->write_pend); + } else { + if (errno == EAGAIN) + msg_warn("%s: write() returns EAGAIN on writable descriptor", + myname); + np->flags |= NBBIO_FLAG_ERROR; + if (msg_verbose) + msg_info("%s: write %s fd=%d: %m", myname, np->label, np->fd); + } + break; + + /* + * Something bad happened. + */ + case EVENT_XCPT: + np->flags |= NBBIO_FLAG_ERROR; + if (msg_verbose) + msg_info("%s: error on %s fd=%d: %m", myname, np->label, np->fd); + break; + + /* + * Something good didn't happen. + */ + case EVENT_TIME: + np->flags |= NBBIO_FLAG_TIMEOUT; + if (msg_verbose) + msg_info("%s: %s timeout on %s fd=%d", + myname, NBBIO_OP_NAME(np), np->label, np->fd); + break; + + default: + msg_panic("%s: unknown event %d", myname, event); + } + + /* + * Application notification. The application will check for any error + * flags, copy application data from or to our buffer pair, and decide + * what I/O happens next. + */ + np->action(event, np->context); +} + +/* nbbio_enable_read - enable reading from socket into buffer */ + +void nbbio_enable_read(NBBIO *np, int timeout) +{ + const char *myname = "nbbio_enable_read"; + + /* + * Sanity checks. + */ + if (np->flags & (NBBIO_MASK_ACTIVE & ~NBBIO_FLAG_READ)) + msg_panic("%s: socket fd=%d is enabled for %s", + myname, np->fd, NBBIO_OP_NAME(np)); + if (timeout <= 0) + msg_panic("%s: socket fd=%d: bad timeout %d", + myname, np->fd, timeout); + if (np->read_pend >= np->bufsize) + msg_panic("%s: socket fd=%d: read buffer is full", + myname, np->fd); + + /* + * Enable events. + */ + if ((np->flags & NBBIO_FLAG_READ) == 0) { + event_enable_read(np->fd, nbbio_event, (void *) np); + np->flags |= NBBIO_FLAG_READ; + } + event_request_timer(nbbio_event, (void *) np, timeout); +} + +/* nbbio_enable_write - enable writing from buffer to socket */ + +void nbbio_enable_write(NBBIO *np, int timeout) +{ + const char *myname = "nbbio_enable_write"; + + /* + * Sanity checks. + */ + if (np->flags & (NBBIO_MASK_ACTIVE & ~NBBIO_FLAG_WRITE)) + msg_panic("%s: socket fd=%d is enabled for %s", + myname, np->fd, NBBIO_OP_NAME(np)); + if (timeout <= 0) + msg_panic("%s: socket fd=%d: bad timeout %d", + myname, np->fd, timeout); + if (np->write_pend <= 0) + msg_panic("%s: socket fd=%d: empty write buffer", + myname, np->fd); + + /* + * Enable events. + */ + if ((np->flags & NBBIO_FLAG_WRITE) == 0) { + event_enable_write(np->fd, nbbio_event, (void *) np); + np->flags |= NBBIO_FLAG_WRITE; + } + event_request_timer(nbbio_event, (void *) np, timeout); +} + +/* nbbio_disable_readwrite - disable read/write/timer events */ + +void nbbio_disable_readwrite(NBBIO *np) +{ + np->flags &= ~NBBIO_MASK_ACTIVE; + event_disable_readwrite(np->fd); + event_cancel_timer(nbbio_event, (void *) np); +} + +/* nbbio_slumber - disable read/write events, keep timer */ + +void nbbio_slumber(NBBIO *np, int timeout) +{ + np->flags &= ~NBBIO_MASK_ACTIVE; + event_disable_readwrite(np->fd); + event_request_timer(nbbio_event, (void *) np, timeout); +} + +/* nbbio_create - create socket buffer */ + +NBBIO *nbbio_create(int fd, ssize_t bufsize, const char *label, + NBBIO_ACTION action, void *context) +{ + NBBIO *np; + + /* + * Sanity checks. + */ + if (fd < 0) + msg_panic("nbbio_create: bad file descriptor: %d", fd); + if (bufsize <= 0) + msg_panic("nbbio_create: bad buffer size: %ld", (long) bufsize); + + /* + * Create a new buffer pair. + */ + np = (NBBIO *) mymalloc(sizeof(*np)); + np->fd = fd; + np->bufsize = bufsize; + np->label = mystrdup(label); + np->action = action; + np->context = context; + np->flags = 0; + + np->read_buf = mymalloc(bufsize); + np->read_pend = 0; + + np->write_buf = mymalloc(bufsize); + np->write_pend = 0; + + return (np); +} + +/* nbbio_free - destroy socket buffer */ + +void nbbio_free(NBBIO *np) +{ + nbbio_disable_readwrite(np); + (void) close(np->fd); + myfree(np->label); + myfree(np->read_buf); + myfree(np->write_buf); + myfree((void *) np); +} diff --git a/src/util/nbbio.h b/src/util/nbbio.h new file mode 100644 index 0000000..d1d2b0e --- /dev/null +++ b/src/util/nbbio.h @@ -0,0 +1,85 @@ +#ifndef _NBBIO_H_INCLUDED_ +#define _NBBIO_H_INCLUDED_ + +/*++ +/* NAME +/* nbbio 3h +/* SUMMARY +/* non-blocking buffered I/O +/* SYNOPSIS +/* #include "nbbio.h" +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <events.h> /* Needed for EVENT_READ etc. */ + + /* + * External interface. All structure members are private. + */ +typedef void (*NBBIO_ACTION) (int, void *); + +typedef struct { + int fd; /* socket file descriptor */ + ssize_t bufsize; /* read/write buffer size */ + char *label; /* diagnostics */ + NBBIO_ACTION action; /* call-back routine */ + void *context; /* call-back context */ + int flags; /* buffer-pair status */ + + char *read_buf; /* start of buffer */ + ssize_t read_pend; /* nr of unread bytes */ + + char *write_buf; /* start of buffer */ + ssize_t write_pend; /* nr of unwritten bytes */ +} NBBIO; + +#define NBBIO_FLAG_READ (1<<0) +#define NBBIO_FLAG_WRITE (1<<1) +#define NBBIO_FLAG_EOF (1<<2) +#define NBBIO_FLAG_ERROR (1<<3) +#define NBBIO_FLAG_TIMEOUT (1<<4) + +#define NBBIO_OP_NAME(np) \ + (((np)->flags & NBBIO_FLAG_READ) ? "read" : \ + ((np)->flags & NBBIO_FLAG_WRITE) ? "write" : \ + "unknown") + +#define NBBIO_MASK_ACTIVE \ + (NBBIO_FLAG_READ | NBBIO_FLAG_WRITE) + +#define NBBIO_MASK_ERROR \ + (NBBIO_FLAG_EOF | NBBIO_FLAG_ERROR | NBBIO_FLAG_TIMEOUT) + +#define NBBIO_BUFSIZE(np) (((np)->bufsize) + 0) /* Read-only */ + +#define NBBIO_READ_PEND(np) ((np)->read_pend) +#define NBBIO_READ_BUF(np) ((np)->read_buf + 0) /* Read-only */ + +#define NBBIO_WRITE_PEND(np) ((np)->write_pend) +#define NBBIO_WRITE_BUF(np) ((np)->write_buf + 0) /* Read-only */ + +#define NBBIO_ACTIVE_FLAGS(np) ((np)->flags & NBBIO_MASK_ACTIVE) +#define NBBIO_ERROR_FLAGS(np) ((np)->flags & NBBIO_MASK_ERROR) + +extern NBBIO *nbbio_create(int, ssize_t, const char *, NBBIO_ACTION, void *); +extern void nbbio_free(NBBIO *); +extern void nbbio_enable_read(NBBIO *, int); +extern void nbbio_enable_write(NBBIO *, int); +extern void nbbio_disable_readwrite(NBBIO *); +extern void nbbio_slumber(NBBIO *, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/netstring.c b/src/util/netstring.c new file mode 100644 index 0000000..0edd80e --- /dev/null +++ b/src/util/netstring.c @@ -0,0 +1,498 @@ +/*++ +/* NAME +/* netstring 3 +/* SUMMARY +/* netstring stream I/O support +/* SYNOPSIS +/* #include <netstring.h> +/* +/* void netstring_setup(stream, timeout) +/* VSTREAM *stream; +/* int timeout; +/* +/* void netstring_except(stream, exception) +/* VSTREAM *stream; +/* int exception; +/* +/* const char *netstring_strerror(err) +/* int err; +/* +/* VSTRING *netstring_get(stream, buf, limit) +/* VSTREAM *stream; +/* VSTRING *buf; +/* ssize_t limit; +/* +/* void netstring_put(stream, data, len) +/* VSTREAM *stream; +/* const char *data; +/* ssize_t len; +/* +/* void netstring_put_multi(stream, data, len, data, len, ..., 0) +/* VSTREAM *stream; +/* const char *data; +/* ssize_t len; +/* +/* void NETSTRING_PUT_BUF(stream, buf) +/* VSTREAM *stream; +/* VSTRING *buf; +/* +/* void netstring_fflush(stream) +/* VSTREAM *stream; +/* +/* VSTRING *netstring_memcpy(buf, data, len) +/* VSTRING *buf; +/* const char *data; +/* ssize_t len; +/* +/* VSTRING *netstring_memcat(buf, data, len) +/* VSTRING *buf; +/* const char *src; +/* ssize_t len; +/* AUXILIARY ROUTINES +/* ssize_t netstring_get_length(stream) +/* VSTREAM *stream; +/* +/* VSTRING *netstring_get_data(stream, buf, len) +/* VSTREAM *stream; +/* VSTRING *buf; +/* ssize_t len; +/* +/* void netstring_get_terminator(stream) +/* VSTREAM *stream; +/* DESCRIPTION +/* This module reads and writes netstrings with error detection: +/* timeouts, unexpected end-of-file, or format errors. Netstring +/* is a data format designed by Daniel Bernstein. +/* +/* netstring_setup() arranges for a time limit on the netstring +/* read and write operations described below. +/* This routine alters the behavior of streams as follows: +/* .IP \(bu +/* The read/write timeout is set to the specified value. +/* .IP \(bu +/* The stream is configured to enable exception handling. +/* .PP +/* netstring_except() raises the specified exception on the +/* named stream. See the DIAGNOSTICS section below. +/* +/* netstring_strerror() converts an exception number to string. +/* +/* netstring_get() reads a netstring from the specified stream +/* and extracts its content. The limit specifies a maximal size. +/* Specify zero to disable the size limit. The result is not null +/* terminated. The result value is the buf argument. +/* +/* netstring_put() encapsulates the specified string as a netstring +/* and sends the result to the specified stream. +/* The stream output buffer is not flushed. +/* +/* netstring_put_multi() encapsulates the content of multiple strings +/* as one netstring and sends the result to the specified stream. The +/* argument list must be terminated with a null data pointer. +/* The stream output buffer is not flushed. +/* +/* NETSTRING_PUT_BUF() is a macro that provides a VSTRING-based +/* wrapper for the netstring_put() routine. +/* +/* netstring_fflush() flushes the output buffer of the specified +/* stream and handles any errors. +/* +/* netstring_memcpy() encapsulates the specified data as a netstring +/* and copies the result over the specified buffer. The result +/* value is the buffer. +/* +/* netstring_memcat() encapsulates the specified data as a netstring +/* and appends the result to the specified buffer. The result +/* value is the buffer. +/* +/* The following routines provide low-level access to a netstring +/* stream. +/* +/* netstring_get_length() reads a length field from the specified +/* stream, and absorbs the netstring length field terminator. +/* +/* netstring_get_data() reads the specified number of bytes from the +/* specified stream into the specified buffer, and absorbs the +/* netstring terminator. The result value is the buf argument. +/* +/* netstring_get_terminator() reads the netstring terminator from +/* the specified stream. +/* DIAGNOSTICS +/* .fi +/* .ad +/* In case of error, a vstream_longjmp() call is performed to the +/* caller-provided context specified with vstream_setjmp(). +/* Error codes passed along with vstream_longjmp() are: +/* .IP NETSTRING_ERR_EOF +/* An I/O error happened, or the peer has disconnected unexpectedly. +/* .IP NETSTRING_ERR_TIME +/* The time limit specified to netstring_setup() was exceeded. +/* .IP NETSTRING_ERR_FORMAT +/* The input contains an unexpected character value. +/* .IP NETSTRING_ERR_SIZE +/* The input is larger than acceptable. +/* BUGS +/* The timeout deadline affects all I/O on the named stream, not +/* just the I/O done on behalf of this module. +/* +/* The timeout deadline overwrites any previously set up state on +/* the named stream. +/* +/* netstrings are not null terminated, which makes printing them +/* a bit awkward. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* SEE ALSO +/* http://cr.yp.to/proto/netstrings.txt, netstring definition +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdarg.h> +#include <ctype.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstream.h> +#include <vstring.h> +#include <compat_va_copy.h> +#include <netstring.h> + +/* Application-specific. */ + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* netstring_setup - initialize netstring stream */ + +void netstring_setup(VSTREAM *stream, int timeout) +{ + vstream_control(stream, + CA_VSTREAM_CTL_TIMEOUT(timeout), + CA_VSTREAM_CTL_EXCEPT, + CA_VSTREAM_CTL_END); +} + +/* netstring_except - process netstring stream exception */ + +void netstring_except(VSTREAM *stream, int exception) +{ + vstream_longjmp(stream, exception); +} + +/* netstring_get_length - read netstring length + terminator */ + +ssize_t netstring_get_length(VSTREAM *stream) +{ + const char *myname = "netstring_get_length"; + ssize_t len = 0; + int ch; + int digit; + + for (;;) { + switch (ch = VSTREAM_GETC(stream)) { + case VSTREAM_EOF: + netstring_except(stream, vstream_ftimeout(stream) ? + NETSTRING_ERR_TIME : NETSTRING_ERR_EOF); + case ':': + if (msg_verbose > 1) + msg_info("%s: read netstring length %ld", myname, (long) len); + return (len); + default: + if (!ISDIGIT(ch)) + netstring_except(stream, NETSTRING_ERR_FORMAT); + digit = ch - '0'; + if (len > SSIZE_T_MAX / 10 + || (len *= 10) > SSIZE_T_MAX - digit) + netstring_except(stream, NETSTRING_ERR_SIZE); + len += digit; + break; + } + } +} + +/* netstring_get_data - read netstring payload + terminator */ + +VSTRING *netstring_get_data(VSTREAM *stream, VSTRING *buf, ssize_t len) +{ + const char *myname = "netstring_get_data"; + + /* + * Read the payload and absorb the terminator. + */ + if (vstream_fread_buf(stream, buf, len) != len) + netstring_except(stream, vstream_ftimeout(stream) ? + NETSTRING_ERR_TIME : NETSTRING_ERR_EOF); + if (msg_verbose > 1) + msg_info("%s: read netstring data %.*s", + myname, (int) (len < 30 ? len : 30), STR(buf)); + netstring_get_terminator(stream); + + /* + * Return the buffer. + */ + return (buf); +} + +/* netstring_get_terminator - absorb netstring terminator */ + +void netstring_get_terminator(VSTREAM *stream) +{ + if (VSTREAM_GETC(stream) != ',') + netstring_except(stream, NETSTRING_ERR_FORMAT); +} + +/* netstring_get - read string from netstring stream */ + +VSTRING *netstring_get(VSTREAM *stream, VSTRING *buf, ssize_t limit) +{ + ssize_t len; + + len = netstring_get_length(stream); + if (ENFORCING_SIZE_LIMIT(limit) && len > limit) + netstring_except(stream, NETSTRING_ERR_SIZE); + netstring_get_data(stream, buf, len); + return (buf); +} + +/* netstring_put - send string as netstring */ + +void netstring_put(VSTREAM *stream, const char *data, ssize_t len) +{ + const char *myname = "netstring_put"; + + if (msg_verbose > 1) + msg_info("%s: write netstring len %ld data %.*s", + myname, (long) len, (int) (len < 30 ? len : 30), data); + vstream_fprintf(stream, "%ld:", (long) len); + vstream_fwrite(stream, data, len); + VSTREAM_PUTC(',', stream); +} + +/* netstring_put_multi - send multiple strings as one netstring */ + +void netstring_put_multi(VSTREAM *stream,...) +{ + const char *myname = "netstring_put_multi"; + ssize_t total; + char *data; + ssize_t data_len; + va_list ap; + va_list ap2; + + /* + * Initialize argument lists. + */ + va_start(ap, stream); + VA_COPY(ap2, ap); + + /* + * Figure out the total result size. + */ + for (total = 0; (data = va_arg(ap, char *)) != 0; total += data_len) + if ((data_len = va_arg(ap, ssize_t)) < 0) + msg_panic("%s: bad data length %ld", myname, (long) data_len); + va_end(ap); + if (total < 0) + msg_panic("%s: bad total length %ld", myname, (long) total); + if (msg_verbose > 1) + msg_info("%s: write total length %ld", myname, (long) total); + + /* + * Send the length, content and terminator. + */ + vstream_fprintf(stream, "%ld:", (long) total); + while ((data = va_arg(ap2, char *)) != 0) { + data_len = va_arg(ap2, ssize_t); + if (msg_verbose > 1) + msg_info("%s: write netstring len %ld data %.*s", + myname, (long) data_len, + (int) (data_len < 30 ? data_len : 30), data); + if (vstream_fwrite(stream, data, data_len) != data_len) + netstring_except(stream, vstream_ftimeout(stream) ? + NETSTRING_ERR_TIME : NETSTRING_ERR_EOF); + } + va_end(ap2); + vstream_fwrite(stream, ",", 1); +} + +/* netstring_fflush - flush netstring stream */ + +void netstring_fflush(VSTREAM *stream) +{ + if (vstream_fflush(stream) == VSTREAM_EOF) + netstring_except(stream, vstream_ftimeout(stream) ? + NETSTRING_ERR_TIME : NETSTRING_ERR_EOF); +} + +/* netstring_memcpy - copy data as in-memory netstring */ + +VSTRING *netstring_memcpy(VSTRING *buf, const char *src, ssize_t len) +{ + vstring_sprintf(buf, "%ld:", (long) len); + vstring_memcat(buf, src, len); + VSTRING_ADDCH(buf, ','); + return (buf); +} + +/* netstring_memcat - append data as in-memory netstring */ + +VSTRING *netstring_memcat(VSTRING *buf, const char *src, ssize_t len) +{ + vstring_sprintf_append(buf, "%ld:", (long) len); + vstring_memcat(buf, src, len); + VSTRING_ADDCH(buf, ','); + return (buf); +} + +/* netstring_strerror - convert error number to string */ + +const char *netstring_strerror(int err) +{ + switch (err) { + case NETSTRING_ERR_EOF: + return ("unexpected disconnect"); + case NETSTRING_ERR_TIME: + return ("time limit exceeded"); + case NETSTRING_ERR_FORMAT: + return ("input format error"); + case NETSTRING_ERR_SIZE: + return ("input exceeds size limit"); + default: + return ("unknown netstring error"); + } +} + + /* + * Proof-of-concept netstring encoder/decoder. + * + * Usage: netstring command... + * + * Run the command as a child process. Then, convert between plain strings on + * our own stdin/stdout, and netstrings on the child program's stdin/stdout. + * + * Example (socketmap test server): netstring nc -l 9999 + */ +#ifdef TEST +#include <unistd.h> +#include <stdlib.h> +#include <events.h> + +static VSTRING *stdin_read_buf; /* stdin line buffer */ +static VSTRING *child_read_buf; /* child read buffer */ +static VSTREAM *child_stream; /* child stream (full-duplex) */ + +/* stdin_read_event - line-oriented event handler */ + +static void stdin_read_event(int event, void *context) +{ + int ch; + + /* + * Send a netstring to the child when we have accumulated an entire line + * of input. + * + * Note: the first VSTREAM_GETCHAR() call implicitly fills the VSTREAM + * buffer. We must drain the entire VSTREAM buffer before requesting the + * next read(2) event. + */ + do { + ch = VSTREAM_GETCHAR(); + switch (ch) { + default: + VSTRING_ADDCH(stdin_read_buf, ch); + break; + case '\n': + NETSTRING_PUT_BUF(child_stream, stdin_read_buf); + vstream_fflush(child_stream); + VSTRING_RESET(stdin_read_buf); + break; + case VSTREAM_EOF: + /* Better: wait for child to terminate. */ + sleep(1); + exit(0); + } + } while (vstream_peek(VSTREAM_IN) > 0); +} + +/* child_read_event - netstring-oriented event handler */ + +static void child_read_event(int event, void *context) +{ + + /* + * Read an entire netstring from the child and send the result to stdout. + * + * This is a simplistic implementation that assumes a server will not + * trickle its data. + * + * Note: the first netstring_get() call implicitly fills the VSTREAM buffer. + * We must drain the entire VSTREAM buffer before requesting the next + * read(2) event. + */ + do { + netstring_get(child_stream, child_read_buf, 10000); + vstream_fwrite(VSTREAM_OUT, STR(child_read_buf), LEN(child_read_buf)); + VSTREAM_PUTC('\n', VSTREAM_OUT); + vstream_fflush(VSTREAM_OUT); + } while (vstream_peek(child_stream) > 0); +} + +int main(int argc, char **argv) +{ + int err; + + /* + * Sanity check. + */ + if (argv[1] == 0) + msg_fatal("usage: %s command...", argv[0]); + + /* + * Run the specified command as a child process with stdin and stdout + * connected to us. + */ + child_stream = vstream_popen(O_RDWR, CA_VSTREAM_POPEN_ARGV(argv + 1), + CA_VSTREAM_POPEN_END); + vstream_control(child_stream, CA_VSTREAM_CTL_DOUBLE, CA_VSTREAM_CTL_END); + netstring_setup(child_stream, 10); + + /* + * Buffer plumbing. + */ + stdin_read_buf = vstring_alloc(100); + child_read_buf = vstring_alloc(100); + + /* + * Monitor both the child's stdout stream and our own stdin stream. If + * there is activity on the child stdout stream, read an entire netstring + * or EOF. If there is activity on stdin, send a netstring to the child + * when we have read an entire line, or terminate in case of EOF. + */ + event_enable_read(vstream_fileno(VSTREAM_IN), stdin_read_event, (void *) 0); + event_enable_read(vstream_fileno(child_stream), child_read_event, + (void *) 0); + + if ((err = vstream_setjmp(child_stream)) == 0) { + for (;;) + event_loop(-1); + } else { + msg_fatal("%s: %s", argv[1], netstring_strerror(err)); + } +} + +#endif diff --git a/src/util/netstring.h b/src/util/netstring.h new file mode 100644 index 0000000..e8f418d --- /dev/null +++ b/src/util/netstring.h @@ -0,0 +1,55 @@ +#ifndef _NETSTRING_H_INCLUDED_ +#define _NETSTRING_H_INCLUDED_ + +/*++ +/* NAME +/* netstring 3h +/* SUMMARY +/* netstring stream I/O support +/* SYNOPSIS +/* #include <netstring.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <vstring.h> +#include <vstream.h> + + /* + * External interface. + */ +#define NETSTRING_ERR_EOF 1 /* unexpected disconnect */ +#define NETSTRING_ERR_TIME 2 /* time out */ +#define NETSTRING_ERR_FORMAT 3 /* format error */ +#define NETSTRING_ERR_SIZE 4 /* netstring too large */ + +extern void netstring_except(VSTREAM *, int); +extern void netstring_setup(VSTREAM *, int); +extern ssize_t netstring_get_length(VSTREAM *); +extern VSTRING *netstring_get_data(VSTREAM *, VSTRING *, ssize_t); +extern void netstring_get_terminator(VSTREAM *); +extern VSTRING *netstring_get(VSTREAM *, VSTRING *, ssize_t); +extern void netstring_put(VSTREAM *, const char *, ssize_t); +extern void netstring_put_multi(VSTREAM *,...); +extern void netstring_fflush(VSTREAM *); +extern VSTRING *netstring_memcpy(VSTRING *, const char *, ssize_t); +extern VSTRING *netstring_memcat(VSTRING *, const char *, ssize_t); +extern const char *netstring_strerror(int); + +#define NETSTRING_PUT_BUF(str, buf) \ + netstring_put((str), vstring_str(buf), VSTRING_LEN(buf)) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/neuter.c b/src/util/neuter.c new file mode 100644 index 0000000..53576fb --- /dev/null +++ b/src/util/neuter.c @@ -0,0 +1,56 @@ +/*++ +/* NAME +/* neuter 3 +/* SUMMARY +/* neutralize characters before they can explode +/* SYNOPSIS +/* #include <stringops.h> +/* +/* char *neuter(buffer, bad, replacement) +/* char *buffer; +/* const char *bad; +/* int replacement; +/* DESCRIPTION +/* neuter() replaces bad characters in its input +/* by the given replacement. +/* +/* Arguments: +/* .IP buffer +/* The null-terminated input string. +/* .IP bad +/* The null-terminated bad character string. +/* .IP replacement +/* Replacement value for characters in \fIbuffer\fR that do not +/* pass the bad character test. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> + +/* Utility library. */ + +#include <stringops.h> + +/* neuter - neutralize bad characters */ + +char *neuter(char *string, const char *bad, int replacement) +{ + char *cp; + int ch; + + for (cp = string; (ch = *(unsigned char *) cp) != 0; cp++) + if (strchr(bad, ch) != 0) + *cp = replacement; + return (string); +} diff --git a/src/util/non_blocking.c b/src/util/non_blocking.c new file mode 100644 index 0000000..6427cd8 --- /dev/null +++ b/src/util/non_blocking.c @@ -0,0 +1,66 @@ +/*++ +/* NAME +/* non_blocking 3 +/* SUMMARY +/* set/clear non-blocking flag +/* SYNOPSIS +/* #include <iostuff.h> +/* +/* int non_blocking(int fd, int on) +/* DESCRIPTION +/* the \fInon_blocking\fR() function manipulates the non-blocking +/* flag for the specified open file, and returns the old setting. +/* +/* Arguments: +/* .IP fd +/* A file descriptor. +/* .IP on +/* For non-blocking I/O, specify a non-zero value (or use the +/* NON_BLOCKING constant); for blocking I/O, specify zero +/* (or use the BLOCKING constant). +/* +/* The result is non-zero when the non-blocking flag was enabled. +/* DIAGNOSTICS +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System interfaces. */ + +#include "sys_defs.h" +#include <fcntl.h> + +/* Utility library. */ + +#include "msg.h" +#include "iostuff.h" + +/* Backwards compatibility */ +#ifndef O_NONBLOCK +#define PATTERN FNDELAY +#else +#define PATTERN O_NONBLOCK +#endif + +/* non_blocking - set/clear non-blocking flag */ + +int non_blocking(fd, on) +int fd; +int on; +{ + int flags; + + if ((flags = fcntl(fd, F_GETFL, 0)) < 0) + msg_fatal("fcntl: get flags: %m"); + if (fcntl(fd, F_SETFL, on ? flags | PATTERN : flags & ~PATTERN) < 0) + msg_fatal("fcntl: set non-blocking flag %s: %m", on ? "on" : "off"); + return ((flags & PATTERN) != 0); +} diff --git a/src/util/nvtable.c b/src/util/nvtable.c new file mode 100644 index 0000000..5238e29 --- /dev/null +++ b/src/util/nvtable.c @@ -0,0 +1,122 @@ +/*++ +/* NAME +/* nvtable 3 +/* SUMMARY +/* attribute list manager +/* SYNOPSIS +/* #include <nvtable.h> +/* +/* typedef struct { +/* .in +4 +/* char *key; +/* char *value; +/* /* private fields... */ +/* .in -4 +/* } NVTABLE_INFO; +/* +/* NVTABLE *nvtable_create(size) +/* int size; +/* +/* NVTABLE_INFO *nvtable_update(table, key, value) +/* NVTABLE *table; +/* const char *key; +/* const char *value; +/* +/* char *nvtable_find(table, key) +/* NVTABLE *table; +/* const char *key; +/* +/* NVTABLE_INFO *nvtable_locate(table, key) +/* NVTABLE *table; +/* const char *key; +/* +/* void nvtable_delete(table, key) +/* NVTABLE *table; +/* const char *key; +/* +/* void nvtable_free(table) +/* NVTABLE *table; +/* +/* void nvtable_walk(table, action, ptr) +/* NVTABLE *table; +/* void (*action)(NVTABLE_INFO *, char *ptr); +/* char *ptr; +/* +/* NVTABLE_INFO **nvtable_list(table) +/* NVTABLE *table; +/* DESCRIPTION +/* This module maintains one or more attribute lists. It provides a +/* more convenient interface than hash tables, although it uses the +/* same underlying implementation. Each attribute list entry consists +/* of a unique string-valued lookup key and a string value. +/* +/* nvtable_create() creates a table of the specified size and returns a +/* pointer to the result. +/* +/* nvtable_update() stores or updates a (key, value) pair in the specified +/* table and returns a pointer to the resulting entry. The key and the +/* value are copied. +/* +/* nvtable_find() returns the value that was stored under the given key, +/* or a null pointer if it was not found. In order to distinguish +/* a null value from a non-existent value, use nvtable_locate(). +/* +/* nvtable_locate() returns a pointer to the entry that was stored +/* for the given key, or a null pointer if it was not found. +/* +/* nvtable_delete() removes one entry that was stored under the given key. +/* +/* nvtable_free() destroys a hash table, including contents. +/* +/* nvtable_walk() invokes the action function for each table entry, with +/* a pointer to the entry as its argument. The ptr argument is passed +/* on to the action function. +/* +/* nvtable_list() returns a null-terminated list of pointers to +/* all elements in the named table. The list should be passed to +/* myfree(). +/* RESTRICTIONS +/* A callback function should not modify the attribute list that is +/* specified to its caller. +/* DIAGNOSTICS +/* The following conditions are reported and cause the program to +/* terminate immediately: memory allocation failure; an attempt +/* to delete a non-existent entry. +/* SEE ALSO +/* mymalloc(3) memory management wrapper +/* htable(3) hash table manager +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* C library */ + +#include <sys_defs.h> + +/* Utility library. */ + +#include <mymalloc.h> +#include <htable.h> +#include <nvtable.h> + +/* nvtable_update - update or enter (key, value) pair */ + +NVTABLE_INFO *nvtable_update(NVTABLE * table, const char *key, const char *value) +{ + NVTABLE_INFO *ht; + + if ((ht = htable_locate(table, key)) != 0) { + myfree(ht->value); + } else { + ht = htable_enter(table, key, (void *) 0); + } + ht->value = mystrdup(value); + return (ht); +} diff --git a/src/util/nvtable.h b/src/util/nvtable.h new file mode 100644 index 0000000..fed366a --- /dev/null +++ b/src/util/nvtable.h @@ -0,0 +1,44 @@ +#ifndef _NVTABLE_H_INCLUDED_ +#define _NVTABLE_H_INCLUDED_ + +/*++ +/* NAME +/* nvtable 3h +/* SUMMARY +/* attribute list manager +/* SYNOPSIS +/* #include <nvtable.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <htable.h> +#include <mymalloc.h> + +typedef struct HTABLE NVTABLE; +typedef struct HTABLE_INFO NVTABLE_INFO; + +#define nvtable_create(size) htable_create(size) +#define nvtable_locate(table, key) htable_locate((table), (key)) +#define nvtable_walk(table, action, ptr) htable_walk((table), HTABLE_ACTION_FN_CAST(action), (ptr)) +#define nvtable_list(table) htable_list(table) +#define nvtable_find(table, key) htable_find((table), (key)) +#define nvtable_delete(table, key) htable_delete((table), (key), myfree) +#define nvtable_free(table) htable_free((table), myfree) + +extern NVTABLE_INFO *nvtable_update(NVTABLE *, const char *, const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/open_as.c b/src/util/open_as.c new file mode 100644 index 0000000..0fa84b7 --- /dev/null +++ b/src/util/open_as.c @@ -0,0 +1,70 @@ +/*++ +/* NAME +/* open_as 3 +/* SUMMARY +/* open file as user +/* SYNOPSIS +/* #include <fcntl.h> +/* #include <open_as.h> +/* +/* int open_as(path, flags, mode, euid, egid) +/* const char *path; +/* int mode; +/* uid_t euid; +/* gid_t egid; +/* DESCRIPTION +/* open_as() opens the named \fIpath\fR with the named \fIflags\fR +/* and \fImode\fR, and with the effective rights specified by \fIeuid\fR +/* and \fIegid\fR. A -1 result means the open failed. +/* DIAGNOSTICS +/* Fatal error: no permission to change privilege level. +/* SEE ALSO +/* set_eugid(3) switch effective rights +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <fcntl.h> +#include <unistd.h> + +/* Utility library. */ + +#include "msg.h" +#include "set_eugid.h" +#include "open_as.h" + +/* open_as - open file as user */ + +int open_as(const char *path, int flags, int mode, uid_t euid, gid_t egid) +{ + uid_t saved_euid = geteuid(); + gid_t saved_egid = getegid(); + int fd; + + /* + * Switch to the target user privileges. + */ + set_eugid(euid, egid); + + /* + * Open that file. + */ + fd = open(path, flags, mode); + + /* + * Restore saved privileges. + */ + set_eugid(saved_euid, saved_egid); + + return (fd); +} diff --git a/src/util/open_as.h b/src/util/open_as.h new file mode 100644 index 0000000..308e009 --- /dev/null +++ b/src/util/open_as.h @@ -0,0 +1,30 @@ +#ifndef _OPEN_H_INCLUDED_ +#define _OPEN_H_INCLUDED_ + +/*++ +/* NAME +/* open_as 3h +/* SUMMARY +/* open file as user +/* SYNOPSIS +/* #include <fcntl.h> +/* #include <open_as.h> +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern int open_as(const char *, int, int, uid_t, gid_t); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/open_limit.c b/src/util/open_limit.c new file mode 100644 index 0000000..8d49433 --- /dev/null +++ b/src/util/open_limit.c @@ -0,0 +1,100 @@ +/*++ +/* NAME +/* open_limit 3 +/* SUMMARY +/* set/get open file limit +/* SYNOPSIS +/* #include <iostuff.h> +/* +/* int open_limit(int limit) +/* DESCRIPTION +/* The \fIopen_limit\fR() routine attempts to change the maximum +/* number of open files to the specified limit. Specify a null +/* argument to effect no change. The result is the actual open file +/* limit for the current process. The number can be smaller or larger +/* than the requested limit. +/* DIAGNOSTICS +/* open_limit() returns -1 in case of problems. The errno +/* variable gives hints about the nature of the problem. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include "sys_defs.h" +#include <sys/time.h> +#include <sys/resource.h> +#include <errno.h> + +#ifdef USE_MAX_FILES_PER_PROC +#include <sys/sysctl.h> +#define MAX_FILES_PER_PROC "kern.maxfilesperproc" +#endif + +/* Application-specific. */ + +#include "iostuff.h" + + /* + * 44BSD compatibility. + */ +#ifndef RLIMIT_NOFILE +#ifdef RLIMIT_OFILE +#define RLIMIT_NOFILE RLIMIT_OFILE +#endif +#endif + +/* open_limit - set/query file descriptor limit */ + +int open_limit(int limit) +{ +#ifdef RLIMIT_NOFILE + struct rlimit rl; +#endif + + if (limit < 0) { + errno = EINVAL; + return (-1); + } +#ifdef RLIMIT_NOFILE + if (getrlimit(RLIMIT_NOFILE, &rl) < 0) + return (-1); + if (limit > 0) { + + /* + * MacOSX incorrectly reports rlim_max as RLIM_INFINITY. The true + * hard limit is finite and equals the kern.maxfilesperproc value. + */ +#ifdef USE_MAX_FILES_PER_PROC + int max_files_per_proc; + size_t len = sizeof(max_files_per_proc); + + if (sysctlbyname(MAX_FILES_PER_PROC, &max_files_per_proc, &len, + (void *) 0, (size_t) 0) < 0) + return (-1); + if (limit > max_files_per_proc) + limit = max_files_per_proc; +#endif + if (limit > rl.rlim_max) + rl.rlim_cur = rl.rlim_max; + else + rl.rlim_cur = limit; + if (setrlimit(RLIMIT_NOFILE, &rl) < 0) + return (-1); + } + return (rl.rlim_cur); +#endif + +#ifndef RLIMIT_NOFILE + return (getdtablesize()); +#endif +} + diff --git a/src/util/open_lock.c b/src/util/open_lock.c new file mode 100644 index 0000000..87e852d --- /dev/null +++ b/src/util/open_lock.c @@ -0,0 +1,76 @@ +/*++ +/* NAME +/* open_lock 3 +/* SUMMARY +/* open or create file and lock it for exclusive access +/* SYNOPSIS +/* #include <open_lock.h> +/* +/* VSTREAM *open_lock(path, flags, mode, why) +/* const char *path; +/* int flags; +/* mode_t mode; +/* VSTRING *why; +/* DESCRIPTION +/* This module opens or creates the named file and attempts to +/* acquire an exclusive lock. The lock is lost when the last +/* process closes the file. +/* +/* Arguments: +/* .IP "path, flags, mode" +/* These are passed on to safe_open(). +/* .IP why +/* storage for diagnostics. +/* SEE ALSO +/* safe_open(3) carefully open or create file +/* myflock(3) get exclusive lock on file +/* DIAGNOSTICS +/* In case of problems the result is a null pointer and a problem +/* description is returned via the global \fIerrno\fR variable. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <unistd.h> +#include <fcntl.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstream.h> +#include <vstring.h> +#include <safe_open.h> +#include <myflock.h> +#include <open_lock.h> + +/* open_lock - open file and lock it for exclusive access */ + +VSTREAM *open_lock(const char *path, int flags, mode_t mode, VSTRING *why) +{ + VSTREAM *fp; + + /* + * Carefully create or open the file, and lock it down. Some systems + * don't have the O_LOCK open() flag, or the flag does not do what we + * want, so we roll our own lock. + */ + if ((fp = safe_open(path, flags, mode, (struct stat *) 0, -1, -1, why)) == 0) + return (0); + if (myflock(vstream_fileno(fp), INTERNAL_LOCK, + MYFLOCK_OP_EXCLUSIVE | MYFLOCK_OP_NOWAIT) < 0) { + vstring_sprintf(why, "unable to set exclusive lock: %m"); + vstream_fclose(fp); + return (0); + } + return (fp); +} diff --git a/src/util/open_lock.h b/src/util/open_lock.h new file mode 100644 index 0000000..ada9f4d --- /dev/null +++ b/src/util/open_lock.h @@ -0,0 +1,41 @@ +#ifndef _OPEN_LOCK_H_INCLUDED_ +#define _OPEN_LOCK_H_INCLUDED_ + +/*++ +/* NAME +/* open_lock 3h +/* SUMMARY +/* open or create file and lock it for exclusive access +/* SYNOPSIS +/* #include <open_lock.h> +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include <fcntl.h> + + /* + * Utility library. + */ +#include <vstream.h> +#include <vstring.h> + + /* + * External interface. + */ +extern VSTREAM *open_lock(const char *, int, mode_t, VSTRING *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/pass_accept.c b/src/util/pass_accept.c new file mode 100644 index 0000000..d06926f --- /dev/null +++ b/src/util/pass_accept.c @@ -0,0 +1,106 @@ +/*++ +/* NAME +/* pass_accept 3 +/* SUMMARY +/* start UNIX-domain file descriptor listener +/* SYNOPSIS +/* #include <listen.h> +/* +/* int pass_accept(listen_fd) +/* int listen_fd; +/* +/* int pass_accept_attr(listen_fd, attr) +/* int listen_fd; +/* HTABLE **attr; +/* DESCRIPTION +/* This module implements a listener that receives one attribute list +/* and file descriptor over each a local connection that is made to it. +/* +/* Arguments: +/* .IP attr +/* Pointer to attribute list pointer. In case of error, or +/* no attributes, the attribute list pointer is set to null. +/* .IP listen_fd +/* File descriptor returned by LOCAL_LISTEN(). +/* DIAGNOSTICS +/* Warnings: I/O errors, timeout. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <errno.h> +#include <unistd.h> + +/* Utility library. */ + +#include <msg.h> +#include <listen.h> +#include <attr.h> + +#define PASS_ACCEPT_TMOUT 100 + +/* pass_accept - accept descriptor */ + +int pass_accept(int listen_fd) +{ + const char *myname = "pass_accept"; + int accept_fd; + int recv_fd = -1; + + accept_fd = LOCAL_ACCEPT(listen_fd); + if (accept_fd < 0) { + if (errno != EAGAIN) + msg_warn("%s: cannot accept connection: %m", myname); + return (-1); + } else { + if (read_wait(accept_fd, PASS_ACCEPT_TMOUT) < 0) + msg_warn("%s: timeout receiving file descriptor: %m", myname); + else if ((recv_fd = LOCAL_RECV_FD(accept_fd)) < 0) + msg_warn("%s: cannot receive file descriptor: %m", myname); + if (close(accept_fd) < 0) + msg_warn("%s: close: %m", myname); + return (recv_fd); + } +} + +/* pass_accept_attr - accept descriptor and attribute list */ + +int pass_accept_attr(int listen_fd, HTABLE **attr) +{ + const char *myname = "pass_accept_attr"; + int accept_fd; + int recv_fd = -1; + + *attr = 0; + accept_fd = LOCAL_ACCEPT(listen_fd); + if (accept_fd < 0) { + if (errno != EAGAIN) + msg_warn("%s: cannot accept connection: %m", myname); + return (-1); + } else { + if (read_wait(accept_fd, PASS_ACCEPT_TMOUT) < 0) + msg_warn("%s: timeout receiving file descriptor: %m", myname); + else if ((recv_fd = LOCAL_RECV_FD(accept_fd)) < 0) + msg_warn("%s: cannot receive file descriptor: %m", myname); + else if (read_wait(accept_fd, PASS_ACCEPT_TMOUT) < 0 + || recv_pass_attr(accept_fd, attr, PASS_ACCEPT_TMOUT, 0) < 0) { + msg_warn("%s: cannot receive connection attributes: %m", myname); + if (close(recv_fd) < 0) + msg_warn("%s: close: %m", myname); + recv_fd = -1; + } + if (close(accept_fd) < 0) + msg_warn("%s: close: %m", myname); + return (recv_fd); + } +} diff --git a/src/util/pass_trigger.c b/src/util/pass_trigger.c new file mode 100644 index 0000000..d7d16f2 --- /dev/null +++ b/src/util/pass_trigger.c @@ -0,0 +1,151 @@ +/*++ +/* NAME +/* pass_trigger 3 +/* SUMMARY +/* trigger file descriptor listener +/* SYNOPSIS +/* #include <trigger.h> +/* +/* int pass_trigger(service, buf, len, timeout) +/* const char *service; +/* const char *buf; +/* ssize_t len; +/* int timeout; +/* DESCRIPTION +/* pass_trigger() connects to the named local server by sending +/* a file descriptor to it and writing the named buffer. +/* +/* The connection is closed by a background thread. Some kernels +/* cannot handle client-side disconnect before the server has +/* received the message. +/* +/* Arguments: +/* .IP service +/* Name of the communication endpoint. +/* .IP buf +/* Address of data to be written. +/* .IP len +/* Amount of data to be written. +/* .IP timeout +/* Deadline in seconds. Specify a value <= 0 to disable +/* the time limit. +/* DIAGNOSTICS +/* The result is zero in case of success, -1 in case of problems. +/* SEE ALSO +/* unix_connect(3), local client +/* stream_connect(3), streams-based client +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <unistd.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <connect.h> +#include <iostuff.h> +#include <mymalloc.h> +#include <events.h> +#include <trigger.h> + +struct pass_trigger { + int connect_fd; + char *service; + int pass_fd[2]; +}; + +/* pass_trigger_event - disconnect from peer */ + +static void pass_trigger_event(int event, void *context) +{ + struct pass_trigger *pp = (struct pass_trigger *) context; + static const char *myname = "pass_trigger_event"; + + /* + * Disconnect. + */ + if (event == EVENT_TIME) + msg_warn("%s: read timeout for service %s", myname, pp->service); + event_disable_readwrite(pp->connect_fd); + event_cancel_timer(pass_trigger_event, context); + /* Don't combine multiple close() calls into one boolean expression. */ + if (close(pp->connect_fd) < 0) + msg_warn("%s: close %s: %m", myname, pp->service); + if (close(pp->pass_fd[0]) < 0) + msg_warn("%s: close pipe: %m", myname); + if (close(pp->pass_fd[1]) < 0) + msg_warn("%s: close pipe: %m", myname); + myfree(pp->service); + myfree((void *) pp); +} + +/* pass_trigger - wakeup local server */ + +int pass_trigger(const char *service, const char *buf, ssize_t len, int timeout) +{ + const char *myname = "pass_trigger"; + int pass_fd[2]; + struct pass_trigger *pp; + int connect_fd; + + if (msg_verbose > 1) + msg_info("%s: service %s", myname, service); + + /* + * Connect... + */ + if ((connect_fd = LOCAL_CONNECT(service, BLOCKING, timeout)) < 0) { + if (msg_verbose) + msg_warn("%s: connect to %s: %m", myname, service); + return (-1); + } + close_on_exec(connect_fd, CLOSE_ON_EXEC); + + /* + * Create a pipe, and send one pipe end to the server. + */ + if (pipe(pass_fd) < 0) + msg_fatal("%s: pipe: %m", myname); + close_on_exec(pass_fd[0], CLOSE_ON_EXEC); + close_on_exec(pass_fd[1], CLOSE_ON_EXEC); + if (LOCAL_SEND_FD(connect_fd, pass_fd[0]) < 0) + msg_fatal("%s: send file descriptor: %m", myname); + + /* + * Stash away context. + */ + pp = (struct pass_trigger *) mymalloc(sizeof(*pp)); + pp->connect_fd = connect_fd; + pp->service = mystrdup(service); + pp->pass_fd[0] = pass_fd[0]; + pp->pass_fd[1] = pass_fd[1]; + + /* + * Write the request... + */ + if (write_buf(pass_fd[1], buf, len, timeout) < 0 + || write_buf(pass_fd[1], "", 1, timeout) < 0) + if (msg_verbose) + msg_warn("%s: write to %s: %m", myname, service); + + /* + * Wakeup when the peer disconnects, or when we lose patience. + */ + if (timeout > 0) + event_request_timer(pass_trigger_event, (void *) pp, timeout + 100); + event_enable_read(connect_fd, pass_trigger_event, (void *) pp); + return (0); +} diff --git a/src/util/peekfd.c b/src/util/peekfd.c new file mode 100644 index 0000000..e9480a2 --- /dev/null +++ b/src/util/peekfd.c @@ -0,0 +1,89 @@ +/*++ +/* NAME +/* peekfd 3 +/* SUMMARY +/* determine amount of data ready to read +/* SYNOPSIS +/* #include <iostuff.h> +/* +/* ssize_t peekfd(fd) +/* int fd; +/* DESCRIPTION +/* peekfd() attempts to find out how many bytes are available to +/* be read from the named file descriptor. The result value is +/* the number of available bytes. +/* DIAGNOSTICS +/* peekfd() returns -1 in case of trouble. The global \fIerrno\fR +/* variable reflects the nature of the problem. +/* BUGS +/* On some systems, non-blocking read() may fail even after a +/* positive return from peekfd(). The smtp-sink program works +/* around this by using the readable() function instead. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/ioctl.h> +#ifdef FIONREAD_IN_SYS_FILIO_H +#include <sys/filio.h> +#endif +#ifdef FIONREAD_IN_TERMIOS_H +#include <termios.h> +#endif +#include <unistd.h> + +#ifndef SHUT_RDWR +#define SHUT_RDWR 2 +#endif + +/* Utility library. */ + +#include "iostuff.h" + +/* peekfd - return amount of data ready to read */ + +ssize_t peekfd(int fd) +{ + + /* + * Anticipate a series of system-dependent code fragments. + */ +#ifdef FIONREAD + int count; + +#ifdef SUNOS5 + + /* + * With Solaris10, write_wait() hangs in poll() until timeout, when + * invoked after peekfd() has received an ECONNRESET error indication. + * This happens when a client sends QUIT and closes the connection + * immediately. + */ + if (ioctl(fd, FIONREAD, (char *) &count) < 0) { + (void) shutdown(fd, SHUT_RDWR); + return (-1); + } else { + return (count); + } +#else /* SUNOS5 */ + return (ioctl(fd, FIONREAD, (char *) &count) < 0 ? -1 : count); +#endif /* SUNOS5 */ +#else +#error "don't know how to look ahead" +#endif +} diff --git a/src/util/poll_fd.c b/src/util/poll_fd.c new file mode 100644 index 0000000..80cd0f6 --- /dev/null +++ b/src/util/poll_fd.c @@ -0,0 +1,269 @@ +/*++ +/* NAME +/* poll_fd 3 +/* SUMMARY +/* wait until file descriptor becomes readable or writable +/* SYNOPSIS +/* #include <iostuff.h> +/* +/* int readable(fd) +/* int fd; +/* +/* int writable(fd) +/* int fd; +/* +/* int read_wait(fd, time_limit) +/* int fd; +/* int time_limit; +/* +/* int write_wait(fd, time_limit) +/* int fd; +/* int time_limit; +/* +/* int poll_fd(fd, request, time_limit, true_res, false_res) +/* int fd; +/* int request; +/* int time_limit; +/* int true_res; +/* int false_res; +/* DESCRIPTION +/* The read*() and write*() functions in this module are macros +/* that provide a convenient interface to poll_fd(). +/* +/* readable() asks the kernel if the specified file descriptor +/* is readable, i.e. a read operation would not block. +/* +/* writable() asks the kernel if the specified file descriptor +/* is writable, i.e. a write operation would not block. +/* +/* read_wait() waits until the specified file descriptor becomes +/* readable, or until the time limit is reached. +/* +/* write_wait() waits until the specified file descriptor +/* becomes writable, or until the time limit is reached. +/* +/* poll_fd() waits until the specified file descriptor becomes +/* readable or writable, or until the time limit is reached. +/* +/* Arguments: +/* .IP fd +/* File descriptor. With implementations based on select(), a +/* best effort is made to handle descriptors >=FD_SETSIZE. +/* .IP request +/* POLL_FD_READ (wait until readable) or POLL_FD_WRITE (wait +/* until writable). +/* .IP time_limit +/* A positive value specifies a time limit in seconds. A zero +/* value effects a poll (return immediately). A negative value +/* means wait until the requested POLL_FD_READ or POLL_FD_WRITE +/* condition becomes true. +/* .IP true_res +/* Result value when the requested POLL_FD_READ or POLL_FD_WRITE +/* condition is true. +/* .IP false_res +/* Result value when the requested POLL_FD_READ or POLL_FD_WRITE +/* condition is false. +/* DIAGNOSTICS +/* Panic: interface violation. All system call errors are fatal +/* unless specified otherwise. +/* +/* readable() and writable() return 1 when the requested +/* POLL_FD_READ or POLL_FD_WRITE condition is true, zero when +/* it is false. They never return an error indication. +/* +/* read_wait() and write_wait() return zero when the requested +/* POLL_FD_READ or POLL_FD_WRITE condition is true, -1 (with +/* errno set to ETIMEDOUT) when it is false. +/* +/* poll_fd() returns true_res when the requested POLL_FD_READ +/* or POLL_FD_WRITE condition is true, false_res when it is +/* false. When poll_fd() returns a false_res value < 0, it +/* also sets errno to ETIMEDOUT. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/time.h> +#include <signal.h> +#include <errno.h> +#include <unistd.h> +#include <string.h> + + /* + * Use poll() with fall-back to select(). MacOSX needs this for devices. + */ +#if defined(USE_SYSV_POLL_THEN_SELECT) +#define poll_fd_sysv poll_fd +#define USE_SYSV_POLL +#define USE_BSD_SELECT +int poll_fd_bsd(int, int, int, int, int); + + /* + * Use select() only. + */ +#elif defined(USE_BSD_SELECT) +#define poll_fd_bsd poll_fd +#undef USE_SYSV_POLL + + /* + * Use poll() only. + */ +#elif defined(USE_SYSV_POLL) +#define poll_fd_sysv poll_fd + + /* + * Sanity check. + */ +#else +#error "specify USE_SYSV_POLL, USE_BSD_SELECT or USE_SYSV_POLL_THEN_SELECT" +#endif + +#ifdef USE_SYSV_POLL +#include <poll.h> +#endif + +#ifdef USE_SYS_SELECT_H +#include <sys/select.h> +#endif + +/* Utility library. */ + +#include <msg.h> +#include <iostuff.h> + +#ifdef USE_BSD_SELECT + +/* poll_fd_bsd - block with time_limit until file descriptor is ready */ + +int poll_fd_bsd(int fd, int request, int time_limit, + int true_res, int false_res) +{ + fd_set req_fds; + fd_set *read_fds; + fd_set *write_fds; + fd_set except_fds; + struct timeval tv; + struct timeval *tp; + int temp_fd = -1; + + /* + * Sanity checks. + */ + if (FD_SETSIZE <= fd) { + if ((temp_fd = dup(fd)) < 0 || temp_fd >= FD_SETSIZE) + msg_fatal("descriptor %d does not fit FD_SETSIZE %d", fd, FD_SETSIZE); + fd = temp_fd; + } + + /* + * Use select() so we do not depend on alarm() and on signal() handlers. + * Restart select() when interrupted by some signal. Some select() + * implementations reduce the time to wait when interrupted, which is + * exactly what we want. + */ + FD_ZERO(&req_fds); + FD_SET(fd, &req_fds); + except_fds = req_fds; + if (request == POLL_FD_READ) { + read_fds = &req_fds; + write_fds = 0; + } else if (request == POLL_FD_WRITE) { + read_fds = 0; + write_fds = &req_fds; + } else { + msg_panic("poll_fd: bad request %d", request); + } + + if (time_limit >= 0) { + tv.tv_usec = 0; + tv.tv_sec = time_limit; + tp = &tv; + } else { + tp = 0; + } + + for (;;) { + switch (select(fd + 1, read_fds, write_fds, &except_fds, tp)) { + case -1: + if (errno != EINTR) + msg_fatal("select: %m"); + continue; + case 0: + if (temp_fd != -1) + (void) close(temp_fd); + if (false_res < 0) + errno = ETIMEDOUT; + return (false_res); + default: + if (temp_fd != -1) + (void) close(temp_fd); + return (true_res); + } + } +} + +#endif + +#ifdef USE_SYSV_POLL + +#ifdef USE_SYSV_POLL_THEN_SELECT +#define HANDLE_SYSV_POLL_ERROR(fd, req, time_limit, true_res, false_res) \ + return (poll_fd_bsd((fd), (req), (time_limit), (true_res), (false_res))) +#else +#define HANDLE_SYSV_POLL_ERROR(fd, req, time_limit, true_res, false_res) \ + msg_fatal("poll: %m") +#endif + +/* poll_fd_sysv - block with time_limit until file descriptor is ready */ + +int poll_fd_sysv(int fd, int request, int time_limit, + int true_res, int false_res) +{ + struct pollfd pollfd; + + /* + * System-V poll() is optimal for polling a few descriptors. + */ +#define WAIT_FOR_EVENT (-1) + + pollfd.fd = fd; + if (request == POLL_FD_READ) { + pollfd.events = POLLIN; + } else if (request == POLL_FD_WRITE) { + pollfd.events = POLLOUT; + } else { + msg_panic("poll_fd: bad request %d", request); + } + + for (;;) { + switch (poll(&pollfd, 1, time_limit < 0 ? + WAIT_FOR_EVENT : time_limit * 1000)) { + case -1: + if (errno != EINTR) + HANDLE_SYSV_POLL_ERROR(fd, request, time_limit, + true_res, false_res); + continue; + case 0: + if (false_res < 0) + errno = ETIMEDOUT; + return (false_res); + default: + if (pollfd.revents & POLLNVAL) + HANDLE_SYSV_POLL_ERROR(fd, request, time_limit, + true_res, false_res); + return (true_res); + } + } +} + +#endif diff --git a/src/util/posix_signals.c b/src/util/posix_signals.c new file mode 100644 index 0000000..8ccddf0 --- /dev/null +++ b/src/util/posix_signals.c @@ -0,0 +1,126 @@ +/*++ +/* NAME +/* posix_signals 3 +/* SUMMARY +/* POSIX signal handling compatibility +/* SYNOPSIS +/* #include <posix_signals.h> +/* +/* int sigemptyset(m) +/* sigset_t *m; +/* +/* int sigaddset(set, signum) +/* sigset_t *set; +/* int signum; +/* +/* int sigprocmask(how, set, old) +/* int how; +/* sigset_t *set; +/* sigset_t *old; +/* +/* int sigaction(sig, act, oact) +/* int sig; +/* struct sigaction *act; +/* struct sigaction *oact; +/* DESCRIPTION +/* These routines emulate the POSIX signal handling interface. +/* AUTHOR(S) +/* Pieter Schoenmakers +/* Eindhoven University of Technology +/* P.O. Box 513 +/* 5600 MB Eindhoven +/* The Netherlands +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <signal.h> +#include <errno.h> + +/* Utility library.*/ + +#include "posix_signals.h" + +#ifdef MISSING_SIGSET_T + +int sigemptyset(sigset_t *m) +{ + return *m = 0; +} + +int sigaddset(sigset_t *set, int signum) +{ + *set |= sigmask(signum); + return 0; +} + +int sigprocmask(int how, sigset_t *set, sigset_t *old) +{ + int previous; + + if (how == SIG_BLOCK) + previous = sigblock(*set); + else if (how == SIG_SETMASK) + previous = sigsetmask(*set); + else if (how == SIG_UNBLOCK) { + int m = sigblock(0); + + previous = sigsetmask(m & ~*set); + } else { + errno = EINVAL; + return -1; + } + + if (old) + *old = previous; + return 0; +} + +#endif + +#ifdef MISSING_SIGACTION + +static struct sigaction actions[NSIG] = {}; + +static int sighandle(int signum) +{ + if (signum == SIGCHLD) { + /* XXX If the child is just stopped, don't invoke the handler. */ + } + actions[signum].sa_handler(signum); +} + +int sigaction(int sig, struct sigaction *act, struct sigaction *oact) +{ + static int initialized = 0; + + if (!initialized) { + int i; + + for (i = 0; i < NSIG; i++) + actions[i].sa_handler = SIG_DFL; + initialized = 1; + } + if (sig <= 0 || sig >= NSIG) { + errno = EINVAL; + return -1; + } + if (oact) + *oact = actions[sig]; + + { + struct sigvec mine = { + sighandle, act->sa_mask, + act->sa_flags & SA_RESTART ? SV_INTERRUPT : 0 + }; + + if (sigvec(sig, &mine, NULL)) + return -1; + } + + actions[sig] = *act; + return 0; +} + +#endif diff --git a/src/util/posix_signals.h b/src/util/posix_signals.h new file mode 100644 index 0000000..12c1664 --- /dev/null +++ b/src/util/posix_signals.h @@ -0,0 +1,59 @@ +#ifndef _POSIX_SIGNALS_H_INCLUDED_ +#define _POSIX_SIGNALS_H_INCLUDED_ +/*++ +/* NAME +/* posix_signals 3h +/* SUMMARY +/* POSIX signal handling compatibility +/* SYNOPSIS +/* #include <posix_signals.h> +/* DESCRIPTION +/* .nf + + /* + * Compatibility interface. + */ + +#ifdef MISSING_SIGSET_T + +typedef int sigset_t; + +enum { + SIG_BLOCK, + SIG_UNBLOCK, + SIG_SETMASK +}; + +extern int sigemptyset(sigset_t *); +extern int sigaddset(sigset_t *, int); +extern int sigprocmask(int, sigset_t *, sigset_t *); + +#endif + +#ifdef MISSING_SIGACTION + +struct sigaction { + void (*sa_handler) (); + sigset_t sa_mask; + int sa_flags; +}; + + /* Possible values for sa_flags. Or them to set multiple. */ +enum { + SA_RESTART, + SA_NOCLDSTOP = 4 /* drop the = 4. */ +}; + +extern int sigaction(int, struct sigaction *, struct sigaction *); + +#endif + +/* AUTHOR(S) +/* Pieter Schoenmakers +/* Eindhoven University of Technology +/* P.O. Box 513 +/* 5600 MB Eindhoven +/* The Netherlands +/*--*/ + +#endif diff --git a/src/util/printable.c b/src/util/printable.c new file mode 100644 index 0000000..6c148fd --- /dev/null +++ b/src/util/printable.c @@ -0,0 +1,100 @@ +/*++ +/* NAME +/* printable 3 +/* SUMMARY +/* mask non-printable characters +/* SYNOPSIS +/* #include <stringops.h> +/* +/* int util_utf8_enable; +/* +/* char *printable(buffer, replacement) +/* char *buffer; +/* int replacement; +/* +/* char *printable_except(buffer, replacement, except) +/* char *buffer; +/* int replacement; +/* const char *except; +/* DESCRIPTION +/* printable() replaces non-printable characters +/* in its input with the given replacement. +/* +/* util_utf8_enable controls whether UTF8 is considered printable. +/* With util_utf8_enable equal to zero, non-ASCII text is replaced. +/* +/* Arguments: +/* .IP buffer +/* The null-terminated input string. +/* .IP replacement +/* Replacement value for characters in \fIbuffer\fR that do not +/* pass the ASCII isprint(3) test or that are not valid UTF8. +/* .IP except +/* Null-terminated sequence of non-replaced ASCII characters. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <ctype.h> +#include <string.h> + +/* Utility library. */ + +#include "stringops.h" + +int util_utf8_enable = 0; + +/* printable - binary compatibility */ + +#undef printable + +char *printable(char *, int); + +char *printable(char *string, int replacement) +{ + return (printable_except(string, replacement, (char *) 0)); +} + +/* printable_except - pass through printable or other preserved characters */ + +char *printable_except(char *string, int replacement, const char *except) +{ + unsigned char *cp; + int ch; + + /* + * XXX Replace invalid UTF8 sequences (too short, over-long encodings, + * out-of-range code points, etc). See valid_utf8_string.c. + */ + cp = (unsigned char *) string; + while ((ch = *cp) != 0) { + if (ISASCII(ch) && (ISPRINT(ch) || (except && strchr(except, ch)))) { + /* ok */ + } else if (util_utf8_enable && ch >= 194 && ch <= 254 + && cp[1] >= 128 && cp[1] < 192) { + /* UTF8; skip the rest of the bytes in the character. */ + while (cp[1] >= 128 && cp[1] < 192) + cp++; + } else { + /* Not ASCII and not UTF8. */ + *cp = replacement; + } + cp++; + } + return (string); +} diff --git a/src/util/rand_sleep.c b/src/util/rand_sleep.c new file mode 100644 index 0000000..69a8cc8 --- /dev/null +++ b/src/util/rand_sleep.c @@ -0,0 +1,89 @@ +/*++ +/* NAME +/* rand_sleep 3 +/* SUMMARY +/* sleep for randomized interval +/* SYNOPSIS +/* #include <iostuff.h> +/* +/* void rand_sleep(delay, variation) +/* unsigned delay; +/* unsigned variation; +/* DESCRIPTION +/* rand_sleep() blocks the current process for an amount of time +/* pseudo-randomly chosen from the interval (delay +- variation/2). +/* +/* Arguments: +/* .IP delay +/* Time to sleep in microseconds. +/* .IP variation +/* Variation in microseconds; must not be larger than delay. +/* DIAGNOSTICS +/* Panic: interface violation. All system call errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdlib.h> +#include <unistd.h> +#include <time.h> + +/* Utility library. */ + +#include <msg.h> +#include <myrand.h> +#include <iostuff.h> + +/* rand_sleep - block for random time */ + +void rand_sleep(unsigned delay, unsigned variation) +{ + const char *myname = "rand_sleep"; + unsigned usec; + + /* + * Sanity checks. + */ + if (delay == 0) + msg_panic("%s: bad delay %d", myname, delay); + if (variation > delay) + msg_panic("%s: bad variation %d", myname, variation); + + /* + * Use the semi-crappy random number generator. + */ + usec = (delay - variation / 2) + variation * (double) myrand() / RAND_MAX; + doze(usec); +} + +#ifdef TEST + +#include <msg_vstream.h> + +int main(int argc, char **argv) +{ + int delay; + int variation; + + msg_vstream_init(argv[0], VSTREAM_ERR); + if (argc != 3) + msg_fatal("usage: %s delay variation", argv[0]); + if ((delay = atoi(argv[1])) <= 0) + msg_fatal("bad delay: %s", argv[1]); + if ((variation = atoi(argv[2])) < 0) + msg_fatal("bad variation: %s", argv[2]); + rand_sleep(delay * 1000000, variation * 1000000); + exit(0); +} + +#endif diff --git a/src/util/readlline.c b/src/util/readlline.c new file mode 100644 index 0000000..015877a --- /dev/null +++ b/src/util/readlline.c @@ -0,0 +1,138 @@ +/*++ +/* NAME +/* readlline 3 +/* SUMMARY +/* read logical line +/* SYNOPSIS +/* #include <readlline.h> +/* +/* VSTRING *readllines(buf, fp, lineno, first_line) +/* VSTRING *buf; +/* VSTREAM *fp; +/* int *lineno; +/* int *first_line; +/* +/* VSTRING *readlline(buf, fp, lineno) +/* VSTRING *buf; +/* VSTREAM *fp; +/* int *lineno; +/* DESCRIPTION +/* readllines() reads one logical line from the named stream. +/* .IP "blank lines and comments" +/* Empty lines and whitespace-only lines are ignored, as +/* are lines whose first non-whitespace character is a `#'. +/* .IP "multi-line text" +/* A logical line starts with non-whitespace text. A line that +/* starts with whitespace continues a logical line. +/* .PP +/* The result value is the input buffer argument or a null pointer +/* when no input is found. +/* +/* readlline() is a backwards-compatibility wrapper. +/* +/* Arguments: +/* .IP buf +/* A variable-length buffer for input. The result is null terminated. +/* .IP fp +/* Handle to an open stream. +/* .IP lineno +/* A null pointer, or a pointer to an integer that is incremented +/* after reading a physical line. +/* .IP first_line +/* A null pointer, or a pointer to an integer that will contain +/* the line number of the first non-blank, non-comment line +/* in the result logical line. +/* DIAGNOSTICS +/* Warning: a continuation line that does not continue preceding text. +/* The invalid input is ignored, to avoid complicating caller code. +/* SECURITY +/* .ad +/* .fi +/* readlline() imposes no logical line length limit therefore it +/* should be used for reading trusted information only. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <ctype.h> + +/* Utility library. */ + +#include "msg.h" +#include "vstream.h" +#include "vstring.h" +#include "readlline.h" + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) +#define END(x) vstring_end(x) + +/* readllines - read one logical line */ + +VSTRING *readllines(VSTRING *buf, VSTREAM *fp, int *lineno, int *first_line) +{ + int ch; + int next; + ssize_t start; + char *cp; + + VSTRING_RESET(buf); + + /* + * Ignore comment lines, all whitespace lines, and empty lines. Terminate + * at EOF or at the beginning of the next logical line. + */ + for (;;) { + /* Read one line, possibly not newline terminated. */ + start = LEN(buf); + while ((ch = VSTREAM_GETC(fp)) != VSTREAM_EOF && ch != '\n') + VSTRING_ADDCH(buf, ch); + if (lineno != 0 && (ch == '\n' || LEN(buf) > start)) + *lineno += 1; + /* Ignore comment line, all whitespace line, or empty line. */ + for (cp = STR(buf) + start; cp < END(buf) && ISSPACE(*cp); cp++) + /* void */ ; + if (cp == END(buf) || *cp == '#') + vstring_truncate(buf, start); + else if (start == 0 && lineno != 0 && first_line != 0) + *first_line = *lineno; + /* Terminate at EOF or at the beginning of the next logical line. */ + if (ch == VSTREAM_EOF) + break; + if (LEN(buf) > 0) { + if ((next = VSTREAM_GETC(fp)) != VSTREAM_EOF) + vstream_ungetc(fp, next); + if (next != '#' && !ISSPACE(next)) + break; + } + } + VSTRING_TERMINATE(buf); + + /* + * Invalid input: continuing text without preceding text. Allowing this + * would complicate "postconf -e", which implements its own multi-line + * parsing routine. Do not abort, just warn, so that critical programs + * like postmap do not leave behind a truncated table. + */ + if (LEN(buf) > 0 && ISSPACE(*STR(buf))) { + msg_warn("%s: logical line must not start with whitespace: \"%.30s%s\"", + VSTREAM_PATH(fp), STR(buf), + LEN(buf) > 30 ? "..." : ""); + return (readllines(buf, fp, lineno, first_line)); + } + + /* + * Done. + */ + return (LEN(buf) > 0 ? buf : 0); +} diff --git a/src/util/readlline.h b/src/util/readlline.h new file mode 100644 index 0000000..d63cf7d --- /dev/null +++ b/src/util/readlline.h @@ -0,0 +1,38 @@ +#ifndef _READLINE_H_INCLUDED_ +#define _READLINE_H_INCLUDED_ + +/*++ +/* NAME +/* readlline 3h +/* SUMMARY +/* read logical line +/* SYNOPSIS +/* #include <readlline.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <vstream.h> +#include <vstring.h> + + /* + * External interface. + */ +extern VSTRING *readllines(VSTRING *, VSTREAM *, int *, int *); + +#define readlline(bp, fp, lp) readllines((bp), (fp), (lp), (int *) 0) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/recv_pass_attr.c b/src/util/recv_pass_attr.c new file mode 100644 index 0000000..0d6c647 --- /dev/null +++ b/src/util/recv_pass_attr.c @@ -0,0 +1,98 @@ +/*++ +/* NAME +/* recv_pass_attr 3 +/* SUMMARY +/* predicate if string is all numerical +/* SYNOPSIS +/* #include <listen.h> +/* +/* int recv_pass_attr(fd, attr, timeout, bufsize) +/* int fd; +/* HTABLE **attr; +/* int timeout; +/* ssize_t bufsize; +/* DESCRIPTION +/* recv_pass_attr() receives named attributes over the specified +/* descriptor. The result value is zero for success, -1 for error. +/* +/* Arguments: +/* .IP fd +/* The file descriptor to read from. +/* .IP attr +/* Pointer to attribute list pointer. The target is set to +/* zero on error or when the received attribute list is empty, +/* otherwise it is assigned a pointer to non-empty attribute +/* list. +/* .IP timeout +/* The deadline for receiving all attributes. +/* .IP bufsize +/* The read buffer size. Specify 1 to avoid reading past the +/* end of the attribute list. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +/* Utility library. */ + +#include <iostuff.h> +#include <htable.h> +#include <vstream.h> +#include <attr.h> +#include <mymalloc.h> +#include <listen.h> + +/* recv_pass_attr - receive connection attributes */ + +int recv_pass_attr(int fd, HTABLE **attr, int timeout, ssize_t bufsize) +{ + VSTREAM *fp; + int stream_err; + + /* + * Set up a temporary VSTREAM to receive the attributes. + * + * XXX We use one-character reads to simplify the implementation. + */ + fp = vstream_fdopen(fd, O_RDWR); + vstream_control(fp, + CA_VSTREAM_CTL_BUFSIZE(bufsize), + CA_VSTREAM_CTL_TIMEOUT(timeout), + CA_VSTREAM_CTL_START_DEADLINE, + CA_VSTREAM_CTL_END); + stream_err = (attr_scan(fp, ATTR_FLAG_NONE, + ATTR_TYPE_HASH, *attr = htable_create(1), + ATTR_TYPE_END) < 0 + || vstream_feof(fp) || vstream_ferror(fp)); + vstream_fdclose(fp); + + /* + * Error reporting and recovery. + */ + if (stream_err) { + htable_free(*attr, myfree); + *attr = 0; + return (-1); + } else { + if ((*attr)->used == 0) { + htable_free(*attr, myfree); + *attr = 0; + } + return (0); + } +} diff --git a/src/util/ring.c b/src/util/ring.c new file mode 100644 index 0000000..d4c5f82 --- /dev/null +++ b/src/util/ring.c @@ -0,0 +1,121 @@ +/*++ +/* NAME +/* ring 3 +/* SUMMARY +/* circular list management +/* SYNOPSIS +/* #include <ring.h> +/* +/* void ring_init(list) +/* RING *list; +/* +/* void ring_prepend(list, element) +/* RING *list; +/* RING *element; +/* +/* void ring_append(list, element) +/* RING *list; +/* RING *element; +/* +/* RING *ring_pred(element) +/* RING *element; +/* +/* RING *ring_succ(element) +/* RING *element; +/* +/* void ring_detach(element) +/* RING *element; +/* +/* RING_FOREACH(RING *element, RING *head) +/* DESCRIPTION +/* This module manages circular, doubly-linked, lists. It provides +/* operations to initialize a list, to add or remove an element, +/* and to iterate over a list. Although the documentation appears +/* to emphasize the special role of the list head, each operation +/* can be applied to each list member. +/* +/* Examples of applications: any sequence of objects such as queue, +/* unordered list, or stack. Typically, an application embeds a RING +/* structure into its own data structure, and uses the RING primitives +/* to maintain the linkage between application-specific data objects. +/* +/* ring_init() initializes its argument to a list of just one element. +/* +/* ring_append() appends the named element to the named list head. +/* +/* ring_prepend() prepends the named element to the named list head. +/* +/* ring_succ() returns the list element that follows its argument. +/* +/* ring_pred() returns the list element that precedes its argument. +/* +/* ring_detach() disconnects a list element from its neighbors +/* and closes the hole. This routine performs no implicit ring_init() +/* on the removed element. +/* +/* RING_FOREACH() is a macro that expands to a for (... ; ... ; ...) +/* statement that iterates over each list element in forward order. +/* Upon completion, the \fIelement\fR variable is set equal to +/* \fIhead\fR. The list head itself is not treated as a list member. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +/* Application-specific. */ + +#include "ring.h" + +/* ring_init - initialize ring head */ + +void ring_init(ring) +RING *ring; +{ + ring->pred = ring->succ = ring; +} + +/* ring_append - insert entry after ring head */ + +void ring_append(ring, entry) +RING *ring; +RING *entry; +{ + entry->succ = ring->succ; + entry->pred = ring; + ring->succ->pred = entry; + ring->succ = entry; +} + +/* ring_prepend - insert new entry before ring head */ + +void ring_prepend(ring, entry) +RING *ring; +RING *entry; +{ + entry->pred = ring->pred; + entry->succ = ring; + ring->pred->succ = entry; + ring->pred = entry; +} + +/* ring_detach - remove entry from ring */ + +void ring_detach(entry) +RING *entry; +{ + RING *succ = entry->succ; + RING *pred = entry->pred; + + pred->succ = succ; + succ->pred = pred; + + entry->succ = entry->pred = 0; +} diff --git a/src/util/ring.h b/src/util/ring.h new file mode 100644 index 0000000..47749e2 --- /dev/null +++ b/src/util/ring.h @@ -0,0 +1,59 @@ +#ifndef _RING_H_INCLUDED_ +#define _RING_H_INCLUDED_ + +/*++ +/* NAME +/* ring 3h +/* SUMMARY +/* circular list management +/* SYNOPSIS +/* #include <ring.h> +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +typedef struct RING RING; + +struct RING { + RING *succ; /* successor */ + RING *pred; /* predecessor */ +}; + +extern void ring_init(RING *); +extern void ring_prepend(RING *, RING *); +extern void ring_append(RING *, RING *); +extern void ring_detach(RING *); + +#define ring_succ(c) ((c)->succ) +#define ring_pred(c) ((c)->pred) + +#define RING_FOREACH(entry, head) \ + for (entry = ring_succ(head); entry != (head); entry = ring_succ(entry)) + + /* + * Typically, an application will embed a RING structure into a larger + * structure that also contains application-specific members. This approach + * gives us the best of both worlds. The application can still use the + * generic RING primitives for manipulating RING structures. The macro below + * transforms a pointer from RING structure to the structure that contains + * it. + */ +#define RING_TO_APPL(ring_ptr,app_type,ring_member) \ + ((app_type *) (((char *) (ring_ptr)) - offsetof(app_type,ring_member))) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* LAST MODIFICATION +/* Tue Jan 28 16:50:20 EST 1997 +/*--*/ + +#endif diff --git a/src/util/safe.h b/src/util/safe.h new file mode 100644 index 0000000..8b75bf4 --- /dev/null +++ b/src/util/safe.h @@ -0,0 +1,30 @@ +#ifndef _SAFE_H_INCLUDED_ +#define _SAFE_H_INCLUDED_ + +/*++ +/* NAME +/* safe 3h +/* SUMMARY +/* miscellaneous taint checks +/* SYNOPSIS +/* #include <safe.h> +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern int unsafe(void); +extern char *safe_getenv(const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/safe_getenv.c b/src/util/safe_getenv.c new file mode 100644 index 0000000..04ca659 --- /dev/null +++ b/src/util/safe_getenv.c @@ -0,0 +1,41 @@ +/*++ +/* NAME +/* safe_getenv 3 +/* SUMMARY +/* guarded getenv() +/* SYNOPSIS +/* #include <safe.h> +/* +/* char *safe_getenv(const name) +/* char *name; +/* DESCRIPTION +/* The \fBsafe_getenv\fR() routine reads the named variable from the +/* environment, provided that the unsafe() routine agrees. +/* SEE ALSO +/* unsafe(3), detect non-user privileges +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdlib.h> + +/* Utility library. */ + +#include "safe.h" + +/* safe_getenv - read environment variable with guard */ + +char *safe_getenv(const char *name) +{ + return (unsafe() == 0 ? getenv(name) : 0); +} diff --git a/src/util/safe_open.c b/src/util/safe_open.c new file mode 100644 index 0000000..c7a80cf --- /dev/null +++ b/src/util/safe_open.c @@ -0,0 +1,283 @@ +/*++ +/* NAME +/* safe_open 3 +/* SUMMARY +/* safely open or create regular file +/* SYNOPSIS +/* #include <safe_open.h> +/* +/* VSTREAM *safe_open(path, flags, mode, st, user, group, why) +/* const char *path; +/* int flags; +/* mode_t mode; +/* struct stat *st; +/* uid_t user; +/* gid_t group; +/* VSTRING *why; +/* DESCRIPTION +/* safe_open() carefully opens or creates a file in a directory +/* that may be writable by untrusted users. If a file is created +/* it is given the specified ownership and permission attributes. +/* If an existing file is opened it must not be a symbolic link, +/* it must not be a directory, and it must have only one hard link. +/* +/* Arguments: +/* .IP "path, flags, mode" +/* These arguments are the same as with open(2). The O_EXCL flag +/* must appear either in combination with O_CREAT, or not at all. +/* .sp +/* No change is made to the permissions of an existing file. +/* .IP st +/* Null pointer, or pointer to storage for the attributes of the +/* opened file. +/* .IP "user, group" +/* File ownership for a file created by safe_open(). Specify -1 +/* in order to disable user and/or group ownership change. +/* .sp +/* No change is made to the ownership of an existing file. +/* .IP why +/* A VSTRING pointer for diagnostics. +/* DIAGNOSTICS +/* Panic: interface violations. +/* +/* A null result means there was a problem. The nature of the +/* problem is returned via the \fIwhy\fR buffer; when an error +/* cannot be reported via \fIerrno\fR, the generic value EPERM +/* (operation not permitted) is used instead. +/* HISTORY +/* .fi +/* .ad +/* A safe open routine was discussed by Casper Dik in article +/* <2rdb0s$568@mail.fwi.uva.nl>, posted to comp.security.unix +/* (May 18, 1994). +/* +/* Olaf Kirch discusses how the lstat()/open()+fstat() test can +/* be fooled by delaying the open() until the inode found with +/* lstat() has been re-used for a sensitive file (article +/* <20000103212443.A5807@monad.swb.de> posted to bugtraq on +/* Jan 3, 2000). This can be a concern for a set-ugid process +/* that runs under the control of a user and that can be +/* manipulated with start/stop signals. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstream.h> +#include <vstring.h> +#include <stringops.h> +#include <safe_open.h> +#include <warn_stat.h> + +/* safe_open_exist - open existing file */ + +static VSTREAM *safe_open_exist(const char *path, int flags, + struct stat * fstat_st, VSTRING *why) +{ + struct stat local_statbuf; + struct stat lstat_st; + int saved_errno; + VSTREAM *fp; + + /* + * Open an existing file. + */ + if ((fp = vstream_fopen(path, flags & ~(O_CREAT | O_EXCL), 0)) == 0) { + saved_errno = errno; + vstring_sprintf(why, "cannot open file: %m"); + errno = saved_errno; + return (0); + } + + /* + * Examine the modes from the open file: it must have exactly one hard + * link (so that someone can't lure us into clobbering a sensitive file + * by making a hard link to it), and it must be a non-symlink file. + */ + if (fstat_st == 0) + fstat_st = &local_statbuf; + if (fstat(vstream_fileno(fp), fstat_st) < 0) { + msg_fatal("%s: bad open file status: %m", path); + } else if (fstat_st->st_nlink != 1) { + vstring_sprintf(why, "file has %d hard links", + (int) fstat_st->st_nlink); + errno = EPERM; + } else if (S_ISDIR(fstat_st->st_mode)) { + vstring_sprintf(why, "file is a directory"); + errno = EISDIR; + } + + /* + * Look up the file again, this time using lstat(). Compare the fstat() + * (open file) modes with the lstat() modes. If there is any difference, + * either we followed a symlink while opening an existing file, someone + * quickly changed the number of hard links, or someone replaced the file + * after the open() call. The link and mode tests aren't really necessary + * in daemon processes. Set-uid programs, on the other hand, can be + * slowed down by arbitrary amounts, and there it would make sense to + * compare even more file attributes, such as the inode generation number + * on systems that have one. + * + * Grr. Solaris /dev/whatever is a symlink. We'll have to make an exception + * for symlinks owned by root. NEVER, NEVER, make exceptions for symlinks + * owned by a non-root user. This would open a security hole when + * delivering mail to a world-writable mailbox directory. + * + * Sebastian Krahmer of SuSE brought to my attention that some systems have + * changed their semantics of link(symlink, newpath), such that the + * result is a hardlink to the symlink. For this reason, we now also + * require that the symlink's parent directory is writable only by root. + */ + else if (lstat(path, &lstat_st) < 0) { + vstring_sprintf(why, "file status changed unexpectedly: %m"); + errno = EPERM; + } else if (S_ISLNK(lstat_st.st_mode)) { + if (lstat_st.st_uid == 0) { + VSTRING *parent_buf = vstring_alloc(100); + const char *parent_path = sane_dirname(parent_buf, path); + struct stat parent_st; + int parent_ok; + + parent_ok = (stat(parent_path, &parent_st) == 0 /* not lstat */ + && parent_st.st_uid == 0 + && (parent_st.st_mode & (S_IWGRP | S_IWOTH)) == 0); + vstring_free(parent_buf); + if (parent_ok) + return (fp); + } + vstring_sprintf(why, "file is a symbolic link"); + errno = EPERM; + } else if (fstat_st->st_dev != lstat_st.st_dev + || fstat_st->st_ino != lstat_st.st_ino +#ifdef HAS_ST_GEN + || fstat_st->st_gen != lstat_st.st_gen +#endif + || fstat_st->st_nlink != lstat_st.st_nlink + || fstat_st->st_mode != lstat_st.st_mode) { + vstring_sprintf(why, "file status changed unexpectedly"); + errno = EPERM; + } + + /* + * We are almost there... + */ + else { + return (fp); + } + + /* + * End up here in case of fstat()/lstat() problems or inconsistencies. + */ + vstream_fclose(fp); + return (0); +} + +/* safe_open_create - create new file */ + +static VSTREAM *safe_open_create(const char *path, int flags, mode_t mode, + struct stat * st, uid_t user, gid_t group, VSTRING *why) +{ + VSTREAM *fp; + + /* + * Create a non-existing file. This relies on O_CREAT | O_EXCL to not + * follow symbolic links. + */ + if ((fp = vstream_fopen(path, flags | (O_CREAT | O_EXCL), mode)) == 0) { + vstring_sprintf(why, "cannot create file exclusively: %m"); + return (0); + } + + /* + * Optionally look up the file attributes. + */ + if (st != 0 && fstat(vstream_fileno(fp), st) < 0) + msg_fatal("%s: bad open file status: %m", path); + + /* + * Optionally change ownership after creating a new file. If there is a + * problem we should not attempt to delete the file. Something else may + * have opened the file in the mean time. + */ +#define CHANGE_OWNER(user, group) (user != (uid_t) -1 || group != (gid_t) -1) + + if (CHANGE_OWNER(user, group) + && fchown(vstream_fileno(fp), user, group) < 0) { + msg_warn("%s: cannot change file ownership: %m", path); + } + + /* + * We are almost there... + */ + else { + return (fp); + } + + /* + * End up here in case of trouble. + */ + vstream_fclose(fp); + return (0); +} + +/* safe_open - safely open or create file */ + +VSTREAM *safe_open(const char *path, int flags, mode_t mode, + struct stat * st, uid_t user, gid_t group, VSTRING *why) +{ + VSTREAM *fp; + + switch (flags & (O_CREAT | O_EXCL)) { + + /* + * Open an existing file, carefully. + */ + case 0: + return (safe_open_exist(path, flags, st, why)); + + /* + * Create a new file, carefully. + */ + case O_CREAT | O_EXCL: + return (safe_open_create(path, flags, mode, st, user, group, why)); + + /* + * Open an existing file or create a new one, carefully. When opening + * an existing file, we are prepared to deal with "no file" errors + * only. When creating a file, we are prepared for "file exists" + * errors only. Any other error means we better give up trying. + */ + case O_CREAT: + fp = safe_open_exist(path, flags, st, why); + if (fp == 0 && errno == ENOENT) { + fp = safe_open_create(path, flags, mode, st, user, group, why); + if (fp == 0 && errno == EEXIST) + fp = safe_open_exist(path, flags, st, why); + } + return (fp); + + /* + * Interface violation. Sorry, but we must be strict. + */ + default: + msg_panic("safe_open: O_EXCL flag without O_CREAT flag"); + } +} diff --git a/src/util/safe_open.h b/src/util/safe_open.h new file mode 100644 index 0000000..88b5344 --- /dev/null +++ b/src/util/safe_open.h @@ -0,0 +1,42 @@ +#ifndef _SAFE_OPEN_H_INCLUDED_ +#define _SAFE_OPEN_H_INCLUDED_ + +/*++ +/* NAME +/* safe_open 3h +/* SUMMARY +/* safely open or create regular file +/* SYNOPSIS +/* #include <safe_open.h> +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include <sys/stat.h> +#include <fcntl.h> + + /* + * Utility library. + */ +#include <vstream.h> +#include <vstring.h> + + /* + * External interface. + */ +extern VSTREAM *safe_open(const char *, int, mode_t, struct stat *, uid_t, gid_t, VSTRING *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/sane_accept.c b/src/util/sane_accept.c new file mode 100644 index 0000000..86e3d34 --- /dev/null +++ b/src/util/sane_accept.c @@ -0,0 +1,125 @@ +/*++ +/* NAME +/* sane_accept 3 +/* SUMMARY +/* sanitize accept() error returns +/* SYNOPSIS +/* #include <sane_accept.h> +/* +/* int sane_accept(sock, buf, len) +/* int sock; +/* struct sockaddr *buf; +/* SOCKADDR_SIZE *len; +/* DESCRIPTION +/* sane_accept() implements the accept(2) socket call, and maps +/* known harmless error results to EAGAIN. +/* +/* If the buf and len arguments are not null, then additional +/* workarounds may be enabled that depend on the socket type. +/* BUGS +/* Bizarre systems may have other harmless error results. Such +/* systems encourage programmers to ignore error results, and +/* penalize programmers who code defensively. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <sys/socket.h> +#include <errno.h> + +/* Utility library. */ + +#include "msg.h" +#include "sane_accept.h" + +/* sane_accept - sanitize accept() error returns */ + +int sane_accept(int sock, struct sockaddr *sa, SOCKADDR_SIZE *len) +{ + static int accept_ok_errors[] = { + EAGAIN, + ECONNREFUSED, + ECONNRESET, + EHOSTDOWN, + EHOSTUNREACH, + EINTR, + ENETDOWN, + ENETUNREACH, + ENOTCONN, + EWOULDBLOCK, + ENOBUFS, /* HPUX11 */ + ECONNABORTED, +#ifdef EPROTO + EPROTO, /* SunOS 5.5.1 */ +#endif + 0, + }; + int count; + int err; + int fd; + + /* + * XXX Solaris 2.4 accept() returns EPIPE when a UNIX-domain client has + * disconnected in the mean time. From then on, UNIX-domain sockets are + * hosed beyond recovery. There is no point treating this as a beneficial + * error result because the program would go into a tight loop. + * + * XXX Solaris 2.5.1 accept() returns EPROTO when a TCP client has + * disconnected in the mean time. Since there is no connection, it is + * safe to map the error code onto EAGAIN. + * + * XXX LINUX < 2.1 accept() wakes up before the three-way handshake is + * complete, so it can fail with ECONNRESET and other "false alarm" + * indications. + * + * XXX FreeBSD 4.2-STABLE accept() returns ECONNABORTED when a UNIX-domain + * client has disconnected in the mean time. The data that was sent with + * connect() write() close() is lost, even though the write() and close() + * reported successful completion. This was fixed shortly before FreeBSD + * 4.3. + * + * XXX HP-UX 11 returns ENOBUFS when the client has disconnected in the mean + * time. + */ + if ((fd = accept(sock, sa, len)) < 0) { + for (count = 0; (err = accept_ok_errors[count]) != 0; count++) { + if (errno == err) { + errno = EAGAIN; + break; + } + } + } + + /* + * XXX Solaris select() produces false read events, so that read() blocks + * forever on a blocking socket, and fails with EAGAIN on a non-blocking + * socket. Turning on keepalives will fix a blocking socket provided that + * the kernel's keepalive timer expires before the Postfix watchdog + * timer. + * + * XXX Work around NAT induced damage by sending a keepalive before an idle + * connection is expired. This requires that the kernel keepalive timer + * is set to a short time, like 100s. + */ + else if (sa && (sa->sa_family == AF_INET +#ifdef HAS_IPV6 + || sa->sa_family == AF_INET6 +#endif + )) { + int on = 1; + + (void) setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, + (void *) &on, sizeof(on)); + } + return (fd); +} diff --git a/src/util/sane_accept.h b/src/util/sane_accept.h new file mode 100644 index 0000000..84cc360 --- /dev/null +++ b/src/util/sane_accept.h @@ -0,0 +1,29 @@ +#ifndef _SANE_ACCEPT_H_ +#define _SANE_ACCEPT_H_ + +/*++ +/* NAME +/* sane_accept 3h +/* SUMMARY +/* sanitize accept() error returns +/* SYNOPSIS +/* #include <sane_accept.h> +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern int sane_accept(int, struct sockaddr *, SOCKADDR_SIZE *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/sane_basename.c b/src/util/sane_basename.c new file mode 100644 index 0000000..6c3a4c1 --- /dev/null +++ b/src/util/sane_basename.c @@ -0,0 +1,181 @@ +/*++ +/* NAME +/* sane_basename 3 +/* SUMMARY +/* split pathname into last component and parent directory +/* SYNOPSIS +/* #include <stringops.h> +/* +/* char *sane_basename(buf, path) +/* VSTRING *buf; +/* const char *path; +/* +/* char *sane_dirname(buf, path) +/* VSTRING *buf; +/* const char *path; +/* DESCRIPTION +/* These functions split a pathname into its last component +/* and its parent directory, excluding any trailing "/" +/* characters from the input. The result is a pointer to "/" +/* when the input is all "/" characters, or a pointer to "." +/* when the input is a null pointer or zero-length string. +/* +/* sane_basename() and sane_dirname() differ as follows +/* from standard basename() and dirname() implementations: +/* .IP \(bu +/* They can use caller-provided storage or private storage. +/* .IP \(bu +/* They never modify their input. +/* .PP +/* sane_basename() returns a pointer to string with the last +/* pathname component. +/* +/* sane_dirname() returns a pointer to string with the parent +/* directory. The result is a pointer to "." when the input +/* contains no '/' character. +/* +/* Arguments: +/* .IP buf +/* Result storage. If a null pointer is specified, each function +/* uses its own private memory that is overwritten upon each call. +/* .IP path +/* The input pathname. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this +/* software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> + +/* Utility library. */ + +#include <vstring.h> +#include <stringops.h> + +#define STR(x) vstring_str(x) + +/* sane_basename - skip directory prefix */ + +char *sane_basename(VSTRING *bp, const char *path) +{ + static VSTRING *buf; + const char *first; + const char *last; + + /* + * Your buffer or mine? + */ + if (bp == 0) { + bp = buf; + if (bp == 0) + bp = buf = vstring_alloc(10); + } + + /* + * Special case: return "." for null or zero-length input. + */ + if (path == 0 || *path == 0) + return (STR(vstring_strcpy(bp, "."))); + + /* + * Remove trailing '/' characters from input. Return "/" if input is all + * '/' characters. + */ + last = path + strlen(path) - 1; + while (*last == '/') { + if (last == path) + return (STR(vstring_strcpy(bp, "/"))); + last--; + } + + /* + * The pathname does not end in '/'. Skip to last '/' character if any. + */ + first = last - 1; + while (first >= path && *first != '/') + first--; + + return (STR(vstring_strncpy(bp, first + 1, last - first))); +} + +/* sane_dirname - keep directory prefix */ + +char *sane_dirname(VSTRING *bp, const char *path) +{ + static VSTRING *buf; + const char *last; + + /* + * Your buffer or mine? + */ + if (bp == 0) { + bp = buf; + if (bp == 0) + bp = buf = vstring_alloc(10); + } + + /* + * Special case: return "." for null or zero-length input. + */ + if (path == 0 || *path == 0) + return (STR(vstring_strcpy(bp, "."))); + + /* + * Remove trailing '/' characters from input. Return "/" if input is all + * '/' characters. + */ + last = path + strlen(path) - 1; + while (*last == '/') { + if (last == path) + return (STR(vstring_strcpy(bp, "/"))); + last--; + } + + /* + * This pathname does not end in '/'. Skip to last '/' character if any. + */ + while (last >= path && *last != '/') + last--; + if (last < path) /* no '/' */ + return (STR(vstring_strcpy(bp, "."))); + + /* + * Strip trailing '/' characters from dirname (not strictly needed). + */ + while (last > path && *last == '/') + last--; + + return (STR(vstring_strncpy(bp, path, last - path + 1))); +} + +#ifdef TEST +#include <vstring_vstream.h> + +int main(int argc, char **argv) +{ + VSTRING *buf = vstring_alloc(10); + char *dir; + char *base; + + while (vstring_get_nonl(buf, VSTREAM_IN) > 0) { + dir = sane_dirname((VSTRING *) 0, STR(buf)); + base = sane_basename((VSTRING *) 0, STR(buf)); + vstream_printf("input=\"%s\" dir=\"%s\" base=\"%s\"\n", + STR(buf), dir, base); + } + vstream_fflush(VSTREAM_OUT); + vstring_free(buf); + return (0); +} + +#endif diff --git a/src/util/sane_basename.in b/src/util/sane_basename.in new file mode 100644 index 0000000..75d613d --- /dev/null +++ b/src/util/sane_basename.in @@ -0,0 +1,18 @@ +/// +/ +fo2/// +fo2/ +fo2 +///fo2 +/fo2 +///fo2///bar/// +fo2///bar/// +fo2///bar/ +fo2/bar + +/usr/lib +/usr/ +usr +/ +. +.. diff --git a/src/util/sane_basename.ref b/src/util/sane_basename.ref new file mode 100644 index 0000000..8edcfcd --- /dev/null +++ b/src/util/sane_basename.ref @@ -0,0 +1,18 @@ +input="///" dir="/" base="/" +input="/" dir="/" base="/" +input="fo2///" dir="." base="fo2" +input="fo2/" dir="." base="fo2" +input="fo2" dir="." base="fo2" +input="///fo2" dir="/" base="fo2" +input="/fo2" dir="/" base="fo2" +input="///fo2///bar///" dir="///fo2" base="bar" +input="fo2///bar///" dir="fo2" base="bar" +input="fo2///bar/" dir="fo2" base="bar" +input="fo2/bar" dir="fo2" base="bar" +input="" dir="." base="." +input="/usr/lib" dir="/usr" base="lib" +input="/usr/" dir="/" base="usr" +input="usr" dir="." base="usr" +input="/" dir="/" base="/" +input="." dir="." base="." +input=".." dir="." base=".." diff --git a/src/util/sane_connect.c b/src/util/sane_connect.c new file mode 100644 index 0000000..a15204b --- /dev/null +++ b/src/util/sane_connect.c @@ -0,0 +1,65 @@ +/*++ +/* NAME +/* sane_connect 3 +/* SUMMARY +/* sanitize connect() results +/* SYNOPSIS +/* #include <sane_connect.h> +/* +/* int sane_connect(sock, buf, len) +/* int sock; +/* struct sockaddr *buf; +/* SOCKADDR_SIZE *len; +/* DESCRIPTION +/* sane_connect() implements the connect(2) socket call, and maps +/* known harmless error results to EAGAIN. +/* BUGS +/* Bizarre systems may have other harmless error results. Such +/* systems encourage programmers to ignore error results, and +/* penalize programmers who code defensively. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <sys/socket.h> +#include <errno.h> + +/* Utility library. */ + +#include "msg.h" +#include "sane_connect.h" + +/* sane_connect - sanitize connect() results */ + +int sane_connect(int sock, struct sockaddr *sa, SOCKADDR_SIZE len) +{ + + /* + * XXX Solaris select() produces false read events, so that read() blocks + * forever on a blocking socket, and fails with EAGAIN on a non-blocking + * socket. Turning on keepalives will fix a blocking socket provided that + * the kernel's keepalive timer expires before the Postfix watchdog + * timer. + * + * XXX Work around NAT induced damage by sending a keepalive before an idle + * connection is expired. This requires that the kernel keepalive timer + * is set to a short time, like 100s. + */ + if (sa->sa_family == AF_INET) { + int on = 1; + + (void) setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, + (void *) &on, sizeof(on)); + } + return (connect(sock, sa, len)); +} diff --git a/src/util/sane_connect.h b/src/util/sane_connect.h new file mode 100644 index 0000000..1f023b0 --- /dev/null +++ b/src/util/sane_connect.h @@ -0,0 +1,29 @@ +#ifndef _SANE_CONNECT_H_ +#define _SANE_CONNECT_H_ + +/*++ +/* NAME +/* sane_connect 3h +/* SUMMARY +/* sanitize connect() results +/* SYNOPSIS +/* #include <sane_connect.h> +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern int sane_connect(int, struct sockaddr *, SOCKADDR_SIZE); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/sane_fsops.h b/src/util/sane_fsops.h new file mode 100644 index 0000000..28e7f67 --- /dev/null +++ b/src/util/sane_fsops.h @@ -0,0 +1,35 @@ +#ifndef _SANE_FSOPS_H_ +#define _SANE_FSOPS_H_ + +/*++ +/* NAME +/* sane_rename 3h +/* SUMMARY +/* sanitize rename() error returns +/* SYNOPSIS +/* #include <sane_rename.h> +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern int WARN_UNUSED_RESULT sane_rename(const char *, const char *); +extern int WARN_UNUSED_RESULT sane_link(const char *, const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/sane_link.c b/src/util/sane_link.c new file mode 100644 index 0000000..40fd56d --- /dev/null +++ b/src/util/sane_link.c @@ -0,0 +1,72 @@ +/*++ +/* NAME +/* sane_link 3 +/* SUMMARY +/* sanitize link() error returns +/* SYNOPSIS +/* #include <sane_fsops.h> +/* +/* int sane_link(from, to) +/* const char *from; +/* const char *to; +/* DESCRIPTION +/* sane_link() implements the link(2) system call, and works +/* around some errors that are possible with NFS file systems. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <sys/stat.h> +#include <errno.h> +#include <unistd.h> + +/* Utility library. */ + +#include "msg.h" +#include "sane_fsops.h" +#include "warn_stat.h" + +/* sane_link - sanitize link() error returns */ + +int sane_link(const char *from, const char *to) +{ + const char *myname = "sane_link"; + int saved_errno; + struct stat from_st; + struct stat to_st; + + /* + * Normal case: link() succeeds. + */ + if (link(from, to) >= 0) + return (0); + + /* + * Woops. Save errno, and see if the error is an NFS artifact. If it is, + * pretend the error never happened. + */ + saved_errno = errno; + if (stat(from, &from_st) >= 0 && stat(to, &to_st) >= 0 + && from_st.st_dev == to_st.st_dev + && from_st.st_ino == to_st.st_ino) { + msg_info("%s(%s,%s): worked around spurious NFS error", + myname, from, to); + return (0); + } + + /* + * Nope, it didn't. Restore errno and report the error. + */ + errno = saved_errno; + return (-1); +} diff --git a/src/util/sane_rename.c b/src/util/sane_rename.c new file mode 100644 index 0000000..3b301bd --- /dev/null +++ b/src/util/sane_rename.c @@ -0,0 +1,69 @@ +/*++ +/* NAME +/* sane_rename 3 +/* SUMMARY +/* sanitize rename() error returns +/* SYNOPSIS +/* #include <sane_fsops.h> +/* +/* int sane_rename(old, new) +/* const char *from; +/* const char *to; +/* DESCRIPTION +/* sane_rename() implements the rename(2) system call, and works +/* around some errors that are possible with NFS file systems. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <sys/stat.h> +#include <errno.h> +#include <stdio.h> /* rename(2) syscall in stdio.h? */ + +/* Utility library. */ + +#include "msg.h" +#include "sane_fsops.h" +#include "warn_stat.h" + +/* sane_rename - sanitize rename() error returns */ + +int sane_rename(const char *from, const char *to) +{ + const char *myname = "sane_rename"; + int saved_errno; + struct stat st; + + /* + * Normal case: rename() succeeds. + */ + if (rename(from, to) >= 0) + return (0); + + /* + * Woops. Save errno, and see if the error is an NFS artifact. If it is, + * pretend the error never happened. + */ + saved_errno = errno; + if (stat(from, &st) < 0 && stat(to, &st) >= 0) { + msg_info("%s(%s,%s): worked around spurious NFS error", + myname, from, to); + return (0); + } + + /* + * Nope, it didn't. Restore errno and report the error. + */ + errno = saved_errno; + return (-1); +} diff --git a/src/util/sane_socketpair.c b/src/util/sane_socketpair.c new file mode 100644 index 0000000..a889934 --- /dev/null +++ b/src/util/sane_socketpair.c @@ -0,0 +1,71 @@ +/*++ +/* NAME +/* sane_socketpair 3 +/* SUMMARY +/* sanitize socketpair() error returns +/* SYNOPSIS +/* #include <sane_socketpair.h> +/* +/* int sane_socketpair(domain, type, protocol, result) +/* int domain; +/* int type; +/* int protocol; +/* int *result; +/* DESCRIPTION +/* sane_socketpair() implements the socketpair(2) socket call, and +/* skips over silly error results such as EINTR. +/* BUGS +/* Bizarre systems may have other harmless error results. Such +/* systems encourage programmers to ignore error results, and +/* penalize programmers who code defensively. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <sys/socket.h> +#include <unistd.h> +#include <errno.h> + +/* Utility library. */ + +#include "msg.h" +#include "sane_socketpair.h" + +/* sane_socketpair - sanitize socketpair() error returns */ + +int sane_socketpair(int domain, int type, int protocol, int *result) +{ + static int socketpair_ok_errors[] = { + EINTR, + 0, + }; + int count; + int err; + int ret; + + /* + * Solaris socketpair() can fail with EINTR. + */ + while ((ret = socketpair(domain, type, protocol, result)) < 0) { + for (count = 0; /* void */ ; count++) { + if ((err = socketpair_ok_errors[count]) == 0) + return (ret); + if (errno == err) { + msg_warn("socketpair: %m (trying again)"); + sleep(1); + break; + } + } + } + return (ret); +} diff --git a/src/util/sane_socketpair.h b/src/util/sane_socketpair.h new file mode 100644 index 0000000..d54a73d --- /dev/null +++ b/src/util/sane_socketpair.h @@ -0,0 +1,34 @@ +#ifndef _SANE_SOCKETPAIR_H_ +#define _SANE_SOCKETPAIR_H_ + +/*++ +/* NAME +/* sane_socketpair 3h +/* SUMMARY +/* sanitize socketpair() error returns +/* SYNOPSIS +/* #include <sane_socketpair.h> +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern int WARN_UNUSED_RESULT sane_socketpair(int, int, int, int *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/sane_strtol.c b/src/util/sane_strtol.c new file mode 100644 index 0000000..b7435dd --- /dev/null +++ b/src/util/sane_strtol.c @@ -0,0 +1,59 @@ +/*++ +/* NAME +/* sane_strtol 3 +/* SUMMARY +/* strtol() with mandatory errno reset +/* SYNOPSIS +/* #include <sane_strtol.h> +/* +/* long sane_strtol( +/* const char *start, +/* char **restrict end, +/* int base) +/* +/* unsigned long sane_strtoul( +/* const char *start, +/* char **restrict end, +/* int base) +/* DESCRIPTION +/* These functions are wrappers around the strtol() and strtoul() +/* standard library functions that reset errno first, so that a +/* prior ERANGE error won't cause false errors. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include <sys_defs.h> +#include <stdlib.h> +#include <errno.h> + + /* + * Utility library. + */ +#include <sane_strtol.h> + +/* sane_strtol - strtol() with mandatory initialization */ + +long sane_strtol(const char *start, char **end, int base) +{ + errno = 0; + return (strtol(start, end, base)); +} + +/* sane_strtoul - strtoul() with mandatory initialization */ + +unsigned long sane_strtoul(const char *start, char **end, int base) +{ + errno = 0; + return (strtoul(start, end, base)); +} diff --git a/src/util/sane_strtol.h b/src/util/sane_strtol.h new file mode 100644 index 0000000..ac08316 --- /dev/null +++ b/src/util/sane_strtol.h @@ -0,0 +1,26 @@ +/*++ +/* NAME +/* sane_strtol 3h +/* SUMMARY +/* strtol() with mandatory errno reset +/* SYNOPSIS +/* #include <sane_strtol.h> +/* DESCRIPTION +/* .nf + + /* + * External API. + */ +extern long sane_strtol(const char *start, char **end, int); +extern unsigned long sane_strtoul(const char *start, char **end, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ diff --git a/src/util/sane_time.c b/src/util/sane_time.c new file mode 100644 index 0000000..cc86de2 --- /dev/null +++ b/src/util/sane_time.c @@ -0,0 +1,127 @@ +/*++ +/* NAME +/* sane_time 3 +/* SUMMARY +/* time(2) with backward time jump protection. +/* SYNOPSIS +/* #include <sane_time.h> +/* +/* time_t sane_time(void) +/* +/* DESCRIPTION +/* This module provides time(2) like call for applications +/* which need monotonically increasing time function rather +/* than the real exact time. It eliminates the need for various +/* workarounds all over the application which would handle +/* potential problems if time suddenly jumps backward. +/* Instead we choose to deal with this problem inside this +/* module and let the application focus on its own tasks. +/* +/* sane_time() returns the current timestamp as obtained from +/* time(2) call, at least most of the time. In case this routine +/* detects that time has jumped backward, it keeps returning +/* whatever timestamp it returned before, until this timestamp +/* and the time(2) timestamp become synchronized again. +/* Additionally, the returned timestamp is slowly increased to +/* prevent the faked clock from freezing for too long. +/* SEE ALSO +/* time(2) get current time +/* DIAGNOSTICS +/* Warning message is logged if backward time jump is detected. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Patrik Rak +/* Modra 6 +/* 155 00, Prague, Czech Republic +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +/* Utility library. */ + +#include <msg.h> + +/* Application-specific. */ + +#include "sane_time.h" + +/* + * How many times shall we slow down the real clock when recovering from + * time jump. + */ +#define SLEW_FACTOR 2 + +/* sane_time - get current time, protected against time warping */ + +time_t sane_time(void) +{ + time_t now; + static time_t last_time, last_real; + long delta; + static int fraction; + static int warned; + + now = time((time_t *) 0); + + if ((delta = now - last_time) < 0 && last_time != 0) { + if ((delta = now - last_real) < 0) { + msg_warn("%sbackward time jump detected -- slewing clock", + warned++ ? "another " : ""); + } else { + delta += fraction; + last_time += delta / SLEW_FACTOR; + fraction = delta % SLEW_FACTOR; + } + } else { + if (warned) { + warned = 0; + msg_warn("backward time jump recovered -- back to normality"); + fraction = 0; + } + last_time = now; + } + last_real = now; + + return (last_time); +} + +#ifdef TEST + + /* + * Proof-of-concept test program. Repeatedly print current system time and + * time returned by sane_time(). Meanwhile, try stepping your system clock + * back and forth to see what happens. + */ + +#include <stdlib.h> +#include <msg_vstream.h> +#include <iostuff.h> /* doze() */ + +int main(int argc, char **argv) +{ + int delay = 1000000; + time_t now; + + msg_vstream_init(argv[0], VSTREAM_ERR); + + if (argc == 2 && (delay = atol(argv[1]) * 1000) > 0) + /* void */ ; + else if (argc != 1) + msg_fatal("usage: %s [delay in ms (default 1 second)]", argv[0]); + + for (;;) { + now = time((time_t *) 0); + vstream_printf("real: %s", ctime(&now)); + now = sane_time(); + vstream_printf("fake: %s\n", ctime(&now)); + vstream_fflush(VSTREAM_OUT); + doze(delay); + } +} + +#endif diff --git a/src/util/sane_time.h b/src/util/sane_time.h new file mode 100644 index 0000000..0fe50d9 --- /dev/null +++ b/src/util/sane_time.h @@ -0,0 +1,34 @@ +#ifndef _SANE_TIME_H_ +#define _SANE_TIME_H_ + +/*++ +/* NAME +/* sane_time 3h +/* SUMMARY +/* time(2) with backward time jump protection +/* SYNOPSIS +/* #include <sane_time.h> +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include <time.h> + + /* + * External interface. + */ +extern time_t sane_time(void); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Patrik Rak +/* Modra 6 +/* 155 00, Prague, Czech Republic +/*--*/ + +#endif diff --git a/src/util/scan_dir.c b/src/util/scan_dir.c new file mode 100644 index 0000000..d94c674 --- /dev/null +++ b/src/util/scan_dir.c @@ -0,0 +1,216 @@ +/*++ +/* NAME +/* scan_dir 3 +/* SUMMARY +/* directory scanning +/* SYNOPSIS +/* #include <scan_dir.h> +/* +/* SCAN_DIR *scan_dir_open(path) +/* const char *path; +/* +/* char *scan_dir_next(scan) +/* SCAN_DIR *scan; +/* +/* char *scan_dir_path(scan) +/* SCAN_DIR *scan; +/* +/* void scan_push(scan, entry) +/* SCAN_DIR *scan; +/* const char *entry; +/* +/* SCAN_DIR *scan_pop(scan) +/* SCAN_DIR *scan; +/* +/* SCAN_DIR *scan_dir_close(scan) +/* SCAN_DIR *scan; +/* DESCRIPTION +/* These functions scan directories for names. The "." and +/* ".." names are skipped. Essentially, this is <dirent> +/* extended with error handling and with knowledge of the +/* name of the directory being scanned. +/* +/* scan_dir_open() opens the named directory and +/* returns a handle for subsequent use. +/* +/* scan_dir_close() terminates the directory scan, cleans up +/* and returns a null pointer. +/* +/* scan_dir_next() returns the next requested object in the specified +/* directory. It skips the "." and ".." entries. +/* +/* scan_dir_path() returns the name of the directory being scanned. +/* +/* scan_dir_push() causes the specified directory scan to enter the +/* named subdirectory. +/* +/* scan_dir_pop() leaves the directory being scanned and returns +/* to the previous one. The result is the argument, null if no +/* previous directory information is available. +/* DIAGNOSTICS +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#ifdef HAVE_DIRENT_H +#include <dirent.h> +#else +#define dirent direct +#ifdef HAVE_SYS_NDIR_H +#include <sys/ndir.h> +#endif +#ifdef HAVE_SYS_DIR_H +#include <sys/dir.h> +#endif +#ifdef HAVE_NDIR_H +#include <ndir.h> +#endif +#endif +#include <string.h> +#include <errno.h> + +/* Utility library. */ + +#include "msg.h" +#include "mymalloc.h" +#include "stringops.h" +#include "vstring.h" +#include "scan_dir.h" + + /* + * The interface is based on an opaque structure, so we don't have to expose + * the user to the guts. Subdirectory info sits in front of parent directory + * info: a simple last-in, first-out list. + */ +typedef struct SCAN_INFO SCAN_INFO; + +struct SCAN_INFO { + char *path; /* directory name */ + DIR *dir; /* directory structure */ + SCAN_INFO *parent; /* linkage */ +}; +struct SCAN_DIR { + SCAN_INFO *current; /* current scan */ +}; + +#define SCAN_DIR_PATH(scan) (scan->current->path) +#define STR(x) vstring_str(x) + +/* scan_dir_path - return the path of the directory being read. */ + +char *scan_dir_path(SCAN_DIR *scan) +{ + return (SCAN_DIR_PATH(scan)); +} + +/* scan_dir_push - enter directory */ + +void scan_dir_push(SCAN_DIR *scan, const char *path) +{ + const char *myname = "scan_dir_push"; + SCAN_INFO *info; + + info = (SCAN_INFO *) mymalloc(sizeof(*info)); + if (scan->current) + info->path = concatenate(SCAN_DIR_PATH(scan), "/", path, (char *) 0); + else + info->path = mystrdup(path); + if ((info->dir = opendir(info->path)) == 0) + msg_fatal("%s: open directory %s: %m", myname, info->path); + if (msg_verbose > 1) + msg_info("%s: open %s", myname, info->path); + info->parent = scan->current; + scan->current = info; +} + +/* scan_dir_pop - leave directory */ + +SCAN_DIR *scan_dir_pop(SCAN_DIR *scan) +{ + const char *myname = "scan_dir_pop"; + SCAN_INFO *info = scan->current; + SCAN_INFO *parent; + + if (info == 0) + return (0); + parent = info->parent; + if (closedir(info->dir)) + msg_fatal("%s: close directory %s: %m", myname, info->path); + if (msg_verbose > 1) + msg_info("%s: close %s", myname, info->path); + myfree(info->path); + myfree((void *) info); + scan->current = parent; + return (parent ? scan : 0); +} + +/* scan_dir_open - start directory scan */ + +SCAN_DIR *scan_dir_open(const char *path) +{ + SCAN_DIR *scan; + + scan = (SCAN_DIR *) mymalloc(sizeof(*scan)); + scan->current = 0; + scan_dir_push(scan, path); + return (scan); +} + +/* scan_dir_next - find next entry */ + +char *scan_dir_next(SCAN_DIR *scan) +{ + const char *myname = "scan_dir_next"; + SCAN_INFO *info = scan->current; + struct dirent *dp; + +#define STREQ(x,y) (strcmp((x),(y)) == 0) + + if (info) { + + /* + * Fix 20150421: readdir() does not reset errno after reaching the + * end-of-directory. This dates back all the way to the initial + * implementation of 19970309. + */ + errno = 0; + while ((dp = readdir(info->dir)) != 0) { + if (STREQ(dp->d_name, ".") || STREQ(dp->d_name, "..")) { + if (msg_verbose > 1) + msg_info("%s: skip %s", myname, dp->d_name); + continue; + } else { + if (msg_verbose > 1) + msg_info("%s: found %s", myname, dp->d_name); + return (dp->d_name); + } + } + } + return (0); +} + +/* scan_dir_close - terminate directory scan */ + +SCAN_DIR *scan_dir_close(SCAN_DIR *scan) +{ + while (scan->current) + scan_dir_pop(scan); + myfree((void *) scan); + return (0); +} diff --git a/src/util/scan_dir.h b/src/util/scan_dir.h new file mode 100644 index 0000000..8f3bf8b --- /dev/null +++ b/src/util/scan_dir.h @@ -0,0 +1,37 @@ +#ifndef _SCAN_DIR_H_INCLUDED_ +#define _SCAN_DIR_H_INCLUDED_ + +/*++ +/* NAME +/* scan_dir 3h +/* SUMMARY +/* directory scanner +/* SYNOPSIS +/* #include <scan_dir.h> +/* DESCRIPTION +/* .nf + + /* + * The directory scanner interface. + */ +typedef struct SCAN_DIR SCAN_DIR; + +extern SCAN_DIR *scan_dir_open(const char *); +extern char *scan_dir_next(SCAN_DIR *); +extern char *scan_dir_path(SCAN_DIR *); +extern void scan_dir_push(SCAN_DIR *, const char *); +extern SCAN_DIR *scan_dir_pop(SCAN_DIR *); +extern SCAN_DIR *scan_dir_close(SCAN_DIR *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/select_bug.c b/src/util/select_bug.c new file mode 100644 index 0000000..9b479ca --- /dev/null +++ b/src/util/select_bug.c @@ -0,0 +1,95 @@ +/*++ +/* NAME +/* select_bug 1 +/* SUMMARY +/* select test program +/* SYNOPSIS +/* select_bug +/* DESCRIPTION +/* select_bug forks child processes that perform select() +/* on a shared socket, and sees if a wakeup affects other +/* processes selecting on a different socket or stdin. +/* DIAGNOSTICS +/* Problems are reported to the standard error stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/time.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> /* bzero() prototype for 44BSD */ + +/* Utility library. */ + +#include <msg.h> +#include <vstream.h> +#include <msg_vstream.h> + +static pid_t fork_and_read_select(const char *what, int delay, int fd) +{ + struct timeval tv; + pid_t pid; + fd_set readfds; + + switch (pid = fork()) { + case -1: + msg_fatal("fork: %m"); + case 0: + tv.tv_sec = delay; + tv.tv_usec = 0; + FD_ZERO(&readfds); + FD_SET(fd, &readfds); + switch (select(fd + 1, &readfds, (fd_set *) 0, &readfds, &tv)) { + case -1: + msg_fatal("select: %m"); + case 0: + msg_info("%s select timed out", what); + exit(0); + default: + msg_info("%s select wakeup", what); + exit(0); + } + default: + return (pid); + } +} + +int main(int argc, char **argv) +{ + int pair1[2]; + int pair2[2]; + + msg_vstream_init(argv[0], VSTREAM_ERR); + +#define DELAY 1 + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair1) < 0) + msg_fatal("socketpair: %m"); + if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair2) < 0) + msg_fatal("socketpair: %m"); + + vstream_printf("Doing multiple select on socket1, then write to it...\n"); + vstream_fflush(VSTREAM_OUT); + fork_and_read_select("socket1", DELAY, pair1[0]); /* one */ + fork_and_read_select("socket1", DELAY, pair1[0]); /* two */ + fork_and_read_select("socket2", DELAY, pair2[0]); + fork_and_read_select("stdin", DELAY, 0); + if (write(pair1[1], "", 1) != 1) + msg_fatal("write: %m"); + while (wait((int *) 0) >= 0) + /* void */ ; + return (0); +} diff --git a/src/util/set_eugid.c b/src/util/set_eugid.c new file mode 100644 index 0000000..ef35380 --- /dev/null +++ b/src/util/set_eugid.c @@ -0,0 +1,70 @@ +/*++ +/* NAME +/* set_eugid 3 +/* SUMMARY +/* set effective user and group attributes +/* SYNOPSIS +/* #include <set_eugid.h> +/* +/* void set_eugid(euid, egid) +/* uid_t euid; +/* gid_t egid; +/* +/* void SAVE_AND_SET_EUGID(uid, gid) +/* uid_t uid; +/* gid_t gid; +/* +/* void RESTORE_SAVED_EUGID() +/* DESCRIPTION +/* set_eugid() sets the effective user and group process attributes +/* and updates the process group access list to be just the specified +/* effective group id. +/* +/* SAVE_AND_SET_EUGID() opens a block that executes with the +/* specified privilege. RESTORE_SAVED_EUGID() closes the block. +/* DIAGNOSTICS +/* All system call errors are fatal. +/* SEE ALSO +/* seteuid(2), setegid(2), setgroups(2) +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <unistd.h> +#include <grp.h> +#include <errno.h> + +/* Utility library. */ + +#include "msg.h" +#include "set_eugid.h" + +/* set_eugid - set effective user and group attributes */ + +void set_eugid(uid_t euid, gid_t egid) +{ + int saved_errno = errno; + + if (geteuid() != 0) + if (seteuid(0)) + msg_fatal("set_eugid: seteuid(0): %m"); + if (setegid(egid) < 0) + msg_fatal("set_eugid: setegid(%ld): %m", (long) egid); + if (setgroups(1, &egid) < 0) + msg_fatal("set_eugid: setgroups(%ld): %m", (long) egid); + if (euid != 0 && seteuid(euid) < 0) + msg_fatal("set_eugid: seteuid(%ld): %m", (long) euid); + if (msg_verbose) + msg_info("set_eugid: euid %ld egid %ld", (long) euid, (long) egid); + errno = saved_errno; +} diff --git a/src/util/set_eugid.h b/src/util/set_eugid.h new file mode 100644 index 0000000..97a523e --- /dev/null +++ b/src/util/set_eugid.h @@ -0,0 +1,43 @@ +#ifndef _SET_EUGID_H_INCLUDED_ +#define _SET_EUGID_H_INCLUDED_ + +/*++ +/* NAME +/* set_eugid 3h +/* SUMMARY +/* set effective user and group attributes +/* SYNOPSIS +/* #include <set_eugid.h> +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern void set_eugid(uid_t, gid_t); + + /* + * The following macros open and close a block that runs at a different + * privilege level. To make mistakes with stray curly braces less likely, we + * shape the macros below as the head and tail of a do-while loop. + */ +#define SAVE_AND_SET_EUGID(uid, gid) do { \ + uid_t __set_eugid_uid = geteuid(); \ + gid_t __set_eugid_gid = getegid(); \ + set_eugid((uid), (gid)); + +#define RESTORE_SAVED_EUGID() \ + set_eugid(__set_eugid_uid, __set_eugid_gid); \ + } while (0) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/set_ugid.c b/src/util/set_ugid.c new file mode 100644 index 0000000..bbcb901 --- /dev/null +++ b/src/util/set_ugid.c @@ -0,0 +1,61 @@ +/*++ +/* NAME +/* set_ugid 3 +/* SUMMARY +/* set real, effective and saved user and group attributes +/* SYNOPSIS +/* #include <set_ugid.h> +/* +/* void set_ugid(uid, gid) +/* uid_t uid; +/* gid_t gid; +/* DESCRIPTION +/* set_ugid() sets the real, effective and saved user and group process +/* attributes and updates the process group access list to be just the +/* user's primary group. This operation is irreversible. +/* DIAGNOSTICS +/* All system call errors are fatal. +/* SEE ALSO +/* setuid(2), setgid(2), setgroups(2) +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <unistd.h> +#include <grp.h> +#include <errno.h> + +/* Utility library. */ + +#include "msg.h" +#include "set_ugid.h" + +/* set_ugid - set real, effective and saved user and group attributes */ + +void set_ugid(uid_t uid, gid_t gid) +{ + int saved_errno = errno; + + if (geteuid() != 0) + if (seteuid(0) < 0) + msg_fatal("seteuid(0): %m"); + if (setgid(gid) < 0) + msg_fatal("setgid(%ld): %m", (long) gid); + if (setgroups(1, &gid) < 0) + msg_fatal("setgroups(1, &%ld): %m", (long) gid); + if (setuid(uid) < 0) + msg_fatal("setuid(%ld): %m", (long) uid); + if (msg_verbose > 1) + msg_info("setugid: uid %ld gid %ld", (long) uid, (long) gid); + errno = saved_errno; +} diff --git a/src/util/set_ugid.h b/src/util/set_ugid.h new file mode 100644 index 0000000..e752beb --- /dev/null +++ b/src/util/set_ugid.h @@ -0,0 +1,29 @@ +#ifndef _SET_UGID_H_INCLUDED_ +#define _SET_UGID_H_INCLUDED_ + +/*++ +/* NAME +/* set_ugid 3h +/* SUMMARY +/* set real, effective and saved user and group attributes +/* SYNOPSIS +/* #include <set_ugid.h> +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern void set_ugid(uid_t, gid_t); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/sigdelay.c b/src/util/sigdelay.c new file mode 100644 index 0000000..6f07a22 --- /dev/null +++ b/src/util/sigdelay.c @@ -0,0 +1,118 @@ +/*++ +/* NAME +/* sigdelay 3 +/* SUMMARY +/* delay/resume signal delivery +/* SYNOPSIS +/* #include <sigdelay.h> +/* +/* void sigdelay() +/* +/* void sigresume() +/* DESCRIPTION +/* sigdelay() delays delivery of signals. Signals that +/* arrive in the mean time will be queued. +/* +/* sigresume() resumes delivery of signals. Signals that have +/* arrived in the mean time will be delivered. +/* DIAGNOSTICS +/* All errors are fatal. +/* BUGS +/* The signal queue may be really short (as in: one per signal type). +/* +/* Some signals such as SIGKILL cannot be blocked. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <signal.h> + +/* Utility library. */ + +#include "msg.h" +#include "posix_signals.h" +#include "sigdelay.h" + +/* Application-specific. */ + +static sigset_t saved_sigmask; +static sigset_t block_sigmask; +static int suspending; +static int siginit_done; + +/* siginit - compute signal mask only once */ + +static void siginit(void) +{ + int sig; + + siginit_done = 1; + sigemptyset(&block_sigmask); + for (sig = 1; sig < NSIG; sig++) + sigaddset(&block_sigmask, sig); +} + +/* sigresume - deliver delayed signals and disable signal delay */ + +void sigresume(void) +{ + if (suspending != 0) { + suspending = 0; + if (sigprocmask(SIG_SETMASK, &saved_sigmask, (sigset_t *) 0) < 0) + msg_fatal("sigresume: sigprocmask: %m"); + } +} + +/* sigdelay - save signal mask and block all signals */ + +void sigdelay(void) +{ + if (siginit_done == 0) + siginit(); + if (suspending == 0) { + suspending = 1; + if (sigprocmask(SIG_BLOCK, &block_sigmask, &saved_sigmask) < 0) + msg_fatal("sigdelay: sigprocmask: %m"); + } +} + +#ifdef TEST + + /* + * Test program - press Ctrl-C twice while signal delivery is delayed, and + * see how many signals are delivered when signal delivery is resumed. + */ + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> + +static void gotsig(int sig) +{ + printf("Got signal %d\n", sig); +} + +int main(int unused_argc, char **unused_argv) +{ + signal(SIGINT, gotsig); + signal(SIGQUIT, gotsig); + + printf("Delaying signal delivery\n"); + sigdelay(); + sleep(5); + printf("Resuming signal delivery\n"); + sigresume(); + exit(0); +} + +#endif diff --git a/src/util/sigdelay.h b/src/util/sigdelay.h new file mode 100644 index 0000000..d3b4ea3 --- /dev/null +++ b/src/util/sigdelay.h @@ -0,0 +1,31 @@ +#ifndef _SIGDELAY_H_INCLUDED_ +#define _SIGDELAY_H_INCLUDED_ + +/*++ +/* NAME +/* sigdelay 3h +/* SUMMARY +/* delay/resume signal delivery +/* SYNOPSIS +/* #include <sigdelay.h> +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern void sigdelay(void); +extern void sigresume(void); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/skipblanks.c b/src/util/skipblanks.c new file mode 100644 index 0000000..fc19284 --- /dev/null +++ b/src/util/skipblanks.c @@ -0,0 +1,43 @@ +/*++ +/* NAME +/* skipblanks 3 +/* SUMMARY +/* skip leading whitespace +/* SYNOPSIS +/* #include <stringops.h> +/* +/* char *skipblanks(string) +/* const char *string; +/* DESCRIPTION +/* skipblanks() returns a pointer to the first non-whitespace +/* character in the specified string, or a pointer to the string +/* terminator when the string contains all white-space characters. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <ctype.h> + +/* Utility library. */ + +#include "stringops.h" + +char *skipblanks(const char *string) +{ + const char *cp; + + for (cp = string; *cp != 0; cp++) + if (!ISSPACE(*cp)) + break; + return ((char *) cp); +} diff --git a/src/util/slmdb.c b/src/util/slmdb.c new file mode 100644 index 0000000..499589d --- /dev/null +++ b/src/util/slmdb.c @@ -0,0 +1,938 @@ +/*++ +/* NAME +/* slmdb 3 +/* SUMMARY +/* Simplified LMDB API +/* SYNOPSIS +/* #include <slmdb.h> +/* +/* int slmdb_init(slmdb, curr_limit, size_incr, hard_limit) +/* SLMDB *slmdb; +/* size_t curr_limit; +/* int size_incr; +/* size_t hard_limit; +/* +/* int slmdb_open(slmdb, path, open_flags, lmdb_flags, slmdb_flags) +/* SLMDB *slmdb; +/* const char *path; +/* int open_flags; +/* int lmdb_flags; +/* int slmdb_flags; +/* +/* int slmdb_close(slmdb) +/* SLMDB *slmdb; +/* +/* int slmdb_get(slmdb, mdb_key, mdb_value) +/* SLMDB *slmdb; +/* MDB_val *mdb_key; +/* MDB_val *mdb_value; +/* +/* int slmdb_put(slmdb, mdb_key, mdb_value, flags) +/* SLMDB *slmdb; +/* MDB_val *mdb_key; +/* MDB_val *mdb_value; +/* int flags; +/* +/* int slmdb_del(slmdb, mdb_key) +/* SLMDB *slmdb; +/* MDB_val *mdb_key; +/* +/* int slmdb_cursor_get(slmdb, mdb_key, mdb_value, op) +/* SLMDB *slmdb; +/* MDB_val *mdb_key; +/* MDB_val *mdb_value; +/* MDB_cursor_op op; +/* AUXILIARY FUNCTIONS +/* int slmdb_fd(slmdb) +/* SLMDB *slmdb; +/* +/* size_t slmdb_curr_limit(slmdb) +/* SLMDB *slmdb; +/* +/* int slmdb_control(slmdb, request, ...) +/* SLMDB *slmdb; +/* int request; +/* DESCRIPTION +/* This module simplifies the LMDB API by hiding recoverable +/* errors from the application. Details are given in the +/* section "ERROR RECOVERY". +/* +/* slmdb_init() performs mandatory initialization before opening +/* an LMDB database. The result value is an LMDB status code +/* (zero in case of success). +/* +/* slmdb_open() opens an LMDB database. The result value is +/* an LMDB status code (zero in case of success). +/* +/* slmdb_close() finalizes an optional bulk-mode transaction +/* and closes a successfully-opened LMDB database. The result +/* value is an LMDB status code (zero in case of success). +/* +/* slmdb_get() is an mdb_get() wrapper with automatic error +/* recovery. The result value is an LMDB status code (zero +/* in case of success). +/* +/* slmdb_put() is an mdb_put() wrapper with automatic error +/* recovery. The result value is an LMDB status code (zero +/* in case of success). +/* +/* slmdb_del() is an mdb_del() wrapper with automatic error +/* recovery. The result value is an LMDB status code (zero +/* in case of success). +/* +/* slmdb_cursor_get() is an mdb_cursor_get() wrapper with +/* automatic error recovery. The result value is an LMDB +/* status code (zero in case of success). This wrapper supports +/* only one cursor per database. +/* +/* slmdb_fd() returns the file descriptor for the specified +/* database. This may be used for file status queries or +/* application-controlled locking. +/* +/* slmdb_curr_limit() returns the current database size limit +/* for the specified database. +/* +/* slmdb_control() specifies optional features. The result is +/* an LMDB status code (zero in case of success). +/* +/* Arguments: +/* .IP slmdb +/* Pointer to caller-provided storage. +/* .IP curr_limit +/* The initial memory mapping size limit. This limit is +/* automatically increased when the database becomes full. +/* .IP size_incr +/* An integer factor by which the memory mapping size limit +/* is increased when the database becomes full. +/* .IP hard_limit +/* The upper bound for the memory mapping size limit. +/* .IP path +/* LMDB database pathname. +/* .IP open_flags +/* Flags that control file open operations. Do not specify +/* locking flags here. +/* .IP lmdb_flags +/* Flags that control the LMDB environment. If MDB_NOLOCK is +/* specified, then each slmdb_get() or slmdb_cursor_get() call +/* must be protected with a shared (or exclusive) external lock, +/* and each slmdb_put() or slmdb_del() call must be protected +/* with an exclusive external lock. A lock may be released +/* after the call returns. A writer may atomically downgrade +/* an exclusive lock to shared, but it must obtain an exclusive +/* lock before making another slmdb(3) write request. +/* .sp +/* Note: when a database is opened with MDB_NOLOCK, external +/* locks such as fcntl() do not protect slmdb(3) requests +/* within the same process against each other. If a program +/* cannot avoid making simultaneous slmdb(3) requests, then +/* it must synchronize these requests with in-process locks, +/* in addition to the per-process fcntl(2) locks. +/* .IP slmdb_flags +/* Bit-wise OR of zero or more of the following: +/* .RS +/* .IP SLMDB_FLAG_BULK +/* Open the database and create a "bulk" transaction that is +/* committed when the database is closed. If MDB_NOLOCK is +/* specified, then the entire transaction must be protected +/* with a persistent external lock. All slmdb_get(), slmdb_put() +/* and slmdb_del() requests will be directed to the "bulk" +/* transaction. +/* .RE +/* .IP mdb_key +/* Pointer to caller-provided lookup key storage. +/* .IP mdb_value +/* Pointer to caller-provided value storage. +/* .IP op +/* LMDB cursor operation. +/* .IP request +/* The start of a list of (name, value) pairs, terminated with +/* CA_SLMDB_CTL_END. The following text enumerates the symbolic +/* request names and the corresponding argument types. +/* .RS +/* .IP "CA_SLMDB_CTL_LONGJMP_FN(void (*)(void *, int))" +/* Call-back function pointer. The function is called to repeat +/* a failed bulk-mode transaction from the start. The arguments +/* are the application context and the setjmp() or sigsetjmp() +/* result value. +/* .IP "CA_SLMDB_CTL_NOTIFY_FN(void (*)(void *, int, ...))" +/* Call-back function pointer. The function is called to report +/* successful error recovery. The arguments are the application +/* context, the MDB error code, and additional arguments that +/* depend on the error code. Details are given in the section +/* "ERROR RECOVERY". +/* .IP "CA_SLMDB_CTL_ASSERT_FN(void (*)(void *, const char *))" +/* Call-back function pointer. The function is called to +/* report an LMDB internal assertion failure. The arguments +/* are the application context, and text that describes the +/* problem. +/* .IP "CA_SLMDB_CTL_CB_CONTEXT(void *)" +/* Application context that is passed in call-back function +/* calls. +/* .IP "CA_SLMDB_CTL_API_RETRY_LIMIT(int)" +/* How many times to recover from LMDB errors within the +/* execution of a single slmdb(3) API call before giving up. +/* .IP "CA_SLMDB_CTL_BULK_RETRY_LIMIT(int)" +/* How many times to recover from a bulk-mode transaction +/* before giving up. +/* .RE +/* ERROR RECOVERY +/* .ad +/* .fi +/* This module automatically repeats failed requests after +/* recoverable errors, up to the limits specified with +/* slmdb_control(). +/* +/* Recoverable errors are reported through an optional +/* notification function specified with slmdb_control(). With +/* recoverable MDB_MAP_FULL and MDB_MAP_RESIZED errors, the +/* additional argument is a size_t value with the updated +/* current database size limit; with recoverable MDB_READERS_FULL +/* errors there is no additional argument. +/* BUGS +/* Recovery from MDB_MAP_FULL involves resizing the database +/* memory mapping. According to LMDB documentation this +/* requires that there is no concurrent activity in the same +/* database by other threads in the same memory address space. +/* SEE ALSO +/* lmdb(3) API manpage (currently, non-existent). +/* AUTHOR(S) +/* Howard Chu +/* Symas Corporation +/* +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * DO NOT include other Postfix-specific header files. This LMDB wrapper + * must be usable outside Postfix. + */ + +#ifdef HAS_LMDB + +/* System library. */ + +#include <sys/stat.h> +#include <errno.h> +#include <fcntl.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> +#include <stdarg.h> +#include <string.h> +#include <stdlib.h> + +/* Application-specific. */ + +#include <slmdb.h> + + /* + * Minimum LMDB patchlevel. + * + * LMDB 0.9.11 allows Postfix daemons to log an LMDB error message instead of + * falling out of the sky without any explanation. Without such logging, + * Postfix with LMDB would be too hard to support. + * + * LMDB 0.9.10 fixes an information leak where LMDB wrote chunks of up to 4096 + * bytes of uninitialized heap memory to a database. This was a security + * violation because it made information persistent that was not meant to be + * persisted, or it was sharing information that was not meant to be shared. + * + * LMDB 0.9.9 allows Postfix to use external (fcntl()-based) locks, instead of + * having to use world-writable LMDB lock files. + * + * LMDB 0.9.8 allows Postfix to update the database size limit on-the-fly, so + * that it can recover from an MDB_MAP_FULL error without having to close + * the database. It also allows an application to "pick up" a new database + * size limit on-the-fly, so that it can recover from an MDB_MAP_RESIZED + * error without having to close the database. + * + * The database size limit that remains is imposed by the hardware memory + * address space (31 or 47 bits, typically) or file system. The LMDB + * implementation is supposed to handle databases larger than physical + * memory. However, this is not necessarily guaranteed for (bulk) + * transactions larger than physical memory. + */ +#if MDB_VERSION_FULL < MDB_VERINT(0, 9, 11) +#error "This Postfix version requires LMDB version 0.9.11 or later" +#endif + + /* + * Error recovery. + * + * The purpose of the slmdb(3) API is to hide LMDB quirks (recoverable + * MAP_FULL, MAP_RESIZED, or MDB_READERS_FULL errors). With these out of the + * way, applications can pretend that those quirks don't exist, and focus on + * their own job. + * + * - To recover from a single-transaction LMDB error, each wrapper function + * uses tail recursion instead of goto. Since LMDB errors are rare, code + * clarity is more important than speed. + * + * - To recover from a bulk-transaction LMDB error, the error-recovery code + * triggers a long jump back into the caller to some pre-arranged point (the + * closest thing that C has to exception handling). The application is then + * expected to repeat the bulk transaction from scratch. + * + * When any code aborts a bulk transaction, it must reset slmdb->txn to null + * to avoid a use-after-free problem in slmdb_close(). + */ + + /* + * Our default retry attempt limits. We allow a few retries per slmdb(3) API + * call for non-bulk transactions. We allow a number of bulk-transaction + * retries that is proportional to the memory address space. + */ +#define SLMDB_DEF_API_RETRY_LIMIT 30 /* Retries per slmdb(3) API call */ +#define SLMDB_DEF_BULK_RETRY_LIMIT \ + (2 * sizeof(size_t) * CHAR_BIT) /* Retries per bulk-mode transaction */ + + /* + * We increment the recursion counter each time we try to recover from + * error, and reset the recursion counter when returning to the application + * from the slmdb(3) API. + */ +#define SLMDB_API_RETURN(slmdb, status) do { \ + (slmdb)->api_retry_count = 0; \ + return (status); \ + } while (0) + + /* + * With MDB_NOLOCK, the application uses an external lock for inter-process + * synchronization. Because the caller may release the external lock after + * an SLMDB API call, each SLMDB API function must use a short-lived + * transaction unless the transaction is a bulk-mode transaction. + */ + +/* slmdb_cursor_close - close cursor and its read transaction */ + +static void slmdb_cursor_close(SLMDB *slmdb) +{ + MDB_txn *txn; + + /* + * Close the cursor and its read transaction. We can restore it later + * from the saved key information. + */ + txn = mdb_cursor_txn(slmdb->cursor); + mdb_cursor_close(slmdb->cursor); + slmdb->cursor = 0; + mdb_txn_abort(txn); +} + +/* slmdb_saved_key_init - initialize saved key info */ + +static void slmdb_saved_key_init(SLMDB *slmdb) +{ + slmdb->saved_key.mv_data = 0; + slmdb->saved_key.mv_size = 0; + slmdb->saved_key_size = 0; +} + +/* slmdb_saved_key_free - destroy saved key info */ + +static void slmdb_saved_key_free(SLMDB *slmdb) +{ + free(slmdb->saved_key.mv_data); + slmdb_saved_key_init(slmdb); +} + +#define HAVE_SLMDB_SAVED_KEY(s) ((s)->saved_key.mv_data != 0) + +/* slmdb_saved_key_assign - copy the saved key */ + +static int slmdb_saved_key_assign(SLMDB *slmdb, MDB_val *key_val) +{ + + /* + * Extend the buffer to fit the key, so that we can avoid malloc() + * overhead most of the time. + */ + if (slmdb->saved_key_size < key_val->mv_size) { + if (slmdb->saved_key.mv_data == 0) + slmdb->saved_key.mv_data = malloc(key_val->mv_size); + else + slmdb->saved_key.mv_data = + realloc(slmdb->saved_key.mv_data, key_val->mv_size); + if (slmdb->saved_key.mv_data == 0) { + slmdb_saved_key_init(slmdb); + return (ENOMEM); + } else { + slmdb->saved_key_size = key_val->mv_size; + } + } + + /* + * Copy the key under the cursor. + */ + memcpy(slmdb->saved_key.mv_data, key_val->mv_data, key_val->mv_size); + slmdb->saved_key.mv_size = key_val->mv_size; + return (0); +} + +/* slmdb_prepare - LMDB-specific (re)initialization before actual access */ + +static int slmdb_prepare(SLMDB *slmdb) +{ + int status = 0; + + /* + * This is called before accessing the database, or after recovery from + * an LMDB error. Note: this code cannot recover from errors itself. + * slmdb->txn is either the database open() transaction or a + * freshly-created bulk-mode transaction. When slmdb_prepare() commits or + * aborts commits a transaction, it must set slmdb->txn to null to avoid + * a use-after-free error in slmdb_close(). + * + * - With O_TRUNC we make a "drop" request before updating the database. + * + * - With a bulk-mode transaction we commit when the database is closed. + */ + if (slmdb->open_flags & O_TRUNC) { + if ((status = mdb_drop(slmdb->txn, slmdb->dbi, 0)) != 0) { + mdb_txn_abort(slmdb->txn); + slmdb->txn = 0; + return (status); + } + if ((slmdb->slmdb_flags & SLMDB_FLAG_BULK) == 0) { + status = mdb_txn_commit(slmdb->txn); + slmdb->txn = 0; + if (status != 0) + return (status); + } + } else if ((slmdb->slmdb_flags & SLMDB_FLAG_BULK) == 0) { + mdb_txn_abort(slmdb->txn); + slmdb->txn = 0; + } + slmdb->api_retry_count = 0; + return (status); +} + +/* slmdb_recover - recover from LMDB errors */ + +static int slmdb_recover(SLMDB *slmdb, int status) +{ + MDB_envinfo info; + int original_status = status; + + /* + * This may be needed in non-MDB_NOLOCK mode. Recovery is rare enough + * that we don't care about a few wasted cycles. + */ + if (slmdb->cursor != 0) + slmdb_cursor_close(slmdb); + + /* + * Limit the number of recovery attempts per slmdb(3) API request. + */ + if ((slmdb->api_retry_count += 1) >= slmdb->api_retry_limit) + return (status); + + /* + * Limit the number of bulk transaction recovery attempts. + */ + if ((slmdb->slmdb_flags & SLMDB_FLAG_BULK) != 0 + && (slmdb->bulk_retry_count += 1) > slmdb->bulk_retry_limit) + return (status); + + /* + * Try to clear the error condition. + */ + switch (status) { + + /* + * As of LMDB 0.9.8 when a non-bulk update runs into a "map full" + * error, we can resize the environment's memory map and clear the + * error condition. The caller should retry immediately. + */ + case MDB_MAP_FULL: + /* Can we increase the memory map? Give up if we can't. */ + if (slmdb->curr_limit < slmdb->hard_limit / slmdb->size_incr) { + slmdb->curr_limit = slmdb->curr_limit * slmdb->size_incr; + } else if (slmdb->curr_limit < slmdb->hard_limit) { + slmdb->curr_limit = slmdb->hard_limit; + } else { + /* Sorry, we are already maxed out. */ + break; + } + if (slmdb->notify_fn) + slmdb->notify_fn(slmdb->cb_context, MDB_MAP_FULL, + slmdb->curr_limit); + status = mdb_env_set_mapsize(slmdb->env, slmdb->curr_limit); + break; + + /* + * When a writer resizes the database, read-only applications must + * increase their LMDB memory map size limit, too. Otherwise, they + * won't be able to read a table after it grows. + * + * As of LMDB 0.9.8 we can import the new memory map size limit into the + * database environment by calling mdb_env_set_mapsize() with a zero + * size argument. Then we extract the map size limit for later use. + * The caller should retry immediately. + */ + case MDB_MAP_RESIZED: + if ((status = mdb_env_set_mapsize(slmdb->env, 0)) == 0) { + /* Do not panic. Maps may shrink after bulk update. */ + mdb_env_info(slmdb->env, &info); + slmdb->curr_limit = info.me_mapsize; + if (slmdb->notify_fn) + slmdb->notify_fn(slmdb->cb_context, MDB_MAP_RESIZED, + slmdb->curr_limit); + } + break; + + /* + * What is it with these built-in hard limits that cause systems to + * stop when demand is at its highest? When the system is under + * stress it should slow down and keep making progress. + */ + case MDB_READERS_FULL: + if (slmdb->notify_fn) + slmdb->notify_fn(slmdb->cb_context, MDB_READERS_FULL); + sleep(1); + status = 0; + break; + + /* + * We can't solve this problem. The application should terminate with + * a fatal run-time error and the program should be re-run later. + */ + default: + break; + } + + /* + * If we cleared the error condition for a non-bulk transaction, return a + * success status. The caller should retry the failed operation + * immediately. + */ + if (status == 0 && (slmdb->slmdb_flags & SLMDB_FLAG_BULK) != 0) { + + /* + * We cleared the error condition for a bulk transaction. If the + * transaction is not restartable, return the original error. The + * caller should terminate with a fatal run-time error, and the + * program should be re-run later. + */ + if (slmdb->longjmp_fn == 0) + return (original_status); + + /* + * Rebuild a bulk transaction from scratch, by making a long jump + * back into the caller at some pre-arranged point. In MDB_NOLOCK + * mode, there is no need to upgrade a lock to "exclusive", because a + * failed write transaction has no side effects. + */ + if ((status = mdb_txn_begin(slmdb->env, (MDB_txn *) 0, + slmdb->lmdb_flags & MDB_RDONLY, + &slmdb->txn)) == 0 + && (status = slmdb_prepare(slmdb)) == 0) + slmdb->longjmp_fn(slmdb->cb_context, 1); + } + return (status); +} + +/* slmdb_txn_begin - mdb_txn_begin() wrapper with LMDB error recovery */ + +static int slmdb_txn_begin(SLMDB *slmdb, int rdonly, MDB_txn **txn) +{ + int status; + + if ((status = mdb_txn_begin(slmdb->env, (MDB_txn *) 0, rdonly, txn)) != 0 + && (status = slmdb_recover(slmdb, status)) == 0) + status = slmdb_txn_begin(slmdb, rdonly, txn); + + return (status); +} + +/* slmdb_get - mdb_get() wrapper with LMDB error recovery */ + +int slmdb_get(SLMDB *slmdb, MDB_val *mdb_key, MDB_val *mdb_value) +{ + MDB_txn *txn; + int status; + + /* + * Start a read transaction if there's no bulk-mode txn. + */ + if (slmdb->txn) + txn = slmdb->txn; + else if ((status = slmdb_txn_begin(slmdb, MDB_RDONLY, &txn)) != 0) + SLMDB_API_RETURN(slmdb, status); + + /* + * Do the lookup. + */ + if ((status = mdb_get(txn, slmdb->dbi, mdb_key, mdb_value)) != 0 + && status != MDB_NOTFOUND) { + mdb_txn_abort(txn); + if (txn == slmdb->txn) + slmdb->txn = 0; + if ((status = slmdb_recover(slmdb, status)) == 0) + status = slmdb_get(slmdb, mdb_key, mdb_value); + SLMDB_API_RETURN(slmdb, status); + } + + /* + * Close the read txn if it's not the bulk-mode txn. + */ + if (slmdb->txn == 0) + mdb_txn_abort(txn); + + SLMDB_API_RETURN(slmdb, status); +} + +/* slmdb_put - mdb_put() wrapper with LMDB error recovery */ + +int slmdb_put(SLMDB *slmdb, MDB_val *mdb_key, + MDB_val *mdb_value, int flags) +{ + MDB_txn *txn; + int status; + + /* + * Start a write transaction if there's no bulk-mode txn. + */ + if (slmdb->txn) + txn = slmdb->txn; + else if ((status = slmdb_txn_begin(slmdb, 0, &txn)) != 0) + SLMDB_API_RETURN(slmdb, status); + + /* + * Do the update. + */ + if ((status = mdb_put(txn, slmdb->dbi, mdb_key, mdb_value, flags)) != 0) { + if (status != MDB_KEYEXIST) { + mdb_txn_abort(txn); + if (txn == slmdb->txn) + slmdb->txn = 0; + if ((status = slmdb_recover(slmdb, status)) == 0) + status = slmdb_put(slmdb, mdb_key, mdb_value, flags); + SLMDB_API_RETURN(slmdb, status); + } else { + /* Abort non-bulk transaction only. */ + if (slmdb->txn == 0) + mdb_txn_abort(txn); + } + } + + /* + * Commit the transaction if it's not the bulk-mode txn. + */ + if (status == 0 && slmdb->txn == 0 && (status = mdb_txn_commit(txn)) != 0 + && (status = slmdb_recover(slmdb, status)) == 0) + status = slmdb_put(slmdb, mdb_key, mdb_value, flags); + + SLMDB_API_RETURN(slmdb, status); +} + +/* slmdb_del - mdb_del() wrapper with LMDB error recovery */ + +int slmdb_del(SLMDB *slmdb, MDB_val *mdb_key) +{ + MDB_txn *txn; + int status; + + /* + * Start a write transaction if there's no bulk-mode txn. + */ + if (slmdb->txn) + txn = slmdb->txn; + else if ((status = slmdb_txn_begin(slmdb, 0, &txn)) != 0) + SLMDB_API_RETURN(slmdb, status); + + /* + * Do the update. + */ + if ((status = mdb_del(txn, slmdb->dbi, mdb_key, (MDB_val *) 0)) != 0) { + if (status != MDB_NOTFOUND) { + mdb_txn_abort(txn); + if (txn == slmdb->txn) + slmdb->txn = 0; + if ((status = slmdb_recover(slmdb, status)) == 0) + status = slmdb_del(slmdb, mdb_key); + SLMDB_API_RETURN(slmdb, status); + } else { + /* Abort non-bulk transaction only. */ + if (slmdb->txn == 0) + mdb_txn_abort(txn); + } + } + + /* + * Commit the transaction if it's not the bulk-mode txn. + */ + if (status == 0 && slmdb->txn == 0 && (status = mdb_txn_commit(txn)) != 0 + && (status = slmdb_recover(slmdb, status)) == 0) + status = slmdb_del(slmdb, mdb_key); + + SLMDB_API_RETURN(slmdb, status); +} + +/* slmdb_cursor_get - mdb_cursor_get() wrapper with LMDB error recovery */ + +int slmdb_cursor_get(SLMDB *slmdb, MDB_val *mdb_key, + MDB_val *mdb_value, MDB_cursor_op op) +{ + MDB_txn *txn; + int status = 0; + + /* + * TODO: figure how we would recover a failing bulk transaction. + */ + if ((slmdb->slmdb_flags & SLMDB_FLAG_BULK) != 0) { + if (slmdb->assert_fn) + slmdb->assert_fn(slmdb->cb_context, + "slmdb_cursor_get: bulk transaction is not supported"); + return (MDB_PANIC); + } + + /* + * Open a read transaction and cursor if needed. + */ + if (slmdb->cursor == 0) { + if ((status = slmdb_txn_begin(slmdb, MDB_RDONLY, &txn)) != 0) + SLMDB_API_RETURN(slmdb, status); + if ((status = mdb_cursor_open(txn, slmdb->dbi, &slmdb->cursor)) != 0) { + mdb_txn_abort(txn); + if ((status = slmdb_recover(slmdb, status)) == 0) + status = slmdb_cursor_get(slmdb, mdb_key, mdb_value, op); + SLMDB_API_RETURN(slmdb, status); + } + + /* + * Restore the cursor position from the saved key information. + */ + if (HAVE_SLMDB_SAVED_KEY(slmdb) && op != MDB_FIRST) + status = mdb_cursor_get(slmdb->cursor, &slmdb->saved_key, + (MDB_val *) 0, MDB_SET); + } + + /* + * Database lookup. + */ + if (status == 0) + status = mdb_cursor_get(slmdb->cursor, mdb_key, mdb_value, op); + + /* + * Save the cursor position if successful. This can fail only with + * ENOMEM. + * + * Close the cursor read transaction if in MDB_NOLOCK mode, because the + * caller may release the external lock after we return. + */ + if (status == 0) { + status = slmdb_saved_key_assign(slmdb, mdb_key); + if (slmdb->lmdb_flags & MDB_NOLOCK) + slmdb_cursor_close(slmdb); + } + + /* + * Handle end-of-database or other error. + */ + else { + /* Do not hand-optimize out the slmdb_cursor_close() calls below. */ + if (status == MDB_NOTFOUND) { + slmdb_cursor_close(slmdb); + if (HAVE_SLMDB_SAVED_KEY(slmdb)) + slmdb_saved_key_free(slmdb); + } else { + slmdb_cursor_close(slmdb); + if ((status = slmdb_recover(slmdb, status)) == 0) + status = slmdb_cursor_get(slmdb, mdb_key, mdb_value, op); + SLMDB_API_RETURN(slmdb, status); + /* Do not hand-optimize out the above return statement. */ + } + } + SLMDB_API_RETURN(slmdb, status); +} + +/* slmdb_assert_cb - report LMDB assertion failure */ + +static void slmdb_assert_cb(MDB_env *env, const char *text) +{ + SLMDB *slmdb = (SLMDB *) mdb_env_get_userctx(env); + + if (slmdb->assert_fn) + slmdb->assert_fn(slmdb->cb_context, text); +} + +/* slmdb_control - control optional settings */ + +int slmdb_control(SLMDB *slmdb, int first,...) +{ + va_list ap; + int status = 0; + int reqno; + int rc; + + va_start(ap, first); + for (reqno = first; status == 0 && reqno != SLMDB_CTL_END; reqno = va_arg(ap, int)) { + switch (reqno) { + case SLMDB_CTL_LONGJMP_FN: + slmdb->longjmp_fn = va_arg(ap, SLMDB_LONGJMP_FN); + break; + case SLMDB_CTL_NOTIFY_FN: + slmdb->notify_fn = va_arg(ap, SLMDB_NOTIFY_FN); + break; + case SLMDB_CTL_ASSERT_FN: + slmdb->assert_fn = va_arg(ap, SLMDB_ASSERT_FN); + if ((rc = mdb_env_set_userctx(slmdb->env, (void *) slmdb)) != 0 + || (rc = mdb_env_set_assert(slmdb->env, slmdb_assert_cb)) != 0) + status = rc; + break; + case SLMDB_CTL_CB_CONTEXT: + slmdb->cb_context = va_arg(ap, void *); + break; + case SLMDB_CTL_API_RETRY_LIMIT: + slmdb->api_retry_limit = va_arg(ap, int); + break; + case SLMDB_CTL_BULK_RETRY_LIMIT: + slmdb->bulk_retry_limit = va_arg(ap, int); + break; + default: + status = errno = EINVAL; + break; + } + } + va_end(ap); + return (status); +} + +/* slmdb_close - wrapper with LMDB error recovery */ + +int slmdb_close(SLMDB *slmdb) +{ + int status = 0; + + /* + * Finish an open bulk transaction. If slmdb_recover() returns after a + * bulk-transaction error, then it was unable to clear the error + * condition, or unable to restart the bulk transaction. + */ + if ((slmdb->slmdb_flags & SLMDB_FLAG_BULK) != 0 && slmdb->txn != 0 + && (status = mdb_txn_commit(slmdb->txn)) != 0) + status = slmdb_recover(slmdb, status); + + /* + * Clean up after an unfinished sequence() operation. + */ + if (slmdb->cursor != 0) + slmdb_cursor_close(slmdb); + + mdb_env_close(slmdb->env); + + /* + * Clean up the saved key information. + */ + if (HAVE_SLMDB_SAVED_KEY(slmdb)) + slmdb_saved_key_free(slmdb); + + SLMDB_API_RETURN(slmdb, status); +} + +/* slmdb_init - mandatory initialization */ + +int slmdb_init(SLMDB *slmdb, size_t curr_limit, int size_incr, + size_t hard_limit) +{ + + /* + * This is a separate operation to keep the slmdb_open() API simple. + * Don't allocate resources here. Just store control information, + */ + slmdb->curr_limit = curr_limit; + slmdb->size_incr = size_incr; + slmdb->hard_limit = hard_limit; + + return (MDB_SUCCESS); +} + +/* slmdb_open - open wrapped LMDB database */ + +int slmdb_open(SLMDB *slmdb, const char *path, int open_flags, + int lmdb_flags, int slmdb_flags) +{ + struct stat st; + MDB_env *env; + MDB_txn *txn; + MDB_dbi dbi; + int db_fd; + int status; + + /* + * Create LMDB environment. + */ + if ((status = mdb_env_create(&env)) != 0) + return (status); + + /* + * Make sure that the memory map has room to store and commit an initial + * "drop" transaction as well as fixed database metadata. We have no way + * to recover from errors before the first application-level I/O request. + */ +#define SLMDB_FUDGE 10240 + + if (slmdb->curr_limit < SLMDB_FUDGE) + slmdb->curr_limit = SLMDB_FUDGE; + if (stat(path, &st) == 0 + && st.st_size > slmdb->curr_limit - SLMDB_FUDGE) { + if (st.st_size > slmdb->hard_limit) + slmdb->hard_limit = st.st_size; + if (st.st_size < slmdb->hard_limit - SLMDB_FUDGE) + slmdb->curr_limit = st.st_size + SLMDB_FUDGE; + else + slmdb->curr_limit = slmdb->hard_limit; + } + + /* + * mdb_open() requires a txn, but since the default DB always exists in + * an LMDB environment, we usually don't need to do anything else with + * the txn. It is currently used for truncate and for bulk transactions. + */ + if ((status = mdb_env_set_mapsize(env, slmdb->curr_limit)) != 0 + || (status = mdb_env_open(env, path, lmdb_flags, 0644)) != 0 + || (status = mdb_txn_begin(env, (MDB_txn *) 0, + lmdb_flags & MDB_RDONLY, &txn)) != 0 + || (status = mdb_open(txn, (const char *) 0, 0, &dbi)) != 0 + || (status = mdb_env_get_fd(env, &db_fd)) != 0) { + mdb_env_close(env); + return (status); + } + + /* + * Bundle up. + */ + slmdb->open_flags = open_flags; + slmdb->lmdb_flags = lmdb_flags; + slmdb->slmdb_flags = slmdb_flags; + slmdb->env = env; + slmdb->dbi = dbi; + slmdb->db_fd = db_fd; + slmdb->cursor = 0; + slmdb_saved_key_init(slmdb); + slmdb->api_retry_count = 0; + slmdb->bulk_retry_count = 0; + slmdb->api_retry_limit = SLMDB_DEF_API_RETRY_LIMIT; + slmdb->bulk_retry_limit = SLMDB_DEF_BULK_RETRY_LIMIT; + slmdb->longjmp_fn = 0; + slmdb->notify_fn = 0; + slmdb->assert_fn = 0; + slmdb->cb_context = 0; + slmdb->txn = txn; + + if ((status = slmdb_prepare(slmdb)) != 0) + mdb_env_close(env); + + return (status); +} + +#endif diff --git a/src/util/slmdb.h b/src/util/slmdb.h new file mode 100644 index 0000000..9469c0f --- /dev/null +++ b/src/util/slmdb.h @@ -0,0 +1,117 @@ +#ifndef _SLMDB_H_INCLUDED_ +#define _SLMDB_H_INCLUDED_ + +/*++ +/* NAME +/* slmdb 3h +/* SUMMARY +/* LMDB API wrapper +/* SYNOPSIS +/* #include <slmdb.h> +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include <setjmp.h> + +#ifdef PATH_LMDB_H +#include PATH_LMDB_H +#else +#include <lmdb.h> +#endif + + /* + * Utility library. + */ +#include <check_arg.h> + + /* + * External interface. + */ +#ifdef NO_SIGSETJMP +#define SLMDB_JMP_BUF jmp_buf +#else +#define SLMDB_JMP_BUF sigjmp_buf +#endif + + /* + * All data structure members are private. + */ +typedef struct { + size_t curr_limit; /* database soft size limit */ + int size_incr; /* database expansion factor */ + size_t hard_limit; /* database hard size limit */ + int open_flags; /* open() flags */ + int lmdb_flags; /* LMDB-specific flags */ + int slmdb_flags; /* bulk-mode flag */ + MDB_env *env; /* database environment */ + MDB_dbi dbi; /* database instance */ + MDB_txn *txn; /* bulk transaction */ + int db_fd; /* database file handle */ + MDB_cursor *cursor; /* iterator */ + MDB_val saved_key; /* saved cursor key buffer */ + size_t saved_key_size; /* saved cursor key buffer size */ + void (*longjmp_fn) (void *, int);/* exception handling */ + void (*notify_fn) (void *, int,...); /* workaround notification */ + void (*assert_fn) (void *, const char *); /* assert notification */ + void *cb_context; /* call-back context */ + int api_retry_count; /* slmdb(3) API call retry count */ + int bulk_retry_count; /* bulk_mode retry count */ + int api_retry_limit; /* slmdb(3) API call retry limit */ + int bulk_retry_limit; /* bulk_mode retry limit */ +} SLMDB; + +#define SLMDB_FLAG_BULK (1 << 0) + +extern int slmdb_init(SLMDB *, size_t, int, size_t); +extern int slmdb_open(SLMDB *, const char *, int, int, int); +extern int slmdb_get(SLMDB *, MDB_val *, MDB_val *); +extern int slmdb_put(SLMDB *, MDB_val *, MDB_val *, int); +extern int slmdb_del(SLMDB *, MDB_val *); +extern int slmdb_cursor_get(SLMDB *, MDB_val *, MDB_val *, MDB_cursor_op); +extern int slmdb_control(SLMDB *, int,...); +extern int slmdb_close(SLMDB *); + +#define slmdb_fd(slmdb) ((slmdb)->db_fd) +#define slmdb_curr_limit(slmdb) ((slmdb)->curr_limit) + +/* Legacy API: type-unchecked arguments, internal use. */ +#define SLMDB_CTL_END 0 +#define SLMDB_CTL_LONGJMP_FN 1 /* exception handling */ +#define SLMDB_CTL_NOTIFY_FN 2 /* debug logging function */ +#define SLMDB_CTL_CB_CONTEXT 3 /* call-back context */ +#define SLMDB_CTL_API_RETRY_LIMIT 5 /* per slmdb(3) API call */ +#define SLMDB_CTL_BULK_RETRY_LIMIT 6 /* per bulk update */ +#define SLMDB_CTL_ASSERT_FN 7 /* report assertion failure */ + +/* Safer API: type-checked arguments, external use. */ +#define CA_SLMDB_CTL_END SLMDB_CTL_END +#define CA_SLMDB_CTL_LONGJMP_FN(v) SLMDB_CTL_LONGJMP_FN, CHECK_VAL(SLMDB_CTL, SLMDB_LONGJMP_FN, (v)) +#define CA_SLMDB_CTL_NOTIFY_FN(v) SLMDB_CTL_NOTIFY_FN, CHECK_VAL(SLMDB_CTL, SLMDB_NOTIFY_FN, (v)) +#define CA_SLMDB_CTL_CB_CONTEXT(v) SLMDB_CTL_CB_CONTEXT, CHECK_PTR(SLMDB_CTL, void, (v)) +#define CA_SLMDB_CTL_API_RETRY_LIMIT(v) SLMDB_CTL_API_RETRY_LIMIT, CHECK_VAL(SLMDB_CTL, int, (v)) +#define CA_SLMDB_CTL_BULK_RETRY_LIMIT(v) SLMDB_CTL_BULK_RETRY_LIMIT, CHECK_VAL(SLMDB_CTL, int, (v)) +#define CA_SLMDB_CTL_ASSERT_FN(v) SLMDB_CTL_ASSERT_FN, CHECK_VAL(SLMDB_CTL, SLMDB_ASSERT_FN, (v)) + +typedef void (*SLMDB_NOTIFY_FN) (void *, int,...); +typedef void (*SLMDB_LONGJMP_FN) (void *, int); +typedef void (*SLMDB_ASSERT_FN) (void *, const char *); + +CHECK_VAL_HELPER_DCL(SLMDB_CTL, int); +CHECK_VAL_HELPER_DCL(SLMDB_CTL, SLMDB_NOTIFY_FN); +CHECK_VAL_HELPER_DCL(SLMDB_CTL, SLMDB_LONGJMP_FN); +CHECK_VAL_HELPER_DCL(SLMDB_CTL, SLMDB_ASSERT_FN); +CHECK_PTR_HELPER_DCL(SLMDB_CTL, void); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Howard Chu +/* Symas Corporation +/*--*/ + +#endif diff --git a/src/util/sock_addr.c b/src/util/sock_addr.c new file mode 100644 index 0000000..dc5c4b1 --- /dev/null +++ b/src/util/sock_addr.c @@ -0,0 +1,173 @@ +/*++ +/* NAME +/* sock_addr 3 +/* SUMMARY +/* sockaddr utilities +/* SYNOPSIS +/* #include <sock_addr.h> +/* +/* int sock_addr_cmp_addr(sa, sb) +/* const struct sockaddr *sa; +/* const struct sockaddr *sb; +/* +/* int sock_addr_cmp_port(sa, sb) +/* const struct sockaddr *sa; +/* const struct sockaddr *sb; +/* +/* int SOCK_ADDR_EQ_ADDR(sa, sb) +/* const struct sockaddr *sa; +/* const struct sockaddr *sb; +/* +/* int SOCK_ADDR_EQ_PORT(sa, sb) +/* const struct sockaddr *sa; +/* const struct sockaddr *sb; +/* +/* int sock_addr_in_loopback(sa) +/* const struct sockaddr *sa; +/* AUXILIARY MACROS +/* struct sockaddr *SOCK_ADDR_PTR(ptr) +/* unsigned char SOCK_ADDR_FAMILY(ptr) +/* unsigned char SOCK_ADDR_LEN(ptr) +/* unsigned short SOCK_ADDR_PORT(ptr) +/* unsigned short *SOCK_ADDR_PORTP(ptr) +/* +/* struct sockaddr_in *SOCK_ADDR_IN_PTR(ptr) +/* unsigned char SOCK_ADDR_IN_FAMILY(ptr) +/* unsigned short SOCK_ADDR_IN_PORT(ptr) +/* struct in_addr SOCK_ADDR_IN_ADDR(ptr) +/* struct in_addr IN_ADDR(ptr) +/* +/* struct sockaddr_in6 *SOCK_ADDR_IN6_PTR(ptr) +/* unsigned char SOCK_ADDR_IN6_FAMILY(ptr) +/* unsigned short SOCK_ADDR_IN6_PORT(ptr) +/* struct in6_addr SOCK_ADDR_IN6_ADDR(ptr) +/* struct in6_addr IN6_ADDR(ptr) +/* DESCRIPTION +/* These utilities take protocol-independent address structures +/* and perform protocol-dependent operations on structure members. +/* Some of the macros described here are called unsafe, +/* because they evaluate one or more arguments multiple times. +/* +/* sock_addr_cmp_addr() or sock_addr_cmp_port() compare the +/* address family and network address or port fields for +/* equality, and return indication of the difference between +/* their arguments: < 0 if the first argument is "smaller", +/* 0 for equality, and > 0 if the first argument is "larger". +/* +/* The unsafe macros SOCK_ADDR_EQ_ADDR() or SOCK_ADDR_EQ_PORT() +/* compare compare the address family and network address or +/* port fields for equality, and return non-zero when their +/* arguments differ. +/* +/* sock_addr_in_loopback() determines if the argument specifies +/* a loopback address. +/* +/* The SOCK_ADDR_PTR() macro casts a generic pointer to (struct +/* sockaddr *). The name is upper case for consistency not +/* safety. SOCK_ADDR_FAMILY() and SOCK_ADDR_LEN() return the +/* address family and length of the real structure that hides +/* inside a generic sockaddr structure. On systems where struct +/* sockaddr has no sa_len member, SOCK_ADDR_LEN() cannot be +/* used as lvalue. SOCK_ADDR_PORT() returns the IPv4 or IPv6 +/* port number, in network byte order; it must not be used as +/* lvalue. SOCK_ADDR_PORTP() returns a pointer to the same. +/* +/* The macros SOCK_ADDR_IN{,6}_{PTR,FAMILY,PORT,ADDR}() cast +/* a generic pointer to a specific socket address structure +/* pointer, or access a specific socket address structure +/* member. These can be used as lvalues. +/* +/* The unsafe INADDR() and IN6_ADDR() macros dereference a +/* generic pointer to a specific address structure. +/* DIAGNOSTICS +/* Panic: unsupported address family. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <sock_addr.h> + +/* sock_addr_cmp_addr - compare addresses for equality */ + +int sock_addr_cmp_addr(const struct sockaddr *sa, + const struct sockaddr *sb) +{ + if (sa->sa_family != sb->sa_family) + return (sa->sa_family - sb->sa_family); + + /* + * With IPv6 address structures, assume a non-hostile implementation that + * stores the address as a contiguous sequence of bits. Any holes in the + * sequence would invalidate the use of memcmp(). + */ + if (sa->sa_family == AF_INET) { + return (SOCK_ADDR_IN_ADDR(sa).s_addr - SOCK_ADDR_IN_ADDR(sb).s_addr); +#ifdef HAS_IPV6 + } else if (sa->sa_family == AF_INET6) { + return (memcmp((void *) &(SOCK_ADDR_IN6_ADDR(sa)), + (void *) &(SOCK_ADDR_IN6_ADDR(sb)), + sizeof(SOCK_ADDR_IN6_ADDR(sa)))); +#endif + } else { + msg_panic("sock_addr_cmp_addr: unsupported address family %d", + sa->sa_family); + } +} + +/* sock_addr_cmp_port - compare ports for equality */ + +int sock_addr_cmp_port(const struct sockaddr *sa, + const struct sockaddr *sb) +{ + if (sa->sa_family != sb->sa_family) + return (sa->sa_family - sb->sa_family); + + if (sa->sa_family == AF_INET) { + return (SOCK_ADDR_IN_PORT(sa) - SOCK_ADDR_IN_PORT(sb)); +#ifdef HAS_IPV6 + } else if (sa->sa_family == AF_INET6) { + return (SOCK_ADDR_IN6_PORT(sa) - SOCK_ADDR_IN6_PORT(sb)); +#endif + } else { + msg_panic("sock_addr_cmp_port: unsupported address family %d", + sa->sa_family); + } +} + +/* sock_addr_in_loopback - determine if address is loopback */ + +int sock_addr_in_loopback(const struct sockaddr *sa) +{ + unsigned long inaddr; + + if (sa->sa_family == AF_INET) { + inaddr = ntohl(SOCK_ADDR_IN_ADDR(sa).s_addr); + return (IN_CLASSA(inaddr) + && ((inaddr & IN_CLASSA_NET) >> IN_CLASSA_NSHIFT) + == IN_LOOPBACKNET); +#ifdef HAS_IPV6 + } else if (sa->sa_family == AF_INET6) { + return (IN6_IS_ADDR_LOOPBACK(&SOCK_ADDR_IN6_ADDR(sa))); +#endif + } else { + msg_panic("sock_addr_in_loopback: unsupported address family %d", + sa->sa_family); + } +} diff --git a/src/util/sock_addr.h b/src/util/sock_addr.h new file mode 100644 index 0000000..1f5407a --- /dev/null +++ b/src/util/sock_addr.h @@ -0,0 +1,105 @@ +#ifndef _SOCK_ADDR_EQ_H_INCLUDED_ +#define _SOCK_ADDR_EQ_H_INCLUDED_ + +/*++ +/* NAME +/* sock_addr 3h +/* SUMMARY +/* socket address utilities +/* SYNOPSIS +/* #include <sock_addr.h> +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include <sys/socket.h> +#include <netinet/in.h> +#include <string.h> + + /* + * External interface. + */ +#define SOCK_ADDR_PTR(ptr) ((struct sockaddr *)(ptr)) +#define SOCK_ADDR_FAMILY(ptr) SOCK_ADDR_PTR(ptr)->sa_family +#ifdef HAS_SA_LEN +#define SOCK_ADDR_LEN(ptr) SOCK_ADDR_PTR(ptr)->sa_len +#endif + +#define SOCK_ADDR_IN_PTR(sa) ((struct sockaddr_in *)(sa)) +#define SOCK_ADDR_IN_FAMILY(sa) SOCK_ADDR_IN_PTR(sa)->sin_family +#define SOCK_ADDR_IN_PORT(sa) SOCK_ADDR_IN_PTR(sa)->sin_port +#define SOCK_ADDR_IN_ADDR(sa) SOCK_ADDR_IN_PTR(sa)->sin_addr +#define IN_ADDR(ia) (*((struct in_addr *) (ia))) + +extern int sock_addr_cmp_addr(const struct sockaddr *, const struct sockaddr *); +extern int sock_addr_cmp_port(const struct sockaddr *, const struct sockaddr *); +extern int sock_addr_in_loopback(const struct sockaddr *); + +#ifdef HAS_IPV6 + +#ifndef HAS_SA_LEN +#define SOCK_ADDR_LEN(sa) \ + (SOCK_ADDR_PTR(sa)->sa_family == AF_INET6 ? \ + sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) +#endif + +#define SOCK_ADDR_PORT(sa) \ + (SOCK_ADDR_PTR(sa)->sa_family == AF_INET6 ? \ + SOCK_ADDR_IN6_PORT(sa) : SOCK_ADDR_IN_PORT(sa)) +#define SOCK_ADDR_PORTP(sa) \ + (SOCK_ADDR_PTR(sa)->sa_family == AF_INET6 ? \ + &SOCK_ADDR_IN6_PORT(sa) : &SOCK_ADDR_IN_PORT(sa)) + +#define SOCK_ADDR_IN6_PTR(sa) ((struct sockaddr_in6 *)(sa)) +#define SOCK_ADDR_IN6_FAMILY(sa) SOCK_ADDR_IN6_PTR(sa)->sin6_family +#define SOCK_ADDR_IN6_PORT(sa) SOCK_ADDR_IN6_PTR(sa)->sin6_port +#define SOCK_ADDR_IN6_ADDR(sa) SOCK_ADDR_IN6_PTR(sa)->sin6_addr +#define IN6_ADDR(ia) (*((struct in6_addr *) (ia))) + +#define SOCK_ADDR_EQ_ADDR(sa, sb) \ + ((SOCK_ADDR_FAMILY(sa) == AF_INET && SOCK_ADDR_FAMILY(sb) == AF_INET \ + && SOCK_ADDR_IN_ADDR(sa).s_addr == SOCK_ADDR_IN_ADDR(sb).s_addr) \ + || (SOCK_ADDR_FAMILY(sa) == AF_INET6 && SOCK_ADDR_FAMILY(sb) == AF_INET6 \ + && memcmp((char *) &(SOCK_ADDR_IN6_ADDR(sa)), \ + (char *) &(SOCK_ADDR_IN6_ADDR(sb)), \ + sizeof(SOCK_ADDR_IN6_ADDR(sa))) == 0)) + +#define SOCK_ADDR_EQ_PORT(sa, sb) \ + ((SOCK_ADDR_FAMILY(sa) == AF_INET && SOCK_ADDR_FAMILY(sb) == AF_INET \ + && SOCK_ADDR_IN_PORT(sa) == SOCK_ADDR_IN_PORT(sb)) \ + || (SOCK_ADDR_FAMILY(sa) == AF_INET6 && SOCK_ADDR_FAMILY(sb) == AF_INET6 \ + && SOCK_ADDR_IN6_PORT(sa) == SOCK_ADDR_IN6_PORT(sb))) + +#else + +#ifndef HAS_SA_LEN +#define SOCK_ADDR_LEN(sa) sizeof(struct sockaddr_in) +#endif + +#define SOCK_ADDR_PORT(sa) SOCK_ADDR_IN_PORT(sa)) +#define SOCK_ADDR_PORTP(sa) &SOCK_ADDR_IN_PORT(sa)) + +#define SOCK_ADDR_EQ_ADDR(sa, sb) \ + (SOCK_ADDR_FAMILY(sa) == AF_INET && SOCK_ADDR_FAMILY(sb) == AF_INET \ + && SOCK_ADDR_IN_ADDR(sa).s_addr == SOCK_ADDR_IN_ADDR(sb).s_addr) + +#define SOCK_ADDR_EQ_PORT(sa, sb) \ + (SOCK_ADDR_FAMILY(sa) == AF_INET && SOCK_ADDR_FAMILY(sb) == AF_INET \ + && SOCK_ADDR_IN_PORT(sa) == SOCK_ADDR_IN_PORT(sb)) + +#endif + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/spawn_command.c b/src/util/spawn_command.c new file mode 100644 index 0000000..739e012 --- /dev/null +++ b/src/util/spawn_command.c @@ -0,0 +1,313 @@ +/*++ +/* NAME +/* spawn_command 3 +/* SUMMARY +/* run external command +/* SYNOPSIS +/* #include <spawn_command.h> +/* +/* WAIT_STATUS_T spawn_command(key, value, ...) +/* int key; +/* DESCRIPTION +/* spawn_command() runs a command in a child process and returns +/* the command exit status. +/* +/* Arguments: +/* .IP key +/* spawn_command() takes a list of macros with arguments, +/* terminated by CA_SPAWN_CMD_END which has no arguments. The +/* following is a listing of macros and expected argument +/* types. +/* .RS +/* .IP "CA_SPAWN_CMD_COMMAND(const char *)" +/* Specifies the command to execute as a string. The string is +/* passed to the shell when it contains shell meta characters +/* or when it appears to be a shell built-in command, otherwise +/* the command is executed without invoking a shell. +/* One of CA_SPAWN_CMD_COMMAND or CA_SPAWN_CMD_ARGV must be specified. +/* See also the SPAWN_CMD_SHELL attribute below. +/* .IP "CA_SPAWN_CMD_ARGV(char **)" +/* The command is specified as an argument vector. This vector is +/* passed without further inspection to the \fIexecvp\fR() routine. +/* One of CA_SPAWN_CMD_COMMAND or CA_SPAWN_CMD_ARGV must be specified. +/* .IP "CA_SPAWN_CMD_ENV(char **)" +/* Additional environment information, in the form of a null-terminated +/* list of name, value, name, value, ... elements. By default only the +/* command search path is initialized to _PATH_DEFPATH. +/* .IP "CA_SPAWN_CMD_EXPORT(char **)" +/* Null-terminated array of names of environment parameters that can +/* be exported. By default, everything is exported. +/* .IP "CA_SPAWN_CMD_STDIN(int)" +/* .IP "CA_SPAWN_CMD_STDOUT(int)" +/* .IP "CA_SPAWN_CMD_STDERR(int)" +/* Each of these specifies I/O redirection of one of the standard file +/* descriptors for the command. +/* .IP "CA_SPAWN_CMD_UID(uid_t)" +/* The user ID to execute the command as. The value -1 is reserved +/* and cannot be specified. +/* .IP "CA_SPAWN_CMD_GID(gid_t)" +/* The group ID to execute the command as. The value -1 is reserved +/* and cannot be specified. +/* .IP "CA_SPAWN_CMD_TIME_LIMIT(int)" +/* The amount of time in seconds the command is allowed to run before +/* it is terminated with SIGKILL. The default is no time limit. +/* .IP "CA_SPAWN_CMD_SHELL(const char *)" +/* The shell to use when executing the command specified with +/* CA_SPAWN_CMD_COMMAND. This shell is invoked regardless of the +/* command content. +/* .RE +/* DIAGNOSTICS +/* Panic: interface violations (for example, a missing command). +/* +/* Fatal error: fork() failure, other system call failures. +/* +/* spawn_command() returns the exit status as defined by wait(2). +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* SEE ALSO +/* exec_command(3) execute command +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/wait.h> +#include <signal.h> +#include <unistd.h> +#include <errno.h> +#include <stdarg.h> +#include <stdlib.h> +#ifdef USE_PATHS_H +#include <paths.h> +#endif +#include <syslog.h> + +/* Utility library. */ + +#include <msg.h> +#include <timed_wait.h> +#include <set_ugid.h> +#include <argv.h> +#include <spawn_command.h> +#include <exec_command.h> +#include <clean_env.h> + +/* Application-specific. */ + +struct spawn_args { + char **argv; /* argument vector */ + char *command; /* or a plain string */ + int stdin_fd; /* read stdin here */ + int stdout_fd; /* write stdout here */ + int stderr_fd; /* write stderr here */ + uid_t uid; /* privileges */ + gid_t gid; /* privileges */ + char **env; /* extra environment */ + char **export; /* exportable environment */ + char *shell; /* command shell */ + int time_limit; /* command time limit */ +}; + +/* get_spawn_args - capture the variadic argument list */ + +static void get_spawn_args(struct spawn_args * args, int init_key, va_list ap) +{ + const char *myname = "get_spawn_args"; + int key; + + /* + * First, set the default values. + */ + args->argv = 0; + args->command = 0; + args->stdin_fd = -1; + args->stdout_fd = -1; + args->stderr_fd = -1; + args->uid = (uid_t) - 1; + args->gid = (gid_t) - 1; + args->env = 0; + args->export = 0; + args->shell = 0; + args->time_limit = 0; + + /* + * Then, override the defaults with user-supplied inputs. + */ + for (key = init_key; key != SPAWN_CMD_END; key = va_arg(ap, int)) { + switch (key) { + case SPAWN_CMD_ARGV: + if (args->command) + msg_panic("%s: specify SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND", + myname); + args->argv = va_arg(ap, char **); + break; + case SPAWN_CMD_COMMAND: + if (args->argv) + msg_panic("%s: specify SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND", + myname); + args->command = va_arg(ap, char *); + break; + case SPAWN_CMD_STDIN: + args->stdin_fd = va_arg(ap, int); + break; + case SPAWN_CMD_STDOUT: + args->stdout_fd = va_arg(ap, int); + break; + case SPAWN_CMD_STDERR: + args->stderr_fd = va_arg(ap, int); + break; + case SPAWN_CMD_UID: + args->uid = va_arg(ap, uid_t); + if (args->uid == (uid_t) (-1)) + msg_panic("spawn_command: request with reserved user ID: -1"); + break; + case SPAWN_CMD_GID: + args->gid = va_arg(ap, gid_t); + if (args->gid == (gid_t) (-1)) + msg_panic("spawn_command: request with reserved group ID: -1"); + break; + case SPAWN_CMD_TIME_LIMIT: + args->time_limit = va_arg(ap, int); + break; + case SPAWN_CMD_ENV: + args->env = va_arg(ap, char **); + break; + case SPAWN_CMD_EXPORT: + args->export = va_arg(ap, char **); + break; + case SPAWN_CMD_SHELL: + args->shell = va_arg(ap, char *); + break; + default: + msg_panic("%s: unknown key: %d", myname, key); + } + } + if (args->command == 0 && args->argv == 0) + msg_panic("%s: missing SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND", myname); + if (args->command == 0 && args->shell != 0) + msg_panic("%s: SPAWN_CMD_ARGV cannot be used with SPAWN_CMD_SHELL", + myname); +} + +/* spawn_command - execute command with extreme prejudice */ + +WAIT_STATUS_T spawn_command(int key,...) +{ + const char *myname = "spawn_comand"; + va_list ap; + pid_t pid; + WAIT_STATUS_T wait_status; + struct spawn_args args; + char **cpp; + ARGV *argv; + int err; + + /* + * Process the variadic argument list. This also does sanity checks on + * what data the caller is passing to us. + */ + va_start(ap, key); + get_spawn_args(&args, key, ap); + va_end(ap); + + /* + * For convenience... + */ + if (args.command == 0) + args.command = args.argv[0]; + + /* + * Spawn off a child process and irrevocably change privilege to the + * user. This includes revoking all rights on open files (via the close + * on exec flag). If we cannot run the command now, try again some time + * later. + */ + switch (pid = fork()) { + + /* + * Error. Instead of trying again right now, back off, give the + * system a chance to recover, and try again later. + */ + case -1: + msg_fatal("fork: %m"); + + /* + * Child. Run the child in a separate process group so that the + * parent can kill not just the child but also its offspring. + */ + case 0: + if (args.uid != (uid_t) - 1 || args.gid != (gid_t) - 1) + set_ugid(args.uid, args.gid); + setsid(); + + /* + * Pipe plumbing. + */ + if ((args.stdin_fd >= 0 && DUP2(args.stdin_fd, STDIN_FILENO) < 0) + || (args.stdout_fd >= 0 && DUP2(args.stdout_fd, STDOUT_FILENO) < 0) + || (args.stderr_fd >= 0 && DUP2(args.stderr_fd, STDERR_FILENO) < 0)) + msg_fatal("%s: dup2: %m", myname); + + /* + * Environment plumbing. Always reset the command search path. XXX + * That should probably be done by clean_env(). + */ + if (args.export) + clean_env(args.export); + if (setenv("PATH", _PATH_DEFPATH, 1)) + msg_fatal("%s: setenv: %m", myname); + if (args.env) + for (cpp = args.env; *cpp; cpp += 2) + if (setenv(cpp[0], cpp[1], 1)) + msg_fatal("setenv: %m"); + + /* + * Process plumbing. If possible, avoid running a shell. + */ + closelog(); + if (args.argv) { + execvp(args.argv[0], args.argv); + msg_fatal("%s: execvp %s: %m", myname, args.argv[0]); + } else if (args.shell && *args.shell) { + argv = argv_split(args.shell, CHARS_SPACE); + argv_add(argv, args.command, (char *) 0); + argv_terminate(argv); + execvp(argv->argv[0], argv->argv); + msg_fatal("%s: execvp %s: %m", myname, argv->argv[0]); + } else { + exec_command(args.command); + } + /* NOTREACHED */ + + /* + * Parent. + */ + default: + + /* + * Be prepared for the situation that the child does not terminate. + * Make sure that the child terminates before the parent attempts to + * retrieve its exit status, otherwise the parent could become stuck, + * and the mail system would eventually run out of exec daemons. Do a + * thorough job, and kill not just the child process but also its + * offspring. + */ + if ((err = timed_waitpid(pid, &wait_status, 0, args.time_limit)) < 0 + && errno == ETIMEDOUT) { + msg_warn("%s: process id %lu: command time limit exceeded", + args.command, (unsigned long) pid); + kill(-pid, SIGKILL); + err = waitpid(pid, &wait_status, 0); + } + if (err < 0) + msg_fatal("wait: %m"); + return (wait_status); + } +} diff --git a/src/util/spawn_command.h b/src/util/spawn_command.h new file mode 100644 index 0000000..7d16413 --- /dev/null +++ b/src/util/spawn_command.h @@ -0,0 +1,66 @@ +#ifndef _SPAWN_COMMAND_H_INCLUDED_ +#define _SPAWN_COMMAND_H_INCLUDED_ + +/*++ +/* NAME +/* spawn_command 3h +/* SUMMARY +/* run external command +/* SYNOPSIS +/* #include <spawn_command.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <check_arg.h> + +/* Legacy API: type-unchecked arguments, internal use. */ +#define SPAWN_CMD_END 0 /* terminator */ +#define SPAWN_CMD_ARGV 1 /* command is array */ +#define SPAWN_CMD_COMMAND 2 /* command is string */ +#define SPAWN_CMD_STDIN 3 /* mail_copy() flags */ +#define SPAWN_CMD_STDOUT 4 /* mail_copy() sender */ +#define SPAWN_CMD_STDERR 5 /* mail_copy() recipient */ +#define SPAWN_CMD_UID 6 /* privileges */ +#define SPAWN_CMD_GID 7 /* privileges */ +#define SPAWN_CMD_TIME_LIMIT 8 /* time limit */ +#define SPAWN_CMD_ENV 9 /* extra environment */ +#define SPAWN_CMD_SHELL 10 /* alternative shell */ +#define SPAWN_CMD_EXPORT 11 /* exportable parameters */ + +/* Safer API: type-checked arguments, external use. */ +#define CA_SPAWN_CMD_END SPAWN_CMD_END +#define CA_SPAWN_CMD_ARGV(v) SPAWN_CMD_ARGV, CHECK_PPTR(CA_SPAWN_CMD, char, (v)) +#define CA_SPAWN_CMD_COMMAND(v) SPAWN_CMD_COMMAND, CHECK_CPTR(CA_SPAWN_CMD, char, (v)) +#define CA_SPAWN_CMD_STDIN(v) SPAWN_CMD_STDIN, CHECK_VAL(CA_SPAWN_CMD, int, (v)) +#define CA_SPAWN_CMD_STDOUT(v) SPAWN_CMD_STDOUT, CHECK_VAL(CA_SPAWN_CMD, int, (v)) +#define CA_SPAWN_CMD_STDERR(v) SPAWN_CMD_STDERR, CHECK_VAL(CA_SPAWN_CMD, int, (v)) +#define CA_SPAWN_CMD_UID(v) SPAWN_CMD_UID, CHECK_VAL(CA_SPAWN_CMD, uid_t, (v)) +#define CA_SPAWN_CMD_GID(v) SPAWN_CMD_GID, CHECK_VAL(CA_SPAWN_CMD, gid_t, (v)) +#define CA_SPAWN_CMD_TIME_LIMIT(v) SPAWN_CMD_TIME_LIMIT, CHECK_VAL(CA_SPAWN_CMD, int, (v)) +#define CA_SPAWN_CMD_ENV(v) SPAWN_CMD_ENV, CHECK_PPTR(CA_SPAWN_CMD, char, (v)) +#define CA_SPAWN_CMD_SHELL(v) SPAWN_CMD_SHELL, CHECK_CPTR(CA_SPAWN_CMD, char, (v)) +#define CA_SPAWN_CMD_EXPORT(v) SPAWN_CMD_EXPORT, CHECK_PPTR(CA_SPAWN_CMD, char, (v)) + +CHECK_VAL_HELPER_DCL(CA_SPAWN_CMD, uid_t); +CHECK_VAL_HELPER_DCL(CA_SPAWN_CMD, int); +CHECK_VAL_HELPER_DCL(CA_SPAWN_CMD, gid_t); +CHECK_PPTR_HELPER_DCL(CA_SPAWN_CMD, char); +CHECK_CPTR_HELPER_DCL(CA_SPAWN_CMD, char); + +extern WAIT_STATUS_T spawn_command(int,...); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/split_at.c b/src/util/split_at.c new file mode 100644 index 0000000..c75e902 --- /dev/null +++ b/src/util/split_at.c @@ -0,0 +1,71 @@ +/*++ +/* NAME +/* split_at 3 +/* SUMMARY +/* trivial token splitter +/* SYNOPSIS +/* #include <split_at.h> +/* +/* char *split_at(string, delimiter) +/* char *string; +/* int delimiter +/* +/* char *split_at_right(string, delimiter) +/* char *string; +/* int delimiter +/* DESCRIPTION +/* split_at() null-terminates the \fIstring\fR at the first +/* occurrence of the \fIdelimiter\fR character found, and +/* returns a pointer to the remainder. +/* +/* split_at_right() looks for the rightmost delimiter +/* occurrence, but is otherwise identical to split_at(). +/* DIAGNOSTICS +/* The result is a null pointer when the delimiter character +/* was not found. +/* HISTORY +/* .ad +/* .fi +/* A split_at() routine appears in the TCP Wrapper software +/* by Wietse Venema. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries */ + +#include <sys_defs.h> +#include <string.h> + +/* Utility library. */ + +#include "split_at.h" + +/* split_at - break string at first delimiter, return remainder */ + +char *split_at(char *string, int delimiter) +{ + char *cp; + + if ((cp = strchr(string, delimiter)) != 0) + *cp++ = 0; + return (cp); +} + +/* split_at_right - break string at last delimiter, return remainder */ + +char *split_at_right(char *string, int delimiter) +{ + char *cp; + + if ((cp = strrchr(string, delimiter)) != 0) + *cp++ = 0; + return (cp); +} diff --git a/src/util/split_at.h b/src/util/split_at.h new file mode 100644 index 0000000..2d03ebb --- /dev/null +++ b/src/util/split_at.h @@ -0,0 +1,35 @@ +#ifndef _SPLIT_AT_H_INCLUDED_ +#define _SPLIT_AT_H_INCLUDED_ + +/*++ +/* NAME +/* split_at 3h +/* SUMMARY +/* trivial token splitter +/* SYNOPSIS +/* #include <split_at.h> +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern char *split_at(char *, int); +extern char *split_at_right(char *, int); + +/* HISTORY +/* .ad +/* .fi +/* A split_at() routine appears in the TCP Wrapper software +/* by Wietse Venema. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/split_nameval.c b/src/util/split_nameval.c new file mode 100644 index 0000000..da74fcf --- /dev/null +++ b/src/util/split_nameval.c @@ -0,0 +1,97 @@ +/*++ +/* NAME +/* split_nameval 3 +/* SUMMARY +/* name-value splitter +/* SYNOPSIS +/* #include <split_nameval.h> +/* +/* const char *split_nameval(buf, name, value) +/* char *buf; +/* char **name; +/* char **value; +/* DESCRIPTION +/* split_nameval() takes a logical line from readlline() and expects +/* text of the form "name = value" or "name =". The buffer +/* argument is broken up into name and value substrings. +/* +/* Arguments: +/* .IP buf +/* Result from readlline() or equivalent. The buffer is modified. +/* .IP name +/* Upon successful completion, this is set to the name +/* substring. +/* .IP value +/* Upon successful completion, this is set to the value +/* substring. +/* FEATURES +/* SEE ALSO +/* dict(3) mid-level dictionary routines +/* BUGS +/* DIAGNOSTICS +/* Fatal errors: out of memory. +/* +/* The result is a null pointer in case of success, a string +/* describing the error otherwise: missing '=' after attribute +/* name; missing attribute name. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include "sys_defs.h" +#include <ctype.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <stringops.h> + +/* split_nameval - split text into name and value */ + +const char *split_nameval(char *buf, char **name, char **value) +{ + char *np; /* name substring */ + char *vp; /* value substring */ + char *cp; + char *ep; + + /* + * Ugly macros to make complex expressions less unreadable. + */ +#define SKIP(start, var, cond) do { \ + for (var = start; *var && (cond); var++) \ + /* void */; \ + } while (0) + +#define TRIM(s) do { \ + char *p; \ + for (p = (s) + strlen(s); p > (s) && ISSPACE(p[-1]); p--) \ + /* void */; \ + *p = 0; \ + } while (0) + + SKIP(buf, np, ISSPACE(*np)); /* find name begin */ + if (*np == 0 || *np == '=') + return ("missing attribute name"); + SKIP(np, ep, !ISSPACE(*ep) && *ep != '='); /* find name end */ + SKIP(ep, cp, ISSPACE(*cp)); /* skip blanks before '=' */ + if (*cp != '=') /* need '=' */ + return ("missing '=' after attribute name"); + *ep = 0; /* terminate name */ + cp++; /* skip over '=' */ + SKIP(cp, vp, ISSPACE(*vp)); /* skip leading blanks */ + TRIM(vp); /* trim trailing blanks */ + *name = np; + *value = vp; + return (0); +} diff --git a/src/util/split_qnameval.c b/src/util/split_qnameval.c new file mode 100644 index 0000000..b166125 --- /dev/null +++ b/src/util/split_qnameval.c @@ -0,0 +1,168 @@ +/*++ +/* NAME +/* split_qnameval 3 +/* SUMMARY +/* name-value splitter +/* SYNOPSIS +/* #include <stringops.h> +/* +/* const char *split_qnameval(buf, name, value) +/* char *buf; +/* char **name; +/* char **value; +/* DESCRIPTION +/* split_qnameval() expects text of the form "key = value" +/* or "key =", where the key may be quoted with backslash or +/* double quotes. The buffer argument is broken up into the key +/* and value substrings. +/* +/* Arguments: +/* .IP buf +/* Result from readlline() or equivalent. The buffer is modified. +/* .IP key +/* Upon successful completion, this is set to the key +/* substring. +/* .IP value +/* Upon successful completion, this is set to the value +/* substring. +/* SEE ALSO +/* split_nameval(3) name-value splitter +/* BUGS +/* DIAGNOSTICS +/* The result is a null pointer in case of success, a string +/* describing the error otherwise: missing '=' after attribute +/* name; missing attribute name. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System libraries. */ + +#include <sys_defs.h> +#include <ctype.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <stringops.h> + +/* split_qnameval - split "key = value", support quoted key */ + +const char *split_qnameval(char *buf, char **pkey, char **pvalue) +{ + int in_quotes = 0; + char *key; + char *key_end; + char *value; + + for (key = buf; *key && ISSPACE(*key); key++) + /* void */ ; + if (*key == 0) + return ("no key found; expected format: key = value"); + + for (key_end = key; *key_end; key_end++) { + if (*key_end == '\\') { + if (*++key_end == 0) + break; + } else if (ISSPACE(*key_end) || *key_end == '=') { + if (!in_quotes) + break; + } else if (*key_end == '"') { + in_quotes = !in_quotes; + } + } + if (in_quotes) { + return ("unbalanced '\"\'"); + } + value = key_end; + while (ISSPACE(*value)) + value++; + if (*value != '=') + return ("missing '=' after attribute name"); + *key_end = 0; + do { + value++; + } while (ISSPACE(*value)); + trimblanks(value, 0)[0] = 0; + *pkey = key; + *pvalue = value; + return (0); +} + +#ifdef TEST + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +#include <mymalloc.h> + +static int compare(int test_number, const char *what, + const char *expect, const char *real) +{ + if ((expect == 0 && real == 0) + || (expect != 0 && real != 0 && strcmp(expect, real) == 0)) { + return (0); + } else { + msg_warn("test %d: %s mis-match: expect='%s', real='%s'", + test_number, what, expect ? expect : "(null)", + real ? real : "(null)"); + return (1); + } +} + +int main(int argc, char **argv) +{ + struct test_info { + const char *input; + const char *expect_result; + const char *expect_key; + const char *expect_value; + }; + static const struct test_info test_info[] = { + /* Unquoted keys. */ + {"xx = yy", 0, "xx", "yy"}, + {"xx=yy", 0, "xx", "yy"}, + {"xx =", 0, "xx", ""}, + {"xx=", 0, "xx", ""}, + {"xx", "missing '=' after attribute name", 0, 0}, + /* Quoted keys. */ + {"\"xx \" = yy", 0, "\"xx \"", "yy"}, + {"\"xx \"= yy", 0, "\"xx \"", "yy"}, + {"\"xx \" =", 0, "\"xx \"", ""}, + {"\"xx \"=", 0, "\"xx \"", ""}, + {"\"xx \"", "missing '=' after attribute name", 0, 0}, + {"\"xx ", "unbalanced '\"'", 0, 0}, + /* Backslash escapes. */ + {"\"\\\"xx \" = yy", 0, "\"\\\"xx \"", "yy"}, + {0,}, + }; + + int errs = 0; + const struct test_info *tp; + + for (tp = test_info; tp->input != 0; tp++) { + const char *result; + char *key = 0; + char *value = 0; + char *buf = mystrdup(tp->input); + int test_number = (int) (tp - test_info); + + result = split_qnameval(buf, &key, &value); + errs += compare(test_number, "result", tp->expect_result, result); + errs += compare(test_number, "key", tp->expect_key, key); + errs += compare(test_number, "value", tp->expect_value, value); + myfree(buf); + } + exit(errs); +} + +#endif diff --git a/src/util/stat_as.c b/src/util/stat_as.c new file mode 100644 index 0000000..3e05ff7 --- /dev/null +++ b/src/util/stat_as.c @@ -0,0 +1,73 @@ +/*++ +/* NAME +/* stat_as 3 +/* SUMMARY +/* stat file as user +/* SYNOPSIS +/* #include <sys/stat.h> +/* #include <stat_as.h> +/* +/* int stat_as(path, st, euid, egid) +/* const char *path; +/* struct stat *st; +/* uid_t euid; +/* gid_t egid; +/* DESCRIPTION +/* stat_as() looks up the file status of the named \fIpath\fR, +/* using the effective rights specified by \fIeuid\fR +/* and \fIegid\fR, and stores the result into the structure pointed +/* to by \fIst\fR. A -1 result means the lookup failed. +/* This call follows symbolic links. +/* DIAGNOSTICS +/* Fatal error: no permission to change privilege level. +/* SEE ALSO +/* set_eugid(3) switch effective rights +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/stat.h> +#include <unistd.h> + +/* Utility library. */ + +#include "msg.h" +#include "set_eugid.h" +#include "stat_as.h" +#include "warn_stat.h" + +/* stat_as - stat file as user */ + +int stat_as(const char *path, struct stat * st, uid_t euid, gid_t egid) +{ + uid_t saved_euid = geteuid(); + gid_t saved_egid = getegid(); + int status; + + /* + * Switch to the target user privileges. + */ + set_eugid(euid, egid); + + /* + * Stat that file. + */ + status = stat(path, st); + + /* + * Restore saved privileges. + */ + set_eugid(saved_euid, saved_egid); + + return (status); +} diff --git a/src/util/stat_as.h b/src/util/stat_as.h new file mode 100644 index 0000000..90bc05d --- /dev/null +++ b/src/util/stat_as.h @@ -0,0 +1,35 @@ +#ifndef _STAT_AS_H_INCLUDED_ +#define _STAT_AS_H_INCLUDED_ + +/*++ +/* NAME +/* stat_as 3h +/* SUMMARY +/* stat file as user +/* SYNOPSIS +/* #include <sys/stat.h> +/* #include <stat_as.h> +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern int WARN_UNUSED_RESULT stat_as(const char *, struct stat *, uid_t, gid_t); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/strcasecmp.c b/src/util/strcasecmp.c new file mode 100644 index 0000000..07e9381 --- /dev/null +++ b/src/util/strcasecmp.c @@ -0,0 +1,66 @@ +/* + * Copyright (c) 1987, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)strcasecmp.c 8.1 (Berkeley) 6/4/93"; +#endif /* LIBC_SCCS and not lint */ + +#include <sys_defs.h> +#include <ctype.h> + +int strcasecmp(const char *s1, const char *s2) +{ + const unsigned char *us1 = (const unsigned char *) s1; + const unsigned char *us2 = (const unsigned char *) s2; + + while (tolower(*us1) == tolower(*us2++)) + if (*us1++ == '\0') + return (0); + return (tolower(*us1) - tolower(*--us2)); +} + +int strncasecmp(const char *s1, const char *s2, size_t n) +{ + if (n != 0) { + const unsigned char *us1 = (const unsigned char *) s1; + const unsigned char *us2 = (const unsigned char *) s2; + + do { + if (tolower(*us1) != tolower(*us2++)) + return (tolower(*us1) - tolower(*--us2)); + if (*us1++ == '\0') + break; + } while (--n != 0); + } + return (0); +} diff --git a/src/util/strcasecmp_utf8.c b/src/util/strcasecmp_utf8.c new file mode 100644 index 0000000..e3f20df --- /dev/null +++ b/src/util/strcasecmp_utf8.c @@ -0,0 +1,216 @@ +/*++ +/* NAME +/* strcasecmp_utf8 3 +/* SUMMARY +/* caseless string comparison +/* SYNOPSIS +/* #include <stringops.h> +/* +/* int strcasecmp_utf8( +/* const char *s1, +/* const char *s2) +/* +/* int strncasecmp_utf8( +/* const char *s1, +/* const char *s2, +/* ssize_t len) +/* AUXILIARY FUNCTIONS +/* int strcasecmp_utf8x( +/* int flags, +/* const char *s1, +/* const char *s2) +/* +/* int strncasecmp_utf8x( +/* int flags, +/* const char *s1, +/* const char *s2, +/* ssize_t len) +/* DESCRIPTION +/* strcasecmp_utf8() implements caseless string comparison for +/* UTF-8 text, with an API similar to strcasecmp(). Only ASCII +/* characters are casefolded when the code is compiled without +/* EAI support or when util_utf8_enable is zero. +/* +/* strncasecmp_utf8() implements caseless string comparison +/* for UTF-8 text, with an API similar to strncasecmp(). Only +/* ASCII characters are casefolded when the code is compiled +/* without EAI support or when util_utf8_enable is zero. +/* +/* strcasecmp_utf8x() and strncasecmp_utf8x() implement a more +/* complex API that provides the above functionality and more. +/* +/* Arguments: +/* .IP "s1, s2" +/* Null-terminated strings to be compared. +/* .IP len +/* String length before casefolding. +/* .IP flags +/* Zero or CASEF_FLAG_UTF8. The latter flag enables UTF-8 case +/* folding instead of folding only ASCII characters. This flag +/* is ignored when compiled without EAI support. +/* SEE ALSO +/* casefold(), casefold text for caseless comparison. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include <sys_defs.h> +#include <string.h> + +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif + + /* + * Utility library. + */ +#include <stringops.h> + +#define STR(x) vstring_str(x) + +static VSTRING *f1; /* casefold result for s1 */ +static VSTRING *f2; /* casefold result for s2 */ + +/* strcasecmp_utf8_init - initialize */ + +static void strcasecmp_utf8_init(void) +{ + f1 = vstring_alloc(100); + f2 = vstring_alloc(100); +} + +/* strcasecmp_utf8x - caseless string comparison */ + +int strcasecmp_utf8x(int flags, const char *s1, const char *s2) +{ + + /* + * Short-circuit optimization for ASCII-only text. This may be slower + * than using a cache for all results. We must not expose strcasecmp(3) + * to non-ASCII text. + */ + if (allascii(s1) && allascii(s2)) + return (strcasecmp(s1, s2)); + + if (f1 == 0) + strcasecmp_utf8_init(); + + /* + * Cross our fingers and hope that strcmp() remains agnostic of + * charactersets and locales. + */ + flags &= CASEF_FLAG_UTF8; + casefoldx(flags, f1, s1, -1); + casefoldx(flags, f2, s2, -1); + return (strcmp(STR(f1), STR(f2))); +} + +/* strncasecmp_utf8x - caseless string comparison */ + +int strncasecmp_utf8x(int flags, const char *s1, const char *s2, + ssize_t len) +{ + + /* + * Consider using a cache for all results. + */ + if (f1 == 0) + strcasecmp_utf8_init(); + + /* + * Short-circuit optimization for ASCII-only text. This may be slower + * than using a cache for all results. See comments above for limitations + * of strcasecmp(). + */ + if (allascii_len(s1, len) && allascii_len(s2, len)) + return (strncasecmp(s1, s2, len)); + + /* + * Caution: casefolding may change the number of bytes. See comments + * above for concerns about strcmp(). + */ + flags &= CASEF_FLAG_UTF8; + casefoldx(flags, f1, s1, len); + casefoldx(flags, f2, s2, len); + return (strcmp(STR(f1), STR(f2))); +} + +#ifdef TEST +#include <stdio.h> +#include <stdlib.h> +#include <vstream.h> +#include <vstring_vstream.h> +#include <msg_vstream.h> +#include <argv.h> + +int main(int argc, char **argv) +{ + VSTRING *buffer = vstring_alloc(1); + ARGV *cmd; + char **args; + int len; + int flags; + int res; + + msg_vstream_init(argv[0], VSTREAM_ERR); + flags = CASEF_FLAG_UTF8; + util_utf8_enable = 1; + while (vstring_fgets_nonl(buffer, VSTREAM_IN)) { + vstream_printf("> %s\n", STR(buffer)); + cmd = argv_split(STR(buffer), CHARS_SPACE); + if (cmd->argc == 0 || cmd->argv[0][0] == '#') + continue; + args = cmd->argv; + + /* + * Compare two strings. + */ + if (strcmp(args[0], "compare") == 0 && cmd->argc == 3) { + res = strcasecmp_utf8x(flags, args[1], args[2]); + vstream_printf("\"%s\" %s \"%s\"\n", + args[1], + res < 0 ? "<" : res == 0 ? "==" : ">", + args[2]); + } + + /* + * Compare two substrings. + */ + else if (strcmp(args[0], "compare-len") == 0 && cmd->argc == 4 + && sscanf(args[3], "%d", &len) == 1 && len >= 0) { + res = strncasecmp_utf8x(flags, args[1], args[2], len); + vstream_printf("\"%.*s\" %s \"%.*s\"\n", + len, args[1], + res < 0 ? "<" : res == 0 ? "==" : ">", + len, args[2]); + } + + /* + * Usage. + */ + else { + vstream_printf("Usage: %s compare <s1> <s2> | compare-len <s1> <s2> <len>\n", + argv[0]); + } + vstream_fflush(VSTREAM_OUT); + argv_free(cmd); + } + exit(0); +} + +#endif /* TEST */ diff --git a/src/util/strcasecmp_utf8_test.in b/src/util/strcasecmp_utf8_test.in new file mode 100644 index 0000000..8bafbf0 --- /dev/null +++ b/src/util/strcasecmp_utf8_test.in @@ -0,0 +1,10 @@ +compare Δημοσθένους.example.com δημοσθένουσ.example.com +compare Δημοσθένους.example.com ηδμοσθένουσ.example.com +compare ηδμοσθένουσ.example.com Δημοσθένους.example.com +compare HeLlO.ExAmPlE.CoM hello.example.com +compare HeLlO hellp +compare hellp HeLlO +compare-len HeLlO hellp 4 +compare-len HeLO help 4 +compare abcde abcdf +compare YYY€€€XXX yyy€€€xxx diff --git a/src/util/strcasecmp_utf8_test.ref b/src/util/strcasecmp_utf8_test.ref new file mode 100644 index 0000000..8c0e1a5 --- /dev/null +++ b/src/util/strcasecmp_utf8_test.ref @@ -0,0 +1,20 @@ +> compare Δημοσθένους.example.com δημοσθένουσ.example.com +"Δημοσθένους.example.com" == "δημοσθένουσ.example.com" +> compare Δημοσθένους.example.com ηδμοσθένουσ.example.com +"Δημοσθένους.example.com" < "ηδμοσθένουσ.example.com" +> compare ηδμοσθένουσ.example.com Δημοσθένους.example.com +"ηδμοσθένουσ.example.com" > "Δημοσθένους.example.com" +> compare HeLlO.ExAmPlE.CoM hello.example.com +"HeLlO.ExAmPlE.CoM" == "hello.example.com" +> compare HeLlO hellp +"HeLlO" < "hellp" +> compare hellp HeLlO +"hellp" > "HeLlO" +> compare-len HeLlO hellp 4 +"HeLl" == "hell" +> compare-len HeLO help 4 +"HeLO" < "help" +> compare abcde abcdf +"abcde" < "abcdf" +> compare YYY€€€XXX yyy€€€xxx +"YYY€€€XXX" == "yyy€€€xxx" diff --git a/src/util/stream_connect.c b/src/util/stream_connect.c new file mode 100644 index 0000000..b8cc624 --- /dev/null +++ b/src/util/stream_connect.c @@ -0,0 +1,109 @@ +/*++ +/* NAME +/* stream_connect 3 +/* SUMMARY +/* connect to stream listener +/* SYNOPSIS +/* #include <connect.h> +/* +/* int stream_connect(path, block_mode, timeout) +/* const char *path; +/* int block_mode; +/* int timeout; +/* DESCRIPTION +/* stream_connect() connects to a stream listener for the specified +/* pathname, and returns the resulting file descriptor. +/* +/* Arguments: +/* .IP path +/* Null-terminated string with listener endpoint name. +/* .IP block_mode +/* Either NON_BLOCKING for a non-blocking stream, or BLOCKING for +/* blocking mode. However, a stream connection succeeds or fails +/* immediately. +/* .IP timeout +/* This argument is ignored; it is present for compatibility with +/* other interfaces. Stream connections succeed or fail immediately. +/* DIAGNOSTICS +/* The result is -1 in case the connection could not be made. +/* Fatal errors: other system call failures. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +#ifdef STREAM_CONNECTIONS + +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <stropts.h> + +#endif + +/* Utility library. */ + +#include <msg.h> +#include <connect.h> + +/* stream_connect - connect to stream listener */ + +int stream_connect(const char *path, int block_mode, int unused_timeout) +{ +#ifdef STREAM_CONNECTIONS + const char *myname = "stream_connect"; + int pair[2]; + int fifo; + + /* + * The requested file system object must exist, otherwise we can't reach + * the server. + */ + if ((fifo = open(path, O_WRONLY | O_NONBLOCK, 0)) < 0) + return (-1); + + /* + * This is for {unix,inet}_connect() compatibility. + */ + if (block_mode == BLOCKING) + non_blocking(fifo, BLOCKING); + + /* + * Create a pipe, and send one pipe end to the server. + */ + if (pipe(pair) < 0) + msg_fatal("%s: pipe: %m", myname); + if (ioctl(fifo, I_SENDFD, pair[1]) < 0) + msg_fatal("%s: send file descriptor: %m", myname); + close(pair[1]); + + /* + * This is for {unix,inet}_connect() compatibility. + */ + if (block_mode == NON_BLOCKING) + non_blocking(pair[0], NON_BLOCKING); + + /* + * Cleanup. + */ + close(fifo); + + /* + * Keep the other end of the pipe. + */ + return (pair[0]); +#else + msg_fatal("stream connections are not implemented"); +#endif +} diff --git a/src/util/stream_listen.c b/src/util/stream_listen.c new file mode 100644 index 0000000..b522b76 --- /dev/null +++ b/src/util/stream_listen.c @@ -0,0 +1,102 @@ +/*++ +/* NAME +/* stream_listen 3 +/* SUMMARY +/* start stream listener +/* SYNOPSIS +/* #include <listen.h> +/* +/* int stream_listen(path, backlog, block_mode) +/* const char *path; +/* int backlog; +/* int block_mode; +/* +/* int stream_accept(fd) +/* int fd; +/* DESCRIPTION +/* This module implements a substitute local IPC for systems that do +/* not have properly-working UNIX-domain sockets. +/* +/* stream_listen() creates a listener endpoint with the specified +/* permissions, and returns a file descriptor to be used for accepting +/* connections. +/* +/* stream_accept() accepts a connection. +/* +/* Arguments: +/* .IP path +/* Null-terminated string with connection destination. +/* .IP backlog +/* This argument exists for compatibility and is ignored. +/* .IP block_mode +/* Either NON_BLOCKING or BLOCKING. This does not affect the +/* mode of accepted connections. +/* .IP fd +/* File descriptor returned by stream_listen(). +/* DIAGNOSTICS +/* Fatal errors: stream_listen() aborts upon any system call failure. +/* stream_accept() leaves all error handling up to the caller. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System interfaces. */ + +#include <sys_defs.h> + +#ifdef STREAM_CONNECTIONS + +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> +#include <stropts.h> +#include <fcntl.h> + +#endif + +/* Utility library. */ + +#include "msg.h" +#include "listen.h" + +/* stream_listen - create stream listener */ + +int stream_listen(const char *path, int unused_backlog, int block_mode) +{ +#ifdef STREAM_CONNECTIONS + + /* + * We can't specify a listen backlog, however, sending file descriptors + * across a FIFO gives us a backlog buffer of 460 on Solaris 2.4/SPARC. + */ + return (fifo_listen(path, 0622, block_mode)); +#else + msg_fatal("stream connections are not implemented"); +#endif +} + +/* stream_accept - accept stream connection */ + +int stream_accept(int fd) +{ +#ifdef STREAM_CONNECTIONS + struct strrecvfd fdinfo; + + /* + * This will return EAGAIN on a non-blocking stream when someone else + * snatched the connection from us. + */ + if (ioctl(fd, I_RECVFD, &fdinfo) < 0) + return (-1); + return (fdinfo.fd); +#else + msg_fatal("stream connections are not implemented"); +#endif +} diff --git a/src/util/stream_recv_fd.c b/src/util/stream_recv_fd.c new file mode 100644 index 0000000..5be02e4 --- /dev/null +++ b/src/util/stream_recv_fd.c @@ -0,0 +1,120 @@ +/*++ +/* NAME +/* stream_recv_fd 3 +/* SUMMARY +/* receive file descriptor +/* SYNOPSIS +/* #include <iostuff.h> +/* +/* int stream_recv_fd(fd) +/* int fd; +/* DESCRIPTION +/* stream_recv_fd() receives a file descriptor via the specified +/* stream. The result value is the received descriptor. +/* +/* Arguments: +/* .IP fd +/* File descriptor that connects the sending and receiving processes. +/* DIAGNOSTICS +/* stream_recv_fd() returns -1 upon failure. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> /* includes <sys/types.h> */ + +#ifdef STREAM_CONNECTIONS + +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> +#include <stropts.h> +#include <fcntl.h> + +#endif + +/* Utility library. */ + +#include <msg.h> +#include <iostuff.h> + +/* stream_recv_fd - receive file descriptor */ + +int stream_recv_fd(int fd) +{ +#ifdef STREAM_CONNECTIONS + struct strrecvfd fdinfo; + + /* + * This will return EAGAIN on a non-blocking stream when someone else + * snatched the connection from us. + */ + if (ioctl(fd, I_RECVFD, &fdinfo) < 0) + return (-1); + return (fdinfo.fd); +#else + msg_fatal("stream connections are not implemented"); +#endif +} + +#ifdef TEST + + /* + * Proof-of-concept program. Receive a descriptor (presumably from the + * stream_send_fd test program) and copy its content until EOF. + */ +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <split_at.h> +#include <listen.h> + +int main(int argc, char **argv) +{ + char *transport; + char *endpoint; + int listen_sock; + int client_sock; + int client_fd; + ssize_t read_count; + char buf[1024]; + + if (argc != 2 + || (endpoint = split_at(transport = argv[1], ':')) == 0 + || *endpoint == 0 || *transport == 0) + msg_fatal("usage: %s transport:endpoint", argv[0]); + + if (strcmp(transport, "stream") == 0) { + listen_sock = stream_listen(endpoint, BLOCKING, 0); + } else { + msg_fatal("invalid transport name: %s", transport); + } + if (listen_sock < 0) + msg_fatal("listen %s:%s: %m", transport, endpoint); + + client_sock = stream_accept(listen_sock); + if (client_sock < 0) + msg_fatal("stream_accept: %m"); + + while ((client_fd = stream_recv_fd(client_sock)) >= 0) { + msg_info("client_fd = %d", client_fd); + while ((read_count = read(client_fd, buf, sizeof(buf))) > 0) + write(1, buf, read_count); + if (read_count < 0) + msg_fatal("read: %m"); + if (close(client_fd) != 0) + msg_fatal("close(%d): %m", client_fd); + } + exit(0); +} + +#endif diff --git a/src/util/stream_send_fd.c b/src/util/stream_send_fd.c new file mode 100644 index 0000000..0a9aebf --- /dev/null +++ b/src/util/stream_send_fd.c @@ -0,0 +1,115 @@ +/*++ +/* NAME +/* stream_send_fd 3 +/* SUMMARY +/* send file descriptor +/* SYNOPSIS +/* #include <iostuff.h> +/* +/* int stream_send_fd(fd, sendfd) +/* int fd; +/* int sendfd; +/* DESCRIPTION +/* stream_send_fd() sends a file descriptor over the specified +/* stream. +/* +/* Arguments: +/* .IP fd +/* File descriptor that connects the sending and receiving processes. +/* .IP sendfd +/* The file descriptor to be sent. +/* DIAGNOSTICS +/* stream_send_fd() returns -1 upon failure. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> /* includes <sys/types.h> */ + +#ifdef STREAM_CONNECTIONS + +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <stropts.h> + +#endif + +/* Utility library. */ + +#include <msg.h> +#include <iostuff.h> + +/* stream_send_fd - send file descriptor */ + +int stream_send_fd(int fd, int sendfd) +{ +#ifdef STREAM_CONNECTIONS + const char *myname = "stream_send_fd"; + + if (ioctl(fd, I_SENDFD, sendfd) < 0) + msg_fatal("%s: send file descriptor %d: %m", myname, sendfd); + return (0); +#else + msg_fatal("stream connections are not implemented"); +#endif +} + +#ifdef TEST + + /* + * Proof-of-concept program. Open a file and send the descriptor, presumably + * to the stream_recv_fd test program. + */ +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <stdlib.h> +#include <split_at.h> +#include <connect.h> + +int main(int argc, char **argv) +{ + char *transport; + char *endpoint; + char *path; + int server_sock; + int client_fd; + + if (argc < 3 + || (endpoint = split_at(transport = argv[1], ':')) == 0 + || *endpoint == 0 || *transport == 0) + msg_fatal("usage: %s transport:endpoint file...", argv[0]); + + if (strcmp(transport, "stream") == 0) { + server_sock = stream_connect(endpoint, BLOCKING, 0); + } else { + msg_fatal("invalid transport name: %s", transport); + } + if (server_sock < 0) + msg_fatal("connect %s:%s: %m", transport, endpoint); + + argv += 2; + while ((path = *argv++) != 0) { + if ((client_fd = open(path, O_RDONLY, 0)) < 0) + msg_fatal("open %s: %m", path); + msg_info("path=%s client_fd=%d", path, client_fd); + if (stream_send_fd(server_sock, client_fd) < 0) + msg_fatal("send file descriptor: %m"); + if (close(client_fd) != 0) + msg_fatal("close(%d): %m", client_fd); + } + exit(0); +} + +#endif diff --git a/src/util/stream_test.c b/src/util/stream_test.c new file mode 100644 index 0000000..5c8f82f --- /dev/null +++ b/src/util/stream_test.c @@ -0,0 +1,111 @@ +#include "sys_defs.h" +#include <sys/stat.h> +#include <unistd.h> +#include <stdlib.h> +#include <fcntl.h> + +#include "iostuff.h" +#include "msg.h" +#include "msg_vstream.h" +#include "listen.h" +#include "connect.h" + +#ifdef SUNOS5 +#include <stropts.h> + +#define FIFO "/tmp/test-fifo" + +static const char *progname; + +static void print_fstat(int fd) +{ + struct stat st; + + if (fstat(fd, &st) < 0) + msg_fatal("fstat: %m"); + vstream_printf("fd %d\n", fd); + vstream_printf("dev %ld\n", (long) st.st_dev); + vstream_printf("ino %ld\n", (long) st.st_ino); + vstream_fflush(VSTREAM_OUT); +} + +static NORETURN usage(void) +{ + msg_fatal("usage: %s [-p] [-n count] [-v]", progname); +} + +int main(int argc, char **argv) +{ + int server_fd; + int client_fd; + int fd; + int print_fstats = 0; + int count = 1; + int ch; + int i; + + progname = argv[0]; + msg_vstream_init(argv[0], VSTREAM_ERR); + + /* + * Parse JCL. + */ + while ((ch = GETOPT(argc, argv, "pn:v")) > 0) { + switch (ch) { + default: + usage(); + case 'p': + print_fstats = 1; + break; + case 'n': + if ((count = atoi(optarg)) < 1) + usage(); + break; + case 'v': + msg_verbose++; + break; + } + } + server_fd = stream_listen(FIFO, 0, 0); + if (readable(server_fd)) + msg_fatal("server fd is readable after create"); + + /* + * Connect in client. + */ + for (i = 0; i < count; i++) { + msg_info("connect attempt %d", i); + if ((client_fd = stream_connect(FIFO, 0, 0)) < 0) + msg_fatal("open %s as client: %m", FIFO); + if (readable(server_fd)) + msg_info("server fd is readable after client open"); + if (close(client_fd) < 0) + msg_fatal("close client fd: %m"); + } + + /* + * Accept in server. + */ + for (i = 0; i < count; i++) { + msg_info("receive attempt %d", i); + if (!readable(server_fd)) { + msg_info("wait for server fd to become readable"); + read_wait(server_fd, -1); + } + if ((fd = stream_accept(server_fd)) < 0) + msg_fatal("receive fd: %m"); + if (print_fstats) + print_fstat(fd); + if (close(fd) < 0) + msg_fatal("close received fd: %m"); + } + if (close(server_fd) < 0) + msg_fatal("close server fd"); + return (0); +} +#else +int main(int argc, char **argv) +{ + return (0); +} +#endif diff --git a/src/util/stream_trigger.c b/src/util/stream_trigger.c new file mode 100644 index 0000000..4feea5f --- /dev/null +++ b/src/util/stream_trigger.c @@ -0,0 +1,130 @@ +/*++ +/* NAME +/* stream_trigger 3 +/* SUMMARY +/* wakeup stream server +/* SYNOPSIS +/* #include <trigger.h> +/* +/* int stream_trigger(service, buf, len, timeout) +/* const char *service; +/* const char *buf; +/* ssize_t len; +/* int timeout; +/* DESCRIPTION +/* stream_trigger() wakes up the named stream server by making +/* a brief connection to it and writing the named buffer. +/* +/* The connection is closed by a background thread. Some kernels +/* cannot handle client-side disconnect before the server has +/* received the message. +/* +/* Arguments: +/* .IP service +/* Name of the communication endpoint. +/* .IP buf +/* Address of data to be written. +/* .IP len +/* Amount of data to be written. +/* .IP timeout +/* Deadline in seconds. Specify a value <= 0 to disable +/* the time limit. +/* DIAGNOSTICS +/* The result is zero in case of success, -1 in case of problems. +/* SEE ALSO +/* stream_connect(3), stream client +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <unistd.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <connect.h> +#include <iostuff.h> +#include <mymalloc.h> +#include <events.h> +#include <trigger.h> + +struct stream_trigger { + int fd; + char *service; +}; + +/* stream_trigger_event - disconnect from peer */ + +static void stream_trigger_event(int event, void *context) +{ + struct stream_trigger *sp = (struct stream_trigger *) context; + static const char *myname = "stream_trigger_event"; + + /* + * Disconnect. + */ + if (event == EVENT_TIME) + msg_warn("%s: read timeout for service %s", myname, sp->service); + event_disable_readwrite(sp->fd); + event_cancel_timer(stream_trigger_event, context); + if (close(sp->fd) < 0) + msg_warn("%s: close %s: %m", myname, sp->service); + myfree(sp->service); + myfree((void *) sp); +} + +/* stream_trigger - wakeup stream server */ + +int stream_trigger(const char *service, const char *buf, ssize_t len, int timeout) +{ + const char *myname = "stream_trigger"; + struct stream_trigger *sp; + int fd; + + if (msg_verbose > 1) + msg_info("%s: service %s", myname, service); + + /* + * Connect... + */ + if ((fd = stream_connect(service, BLOCKING, timeout)) < 0) { + if (msg_verbose) + msg_warn("%s: connect to %s: %m", myname, service); + return (-1); + } + close_on_exec(fd, CLOSE_ON_EXEC); + + /* + * Stash away context. + */ + sp = (struct stream_trigger *) mymalloc(sizeof(*sp)); + sp->fd = fd; + sp->service = mystrdup(service); + + /* + * Write the request... + */ + if (write_buf(fd, buf, len, timeout) < 0 + || write_buf(fd, "", 1, timeout) < 0) + if (msg_verbose) + msg_warn("%s: write to %s: %m", myname, service); + + /* + * Wakeup when the peer disconnects, or when we lose patience. + */ + if (timeout > 0) + event_request_timer(stream_trigger_event, (void *) sp, timeout + 100); + event_enable_read(fd, stream_trigger_event, (void *) sp); + return (0); +} diff --git a/src/util/stringops.h b/src/util/stringops.h new file mode 100644 index 0000000..8ac177b --- /dev/null +++ b/src/util/stringops.h @@ -0,0 +1,107 @@ +#ifndef _STRINGOPS_H_INCLUDED_ +#define _STRINGOPS_H_INCLUDED_ + +/*++ +/* NAME +/* stringops 3h +/* SUMMARY +/* string operations +/* SYNOPSIS +/* #include <stringops.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <vstring.h> + + /* + * External interface. + */ +extern int util_utf8_enable; +extern char *printable_except(char *, int, const char *); +extern char *neuter(char *, const char *, int); +extern char *lowercase(char *); +extern char *casefoldx(int, VSTRING *, const char *, ssize_t); +extern char *uppercase(char *); +extern char *skipblanks(const char *); +extern char *trimblanks(char *, ssize_t); +extern char *concatenate(const char *,...); +extern char *mystrtok(char **, const char *); +extern char *mystrtokq(char **, const char *, const char *); +extern char *mystrtokdq(char **, const char *); +extern char *translit(char *, const char *, const char *); + +#define printable(string, replacement) \ + printable_except((string), (replacement), (char *) 0) + +#ifndef HAVE_BASENAME +#define basename postfix_basename +extern char *basename(const char *); + +#endif +extern char *sane_basename(VSTRING *, const char *); +extern char *sane_dirname(VSTRING *, const char *); +extern VSTRING *unescape(VSTRING *, const char *); +extern VSTRING *escape(VSTRING *, const char *, ssize_t); +extern int alldig(const char *); +extern int allalnum(const char *); +extern int allprint(const char *); +extern int allspace(const char *); +extern int allascii_len(const char *, ssize_t); +extern const char *WARN_UNUSED_RESULT split_nameval(char *, char **, char **); +extern const char *WARN_UNUSED_RESULT split_qnameval(char *, char **, char **); +extern int valid_utf8_string(const char *, ssize_t); +extern size_t balpar(const char *, const char *); +extern char *WARN_UNUSED_RESULT extpar(char **, const char *, int); +extern int strcasecmp_utf8x(int, const char *, const char *); +extern int strncasecmp_utf8x(int, const char *, const char *, ssize_t); + +#define EXTPAR_FLAG_NONE (0) +#define EXTPAR_FLAG_STRIP (1<<0) /* "{ text }" -> "text" */ +#define EXTPAR_FLAG_EXTRACT (1<<1) /* hint from caller's caller */ + +#define CASEF_FLAG_UTF8 (1<<0) +#define CASEF_FLAG_APPEND (1<<1) + + /* + * Convenience wrappers for most-common use cases. + */ +#define allascii(s) allascii_len((s), -1) +#define casefold(dst, src) \ + casefoldx(util_utf8_enable ? CASEF_FLAG_UTF8 : 0, (dst), (src), -1) +#define casefold_len(dst, src, len) \ + casefoldx(util_utf8_enable ? CASEF_FLAG_UTF8 : 0, (dst), (src), (len)) +#define casefold_append(dst, src) \ + casefoldx((util_utf8_enable ? CASEF_FLAG_UTF8 : 0) | CASEF_FLAG_APPEND, \ + (dst), (src), -1) + +#define strcasecmp_utf8(s1, s2) \ + strcasecmp_utf8x(util_utf8_enable ? CASEF_FLAG_UTF8 : 0, (s1), (s2)) +#define strncasecmp_utf8(s1, s2, l) \ + strncasecmp_utf8x(util_utf8_enable ? CASEF_FLAG_UTF8 : 0, (s1), (s2), (l)) + + /* + * Use STRREF(x) instead of x, to shut up compiler warnings when the operand + * is a string literal. + */ +#define STRREF(x) (&x[0]) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/surrogate.ref b/src/util/surrogate.ref new file mode 100644 index 0000000..b8ecd8e --- /dev/null +++ b/src/util/surrogate.ref @@ -0,0 +1,55 @@ +./dict_open: error: cidr:/xx map requires O_RDONLY access mode +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: cidr:/xx is unavailable. cidr:/xx map requires O_RDONLY access mode +foo: error +./dict_open: error: open /xx: No such file or directory +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: cidr:/xx is unavailable. open /xx: No such file or directory +foo: error +./dict_open: error: pcre:/xx map requires O_RDONLY access mode +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: pcre:/xx is unavailable. pcre:/xx map requires O_RDONLY access mode +foo: error +./dict_open: error: open /xx: No such file or directory +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: pcre:/xx is unavailable. open /xx: No such file or directory +foo: error +./dict_open: error: regexp:/xx map requires O_RDONLY access mode +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: regexp:/xx is unavailable. regexp:/xx map requires O_RDONLY access mode +foo: error +./dict_open: error: open /xx: No such file or directory +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: regexp:/xx is unavailable. open /xx: No such file or directory +foo: error +./dict_open: error: unix:xx map requires O_RDONLY access mode +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: unix:xx is unavailable. unix:xx map requires O_RDONLY access mode +foo: error +./dict_open: error: unknown table: unix:xx +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: unix:xx is unavailable. unknown table: unix:xx +foo: error +./dict_open: error: texthash:/xx map requires O_RDONLY access mode +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: texthash:/xx is unavailable. texthash:/xx map requires O_RDONLY access mode +foo: error +./dict_open: error: open database /xx: No such file or directory +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: texthash:/xx is unavailable. open database /xx: No such file or directory +foo: error +./dict_open: error: open database /xx.db: No such file or directory +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: hash:/xx is unavailable. open database /xx.db: No such file or directory +foo: error diff --git a/src/util/sys_compat.c b/src/util/sys_compat.c new file mode 100644 index 0000000..8bf8e58 --- /dev/null +++ b/src/util/sys_compat.c @@ -0,0 +1,389 @@ +/*++ +/* NAME +/* sys_compat 3 +/* SUMMARY +/* compatibility routines +/* SYNOPSIS +/* #include <sys_defs.h> +/* +/* void closefrom(int lowfd) +/* int lowfd; +/* +/* const char *strerror(err) +/* int err; +/* +/* int setenv(name, value, clobber) +/* const char *name; +/* const char *value; +/* int clobber; +/* +/* int unsetenv(name) +/* const char *name; +/* +/* int seteuid(euid) +/* uid_t euid; +/* +/* int setegid(egid) +/* gid_t euid; +/* +/* int mkfifo(path, mode) +/* char *path; +/* int mode; +/* +/* int waitpid(pid, statusp, options) +/* int pid; +/* WAIT_STATUS_T *statusp; +/* int options; +/* +/* int setsid() +/* +/* void dup2_pass_on_exec(int oldd, int newd) +/* +/* char *inet_ntop(af, src, dst, size) +/* int af; +/* const void *src; +/* char *dst; +/* SOCKADDR_SIZE size; +/* +/* int inet_pton(af, src, dst) +/* int af; +/* const char *src; +/* void *dst; +/* DESCRIPTION +/* These routines are compiled for platforms that lack the functionality +/* or that have broken versions that we prefer to stay away from. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" + + /* + * ANSI strerror() emulation + */ +#ifdef MISSING_STRERROR + +extern int errno; +extern char *sys_errlist[]; +extern int sys_nerr; + +#include <vstring.h> + +/* strerror - print text corresponding to error */ + +const char *strerror(int err) +{ + static VSTRING *buf; + + if (err < 0 || err >= sys_nerr) { + if (buf == 0) + buf = vstring_alloc(10); + vstring_sprintf(buf, "Unknown error %d", err); + return (vstring_str(buf)); + } else { + return (sys_errlist[errno]); + } +} + +#endif + + /* + * setenv() emulation on top of putenv(). + */ +#ifdef MISSING_SETENV + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +/* setenv - update or insert environment (name,value) pair */ + +int setenv(const char *name, const char *value, int clobber) +{ + char *cp; + + if (clobber == 0 && getenv(name) != 0) + return (0); + if ((cp = malloc(strlen(name) + strlen(value) + 2)) == 0) + return (1); + sprintf(cp, "%s=%s", name, value); + return (putenv(cp)); +} + +/* unsetenv - remove all instances of the name */ + +int unsetenv(const char *name) +{ + extern char **environ; + ssize_t name_len = strlen(name); + char **src_pp; + char **dst_pp; + + for (dst_pp = src_pp = environ; *src_pp; src_pp++, dst_pp++) { + if (strncmp(*src_pp, name, name_len) == 0 + && *(*src_pp + name_len) == '=') { + dst_pp--; + } else if (dst_pp != src_pp) { + *dst_pp = *src_pp; + } + } + *dst_pp = 0; + return (0); +} + +#endif + + /* + * seteuid() and setegid() emulation, the HP-UX way + */ +#ifdef MISSING_SETEUID +#ifdef HAVE_SETRESUID +#include <unistd.h> + +int seteuid(uid_t euid) +{ + return setresuid(-1, euid, -1); +} + +#else +#error MISSING_SETEUID +#endif + +#endif + +#ifdef MISSING_SETEGID +#ifdef HAVE_SETRESGID +#include <unistd.h> + +int setegid(gid_t egid) +{ + return setresgid(-1, egid, -1); +} + +#else +#error MISSING_SETEGID +#endif + +#endif + + /* + * mkfifo() emulation - requires superuser privileges + */ +#ifdef MISSING_MKFIFO + +#include <sys/stat.h> + +int mkfifo(char *path, int mode) +{ + return mknod(path, (mode & ~_S_IFMT) | _S_IFIFO, 0); +} + +#endif + + /* + * waitpid() emulation on top of Berkeley UNIX wait4() + */ +#ifdef MISSING_WAITPID +#ifdef HAS_WAIT4 + +#include <sys/wait.h> +#include <errno.h> + +int waitpid(int pid, WAIT_STATUS_T *status, int options) +{ + if (pid == -1) + pid = 0; + return wait4(pid, status, options, (struct rusage *) 0); +} + +#else +#error MISSING_WAITPID +#endif + +#endif + + /* + * setsid() emulation, the Berkeley UNIX way + */ +#ifdef MISSING_SETSID + +#include <sys/ioctl.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +#ifdef TIOCNOTTY + +#include <msg.h> + +int setsid(void) +{ + int p = getpid(); + int fd; + + if (setpgrp(p, p)) + return -1; + + fd = open("/dev/tty", O_RDONLY, 0); + if (fd >= 0 || errno != ENXIO) { + if (fd < 0) { + msg_warn("open /dev/tty: %m"); + return -1; + } + if (ioctl(fd, TIOCNOTTY, 0)) { + msg_warn("ioctl TIOCNOTTY: %m"); + return -1; + } + close(fd); + } + return 0; +} + +#else +#error MISSING_SETSID +#endif + +#endif + + /* + * dup2_pass_on_exec() - dup2() and clear close-on-exec flag on the result + */ +#ifdef DUP2_DUPS_CLOSE_ON_EXEC + +#include "iostuff.h" + +int dup2_pass_on_exec(int oldd, int newd) +{ + int res; + + if ((res = dup2(oldd, newd)) >= 0) + close_on_exec(newd, PASS_ON_EXEC); + + return res; +} + +#endif + +#ifndef HAS_CLOSEFROM + +#include <unistd.h> +#include <errno.h> +#include <iostuff.h> + +/* closefrom() - closes all file descriptors from the given one up */ + +int closefrom(int lowfd) +{ + int fd_limit = open_limit(0); + int fd; + + /* + * lowfrom does not have an easy to determine upper limit. A process may + * have files open that were inherited from a parent process with a less + * restrictive resource limit. + */ + if (lowfd < 0) { + errno = EBADF; + return (-1); + } + if (fd_limit > 500) + fd_limit = 500; + for (fd = lowfd; fd < fd_limit; fd++) + (void) close(fd); + + return (0); +} + +#endif + +#ifdef MISSING_INET_NTOP + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> + +/* inet_ntop - convert binary address to printable address */ + +const char *inet_ntop(int af, const void *src, char *dst, SOCKADDR_SIZE size) +{ + const unsigned char *addr; + char buffer[sizeof("255.255.255.255")]; + int len; + + if (af != AF_INET) { + errno = EAFNOSUPPORT; + return (0); + } + addr = (const unsigned char *) src; +#if (CHAR_BIT > 8) + sprintf(buffer, "%d.%d.%d.%d", addr[0] & 0xff, + addr[1] & 0xff, addr[2] & 0xff, addr[3] & 0xff); +#else + sprintf(buffer, "%d.%d.%d.%d", addr[0], addr[1], addr[2], addr[3]); +#endif + if ((len = strlen(buffer)) >= size) { + errno = ENOSPC; + return (0); + } else { + memcpy(dst, buffer, len + 1); + return (dst); + } +} + +#endif + +#ifdef MISSING_INET_PTON + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <string.h> +#include <errno.h> + +#ifndef INADDR_NONE +#define INADDR_NONE 0xffffffff +#endif + +/* inet_pton - convert printable address to binary address */ + +int inet_pton(int af, const char *src, void *dst) +{ + struct in_addr addr; + + /* + * inet_addr() accepts a wider range of input formats than inet_pton(); + * the former accepts 1-, 2-, or 3-part dotted addresses, while the + * latter requires dotted quad form. + */ + if (af != AF_INET) { + errno = EAFNOSUPPORT; + return (-1); + } else if ((addr.s_addr = inet_addr(src)) == INADDR_NONE + && strcmp(src, "255.255.255.255") != 0) { + return (0); + } else { + memcpy(dst, (void *) &addr, sizeof(addr)); + return (1); + } +} + +#endif diff --git a/src/util/sys_defs.h b/src/util/sys_defs.h new file mode 100644 index 0000000..37e460f --- /dev/null +++ b/src/util/sys_defs.h @@ -0,0 +1,1797 @@ +#ifndef _SYS_DEFS_H_INCLUDED_ +#define _SYS_DEFS_H_INCLUDED_ + +/*++ +/* NAME +/* sys_defs 3h +/* SUMMARY +/* portability header +/* SYNOPSIS +/* #include <sys_defs.h> +/* DESCRIPTION +/* .nf + + /* + * Specific platforms. Major release numbers differ for a good reason. So be + * a good girl, plan for the future, and at least include the major release + * number in the system type (for example, SUNOS5 or FREEBSD2). The system + * type is determined by the makedefs shell script in the top-level + * directory. Adding support for a new system type means updating the + * makedefs script, and adding a section below for the new system. + */ +#ifdef SUNOS5 +#define _SVID_GETTOD /* Solaris 2.5, XSH4.2 versus SVID */ +#endif +#include <sys/types.h> + + /* + * 4.4BSD and close derivatives. + */ +#if defined(FREEBSD2) || defined(FREEBSD3) || defined(FREEBSD4) \ + || defined(FREEBSD5) || defined(FREEBSD6) || defined(FREEBSD7) \ + || defined(FREEBSD8) || defined(FREEBSD9) || defined(FREEBSD10) \ + || defined(FREEBSD11) || defined(FREEBSD12) || defined(FREEBSD13) \ + || defined(FREEBSD14) \ + || defined(BSDI2) || defined(BSDI3) || defined(BSDI4) \ + || defined(OPENBSD2) || defined(OPENBSD3) || defined(OPENBSD4) \ + || defined(OPENBSD5) || defined(OPENBSD6) || defined(OPENBSD7) \ + || defined(NETBSD1) || defined(NETBSD2) || defined(NETBSD3) \ + || defined(NETBSD4) || defined(NETBSD5) || defined(NETBSD6) \ + || defined(NETBSD7) | defined(NETBSD8) || defined(NETBSD9) \ + || defined(NETBSD10) \ + || defined(EKKOBSD1) || defined(DRAGONFLY) +#define SUPPORTED +#include <sys/param.h> +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define USE_PATHS_H +#define HAS_FLOCK_LOCK +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK +#define DEF_MAILBOX_LOCK "flock, dotlock" +#define HAS_SUN_LEN +#define HAS_FSYNC +#define HAS_DB +#define HAS_SA_LEN +#define NATIVE_DB_TYPE "hash" +#if (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 104250000) +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases" /* sendmail 8.10 */ +#endif +#if (defined(OpenBSD) && OpenBSD >= 200006) +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases" /* OpenBSD 2.7 */ +#endif +#ifndef ALIAS_DB_MAP +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases" +#endif +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin" +#if (defined(__NetBSD_Version__) && __NetBSD_Version__ > 299000900) +#define USE_STATVFS +#define STATVFS_IN_SYS_STATVFS_H +#else +#define USE_STATFS +#define STATFS_IN_SYS_MOUNT_H +#endif +#define HAS_POSIX_REGEXP +#define HAS_ST_GEN /* struct stat contains inode + * generation number */ +#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail" +#define NATIVE_MAILQ_PATH "/usr/bin/mailq" +#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases" +#define NATIVE_COMMAND_DIR "/usr/sbin" +#define NATIVE_DAEMON_DIR "/usr/libexec/postfix" +#define HAS_DLOPEN +#endif + +#ifdef FREEBSD2 +#define getsid(p) getpgrp() +#ifndef CMSG_SPACE +#define CMSG_SPACE(len) (CMSG_ALIGN(sizeof(struct cmsghdr)) + CMSG_ALIGN(len)) +#endif +#ifndef CMSG_LEN +#define CMSG_LEN(len) (CMSG_ALIGN(sizeof(struct cmsghdr)) + (len)) +#endif +#ifndef CMSG_ALIGN +#define CMSG_ALIGN(n) ALIGN(n) +#endif +#endif /* FREEBSD2 */ + +#ifdef BSDI4 +/* #define HAS_IPV6 find out interface lookup method */ +#endif + +/* __FreeBSD_version version is major+minor */ + +#if __FreeBSD_version >= 220000 +#define PREFERRED_RAND_SOURCE "dev:/dev/urandom" /* introduced 2.1.5 */ +#endif + +#if __FreeBSD_version >= 300000 +#define HAS_ISSETUGID +#define HAS_FUTIMES +#endif + +#if __FreeBSD_version >= 400000 +#define SOCKADDR_SIZE socklen_t +#define SOCKOPT_SIZE socklen_t +#endif + +#if __FreeBSD_version >= 420000 +#define HAS_DUPLEX_PIPE /* 4.1 breaks with kqueue(2) */ +#endif + +#if (__FreeBSD_version >= 702104 && __FreeBSD_version <= 800000) \ + || __FreeBSD_version >= 800100 +#define HAS_CLOSEFROM +#endif + +/* OpenBSD version is year+month */ + +#if OpenBSD >= 199805 /* XXX */ +#define HAS_FUTIMES /* XXX maybe earlier */ +#endif + +#if (defined(OpenBSD) && OpenBSD >= 199608 && OpenBSD < 201105) +#define PREFERRED_RAND_SOURCE "dev:/dev/arandom" /* XXX earlier */ +#endif + +#if OpenBSD >= 200000 /* XXX */ +#define HAS_ISSETUGID +#endif + +#if OpenBSD >= 200200 /* XXX */ +#define SOCKADDR_SIZE socklen_t +#define SOCKOPT_SIZE socklen_t +#endif + +#if OpenBSD >= 200405 /* 3.5 */ +#define HAS_CLOSEFROM +#endif + +/* __NetBSD_Version__ is major+minor */ + +#if __NetBSD_Version__ >= 103000000 /* XXX maybe earlier */ +#undef DEF_MAILBOX_LOCK +#define DEF_MAILBOX_LOCK "flock, dotlock" +#define PREFERRED_RAND_SOURCE "dev:/dev/urandom" /* XXX maybe earlier */ +#endif + +#if __NetBSD_Version__ >= 105000000 +#define HAS_ISSETUGID /* XXX maybe earlier */ +#endif + +#if __NetBSD_Version__ >= 106000000 /* XXX maybe earlier */ +#define SOCKADDR_SIZE socklen_t +#define SOCKOPT_SIZE socklen_t +#endif + +#if __NetBSD_Version__ >= 299000900 /* 2.99.9 */ +#define HAS_CLOSEFROM +#endif + +#if (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 102000000) +#define HAS_FUTIMES +#endif + +#if defined(__DragonFly__) +#define HAS_DEV_URANDOM +#define HAS_ISSETUGID +#define HAS_FUTIMES +#define SOCKADDR_SIZE socklen_t +#define SOCKOPT_SIZE socklen_t +#define HAS_DUPLEX_PIPE +#endif + +#if (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 105000000) \ + || (defined(__FreeBSD__) && __FreeBSD__ >= 4) \ + || (defined(OpenBSD) && OpenBSD >= 200003) \ + || defined(__DragonFly__) \ + || defined(USAGI_LIBINET6) +#ifndef NO_IPV6 +#define HAS_IPV6 +#define HAVE_GETIFADDRS +#endif + +#if (defined(__FreeBSD_version) && __FreeBSD_version >= 300000) \ + || (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 103000000) \ + || (defined(OpenBSD) && OpenBSD >= 199700) /* OpenBSD 2.0?? */ \ + || defined(__DragonFly__) +#define USE_SYSV_POLL +#endif + +#ifndef NO_KQUEUE +#if (defined(__FreeBSD_version) && __FreeBSD_version >= 410000) \ + || (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 200000000) \ + || (defined(OpenBSD) && OpenBSD >= 200105) /* OpenBSD 2.9 */ \ + || defined(__DragonFly__) +#define EVENTS_STYLE EVENTS_STYLE_KQUEUE +#endif +#endif + +#ifndef NO_POSIX_GETPW_R +#if (defined(__FreeBSD_version) && __FreeBSD_version >= 510000) \ + || (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 300000000) \ + || (defined(OpenBSD) && OpenBSD >= 200811) /* OpenBSD 4.4 */ +#define HAVE_POSIX_GETPW_R +#endif +#endif + +#endif + + /* + * UNIX on MAC. + */ +#if defined(RHAPSODY5) || defined(MACOSX) +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define USE_PATHS_H +#define HAS_FLOCK_LOCK +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK +#define DEF_MAILBOX_LOCK "flock, dotlock" +#define HAS_SUN_LEN +#define HAS_FSYNC +#define HAS_DB +#define HAS_SA_LEN +#define NATIVE_DB_TYPE "hash" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases" +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin" +#define USE_STATFS +#define STATFS_IN_SYS_MOUNT_H +#define HAS_POSIX_REGEXP +#ifndef NO_NETINFO +#define HAS_NETINFO +#endif +#ifndef NO_IPV6 +#define HAS_IPV6 +#define HAVE_GETIFADDRS +#endif +#define HAS_FUTIMES /* XXX Guessing */ +#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail" +#define NATIVE_MAILQ_PATH "/usr/bin/mailq" +#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases" +#define NATIVE_COMMAND_DIR "/usr/sbin" +#define NATIVE_DAEMON_DIR "/usr/libexec/postfix" +#define SOCKADDR_SIZE socklen_t +#define SOCKOPT_SIZE socklen_t +#ifndef NO_KQUEUE +#define EVENTS_STYLE EVENTS_STYLE_KQUEUE +#define USE_SYSV_POLL_THEN_SELECT +#endif +#define USE_MAX_FILES_PER_PROC +#ifndef NO_POSIX_GETPW_R +#define HAVE_POSIX_GETPW_R +#endif +#define HAS_DLOPEN +#define PREFERRED_RAND_SOURCE "dev:/dev/urandom" +#endif + + /* + * Ultrix 4.x, a sort of 4.[1-2] BSD system with System V.2 compatibility + * and POSIX. + */ +#ifdef ULTRIX4 +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +/* Ultrix by default has only 64 descriptors per process */ +#ifndef FD_SETSIZE +#define FD_SETSIZE 96 +#endif +#define _PATH_MAILDIR "/var/spool/mail" +#define _PATH_BSHELL "/bin/sh" +#define _PATH_DEFPATH "/bin:/usr/bin:/usr/ucb" +#define _PATH_STDPATH "/bin:/usr/bin:/usr/etc:/usr/ucb" +#define HAS_FLOCK_LOCK +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK +#define DEF_MAILBOX_LOCK "flock, dotlock" +#define HAS_FSYNC +/* might be set by makedef */ +#ifdef HAS_DB +#define NATIVE_DB_TYPE "hash" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases" +#else +#define HAS_DBM +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases" +#endif +extern int optind; +extern char *optarg; +extern int opterr; +extern int h_errno; + +#define MISSING_STRFTIME_E +#ifndef NO_NIS +#define HAS_NIS +#endif +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define ROOT_PATH "/bin:/usr/bin:/etc:/usr/etc:/usr/ucb" +#define USE_STATFS +#define USE_STRUCT_FS_DATA +#define STATFS_IN_SYS_MOUNT_H +/* Ultrix misses just S_ISSOCK, the others are there */ +#define S_ISSOCK(mode) (((mode) & (S_IFMT)) == (S_IFSOCK)) +#define DUP2_DUPS_CLOSE_ON_EXEC +#define MISSING_USLEEP +#define NO_HERRNO +#define NATIVE_SENDMAIL_PATH "/usr/lib/sendmail" +#define NATIVE_COMMAND_DIR "/usr/etc" +#define NATIVE_DAEMON_DIR "/usr/libexec/postfix" +#endif + + /* + * OSF, then Digital UNIX, then Compaq. A BSD-flavored hybrid. + */ +#ifdef OSF1 +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define MISSING_SETENV +#define USE_PATHS_H +#define _PATH_DEFPATH "/usr/bin:/usr/ucb" +#define HAS_FLOCK_LOCK +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK +#define DEF_MAILBOX_LOCK "flock, dotlock" +#define HAS_FSYNC +#define HAVE_BASENAME +#define HAS_DBM +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/var/adm/sendmail/aliases" +extern int optind; /* XXX use <getopt.h> */ +extern char *optarg; /* XXX use <getopt.h> */ +extern int opterr; /* XXX use <getopt.h> */ + +#ifndef NO_NIS +#define HAS_NIS +#endif +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb" +#define USE_STATFS +#define STATFS_IN_SYS_MOUNT_H +#define HAS_POSIX_REGEXP +#define BROKEN_WRITE_SELECT_ON_NON_BLOCKING_PIPE +#define NO_MSGHDR_MSG_CONTROL +#ifndef NO_IPV6 +#define HAS_IPV6 +#endif + +#endif + + /* + * SunOS 4.x, a mostly 4.[2-3] BSD system with System V.2 compatibility and + * POSIX support. + */ +#ifdef SUNOS4 +#define SUPPORTED +#include <memory.h> +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define UNSAFE_CTYPE +#define fpos_t long +#define MISSING_SETENV +#define MISSING_STRERROR +#define MISSING_STRTOUL +#define _PATH_MAILDIR "/var/spool/mail" +#define _PATH_BSHELL "/bin/sh" +#define _PATH_DEFPATH "/usr/bin:/usr/ucb" +#define _PATH_STDPATH "/usr/bin:/usr/etc:/usr/ucb" +#define HAS_FLOCK_LOCK +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK +#define DEF_MAILBOX_LOCK "flock, dotlock" +#define HAS_FSYNC +#define HAS_DBM +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases" +extern int optind; +extern char *optarg; +extern int opterr; + +#ifndef NO_NIS +#define HAS_NIS +#endif +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define ROOT_PATH "/bin:/usr/bin:/etc:/usr/etc:/usr/ucb" +#define USE_STATFS +#define STATFS_IN_SYS_VFS_H +#define memmove(d,s,l) bcopy(s,d,l) +#define NO_HERRNO +#define NATIVE_SENDMAIL_PATH "/usr/lib/sendmail" +#define NATIVE_MAILQ_PATH "/usr/ucb/mailq" +#define NATIVE_NEWALIAS_PATH "/usr/ucb/newaliases" +#define NATIVE_COMMAND_DIR "/usr/etc" +#define NATIVE_DAEMON_DIR "/usr/libexec/postfix" +#define STRCASECMP_IN_STRINGS_H +#define OCTAL_TO_UNSIGNED(res, str) sscanf((str), "%o", &(res)) +#define size_t unsigned +#define ssize_t int +#define getsid getpgrp +#define NO_SNPRINTF +#endif + + /* + * SunOS 5.x, mostly System V Release 4. + */ +#ifdef SUNOS5 +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define _PATH_MAILDIR "/var/mail" +#define _PATH_BSHELL "/bin/sh" +#define _PATH_DEFPATH "/usr/bin:/usr/ucb" +#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/ucb" +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL +#define DEF_MAILBOX_LOCK "fcntl, dotlock" +#define HAS_FSYNC +#define HAS_DBM +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases" +#ifndef NO_NIS +#define HAS_NIS +#ifndef NO_NISPLUS +#define HAS_NISPLUS +#endif /* NO_NISPLUS */ +#endif +#define USE_SYS_SOCKIO_H /* Solaris 2.5, changed sys/ioctl.h */ +#define GETTIMEOFDAY(t) gettimeofday(t) +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb" +#define FIONREAD_IN_SYS_FILIO_H +#define USE_STATVFS +#define STATVFS_IN_SYS_STATVFS_H +#define INT_MAX_IN_LIMITS_H +#ifdef STREAM_CONNECTIONS /* avoid UNIX-domain sockets */ +#define LOCAL_LISTEN stream_listen +#define LOCAL_ACCEPT stream_accept +#define LOCAL_CONNECT stream_connect +#define LOCAL_TRIGGER stream_trigger +#define LOCAL_SEND_FD stream_send_fd +#define LOCAL_RECV_FD stream_recv_fd +#endif +#define HAS_VOLATILE_LOCKS +#define BROKEN_READ_SELECT_ON_TCP_SOCKET +#define CANT_WRITE_BEFORE_SENDING_FD +#ifndef NO_POSIX_REGEXP +#define HAS_POSIX_REGEXP +#endif +#ifndef NO_IPV6 +#define HAS_IPV6 +#define HAS_SIOCGLIF +#endif +#ifndef NO_CLOSEFROM +#define HAS_CLOSEFROM +#endif +#ifndef NO_DEV_URANDOM +#define PREFERRED_RAND_SOURCE "dev:/dev/urandom" +#endif +#ifndef NO_FUTIMESAT +#define HAS_FUTIMESAT +#endif +#define USE_SYSV_POLL +#ifndef NO_DEVPOLL +#define EVENTS_STYLE EVENTS_STYLE_DEVPOLL +#endif +#ifndef NO_POSIX_GETPW_R +#define HAVE_POSIX_GETPW_R +#define GETPW_R_NEEDS_POSIX_PTHREAD_SEMANTICS +#endif + +/* + * Allow build environment to override paths. + */ +#define NATIVE_SENDMAIL_PATH "/usr/lib/sendmail" +#define NATIVE_MAILQ_PATH "/usr/bin/mailq" +#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases" +#define NATIVE_COMMAND_DIR "/usr/sbin" +#define NATIVE_DAEMON_DIR "/usr/libexec/postfix" + +#define HAS_DLOPEN +#endif + + /* + * UnixWare, System Release 4. + */ +#ifdef UW7 /* UnixWare 7 */ +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define _PATH_MAILDIR "/var/mail" +#define _PATH_BSHELL "/bin/sh" +#define _PATH_DEFPATH "/usr/bin:/usr/ucb" +#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/ucb" +#define MISSING_SETENV +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL +#define DEF_MAILBOX_LOCK "fcntl, dotlock" +#define HAS_FSYNC +#define HAS_DBM +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases" +#ifndef NO_NIS +#define HAS_NIS +#endif +#define USE_SYS_SOCKIO_H +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb" +#define FIONREAD_IN_SYS_FILIO_H +#define DBM_NO_TRAILING_NULL +#define USE_STATVFS +#define STATVFS_IN_SYS_STATVFS_H +#define STRCASECMP_IN_STRINGS_H +#define USE_SET_H_ERRNO +#endif + +#ifdef UW21 /* UnixWare 2.1.x */ +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define _PATH_MAILDIR "/var/mail" +#define _PATH_BSHELL "/bin/sh" +#define _PATH_DEFPATH "/usr/bin:/usr/ucb" +#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/ucb" +#define MISSING_SETENV +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL +#define DEF_MAILBOX_LOCK "fcntl, dotlock" +#define HAS_FSYNC +#define HAS_DBM +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases" +#ifndef NO_NIS +#define HAS_NIS */ +#endif +#define USE_SYS_SOCKIO_H +#define GETTIMEOFDAY(t) gettimeofday(t,NULL) +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb" +#define FIONREAD_IN_SYS_FILIO_H +#define DBM_NO_TRAILING_NULL +#define USE_STATVFS +#define STATVFS_IN_SYS_STATVFS_H +#endif + + /* + * AIX: a SYSV-flavored hybrid. NB: fcntl() and flock() access the same + * underlying locking primitives. + */ +#if defined(AIX5) || defined(AIX6) +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define MISSING_SETENV +#define USE_PATHS_H +#ifndef _PATH_BSHELL +#define _PATH_BSHELL "/bin/sh" +#endif +#ifndef _PATH_MAILDIR +#define _PATH_MAILDIR "/var/spool/mail" /* paths.h lies */ +#endif +#ifndef _PATH_DEFPATH +#define _PATH_DEFPATH "/usr/bin:/usr/ucb" +#endif +#ifndef _PATH_STDPATH +#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/ucb" +#endif +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL +#define DEF_MAILBOX_LOCK "fcntl, dotlock" +#define USE_SYS_SELECT_H +#define HAS_FSYNC +#define HAS_DBM +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases" +#ifndef NO_NIS +#define HAS_NIS +#endif +#define HAS_SA_LEN +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb" +#define SOCKADDR_SIZE socklen_t +#define SOCKOPT_SIZE socklen_t +#define USE_STATVFS +#define STATVFS_IN_SYS_STATVFS_H +#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail" +#define NATIVE_MAILQ_PATH "/usr/sbin/mailq" +#define NATIVE_NEWALIAS_PATH "/usr/sbin/newaliases" +#define NATIVE_COMMAND_DIR "/usr/sbin" +#define NATIVE_DAEMON_DIR "/usr/libexec/postfix" + + /* + * XXX Need CMSG_SPACE() and CMSG_LEN() but don't want to drag in everything + * that comes with _LINUX_SOURCE_COMPAT. + */ +#include <sys/socket.h> +#ifndef CMSG_SPACE +#define CMSG_SPACE(len) (_CMSG_ALIGN(sizeof(struct cmsghdr)) + _CMSG_ALIGN(len)) +#endif +#ifndef CMSG_LEN +#define CMSG_LEN(len) (_CMSG_ALIGN(sizeof(struct cmsghdr)) + (len)) +#endif +#ifndef NO_IPV6 +#define HAS_IPV6 +#endif +#define BROKEN_AI_PASSIVE_NULL_HOST +#define BROKEN_AI_NULL_SERVICE +#define USE_SYSV_POLL +#define MYMALLOC_FUZZ 1 +#endif + +#ifdef AIX4 +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define MISSING_SETENV +#define _PATH_BSHELL "/bin/sh" +#define _PATH_MAILDIR "/var/spool/mail" /* paths.h lies */ +#define _PATH_DEFPATH "/usr/bin:/usr/ucb" +#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/ucb" +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL +#define DEF_MAILBOX_LOCK "fcntl, dotlock" +#define USE_SYS_SELECT_H +#define HAS_FSYNC +#define HAS_DBM +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases" +#ifndef NO_NIS +#define HAS_NIS +#endif +#define HAS_SA_LEN +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define RESOLVE_H_NEEDS_STDIO_H +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb" +#define SOCKADDR_SIZE size_t +#define SOCKOPT_SIZE size_t +#define USE_STATVFS +#define STATVFS_IN_SYS_STATVFS_H +#define STRCASECMP_IN_STRINGS_H +#if 0 +extern time_t time(time_t *); +extern int seteuid(uid_t); +extern int setegid(gid_t); +extern int initgroups(const char *, int); + +#endif +#define NATIVE_SENDMAIL_PATH "/usr/lib/sendmail" +#define NATIVE_MAILQ_PATH "/usr/sbin/mailq" +#define NATIVE_NEWALIAS_PATH "/usr/sbin/newaliases" +#define NATIVE_COMMAND_DIR "/usr/sbin" +#define NATIVE_DAEMON_DIR "/usr/libexec/postfix" + +#define CANT_USE_SEND_RECV_MSG +#endif + +#ifdef AIX3 +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define MISSING_SETENV +#define _PATH_BSHELL "/bin/sh" +#define _PATH_MAILDIR "/var/spool/mail" /* paths.h lies */ +#define _PATH_DEFPATH "/usr/bin:/usr/ucb" +#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/ucb" +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL +#define DEF_MAILBOX_LOCK "fcntl, dotlock" +#define USE_SYS_SELECT_H +#define HAS_FSYNC +#define HAS_DBM +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases" +#ifndef NO_NIS +#define HAS_NIS +#endif +#define HAS_SA_LEN +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define RESOLVE_H_NEEDS_STDIO_H +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb" +#define SOCKADDR_SIZE size_t +#define SOCKOPT_SIZE size_t +#define USE_STATFS +#define STATFS_IN_SYS_STATFS_H +#define STRCASECMP_IN_STRINGS_H +extern time_t time(time_t *); +extern int seteuid(uid_t); +extern int setegid(gid_t); +extern int initgroups(const char *, int); + +#define NATIVE_SENDMAIL_PATH "/usr/lib/sendmail" + +#define CANT_USE_SEND_RECV_MSG +#endif + + /* + * IRIX, a mix of System V Releases. + */ +#if defined(IRIX5) || defined(IRIX6) +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define MISSING_SETENV +#define _PATH_MAILDIR "/var/mail" +#define _PATH_BSHELL "/bin/sh" +#define _PATH_DEFPATH "/usr/bin:/usr/bsd" +#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/bsd" +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL +#define DEF_MAILBOX_LOCK "fcntl, dotlock" +#define HAS_FSYNC +#define HAS_DBM +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases" +#ifndef NO_NIS +#define HAS_NIS +#endif +#define USE_SYS_SOCKIO_H /* XXX check */ +#define GETTIMEOFDAY(t) gettimeofday(t) +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/bsd" +#define FIONREAD_IN_SYS_FILIO_H /* XXX check */ +#define DBM_NO_TRAILING_NULL /* XXX check */ +#define USE_STATVFS +#define STATVFS_IN_SYS_STATVFS_H +#define BROKEN_WRITE_SELECT_ON_NON_BLOCKING_PIPE +#define CANT_USE_SEND_RECV_MSG +#endif + +#if defined(IRIX5) +#define MISSING_USLEEP +#endif + +#if defined(IRIX6) +#ifndef NO_IPV6 +#define HAS_IPV6 +#endif +#define HAS_POSIX_REGEXP +#define PIPES_CANT_FIONREAD +#endif + + /* + * LINUX. + */ +#if defined(LINUX2) || defined(LINUX3) || defined(LINUX4) || defined(LINUX5) \ + || defined(LINUX6) +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#include <features.h> +#define USE_PATHS_H +#define HAS_FLOCK_LOCK +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK +#define DEF_MAILBOX_LOCK "fcntl, dotlock" /* RedHat >= 4.x */ +#define HAS_FSYNC +#define HAS_DB +#define NATIVE_DB_TYPE "hash" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases" +#ifndef NO_NIS +#define HAS_NIS +#endif +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin" +#define FIONREAD_IN_TERMIOS_H +#define USE_STATFS +#define STATFS_IN_SYS_VFS_H +#define PREPEND_PLUS_TO_OPTSTRING +#define HAS_POSIX_REGEXP +#define HAS_DLOPEN +#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail" +#define NATIVE_MAILQ_PATH "/usr/bin/mailq" +#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases" +#define NATIVE_COMMAND_DIR "/usr/sbin" +#define NATIVE_DAEMON_DIR "/usr/libexec/postfix" +#ifdef __GLIBC_PREREQ +#define HAVE_GLIBC_API_VERSION_SUPPORT(maj, min) __GLIBC_PREREQ(maj, min) +#else +#define HAVE_GLIBC_API_VERSION_SUPPORT(maj, min) \ + (defined(__GLIBC__) && \ + ((__GLIBC__ << 16) + __GLIBC_MINOR__ >= ((maj) << 16) + (min))) +#endif +#if HAVE_GLIBC_API_VERSION_SUPPORT(2, 1) +#define SOCKADDR_SIZE socklen_t +#define SOCKOPT_SIZE socklen_t +#else +#define NO_SNPRINTF +#endif +#ifndef NO_IPV6 +#define HAS_IPV6 +#if HAVE_GLIBC_API_VERSION_SUPPORT(2, 4) +/* Really 2.3.3 or later, but there's no __GLIBC_MICRO version macro. */ +#define HAVE_GETIFADDRS +#else +#define HAS_PROCNET_IFINET6 +#define _PATH_PROCNET_IFINET6 "/proc/net/if_inet6" +#endif +#endif +#include <linux/version.h> +#if !defined(KERNEL_VERSION) +#define KERNEL_VERSION(a,b,c) (LINUX_VERSION_CODE + 1) +#endif +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,2,0)) \ + || (defined(__GLIBC__) && __GLIBC__ < 2) +#define CANT_USE_SEND_RECV_MSG +#define DEF_SMTP_CACHE_DEMAND 0 +#else +#define CANT_WRITE_BEFORE_SENDING_FD +#endif +#define PREFERRED_RAND_SOURCE "dev:/dev/urandom" /* introduced in 1.1 */ +#ifndef NO_EPOLL +#define EVENTS_STYLE EVENTS_STYLE_EPOLL /* introduced in 2.5 */ +#endif +#define USE_SYSV_POLL +#ifndef NO_POSIX_GETPW_R +#if (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 1) \ + || (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 1) \ + || (defined(_BSD_SOURCE) && _BSD_SOURCE >= 1) \ + || (defined(_SVID_SOURCE) && _SVID_SOURCE >= 1) \ + || (defined(_POSIX_SOURCE) && _POSIX_SOURCE >= 1) +#define HAVE_POSIX_GETPW_R +#endif +#endif +#if HAVE_GLIBC_API_VERSION_SUPPORT(2, 34) +#define HAS_CLOSEFROM +#endif + +#endif + +#ifdef LINUX1 +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define USE_PATHS_H +#define HAS_FLOCK_LOCK +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK +#define DEF_MAILBOX_LOCK "dotlock" /* verified RedHat 3.03 */ +#define HAS_FSYNC +#define HAS_DB +#define NATIVE_DB_TYPE "hash" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases" +#ifndef NO_NIS +#define HAS_NIS +#endif +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin" +#define FIONREAD_IN_TERMIOS_H /* maybe unnecessary */ +#define USE_STATFS +#define STATFS_IN_SYS_VFS_H +#define PREPEND_PLUS_TO_OPTSTRING +#define HAS_POSIX_REGEXP +#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail" +#define NATIVE_MAILQ_PATH "/usr/bin/mailq" +#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases" +#define NATIVE_COMMAND_DIR "/usr/sbin" +#define NATIVE_DAEMON_DIR "/usr/libexec/postfix" +#define CANT_USE_SEND_RECV_MSG +#define DEF_SMTP_CACHE_DEMAND 0 +#endif + + /* + * GNU. + */ +#ifdef GNU0 +#define SUPPORTED +#include <features.h> +#define USE_PATHS_H +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL +#define DEF_MAILBOX_LOCK "fcntl, dotlock" /* RedHat >= 4.x */ +#define HAS_FSYNC +#define HAS_DB +#define NATIVE_DB_TYPE "hash" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases" +#ifndef NO_NIS +#define HAS_NIS +#endif +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin" +#define FIONREAD_IN_TERMIOS_H +#define USE_STATFS +#define STATFS_IN_SYS_VFS_H +#define UNIX_DOMAIN_CONNECT_BLOCKS_FOR_ACCEPT +#define PREPEND_PLUS_TO_OPTSTRING +#define HAS_POSIX_REGEXP +#define HAS_DLOPEN +#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail" +#define NATIVE_MAILQ_PATH "/usr/bin/mailq" +#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases" +#define NATIVE_COMMAND_DIR "/usr/sbin" +#ifdef DEBIAN +#define NATIVE_DAEMON_DIR "/usr/lib/postfix" +#ifndef DEF_MANPAGE_DIR +#define DEF_MANPAGE_DIR "/usr/share/man" +#endif +#ifndef DEF_SAMPLE_DIR +#define DEF_SAMPLE_DIR "/usr/share/doc/postfix/examples" +#endif +#ifndef DEF_README_DIR +#define DEF_README_DIR "/usr/share/doc/postfix" +#endif +#else +#define NATIVE_DAEMON_DIR "/usr/libexec/postfix" +#endif +#define SOCKADDR_SIZE socklen_t +#define SOCKOPT_SIZE socklen_t +#ifdef __FreeBSD_kernel__ +#define HAS_DUPLEX_PIPE +#define HAS_ISSETUGID +#endif +#ifndef NO_IPV6 +#define HAS_IPV6 +#ifdef __FreeBSD_kernel__ +#define HAVE_GETIFADDRS +#else +#define HAS_PROCNET_IFINET6 +#define _PATH_PROCNET_IFINET6 "/proc/net/if_inet6" +#endif +#endif +#define CANT_USE_SEND_RECV_MSG +#define DEF_SMTP_CACHE_DEMAND 0 +#define PREFERRED_RAND_SOURCE "dev:/dev/urandom" +#endif + + /* + * HPUX11 was copied from HPUX10, but can perhaps be trimmed down a bit. + */ +#ifdef HPUX11 +#define SUPPORTED +#define USE_SIG_RETURN +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define HAS_DBM +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL +#define DEF_MAILBOX_LOCK "fcntl, dotlock" +#define HAS_FSYNC +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases" +#define ROOT_PATH "/usr/bin:/sbin:/usr/sbin" +#define MISSING_SETENV +#ifndef NO_NIS +#define HAS_NIS +#endif +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define _PATH_BSHELL "/bin/sh" +#define _PATH_MAILDIR "/var/mail" +#define _PATH_DEFPATH "/usr/bin" +#define _PATH_STDPATH "/usr/bin:/sbin:/usr/sbin" +#define MISSING_SETEUID +#define HAVE_SETRESUID +#define MISSING_SETEGID +#define HAVE_SETRESGID +extern int h_errno; /* <netdb.h> imports too much stuff */ + +#define USE_STATFS +#define STATFS_IN_SYS_VFS_H +#define HAS_POSIX_REGEXP +#define HAS_DLOPEN +#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail" +#define NATIVE_MAILQ_PATH "/usr/bin/mailq" +#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases" +#define NATIVE_COMMAND_DIR "/usr/sbin" +#define NATIVE_DAEMON_DIR "/usr/libexec/postfix" +#endif + +#ifdef HPUX10 +#define SUPPORTED +#define USE_SIG_RETURN +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define HAS_DBM +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL +#define DEF_MAILBOX_LOCK "fcntl, dotlock" +#define HAS_FSYNC +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases" +#define ROOT_PATH "/usr/bin:/sbin:/usr/sbin" +#define MISSING_SETENV +#ifndef NO_NIS +#define HAS_NIS +#endif +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define _PATH_BSHELL "/bin/sh" +#define _PATH_MAILDIR "/var/mail" +#define _PATH_DEFPATH "/usr/bin" +#define _PATH_STDPATH "/usr/bin:/sbin:/usr/sbin" +#define MISSING_SETEUID +#define HAVE_SETRESUID +#define MISSING_SETEGID +#define HAVE_SETRESGID +extern int h_errno; /* <netdb.h> imports too much stuff */ + +#define USE_STATFS +#define STATFS_IN_SYS_VFS_H +#define HAS_POSIX_REGEXP +#define HAS_SHL_LOAD +#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail" +#define NATIVE_MAILQ_PATH "/usr/bin/mailq" +#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases" +#define NATIVE_COMMAND_DIR "/usr/sbin" +#define NATIVE_DAEMON_DIR "/usr/libexec/postfix" +#endif + +#ifdef HPUX9 +#define SUPPORTED +#define USE_SIG_RETURN +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define HAS_DBM +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL +#define DEF_MAILBOX_LOCK "fcntl, dotlock" +#define HAS_FSYNC +#ifndef NO_NIS +#define HAS_NIS +#endif +#define MISSING_SETENV +#define MISSING_RLIMIT_FSIZE +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/usr/lib/aliases" +#define ROOT_PATH "/bin:/usr/bin:/etc" +#define _PATH_BSHELL "/bin/sh" +#define _PATH_MAILDIR "/usr/mail" +#define _PATH_DEFPATH "/bin:/usr/bin" +#define _PATH_STDPATH "/bin:/usr/bin:/etc" +#define MISSING_SETEUID +#define HAVE_SETRESUID +#define MISSING_SETEGID +#define HAVE_SETRESGID +extern int h_errno; + +#define USE_ULIMIT /* no setrlimit() */ +#define USE_STATFS +#define STATFS_IN_SYS_VFS_H +#define HAS_POSIX_REGEXP +#define HAS_SHL_LOAD +#define NATIVE_SENDMAIL_PATH "/usr/bin/sendmail" +#define NATIVE_MAILQ_PATH "/usr/bin/mailq" +#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases" +#define NATIVE_DAEMON_DIR "/usr/libexec/postfix" +#endif + + /* + * NEXTSTEP3, without -lposix, because its naming service is broken. + */ +#ifdef NEXTSTEP3 +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define HAS_DBM +#define HAS_FLOCK_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK +#define DEF_MAILBOX_LOCK "flock, dotlock" +#define USE_STATFS +#define HAVE_SYS_DIR_H +#define STATFS_IN_SYS_VFS_H +#define HAS_FSYNC +#ifndef NO_NIS +#define HAS_NIS +#endif +#define HAS_NETINFO +#define MISSING_SETENV_PUTENV +#define MISSING_MKFIFO +#define MISSING_SIGSET_T +#define MISSING_SIGACTION +#define MISSING_STD_FILENOS +#define MISSING_SETSID +#define MISSING_WAITPID +#define MISSING_UTIMBUF +#define HAS_WAIT4 +#define WAIT_STATUS_T union wait +#define NORMAL_EXIT_STATUS(x) (WIFEXITED(x) && !WEXITSTATUS (x)) +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define _PATH_MAILDIR "/usr/spool/mail" +#define _PATH_BSHELL "/bin/sh" +#define _PATH_DEFPATH "/bin:/usr/bin:/usr/ucb" +#define _PATH_STDPATH "/bin:/usr/bin:/usr/ucb" +#define ROOT_PATH "/bin:/usr/bin:/usr/etc:/usr/ucb" +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP "netinfo:/aliases" +#include <libc.h> +#define MISSING_POSIX_S_IS +#define MISSING_POSIX_S_MODES +/* It's amazing what is all missing... */ +#define isascii(c) ((unsigned)(c)<=0177) +extern int opterr; +typedef unsigned short mode_t; + +#define MISSING_PID_T +#define MISSING_STRFTIME_E +#define FD_CLOEXEC 1 +#define O_NONBLOCK O_NDELAY +#define WEXITSTATUS(x) ((x).w_retcode) +#define WTERMSIG(x) ((x).w_termsig) +#endif + + /* + * OPENSTEP does not have posix (some fix...) + */ +#ifdef OPENSTEP4 +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define HAS_DBM +#define HAS_FLOCK_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK +#define DEF_MAILBOX_LOCK "flock, dotlock" +#define USE_STATFS +#define HAVE_SYS_DIR_H +#define STATFS_IN_SYS_VFS_H +#define HAS_FSYNC +#ifndef NO_NIS +#define HAS_NIS +#endif +#define HAS_NETINFO +#define MISSING_SETENV_PUTENV +#define MISSING_MKFIFO +#define MISSING_SIGSET_T +#define MISSING_SIGACTION +#define MISSING_STD_FILENOS +#define MISSING_SETSID +#define MISSING_WAITPID +#define MISSING_UTIMBUF +#define HAS_WAIT4 +#define WAIT_STATUS_T union wait +#define NORMAL_EXIT_STATUS(x) (WIFEXITED(x) && !WEXITSTATUS (x)) +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define _PATH_MAILDIR "/usr/spool/mail" +#define _PATH_BSHELL "/bin/sh" +#define _PATH_DEFPATH "/bin:/usr/bin:/usr/ucb" +#define _PATH_STDPATH "/bin:/usr/bin:/usr/ucb" +#define ROOT_PATH "/bin:/usr/bin:/usr/etc:/usr/ucb" +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP "netinfo:/aliases" +#include <libc.h> +#define MISSING_POSIX_S_IS +#define MISSING_POSIX_S_MODES +/* It's amazing what is all missing... */ +#define isascii(c) ((unsigned)(c)<=0177) +extern int opterr; +typedef unsigned short mode_t; + +#define MISSING_PID_T +#define MISSING_STRFTIME_E +#define FD_CLOEXEC 1 +#define O_NONBLOCK O_NDELAY +#define WEXITSTATUS(x) ((x).w_retcode) +#define WTERMSIG(x) ((x).w_termsig) +#endif + +#ifdef ReliantUnix543 +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define MISSING_SETENV +#define _PATH_DEFPATH "/usr/bin:/usr/ucb" +#define _PATH_BSHELL "/bin/sh" +#define _PATH_MAILDIR "/var/spool/mail" +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL +#define DEF_MAILBOX_LOCK "fcntl, dotlock" +#define HAS_FSYNC +#define FIONREAD_IN_SYS_FILIO_H +#define USE_SYS_SOCKIO_H +#define HAS_DBM +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/var/adm/sendmail/aliases" +extern int optind; /* XXX use <getopt.h> */ +extern char *optarg; /* XXX use <getopt.h> */ +extern int opterr; /* XXX use <getopt.h> */ + +#ifndef NO_NIS +#define HAS_NIS +#endif +#define GETTIMEOFDAY(t) gettimeofday(t) +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb" +#define USE_STATVFS +#define STATVFS_IN_SYS_STATVFS_H +#define MISSING_USLEEP +#endif + +#ifdef DCOSX1 /* Siemens Pyramid */ +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define _PATH_MAILDIR "/var/mail" +#define _PATH_BSHELL "/bin/sh" +#define _PATH_DEFPATH "/usr/bin:/usr/ucb" +#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/ucb" +#define MISSING_SETENV +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL +#define DEF_MAILBOX_LOCK "fcntl, dotlock" +#define HAS_FSYNC +#define NATIVE_DB_TYPE "hash" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases" +/* Uncomment the following line if you have NIS package installed */ +/* #define HAS_NIS */ +#define USE_SYS_SOCKIO_H +#define GETTIMEOFDAY(t) gettimeofday(t,NULL) +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb" +#define FIONREAD_IN_SYS_FILIO_H +#define DBM_NO_TRAILING_NULL +#define USE_STATVFS +#define STATVFS_IN_SYS_STATVFS_H +#ifndef S_ISSOCK +#define S_ISSOCK(mode) ((mode&0xF000) == 0xC000) +#endif +#endif + +#ifdef SCO5 +#define SUPPORTED +#include <sys/socket.h> +extern int h_errno; + +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define _PATH_MAILDIR "/usr/spool/mail" +#define _PATH_BSHELL "/bin/sh" +#define _PATH_DEFPATH "/bin:/usr/bin" +#define USE_PATHS_H +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL +#define DEF_MAILBOX_LOCK "fcntl, dotlock" +#define HAS_FSYNC +#define HAS_DBM +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases" +#define DBM_NO_TRAILING_NULL +#ifndef NO_NIS +#define HAS_NIS +#endif +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define ROOT_PATH "/bin:/etc:/usr/bin:/tcb/bin" +#define USE_STATVFS +#define STATVFS_IN_SYS_STATVFS_H +#define MISSING_SETENV +#define STRCASECMP_IN_STRINGS_H +/* SCO5 misses just S_ISSOCK, the others are there + * Use C_ISSOCK definition from cpio.h. + */ +#include <cpio.h> +#define S_ISSOCK(mode) (((mode) & (S_IFMT)) == (C_ISSOCK)) +#define CANT_USE_SEND_RECV_MSG +#define DEF_SMTP_CACHE_DEMAND 0 +#endif + + /* + * We're not going to try to guess like configure does. + */ +#ifndef SUPPORTED +#error "unsupported platform" +#endif + + /* + * Allow command line flags to override native settings + */ +#ifndef DEF_COMMAND_DIR +#ifdef NATIVE_COMMAND_DIR +#define DEF_COMMAND_DIR NATIVE_COMMAND_DIR +#endif +#endif + +#ifndef DEF_DAEMON_DIR +#ifdef NATIVE_DAEMON_DIR +#define DEF_DAEMON_DIR NATIVE_DAEMON_DIR +#endif +#endif + +#ifndef DEF_SENDMAIL_PATH +#ifdef NATIVE_SENDMAIL_PATH +#define DEF_SENDMAIL_PATH NATIVE_SENDMAIL_PATH +#endif +#endif + +#ifndef DEF_MAILQ_PATH +#ifdef NATIVE_MAILQ_PATH +#define DEF_MAILQ_PATH NATIVE_MAILQ_PATH +#endif +#endif + +#ifndef DEF_NEWALIAS_PATH +#ifdef NATIVE_NEWALIAS_PATH +#define DEF_NEWALIAS_PATH NATIVE_NEWALIAS_PATH +#endif +#endif + +#ifndef DEF_DB_TYPE +#define DEF_DB_TYPE NATIVE_DB_TYPE +#endif + +#define CAST_ANY_PTR_TO_INT(cptr) ((int) (long) (cptr)) +#define CAST_INT_TO_VOID_PTR(ival) ((void *) (long) (ival)) + +#ifdef DUP2_DUPS_CLOSE_ON_EXEC +/* dup2_pass_on_exec() can be found in util/sys_compat.c */ +extern int dup2_pass_on_exec(int oldd, int newd); + +#define DUP2 dup2_pass_on_exec +#else +#define DUP2 dup2 +#endif + +#ifdef PREPEND_PLUS_TO_OPTSTRING +#define GETOPT(argc, argv, str) getopt((argc), (argv), "+" str) +#else +#define GETOPT(argc, argv, str) getopt((argc), (argv), (str)) +#endif +#define OPTIND (optind > 0 ? optind : 1) + +#if !defined(__UCLIBC__) && !defined(NO_RES_SEND) +#define HAVE_RES_SEND +#else +#undef HAVE_RES_SEND +#endif + + /* + * Check for required but missing definitions. + */ +#if !defined(HAS_FCNTL_LOCK) && !defined(HAS_FLOCK_LOCK) +#error "define HAS_FCNTL_LOCK and/or HAS_FLOCK_LOCK" +#endif + +#if !defined(DEF_MAILBOX_LOCK) +#error "define DEF_MAILBOX_LOCK" +#endif + +#if !defined(INTERNAL_LOCK) +#error "define INTERNAL_LOCK" +#endif + +#if defined(USE_STATFS) && defined(USE_STATVFS) +#error "define USE_STATFS or USE_STATVFS, not both" +#endif + +#if !defined(USE_STATFS) && !defined(USE_STATVFS) +#error "define USE_STATFS or USE_STATVFS" +#endif + + /* + * Defaults for systems that pre-date IPv6 support. + */ +#ifndef HAS_IPV6 +#include <sys/socket.h> +#define EMULATE_IPV4_ADDRINFO +#define MISSING_INET_PTON +#define MISSING_INET_NTOP +extern const char *inet_ntop(int, const void *, char *, SOCKADDR_SIZE); +extern int inet_pton(int, const char *, void *); + +#endif + + /* + * Workaround: after a watchdog alarm signal, wake up from select/poll/etc. + * by writing to a pipe. Solaris needs this, and HP-UX apparently, too. The + * run-time cost is negligible so we just turn it on for all systems. As a + * side benefit, making this code system-independent will simplify the + * detection of bit-rot problems. + */ +#ifndef NO_WATCHDOG_PIPE +#define USE_WATCHDOG_PIPE +#endif + + /* + * If we don't have defined a preferred random device above, but the system + * has /dev/urandom, then we use that. + */ +#if !defined(PREFERRED_RAND_SOURCE) && defined(HAS_DEV_URANDOM) +#define PREFERRED_RAND_SOURCE "dev:/dev/urandom" +#endif + + /* + * Defaults for systems without kqueue, /dev/poll or epoll support. + * master/multi-server.c and *qmgr/qmgr_transport.c depend on this. + */ +#if !defined(EVENTS_STYLE) +#define EVENTS_STYLE EVENTS_STYLE_SELECT +#endif + +#define EVENTS_STYLE_SELECT 1 /* Traditional BSD select */ +#define EVENTS_STYLE_KQUEUE 2 /* FreeBSD kqueue */ +#define EVENTS_STYLE_DEVPOLL 3 /* Solaris /dev/poll */ +#define EVENTS_STYLE_EPOLL 4 /* Linux epoll */ + + /* + * We use poll() for read/write time limit enforcement on modern systems. We + * use select() on historical systems without poll() support. And on systems + * where poll() is not implemented for some file handle types, we try to use + * select() as a fall-back solution (MacOS X needs this). + */ +#if !defined(USE_SYSV_POLL) && !defined(USE_SYSV_POLL_THEN_SELECT) +#define USE_BSD_SELECT +#endif + + /* + * The Postfix 2.9 post-install workaround assumes that the inet_protocols + * default value is "ipv4" when Postfix is compiled without IPv6 support. + */ +#ifndef DEF_INET_PROTOCOLS +#ifdef HAS_IPV6 +#define DEF_INET_PROTOCOLS INET_PROTO_NAME_ALL +#else +#define DEF_INET_PROTOCOLS INET_PROTO_NAME_IPV4 +#endif +#endif + + /* + * Defaults for systems that pre-date POSIX socklen_t. + */ +#ifndef SOCKADDR_SIZE +#define SOCKADDR_SIZE int +#endif + +#ifndef SOCKOPT_SIZE +#define SOCKOPT_SIZE int +#endif + + /* + * Defaults for normal systems. + */ +#ifndef LOCAL_LISTEN +#define LOCAL_LISTEN unix_listen +#define LOCAL_ACCEPT unix_accept +#define LOCAL_CONNECT unix_connect +#define LOCAL_TRIGGER unix_trigger +#define LOCAL_SEND_FD unix_send_fd +#define LOCAL_RECV_FD unix_recv_fd +#endif + +#if !defined (HAVE_SYS_NDIR_H) && !defined (HAVE_SYS_DIR_H) \ + && !defined (HAVE_NDIR_H) +#define HAVE_DIRENT_H +#endif + +#ifndef WAIT_STATUS_T +typedef int WAIT_STATUS_T; + +#define NORMAL_EXIT_STATUS(status) ((status) == 0) +#endif + +#ifdef NO_POSIX_GETPW_R +#undef HAVE_POSIX_GETPW_R +#endif + +#ifdef NO_DB +#undef HAS_DB +#endif + +#ifndef OCTAL_TO_UNSIGNED +#define OCTAL_TO_UNSIGNED(res, str) ((res) = strtoul((str), (char **) 0, 8)) +#endif + + /* + * Avoid useless type mis-matches when using sizeof in an integer context. + */ +#define INT_SIZEOF(foo) ((int) sizeof(foo)) + + /* + * Turn on the compatibility stuff. + */ +#ifdef MISSING_UTIMBUF +struct utimbuf { + time_t actime; + time_t modtime; +}; + +#endif + +#ifdef MISSING_STRERROR +extern const char *strerror(int); + +#endif + +#if defined (MISSING_SETENV) || defined (MISSING_SETENV_PUTENV) +extern int setenv(const char *, const char *, int); + +#endif + +#ifdef MISSING_SETEUID +extern int seteuid(uid_t euid); + +#endif + +#ifdef MISSING_SETEGID +extern int setegid(gid_t egid); + +#endif + +#ifdef MISSING_MKFIFO +extern int mkfifo(char *, int); + +#endif + +#ifdef MISSING_WAITPID +extern int waitpid(int, WAIT_STATUS_T *status, int options); + +#endif + +#ifdef MISSING_SETSID +extern int setsid(void); + +#endif + +#ifndef HAS_CLOSEFROM +extern int closefrom(int); + +#endif + +#ifdef MISSING_STD_FILENOS +#define STDIN_FILENO 0 +#define STDOUT_FILENO 1 +#define STDERR_FILENO 2 +#endif + +#ifdef MISSING_PID_T +typedef int pid_t; + +#endif + +#ifdef MISSING_POSIX_S_IS +#define S_ISBLK(mode) (((mode) & (_S_IFMT)) == (_S_IFBLK)) +#define S_ISCHR(mode) (((mode) & (_S_IFMT)) == (_S_IFCHR)) +#define S_ISDIR(mode) (((mode) & (_S_IFMT)) == (_S_IFDIR)) +#define S_ISSOCK(mode) (((mode) & (_S_IFMT)) == (_S_IFSOCK)) +#define S_ISFIFO(mode) (((mode) & (_S_IFMT)) == (_S_IFIFO)) +#define S_ISREG(mode) (((mode) & (_S_IFMT)) == (_S_IFREG)) +#define S_ISLNK(mode) (((mode) & (_S_IFMT)) == (_S_IFLNK)) +#endif + +#ifdef MISSING_POSIX_S_MODES +#define S_IRUSR _S_IRUSR +#define S_IRGRP 0000040 +#define S_IROTH 0000004 +#define S_IWUSR _S_IWUSR +#define S_IWGRP 0000020 +#define S_IWOTH 0000002 +#define S_IXUSR _S_IXUSR +#define S_IXGRP 0000010 +#define S_IXOTH 0000001 +#define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR) +#endif + + /* + * Memory alignment of memory allocator results. By default we align for + * doubles. + */ +#ifndef ALIGN_TYPE +#if defined(__hpux) && defined(__ia64) +#define ALIGN_TYPE __float80 +#elif defined(__ia64__) +#define ALIGN_TYPE long double +#else +#define ALIGN_TYPE double +#endif +#endif + + /* + * Clang-style attribute tests. + * + * XXX Without the unconditional test below, gcc 4.6 will barf on ``elif + * defined(__clang__) && __has_attribute(__whatever__)'' with error message + * ``missing binary operator before token "("''. + */ +#ifndef __has_attribute +#define __has_attribute(x) 0 +#endif /* __has_attribute */ + + /* + * Need to specify what functions never return, so that the compiler can + * warn for missing initializations and other trouble. However, OPENSTEP4 + * gcc 2.7.x cannot handle this so we define this only if NORETURN isn't + * already defined above. + * + * Data point: gcc 2.7.2 has __attribute__ (Wietse Venema) but gcc 2.6.3 does + * not (Clive Jones). So we'll set the threshold at 2.7. + */ +#ifndef NORETURN +#if (__GNUC__ == 2 && __GNUC_MINOR__ >= 7) || __GNUC__ >= 3 +#define NORETURN void __attribute__((__noreturn__)) +#elif defined(__clang__) && __has_attribute(__noreturn__) +#define NORETURN void __attribute__((__noreturn__)) +#else +#define NORETURN void +#endif +#endif /* NORETURN */ + + /* + * Turn on format string argument checking. This is more accurate than + * printfck, but it misses #ifdef-ed code. XXX I am just guessing at what + * gcc versions support this. In order to turn this off for some platforms, + * specify #define PRINTFLIKE and #define SCANFLIKE in the system-dependent + * sections above. + */ +#ifndef PRINTFLIKE +#if (__GNUC__ == 2 && __GNUC_MINOR__ >= 7) || __GNUC__ >= 3 +#define PRINTFLIKE(x,y) __attribute__ ((format (printf, (x), (y)))) +#elif defined(__clang__) && __has_attribute(__format__) +#define PRINTFLIKE(x,y) __attribute__ ((__format__ (__printf__, (x), (y)))) +#else +#define PRINTFLIKE(x,y) +#endif +#endif /* PRINTFLIKE */ + +#ifndef SCANFLIKE +#if (__GNUC__ == 2 && __GNUC_MINOR__ >= 7) || __GNUC__ >= 3 +#define SCANFLIKE(x,y) __attribute__ ((format (scanf, (x), (y)))) +#elif defined(__clang__) && __has_attribute(__format__) +#define SCANFLIKE(x,y) __attribute__ ((__format__ (__scanf__, (x), (y)))) +#else +#define SCANFLIKE(x,y) +#endif +#endif /* SCANFLIKE */ + + /* + * Some gcc implementations don't grok these attributes with pointer to + * function. Again, wild guess of what is supported. To override, specify + * #define PRINTFPTRLIKE in the system-dependent sections above. + */ +#ifndef PRINTFPTRLIKE +#if (__GNUC__ >= 3) /* XXX Rough estimate */ +#define PRINTFPTRLIKE(x,y) PRINTFLIKE(x,y) +#elif defined(__clang__) && __has_attribute(__format__) +#define PRINTFPTRLIKE(x,y) __attribute__ ((__format__ (__printf__, (x), (y)))) +#else +#define PRINTFPTRLIKE(x,y) +#endif +#endif + + /* + * Compiler optimization hint. This makes sense only for code in a + * performance-critical loop. + */ +#ifndef EXPECTED +#if defined(__GNUC__) && (__GNUC__ > 2) +#define EXPECTED(x) __builtin_expect(!!(x), 1) +#define UNEXPECTED(x) __builtin_expect(!!(x), 0) +#else +#define EXPECTED(x) (x) +#define UNEXPECTED(x) (x) +#endif +#endif + + /* + * Warn about ignored function result values that must never be ignored. + * Typically, this is for error results from "read" functions that normally + * write to output parameters (for example, stat- or scanf-like functions) + * or from functions that have other useful side effects (for example, + * fseek- or rename-like functions). + * + * DO NOT use this for functions that write to a stream; it is entirely + * legitimate to detect write errors with fflush() or fclose() only. On the + * other hand most (but not all) functions that read from a stream must + * never ignore result values. + * + * XXX Prepending "(void)" won't shut up GCC. Clang behaves as expected. + */ +#if ((__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || __GNUC__ > 3) +#define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#elif defined(__clang__) && __has_attribute(warn_unused_result) +#define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define WARN_UNUSED_RESULT +#endif + + /* + * ISO C says that the "volatile" qualifier protects against optimizations + * that cause longjmp() to clobber local variables. + */ +#ifndef NOCLOBBER +#define NOCLOBBER volatile +#endif + + /* + * Bit banging!! There is no official constant that defines the INT_MAX + * equivalent for off_t, ssize_t, etc. Wietse came up with the following + * macro that works as long as off_t, ssize_t, etc. use one's or two's + * complement logic (that is, the maximum value is binary 01...1). Don't use + * right-shift for signed types: the result is implementation-defined. + */ +#include <limits.h> +#define __MAXINT__(T) ((T) ~(((T) 1) << ((sizeof(T) * CHAR_BIT) - 1))) +#ifndef OFF_T_MAX +#define OFF_T_MAX __MAXINT__(off_t) +#endif + +#ifndef SSIZE_T_MAX +#define SSIZE_T_MAX __MAXINT__(ssize_t) +#endif + + /* + * Consistent enforcement of size limits. + */ +#define ENFORCING_SIZE_LIMIT(param) ((param) > 0) + + /* + * Don't mix socket message send/receive calls with socket stream read/write + * calls. The fact that you can get away with it only on some stacks implies + * that there is no long-term guarantee. + */ +#ifndef CAN_WRITE_BEFORE_SENDING_FD +#define CANT_WRITE_BEFORE_SENDING_FD +#endif + + /* + * FreeBSD sendmsg(2) says that after sending a file descriptor, the sender + * must not immediately close the descriptor, otherwise it may close the + * descriptor before it is actually sent. + */ +#ifndef DONT_WAIT_AFTER_SENDING_FD +#define MUST_READ_AFTER_SENDING_FD +#endif + + /* + * Hope for the best. + */ +#ifndef UINT32_TYPE +#define UINT32_TYPE uint32_t +#define UINT16_TYPE uint16_t +#endif +#define UINT32_SIZE 4 +#define UINT16_SIZE 2 + + /* + * For the sake of clarity. + */ +#ifndef HAVE_CONST_CHAR_STAR +typedef const char *CONST_CHAR_STAR; + +#endif + + /* + * Safety. On some systems, ctype.h misbehaves with non-ASCII or negative + * characters. More importantly, Postfix uses the ISXXX() macros to ensure + * protocol compliance, so we have to rule out non-ASCII characters. + * + * XXX The (unsigned char) casts in isalnum() etc arguments are unnecessary + * because the ISASCII() guard already ensures that the values are + * non-negative; the casts are done anyway to shut up chatty compilers. + */ +#define ISASCII(c) isascii(_UCHAR_(c)) +#define _UCHAR_(c) ((unsigned char)(c)) +#define ISALNUM(c) (ISASCII(c) && isalnum((unsigned char)(c))) +#define ISALPHA(c) (ISASCII(c) && isalpha((unsigned char)(c))) +#define ISCNTRL(c) (ISASCII(c) && iscntrl((unsigned char)(c))) +#define ISDIGIT(c) (ISASCII(c) && isdigit((unsigned char)(c))) +#define ISGRAPH(c) (ISASCII(c) && isgraph((unsigned char)(c))) +#define ISLOWER(c) (ISASCII(c) && islower((unsigned char)(c))) +#define ISPRINT(c) (ISASCII(c) && isprint((unsigned char)(c))) +#define ISPUNCT(c) (ISASCII(c) && ispunct((unsigned char)(c))) +#define ISSPACE(c) (ISASCII(c) && isspace((unsigned char)(c))) +#define ISUPPER(c) (ISASCII(c) && isupper((unsigned char)(c))) +#define TOLOWER(c) (ISUPPER(c) ? tolower((unsigned char)(c)) : (c)) +#define TOUPPER(c) (ISLOWER(c) ? toupper((unsigned char)(c)) : (c)) + + /* + * Character sets for parsing. + */ +#define CHARS_COMMA_SP ", \t\r\n" /* list separator */ +#define CHARS_SPACE " \t\r\n" /* word separator */ +#define CHARS_BRACE "{}" /* grouping */ + + /* + * Scaffolding. I don't want to lose messages while the program is under + * development. + */ +extern int REMOVE(const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/testdb b/src/util/testdb new file mode 100644 index 0000000..097c6c0 --- /dev/null +++ b/src/util/testdb @@ -0,0 +1,2 @@ +foo fooval +bar barval diff --git a/src/util/timecmp.c b/src/util/timecmp.c new file mode 100644 index 0000000..607a9ae --- /dev/null +++ b/src/util/timecmp.c @@ -0,0 +1,93 @@ +/*++ +/* NAME +/* timecmp 3 +/* SUMMARY +/* compare two time_t values +/* SYNOPSIS +/* #include <timecmp.h> +/* +/* int timecmp(t1, t2) +/* time_t t1; +/* time_t t2; +/* DESCRIPTION +/* The timecmp() function return an integer greater than, equal to, or +/* less than 0, according as the time t1 is greater than, equal to, or +/* less than the time t2. The comparison is made in a manner that is +/* insensitive to clock wrap-around, provided the underlying times are +/* within half of the time interval between the smallest and largest +/* representable time values. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Viktor Dukhovni +/*--*/ + +#include "timecmp.h" + +/* timecmp - wrap-safe time_t comparison */ + +int timecmp(time_t t1, time_t t2) +{ + time_t delta = t1 - t2; + + if (delta == 0) + return 0; + +#define UNSIGNED(type) ( ((type)-1) > ((type)0) ) + + /* + * With a constant switch value, the compiler will emit only the code for + * the correct case, so the signed/unsigned test happens at compile time. + */ + switch (UNSIGNED(time_t) ? 0 : 1) { + case 0: + return ((2 * delta > delta) ? 1 : -1); + case 1: + return ((delta > (time_t) 0) ? 1 : -1); + } +} + +#ifdef TEST +#include <assert.h> + + /* + * Bit banging!! There is no official constant that defines the INT_MAX + * equivalent of the off_t type. Wietse came up with the following macro + * that works as long as off_t is some two's complement number. + * + * Note, however, that C99 permits signed integer representations other than + * two's complement. + */ +#include <limits.h> +#define __MAXINT__(T) ((T) (((((T) 1) << ((sizeof(T) * CHAR_BIT) - 1)) ^ ((T) -1)))) + +int main(void) +{ + time_t now = time((time_t *) 0); + + /* Test that it works for normal times */ + assert(timecmp(now + 10, now) > 0); + assert(timecmp(now, now) == 0); + assert(timecmp(now - 10, now) < 0); + + /* Test that it works at a boundary time */ + if (UNSIGNED(time_t)) + now = (time_t) -1; + else + now = __MAXINT__(time_t); + + assert(timecmp(now + 10, now) > 0); + assert(timecmp(now, now) == 0); + assert(timecmp(now - 10, now) < 0); + + return (0); +} + +#endif diff --git a/src/util/timecmp.h b/src/util/timecmp.h new file mode 100644 index 0000000..b6efeab --- /dev/null +++ b/src/util/timecmp.h @@ -0,0 +1,37 @@ +#ifndef _TIMECMP_H_INCLUDED_ +#define _TIMECMP_H_INCLUDED_ + +#include <time.h> + +/*++ +/* NAME +/* timecmp 3h +/* SUMMARY +/* compare two time_t values +/* SYNOPSIS +/* #include <timecmp.h> +/* +/* int timecmp(t1, t2) +/* time_t t1; +/* time_t t2; +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern int timecmp(time_t, time_t); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Viktor Dukhovni +/*--*/ + +#endif diff --git a/src/util/timed_connect.c b/src/util/timed_connect.c new file mode 100644 index 0000000..962545f --- /dev/null +++ b/src/util/timed_connect.c @@ -0,0 +1,111 @@ +/*++ +/* NAME +/* timed_connect 3 +/* SUMMARY +/* connect operation with timeout +/* SYNOPSIS +/* #include <sys/socket.h> +/* #include <timed_connect.h> +/* +/* int timed_connect(fd, buf, buf_len, timeout) +/* int fd; +/* struct sockaddr *buf; +/* int buf_len; +/* int timeout; +/* DESCRIPTION +/* timed_connect() implement a BSD socket connect() operation that is +/* bounded in time. +/* +/* Arguments: +/* .IP fd +/* File descriptor in the range 0..FD_SETSIZE. This descriptor +/* must be set to non-blocking mode prior to calling timed_connect(). +/* .IP buf +/* Socket address buffer pointer. +/* .IP buf_len +/* Size of socket address buffer. +/* .IP timeout +/* The deadline in seconds. This must be a number > 0. +/* DIAGNOSTICS +/* Panic: interface violations. +/* When the operation does not complete within the deadline, the +/* result value is -1, and errno is set to ETIMEDOUT. +/* All other returns are identical to those of a blocking connect(2) +/* operation. +/* WARNINGS +/* .ad +/* .fi +/* A common error is to call timed_connect() without enabling +/* non-blocking I/O on the socket. In that case, the \fItimeout\fR +/* parameter takes no effect. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <errno.h> + +/* Utility library. */ + +#include "msg.h" +#include "iostuff.h" +#include "sane_connect.h" +#include "timed_connect.h" + +/* timed_connect - connect with deadline */ + +int timed_connect(int sock, struct sockaddr *sa, int len, int timeout) +{ + int error; + SOCKOPT_SIZE error_len; + + /* + * Sanity check. Just like with timed_wait(), the timeout must be a + * positive number. + */ + if (timeout <= 0) + msg_panic("timed_connect: bad timeout: %d", timeout); + + /* + * Start the connection, and handle all possible results. + */ + if (sane_connect(sock, sa, len) == 0) + return (0); + if (errno != EINPROGRESS) + return (-1); + + /* + * A connection is in progress. Wait for a limited amount of time for + * something to happen. If nothing happens, report an error. + */ + if (write_wait(sock, timeout) < 0) + return (-1); + + /* + * Something happened. Some Solaris 2 versions have getsockopt() itself + * return the error, instead of returning it via the parameter list. + */ + error = 0; + error_len = sizeof(error); + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void *) &error, &error_len) < 0) + return (-1); + if (error) { + errno = error; + return (-1); + } + + /* + * No problems. + */ + return (0); +} diff --git a/src/util/timed_connect.h b/src/util/timed_connect.h new file mode 100644 index 0000000..76ac715 --- /dev/null +++ b/src/util/timed_connect.h @@ -0,0 +1,31 @@ +#ifndef _TIMED_CONNECT_H_INCLUDED_ +#define _TIMED_CONNECT_H_INCLUDED_ + +/*++ +/* NAME +/* timed_connect 3h +/* SUMMARY +/* connect operation with timeout +/* SYNOPSIS +/* #include <sys/socket.h> +/* #include <timed_connect.h> +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern int timed_connect(int, struct sockaddr *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/timed_read.c b/src/util/timed_read.c new file mode 100644 index 0000000..fdae8fa --- /dev/null +++ b/src/util/timed_read.c @@ -0,0 +1,86 @@ +/*++ +/* NAME +/* timed_read 3 +/* SUMMARY +/* read operation with pre-read timeout +/* SYNOPSIS +/* #include <iostuff.h> +/* +/* ssize_t timed_read(fd, buf, len, timeout, context) +/* int fd; +/* void *buf; +/* size_t len; +/* int timeout; +/* void *context; +/* DESCRIPTION +/* timed_read() performs a read() operation when the specified +/* descriptor becomes readable within a user-specified deadline. +/* +/* Arguments: +/* .IP fd +/* File descriptor in the range 0..FD_SETSIZE. +/* .IP buf +/* Read buffer pointer. +/* .IP len +/* Read buffer size. +/* .IP timeout +/* The deadline in seconds. If this is <= 0, the deadline feature +/* is disabled. +/* .IP context +/* Application context. This parameter is unused. It exists only +/* for the sake of VSTREAM compatibility. +/* DIAGNOSTICS +/* When the operation does not complete within the deadline, the +/* result value is -1, and errno is set to ETIMEDOUT. +/* All other returns are identical to those of a read(2) operation. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <unistd.h> +#include <errno.h> + +/* Utility library. */ + +#include <msg.h> +#include <iostuff.h> + +/* timed_read - read with deadline */ + +ssize_t timed_read(int fd, void *buf, size_t len, + int timeout, void *unused_context) +{ + ssize_t ret; + + /* + * Wait for a limited amount of time for something to happen. If nothing + * happens, report an ETIMEDOUT error. + * + * XXX Solaris 8 read() fails with EAGAIN after read-select() returns + * success. + */ + for (;;) { + if (timeout > 0 && read_wait(fd, timeout) < 0) + return (-1); + if ((ret = read(fd, buf, len)) < 0 && timeout > 0 && errno == EAGAIN) { + msg_warn("read() returns EAGAIN on a readable file descriptor!"); + msg_warn("pausing to avoid going into a tight select/read loop!"); + sleep(1); + continue; + } else if (ret < 0 && errno == EINTR) { + continue; + } else { + return (ret); + } + } +} diff --git a/src/util/timed_wait.c b/src/util/timed_wait.c new file mode 100644 index 0000000..45fbb69 --- /dev/null +++ b/src/util/timed_wait.c @@ -0,0 +1,122 @@ +/*++ +/* NAME +/* timed_wait 3 +/* SUMMARY +/* wait operations with timeout +/* SYNOPSIS +/* #include <timed_wait.h> +/* +/* int timed_waitpid(pid, statusp, options, time_limit) +/* pid_t pid; +/* WAIT_STATUS_T *statusp; +/* int options; +/* int time_limit; +/* DESCRIPTION +/* \fItimed_waitpid\fR() waits at most \fItime_limit\fR seconds +/* for process termination. +/* +/* Arguments: +/* .IP "pid, statusp, options" +/* The process ID, status pointer and options passed to waitpid(3). +/* .IP time_limit +/* The time in seconds that timed_waitpid() will wait. +/* This must be a number > 0. +/* DIAGNOSTICS +/* Panic: interface violation. +/* +/* When the time limit is exceeded, the result is -1 and errno +/* is set to ETIMEDOUT. Otherwise, the result value is the result +/* from the underlying waitpid() routine. +/* BUGS +/* If there were a \fIportable\fR way to select() on process status +/* information, these routines would not have to use a steenkeeng +/* alarm() timer and signal() handler. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/wait.h> +#include <unistd.h> +#include <signal.h> +#include <errno.h> + +/* Utility library. */ + +#include <msg.h> +#include <posix_signals.h> +#include <timed_wait.h> + +/* Application-specific. */ + +static int timed_wait_expired; + +/* timed_wait_alarm - timeout handler */ + +static void timed_wait_alarm(int unused_sig) +{ + + /* + * WARNING WARNING WARNING. + * + * This code runs at unpredictable moments, as a signal handler. This code + * is here only so that we can break out of waitpid(). Don't put any code + * here other than for setting a global flag. + */ + timed_wait_expired = 1; +} + +/* timed_waitpid - waitpid with time limit */ + +int timed_waitpid(pid_t pid, WAIT_STATUS_T *statusp, int options, + int time_limit) +{ + const char *myname = "timed_waitpid"; + struct sigaction action; + struct sigaction old_action; + int time_left; + int wpid; + + /* + * Sanity checks. + */ + if (time_limit <= 0) + msg_panic("%s: bad time limit: %d", myname, time_limit); + + /* + * Set up a timer. + */ + sigemptyset(&action.sa_mask); + action.sa_flags = 0; + action.sa_handler = timed_wait_alarm; + if (sigaction(SIGALRM, &action, &old_action) < 0) + msg_fatal("%s: sigaction(SIGALRM): %m", myname); + timed_wait_expired = 0; + time_left = alarm(time_limit); + + /* + * Wait for only a limited amount of time. + */ + if ((wpid = waitpid(pid, statusp, options)) < 0 && timed_wait_expired) + errno = ETIMEDOUT; + + /* + * Cleanup. + */ + alarm(0); + if (sigaction(SIGALRM, &old_action, (struct sigaction *) 0) < 0) + msg_fatal("%s: sigaction(SIGALRM): %m", myname); + if (time_left) + alarm(time_left); + + return (wpid); +} diff --git a/src/util/timed_wait.h b/src/util/timed_wait.h new file mode 100644 index 0000000..6a153de --- /dev/null +++ b/src/util/timed_wait.h @@ -0,0 +1,35 @@ +#ifndef _TIMED_WAIT_H_INCLUDED_ +#define _TIMED_WAIT_H_INCLUDED_ + +/*++ +/* NAME +/* timed_wait 3h +/* SUMMARY +/* wait operations with timeout +/* SYNOPSIS +/* #include <timed_wait.h> +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern int WARN_UNUSED_RESULT timed_waitpid(pid_t, WAIT_STATUS_T *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/timed_write.c b/src/util/timed_write.c new file mode 100644 index 0000000..220b4c4 --- /dev/null +++ b/src/util/timed_write.c @@ -0,0 +1,92 @@ +/*++ +/* NAME +/* timed_write 3 +/* SUMMARY +/* write operation with pre-write timeout +/* SYNOPSIS +/* #include <iostuff.h> +/* +/* ssize_t timed_write(fd, buf, len, timeout, context) +/* int fd; +/* const void *buf; +/* size_t len; +/* int timeout; +/* void *context; +/* DESCRIPTION +/* timed_write() performs a write() operation when the specified +/* descriptor becomes writable within a user-specified deadline. +/* +/* Arguments: +/* .IP fd +/* File descriptor in the range 0..FD_SETSIZE. +/* .IP buf +/* Write buffer pointer. +/* .IP len +/* Write buffer size. +/* .IP timeout +/* The deadline in seconds. If this is <= 0, the deadline feature +/* is disabled. +/* .IP context +/* Application context. This parameter is unused. It exists only +/* for the sake of VSTREAM compatibility. +/* DIAGNOSTICS +/* When the operation does not complete within the deadline, the +/* result value is -1, and errno is set to ETIMEDOUT. +/* All other returns are identical to those of a write(2) operation. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <unistd.h> +#include <errno.h> + +/* Utility library. */ + +#include <msg.h> +#include <iostuff.h> + +/* timed_write - write with deadline */ + +ssize_t timed_write(int fd, const void *buf, size_t len, + int timeout, void *unused_context) +{ + ssize_t ret; + + /* + * Wait for a limited amount of time for something to happen. If nothing + * happens, report an ETIMEDOUT error. + * + * XXX Solaris 8 read() fails with EAGAIN after read-select() returns + * success. The code below exists just in case their write implementation + * is equally broken. + * + * This condition may also be found on systems where select() returns + * success on pipes with less than PIPE_BUF bytes of space, and with + * badly designed software where multiple writers are fighting for access + * to the same resource. + */ + for (;;) { + if (timeout > 0 && write_wait(fd, timeout) < 0) + return (-1); + if ((ret = write(fd, buf, len)) < 0 && timeout > 0 && errno == EAGAIN) { + msg_warn("write() returns EAGAIN on a writable file descriptor!"); + msg_warn("pausing to avoid going into a tight select/write loop!"); + sleep(1); + continue; + } else if (ret < 0 && errno == EINTR) { + continue; + } else { + return (ret); + } + } +} diff --git a/src/util/translit.c b/src/util/translit.c new file mode 100644 index 0000000..ba04bf2 --- /dev/null +++ b/src/util/translit.c @@ -0,0 +1,86 @@ +/*++ +/* NAME +/* translit 3 +/* SUMMARY +/* transliterate characters +/* SYNOPSIS +/* #include <stringops.h> +/* +/* char *translit(buf, original, replacement) +/* char *buf; +/* char *original; +/* char *replacement; +/* DESCRIPTION +/* translit() takes a null-terminated string, and replaces characters +/* given in its \fIoriginal\fR argument by the corresponding characters +/* in the \fIreplacement\fR string. The result value is the \fIbuf\fR +/* argument. +/* BUGS +/* Cannot replace null characters. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <string.h> + +/* Utility library. */ + +#include "stringops.h" + +char *translit(char *string, const char *original, const char *replacement) +{ + char *cp; + const char *op; + + /* + * For large inputs, should use a lookup table. + */ + for (cp = string; *cp != 0; cp++) { + for (op = original; *op != 0; op++) { + if (*cp == *op) { + *cp = replacement[op - original]; + break; + } + } + } + return (string); +} + +#ifdef TEST + + /* + * Usage: translit string1 string2 + * + * test program to perform the most basic operation of the UNIX tr command. + */ +#include <msg.h> +#include <vstring.h> +#include <vstream.h> +#include <vstring_vstream.h> + +#define STR vstring_str + +int main(int argc, char **argv) +{ + VSTRING *buf = vstring_alloc(100); + + if (argc != 3) + msg_fatal("usage: %s string1 string2", argv[0]); + while (vstring_fgets(buf, VSTREAM_IN)) + vstream_fputs(translit(STR(buf), argv[1], argv[2]), VSTREAM_OUT); + vstream_fflush(VSTREAM_OUT); + vstring_free(buf); + return (0); +} + +#endif diff --git a/src/util/trigger.h b/src/util/trigger.h new file mode 100644 index 0000000..e716d53 --- /dev/null +++ b/src/util/trigger.h @@ -0,0 +1,34 @@ +#ifndef _TRIGGER_H_INCLUDED_ +#define _TRIGGER_H_INCLUDED_ + +/*++ +/* NAME +/* trigger 3h +/* SUMMARY +/* client interface file +/* SYNOPSIS +/* #include <trigger.h> +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern int unix_trigger(const char *, const char *, ssize_t, int); +extern int inet_trigger(const char *, const char *, ssize_t, int); +extern int fifo_trigger(const char *, const char *, ssize_t, int); +extern int stream_trigger(const char *, const char *, ssize_t, int); +extern int pass_trigger(const char *, const char *, ssize_t, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/trimblanks.c b/src/util/trimblanks.c new file mode 100644 index 0000000..a4d9cb2 --- /dev/null +++ b/src/util/trimblanks.c @@ -0,0 +1,50 @@ +/*++ +/* NAME +/* trimblanks 3 +/* SUMMARY +/* skip leading whitespace +/* SYNOPSIS +/* #include <stringops.h> +/* +/* char *trimblanks(string, len) +/* char *string; +/* ssize_t len; +/* DESCRIPTION +/* trimblanks() returns a pointer to the beginning of the trailing +/* whitespace in \fIstring\fR, or a pointer to the string terminator +/* when the string contains no trailing whitespace. +/* The \fIlen\fR argument is either zero or the string length. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <ctype.h> + +/* Utility library. */ + +#include "stringops.h" + +char *trimblanks(char *string, ssize_t len) +{ + char *curr; + + if (len) { + curr = string + len; + } else { + for (curr = string; *curr != 0; curr++) + /* void */ ; + } + while (curr > string && ISSPACE(curr[-1])) + curr -= 1; + return (curr); +} diff --git a/src/util/unescape.c b/src/util/unescape.c new file mode 100644 index 0000000..4eacbba --- /dev/null +++ b/src/util/unescape.c @@ -0,0 +1,208 @@ +/*++ +/* NAME +/* unescape 3 +/* SUMMARY +/* translate C-like escape sequences +/* SYNOPSIS +/* #include <stringops.h> +/* +/* VSTRING *unescape(result, input) +/* VSTRING *result; +/* const char *input; +/* +/* VSTRING *escape(result, input, len) +/* VSTRING *result; +/* const char *input; +/* ssize_t len; +/* DESCRIPTION +/* unescape() translates C-like escape sequences in the null-terminated +/* string \fIinput\fR and places the result in \fIresult\fR. The result +/* is null-terminated, and is the function result value. +/* +/* escape() does the reverse transformation. +/* +/* Escape sequences and their translations: +/* .IP \ea +/* Bell character. +/* .IP \eb +/* Backspace character. +/* .IP \ef +/* formfeed character. +/* .IP \en +/* newline character +/* .IP \er +/* Carriage-return character. +/* .IP \et +/* Horizontal tab character. +/* .IP \ev +/* Vertical tab character. +/* .IP \e\e +/* Backslash character. +/* .IP \e\fInum\fR +/* 8-bit character whose ASCII value is the 1..3 digit +/* octal number \fInum\fR. +/* .IP \e\fIother\fR +/* The backslash character is discarded. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <ctype.h> + +/* Utility library. */ + +#include <vstring.h> +#include <stringops.h> + +/* unescape - process escape sequences */ + +VSTRING *unescape(VSTRING *result, const char *data) +{ + int ch; + int oval; + int i; + +#define UCHAR(cp) ((unsigned char *) (cp)) +#define ISOCTAL(ch) (ISDIGIT(ch) && (ch) != '8' && (ch) != '9') + + VSTRING_RESET(result); + + while ((ch = *UCHAR(data++)) != 0) { + if (ch == '\\') { + if ((ch = *UCHAR(data++)) == 0) + break; + switch (ch) { + case 'a': /* \a -> audible bell */ + ch = '\a'; + break; + case 'b': /* \b -> backspace */ + ch = '\b'; + break; + case 'f': /* \f -> formfeed */ + ch = '\f'; + break; + case 'n': /* \n -> newline */ + ch = '\n'; + break; + case 'r': /* \r -> carriagereturn */ + ch = '\r'; + break; + case 't': /* \t -> horizontal tab */ + ch = '\t'; + break; + case 'v': /* \v -> vertical tab */ + ch = '\v'; + break; + case '0': /* \nnn -> ASCII value */ + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + for (oval = ch - '0', i = 0; + i < 2 && (ch = *UCHAR(data)) != 0 && ISOCTAL(ch); + i++, data++) { + oval = (oval << 3) | (ch - '0'); + } + ch = oval; + break; + default: /* \any -> any */ + break; + } + } + VSTRING_ADDCH(result, ch); + } + VSTRING_TERMINATE(result); + return (result); +} + +/* escape - reverse transformation */ + +VSTRING *escape(VSTRING *result, const char *data, ssize_t len) +{ + int ch; + + VSTRING_RESET(result); + while (len-- > 0) { + ch = *UCHAR(data++); + if (ISASCII(ch)) { + if (ISPRINT(ch)) { + if (ch == '\\') + VSTRING_ADDCH(result, ch); + VSTRING_ADDCH(result, ch); + continue; + } else if (ch == '\a') { /* \a -> audible bell */ + vstring_strcat(result, "\\a"); + continue; + } else if (ch == '\b') { /* \b -> backspace */ + vstring_strcat(result, "\\b"); + continue; + } else if (ch == '\f') { /* \f -> formfeed */ + vstring_strcat(result, "\\f"); + continue; + } else if (ch == '\n') { /* \n -> newline */ + vstring_strcat(result, "\\n"); + continue; + } else if (ch == '\r') { /* \r -> carriagereturn */ + vstring_strcat(result, "\\r"); + continue; + } else if (ch == '\t') { /* \t -> horizontal tab */ + vstring_strcat(result, "\\t"); + continue; + } else if (ch == '\v') { /* \v -> vertical tab */ + vstring_strcat(result, "\\v"); + continue; + } + } + vstring_sprintf_append(result, "\\%03o", ch); + } + VSTRING_TERMINATE(result); + return (result); +} + +#ifdef TEST + +#include <stdlib.h> +#include <string.h> +#include <msg.h> +#include <vstring_vstream.h> + +int main(int argc, char **argv) +{ + VSTRING *in = vstring_alloc(10); + VSTRING *out = vstring_alloc(10); + int un_escape = 1; + + if (argc > 2 || (argc > 1 && (un_escape = strcmp(argv[1], "-e"))) != 0) + msg_fatal("usage: %s [-e (escape)]", argv[0]); + + if (un_escape) { + while (vstring_fgets_nonl(in, VSTREAM_IN)) { + unescape(out, vstring_str(in)); + vstream_fwrite(VSTREAM_OUT, vstring_str(out), VSTRING_LEN(out)); + VSTREAM_PUTC('\n', VSTREAM_OUT); + } + } else { + while (vstring_fgets_nonl(in, VSTREAM_IN)) { + escape(out, vstring_str(in), VSTRING_LEN(in)); + vstream_fwrite(VSTREAM_OUT, vstring_str(out), VSTRING_LEN(out)); + VSTREAM_PUTC('\n', VSTREAM_OUT); + } + } + vstream_fflush(VSTREAM_OUT); + exit(0); +} + +#endif diff --git a/src/util/unescape.in b/src/util/unescape.in new file mode 100644 index 0000000..41f24a7 --- /dev/null +++ b/src/util/unescape.in @@ -0,0 +1,4 @@ +\a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z +\1\2\3\4\5\6\7\8\9 +\1234\2345\3456\4567 +rcpt to:<wietse@\317\200.porcupine.org> diff --git a/src/util/unescape.ref b/src/util/unescape.ref new file mode 100644 index 0000000..db16fa8 --- /dev/null +++ b/src/util/unescape.ref @@ -0,0 +1,11 @@ +0000000 \a \b c d e \f g h i j k l m \n o p + 007 010 143 144 145 014 147 150 151 152 153 154 155 012 157 160 +0000020 q \r s \t u \v w x y z \n 001 002 003 004 005 + 161 015 163 011 165 013 167 170 171 172 012 001 002 003 004 005 +0000040 006 \a 8 9 \n S 4 234 5 345 6 . 7 \n r c + 006 007 070 071 012 123 064 234 065 345 066 056 067 012 162 143 +0000060 p t t o : < w i e t s e @ Ï€ ** + 160 164 040 164 157 072 074 167 151 145 164 163 145 100 317 200 +0000100 . p o r c u p i n e . o r g > \n + 056 160 157 162 143 165 160 151 156 145 056 157 162 147 076 012 +0000120 diff --git a/src/util/unix_connect.c b/src/util/unix_connect.c new file mode 100644 index 0000000..cbd8c0d --- /dev/null +++ b/src/util/unix_connect.c @@ -0,0 +1,110 @@ +/*++ +/* NAME +/* unix_connect 3 +/* SUMMARY +/* connect to UNIX-domain listener +/* SYNOPSIS +/* #include <connect.h> +/* +/* int unix_connect(addr, block_mode, timeout) +/* const char *addr; +/* int block_mode; +/* int timeout; +/* DESCRIPTION +/* unix_connect() connects to a listener in the UNIX domain at the +/* specified address, and returns the resulting file descriptor. +/* +/* Arguments: +/* .IP addr +/* Null-terminated string with connection destination. +/* .IP block_mode +/* Either NON_BLOCKING for a non-blocking socket, or BLOCKING for +/* blocking mode. +/* .IP timeout +/* Bounds the number of seconds that the operation may take. Specify +/* a value <= 0 to disable the time limit. +/* DIAGNOSTICS +/* The result is -1 in case the connection could not be made. +/* Fatal errors: other system call failures. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System interfaces. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> + +/* Utility library. */ + +#include "msg.h" +#include "iostuff.h" +#include "sane_connect.h" +#include "connect.h" +#include "timed_connect.h" + +/* unix_connect - connect to UNIX-domain listener */ + +int unix_connect(const char *addr, int block_mode, int timeout) +{ +#undef sun + struct sockaddr_un sun; + ssize_t len = strlen(addr); + int sock; + + /* + * Translate address information to internal form. + */ + if (len >= sizeof(sun.sun_path)) + msg_fatal("unix-domain name too long: %s", addr); + memset((void *) &sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; +#ifdef HAS_SUN_LEN + sun.sun_len = len + 1; +#endif + memcpy(sun.sun_path, addr, len + 1); + + /* + * Create a client socket. + */ + if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) + return (-1); + + /* + * Timed connect. + */ + if (timeout > 0) { + non_blocking(sock, NON_BLOCKING); + if (timed_connect(sock, (struct sockaddr *) &sun, sizeof(sun), timeout) < 0) { + close(sock); + return (-1); + } + if (block_mode != NON_BLOCKING) + non_blocking(sock, block_mode); + return (sock); + } + + /* + * Maybe block until connected. + */ + else { + non_blocking(sock, block_mode); + if (sane_connect(sock, (struct sockaddr *) &sun, sizeof(sun)) < 0 + && errno != EINPROGRESS) { + close(sock); + return (-1); + } + return (sock); + } +} diff --git a/src/util/unix_dgram_connect.c b/src/util/unix_dgram_connect.c new file mode 100644 index 0000000..3df8963 --- /dev/null +++ b/src/util/unix_dgram_connect.c @@ -0,0 +1,91 @@ +/*++ +/* NAME +/* unix_dgram_connect 3 +/* SUMMARY +/* connect to UNIX-domain datagram server +/* SYNOPSIS +/* #include <connect.h> +/* +/* int unix_dgram_connect( +/* const char *path, +/* int block_mode) +/* DESCRIPTION +/* unix_dgram_connect() connects to the specified UNIX-domain +/* datagram server, and returns the resulting file descriptor. +/* +/* Arguments: +/* .IP path +/* Null-terminated string with connection destination.` +/* .IP block_mode +/* Either NON_BLOCKING for a non-blocking socket, or BLOCKING for +/* blocking mode. +/* DIAGNOSTICS +/* Fatal errors: path too large, can't create socket. +/* +/* Other errors result in a -1 result value, with errno indicating +/* why the service is unavailable. +/* .sp +/* ENOENT: the named socket does not exist. +/* .sp +/* ECONNREFUSED: the named socket is not open. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include <sys_defs.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#include <string.h> + + /* + * Utility library. + */ +#include <msg.h> +#include <connect.h> +#include <iostuff.h> + +/* unix_dgram_connect - connect to UNIX-domain datagram service */ + +int unix_dgram_connect(const char *path, int block_mode) +{ + const char myname[] = "unix_dgram_connect"; +#undef sun + struct sockaddr_un sun; + ssize_t path_len; + int sock; + + /* + * Translate address information to internal form. + */ + if ((path_len = strlen(path)) >= sizeof(sun.sun_path)) + msg_fatal("%s: unix-domain name too long: %s", myname, path); + memset((void *) &sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; +#ifdef HAS_SUN_LEN + sun.sun_len = path_len + 1; +#endif + memcpy(sun.sun_path, path, path_len + 1); + + /* + * Create a client socket. + */ + if ((sock = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) + msg_fatal("%s: socket: %m", myname); + if (connect(sock, (struct sockaddr *) &sun, sizeof(sun)) < 0) { + close(sock); + return (-1); + } + non_blocking(sock, block_mode); + return (sock); +} diff --git a/src/util/unix_dgram_listen.c b/src/util/unix_dgram_listen.c new file mode 100644 index 0000000..e73ad4e --- /dev/null +++ b/src/util/unix_dgram_listen.c @@ -0,0 +1,93 @@ +/*++ +/* NAME +/* unix_dgram_listen 3 +/* SUMMARY +/* listen to UNIX-domain datagram server +/* SYNOPSIS +/* #include <listen.h> +/* +/* int unix_dgram_listen( +/* const char *path, +/* int block_mode) +/* DESCRIPTION +/* unix_dgram_listen() binds to the specified UNIX-domain +/* datagram endpoint, and returns the resulting file descriptor. +/* +/* Arguments: +/* .IP path +/* Null-terminated string with connection destination. +/* .IP backlog +/* Either NON_BLOCKING for a non-blocking socket, or BLOCKING for +/* blocking mode. +/* DIAGNOSTICS +/* Fatal errors: path too large, can't create socket. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include <sys_defs.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/stat.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + + /* + * Utility library. + */ +#include <iostuff.h> +#include <listen.h> +#include <msg.h> + +/* unix_dgram_listen - bind to UNIX-domain datagram endpoint */ + +int unix_dgram_listen(const char *path, int block_mode) +{ + const char myname[] = "unix_dgram_listen"; +#undef sun + struct sockaddr_un sun; + ssize_t path_len; + int sock; + + /* + * Translate address information to internal form. + */ + if ((path_len = strlen(path)) >= sizeof(sun.sun_path)) + msg_fatal("%s: unix-domain name too long: %s", myname, path); + memset((void *) &sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; +#ifdef HAS_SUN_LEN + sun.sun_len = path_len + 1; +#endif + memcpy(sun.sun_path, path, path_len + 1); + + /* + * Create a 'server' socket. + */ + if ((sock = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) + msg_fatal("%s: socket: %m", myname); + if (unlink(path) < 0 && errno != ENOENT) + msg_fatal( "remove %s: %m", path); + if (bind(sock, (struct sockaddr *) & sun, sizeof(sun)) < 0) + msg_fatal( "bind: %s: %m", path); +#ifdef FCHMOD_UNIX_SOCKETS + if (fchmod(sock, 0666) < 0) + msg_fatal("fchmod socket %s: %m", path); +#else + if (chmod(path, 0666) < 0) + msg_fatal("chmod socket %s: %m", path); +#endif + non_blocking(sock, block_mode); + return (sock); +} diff --git a/src/util/unix_listen.c b/src/util/unix_listen.c new file mode 100644 index 0000000..6440406 --- /dev/null +++ b/src/util/unix_listen.c @@ -0,0 +1,113 @@ +/*++ +/* NAME +/* unix_listen 3 +/* SUMMARY +/* start UNIX-domain listener +/* SYNOPSIS +/* #include <listen.h> +/* +/* int unix_listen(addr, backlog, block_mode) +/* const char *addr; +/* int backlog; +/* int block_mode; +/* +/* int unix_accept(fd) +/* int fd; +/* DESCRIPTION +/* The \fBunix_listen\fR() routine starts a listener in the UNIX domain +/* on the specified address, with the specified backlog, and returns +/* the resulting file descriptor. +/* +/* unix_accept() accepts a connection and sanitizes error results. +/* +/* Arguments: +/* .IP addr +/* Null-terminated string with connection destination. +/* .IP backlog +/* This argument is passed on to the \fIlisten(2)\fR routine. +/* .IP block_mode +/* Either NON_BLOCKING for a non-blocking socket, or BLOCKING for +/* blocking mode. +/* .IP fd +/* File descriptor returned by unix_listen(). +/* DIAGNOSTICS +/* Fatal errors: unix_listen() aborts upon any system call failure. +/* unix_accept() leaves all error handling up to the caller. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System interfaces. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <string.h> +#include <unistd.h> +#include <sys/stat.h> +#include <errno.h> + +/* Utility library. */ + +#include "msg.h" +#include "iostuff.h" +#include "listen.h" +#include "sane_accept.h" + +/* unix_listen - create UNIX-domain listener */ + +int unix_listen(const char *addr, int backlog, int block_mode) +{ +#undef sun + struct sockaddr_un sun; + ssize_t len = strlen(addr); + int sock; + + /* + * Translate address information to internal form. + */ + if (len >= sizeof(sun.sun_path)) + msg_fatal("unix-domain name too long: %s", addr); + memset((void *) &sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; +#ifdef HAS_SUN_LEN + sun.sun_len = len + 1; +#endif + memcpy(sun.sun_path, addr, len + 1); + + /* + * Create a listener socket. Do whatever we can so we don't run into + * trouble when this process is restarted after crash. + */ + if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) + msg_fatal("socket: %m"); + if (unlink(addr) < 0 && errno != ENOENT) + msg_fatal("remove %s: %m", addr); + if (bind(sock, (struct sockaddr *) &sun, sizeof(sun)) < 0) + msg_fatal("bind: %s: %m", addr); +#ifdef FCHMOD_UNIX_SOCKETS + if (fchmod(sock, 0666) < 0) + msg_fatal("fchmod socket %s: %m", addr); +#else + if (chmod(addr, 0666) < 0) + msg_fatal("chmod socket %s: %m", addr); +#endif + non_blocking(sock, block_mode); + if (listen(sock, backlog) < 0) + msg_fatal("listen: %m"); + return (sock); +} + +/* unix_accept - accept connection */ + +int unix_accept(int fd) +{ + return (sane_accept(fd, (struct sockaddr *) 0, (SOCKADDR_SIZE *) 0)); +} diff --git a/src/util/unix_pass_fd_fix.c b/src/util/unix_pass_fd_fix.c new file mode 100644 index 0000000..9522e61 --- /dev/null +++ b/src/util/unix_pass_fd_fix.c @@ -0,0 +1,67 @@ +/*++ +/* NAME +/* unix_pass_fd_fix 3 +/* SUMMARY +/* file descriptor passing bug workarounds +/* SYNOPSIS +/* #include <iostuff.h> +/* +/* void set_unix_pass_fd_fix(workarounds) +/* const char *workarounds; +/* DESCRIPTION +/* This module supports programmatic control over workarounds +/* for sending or receiving file descriptors over UNIX-domain +/* sockets. +/* +/* set_unix_pass_fd_fix() takes a list of workarounds in external +/* form, and stores their internal representation. The result +/* is used by unix_send_fd() and unix_recv_fd(). +/* +/* Arguments: +/* .IP workarounds +/* List of zero or more of the following, separated by comma +/* or whitespace. +/* .RS +/* .IP cmsg_len +/* Send the CMSG_LEN of the file descriptor, instead of +/* the total message buffer length. +/* .RE +/* SEE ALSO +/* unix_send_fd(3) send file descriptor +/* unix_recv_fd(3) receive file descriptor +/* DIAGNOSTICS +/* Fatal errors: non-existent workaround. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +/* Utility library. */ + +#include <iostuff.h> +#include <name_mask.h> + +int unix_pass_fd_fix = 0; + +/* set_unix_pass_fd_fix - set workaround programmatically */ + +void set_unix_pass_fd_fix(const char *workarounds) +{ + const static NAME_MASK table[] = { + "cmsg_len", UNIX_PASS_FD_FIX_CMSG_LEN, + 0, + }; + + unix_pass_fd_fix = name_mask("descriptor passing workarounds", + table, workarounds); +} diff --git a/src/util/unix_recv_fd.c b/src/util/unix_recv_fd.c new file mode 100644 index 0000000..3410cff --- /dev/null +++ b/src/util/unix_recv_fd.c @@ -0,0 +1,178 @@ +/*++ +/* NAME +/* unix_recv_fd 3 +/* SUMMARY +/* receive file descriptor +/* SYNOPSIS +/* #include <iostuff.h> +/* +/* int unix_recv_fd(fd) +/* int fd; +/* DESCRIPTION +/* unix_recv_fd() receives a file descriptor via the specified +/* UNIX-domain socket. The result value is the received descriptor. +/* +/* Arguments: +/* .IP fd +/* File descriptor that connects the sending and receiving processes. +/* DIAGNOSTICS +/* unix_recv_fd() returns -1 upon failure. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> /* includes <sys/types.h> */ +#include <sys/socket.h> +#include <sys/uio.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <iostuff.h> + +/* unix_recv_fd - receive file descriptor */ + +int unix_recv_fd(int fd) +{ + const char *myname = "unix_recv_fd"; + + /* + * This code does not work with version <2.2 Linux kernels, and it does + * not compile with version <2 Linux libraries. + */ +#ifdef CANT_USE_SEND_RECV_MSG + msg_warn("%s: your system has no support for file descriptor passing", + myname); + return (-1); +#else + struct msghdr msg; + int newfd; + struct iovec iov[1]; + char buf[1]; + + /* + * Adapted from: W. Richard Stevens, UNIX Network Programming, Volume 1, + * Second edition. Except that we use CMSG_LEN instead of CMSG_SPACE, for + * portability to some LP64 environments. See also unix_send_fd.c. + */ +#if defined(CMSG_SPACE) && !defined(NO_MSGHDR_MSG_CONTROL) + union { + struct cmsghdr just_for_alignment; + char control[CMSG_SPACE(sizeof(newfd))]; + } control_un; + struct cmsghdr *cmptr; + + memset((void *) &msg, 0, sizeof(msg)); /* Fix 200512 */ + msg.msg_control = control_un.control; + if (unix_pass_fd_fix & UNIX_PASS_FD_FIX_CMSG_LEN) { + msg.msg_controllen = CMSG_LEN(sizeof(newfd)); /* Fix 200506 */ + } else { + msg.msg_controllen = sizeof(control_un.control); /* normal */ + } +#else + msg.msg_accrights = (char *) &newfd; + msg.msg_accrightslen = sizeof(newfd); +#endif + + msg.msg_name = 0; + msg.msg_namelen = 0; + + /* + * XXX We don't want to pass any data, just a file descriptor. However, + * setting msg.msg_iov = 0 and msg.msg_iovlen = 0 causes trouble: we need + * to read_wait() before we can receive the descriptor, and the code + * fails after the first descriptor when we attempt to receive a sequence + * of descriptors. + */ + iov->iov_base = buf; + iov->iov_len = sizeof(buf); + msg.msg_iov = iov; + msg.msg_iovlen = 1; + + if (recvmsg(fd, &msg, 0) < 0) + return (-1); + +#if defined(CMSG_SPACE) && !defined(NO_MSGHDR_MSG_CONTROL) + if ((cmptr = CMSG_FIRSTHDR(&msg)) != 0 + && cmptr->cmsg_len == CMSG_LEN(sizeof(newfd))) { + if (cmptr->cmsg_level != SOL_SOCKET) + msg_fatal("%s: control level %d != SOL_SOCKET", + myname, cmptr->cmsg_level); + if (cmptr->cmsg_type != SCM_RIGHTS) + msg_fatal("%s: control type %d != SCM_RIGHTS", + myname, cmptr->cmsg_type); + return (*(int *) CMSG_DATA(cmptr)); + } else + return (-1); +#else + if (msg.msg_accrightslen == sizeof(newfd)) + return (newfd); + else + return (-1); +#endif +#endif +} + +#ifdef TEST + + /* + * Proof-of-concept program. Receive a descriptor (presumably from the + * unix_send_fd test program) and copy its content until EOF. + */ +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <split_at.h> +#include <listen.h> + +int main(int argc, char **argv) +{ + char *transport; + char *endpoint; + int listen_sock; + int client_sock; + int client_fd; + ssize_t read_count; + char buf[1024]; + + if (argc < 2 || argc > 3 + || (endpoint = split_at(transport = argv[1], ':')) == 0 + || *endpoint == 0 || *transport == 0) + msg_fatal("usage: %s transport:endpoint [workaround]", argv[0]); + + if (strcmp(transport, "unix") == 0) { + listen_sock = unix_listen(endpoint, 10, BLOCKING); + } else { + msg_fatal("invalid transport name: %s", transport); + } + if (listen_sock < 0) + msg_fatal("listen %s:%s: %m", transport, endpoint); + + client_sock = accept(listen_sock, (struct sockaddr *) 0, (SOCKADDR_SIZE) 0); + if (client_sock < 0) + msg_fatal("accept: %m"); + + set_unix_pass_fd_fix(argv[2] ? argv[2] : ""); + + while ((client_fd = unix_recv_fd(client_sock)) >= 0) { + msg_info("client_fd = %d, fix=%d", client_fd, unix_pass_fd_fix); + while ((read_count = read(client_fd, buf, sizeof(buf))) > 0) + write(1, buf, read_count); + if (read_count < 0) + msg_fatal("read: %m"); + close(client_fd); + } + exit(0); +} + +#endif diff --git a/src/util/unix_send_fd.c b/src/util/unix_send_fd.c new file mode 100644 index 0000000..1998a7c --- /dev/null +++ b/src/util/unix_send_fd.c @@ -0,0 +1,193 @@ +/*++ +/* NAME +/* unix_send_fd 3 +/* SUMMARY +/* send file descriptor +/* SYNOPSIS +/* #include <iostuff.h> +/* +/* int unix_send_fd(fd, sendfd) +/* int fd; +/* int sendfd; +/* DESCRIPTION +/* unix_send_fd() sends a file descriptor over the specified +/* UNIX-domain socket. +/* +/* Arguments: +/* .IP fd +/* File descriptor that connects the sending and receiving processes. +/* .IP sendfd +/* The file descriptor to be sent. +/* DIAGNOSTICS +/* unix_send_fd() returns -1 upon failure. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> /* includes <sys/types.h> */ +#include <sys/socket.h> +#include <sys/uio.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <iostuff.h> + +/* unix_send_fd - send file descriptor */ + +int unix_send_fd(int fd, int sendfd) +{ + + /* + * This code does not work with version <2.2 Linux kernels, and it does + * not compile with version <2 Linux libraries. + */ +#ifdef CANT_USE_SEND_RECV_MSG + const char *myname = "unix_send_fd"; + + msg_warn("%s: your system has no support for file descriptor passing", + myname); + return (-1); +#else + struct msghdr msg; + struct iovec iov[1]; + + /* + * Adapted from: W. Richard Stevens, UNIX Network Programming, Volume 1, + * Second edition. Except that we use CMSG_LEN instead of CMSG_SPACE, for + * portability to some LP64 environments. See also unix_recv_fd.c. + */ +#if defined(CMSG_SPACE) && !defined(NO_MSGHDR_MSG_CONTROL) + union { + struct cmsghdr just_for_alignment; + char control[CMSG_SPACE(sizeof(sendfd))]; + } control_un; + struct cmsghdr *cmptr; + + memset((void *) &msg, 0, sizeof(msg)); /* Fix 200512 */ + msg.msg_control = control_un.control; + if (unix_pass_fd_fix & UNIX_PASS_FD_FIX_CMSG_LEN) { + msg.msg_controllen = CMSG_LEN(sizeof(sendfd)); /* Fix 200506 */ + } else { + msg.msg_controllen = sizeof(control_un.control); /* normal */ + } + cmptr = CMSG_FIRSTHDR(&msg); + cmptr->cmsg_len = CMSG_LEN(sizeof(sendfd)); + cmptr->cmsg_level = SOL_SOCKET; + cmptr->cmsg_type = SCM_RIGHTS; + *(int *) CMSG_DATA(cmptr) = sendfd; +#else + msg.msg_accrights = (char *) &sendfd; + msg.msg_accrightslen = sizeof(sendfd); +#endif + + msg.msg_name = 0; + msg.msg_namelen = 0; + + /* + * XXX We don't want to pass any data, just a file descriptor. However, + * setting msg.msg_iov = 0 and msg.msg_iovlen = 0 causes trouble. See the + * comments in the unix_recv_fd() routine. + */ + iov->iov_base = ""; + iov->iov_len = 1; + msg.msg_iov = iov; + msg.msg_iovlen = 1; + + /* + * The CMSG_LEN send/receive workaround was originally developed for + * OpenBSD 3.6 on SPARC64. After the workaround was verified to not break + * Solaris 8 on SPARC64, it was hard-coded with Postfix 2.3 for all + * platforms because of increasing pressure to work on other things. The + * workaround does nothing for 32-bit systems. + * + * The investigation was reopened with Postfix 2.7 because the workaround + * broke with NetBSD 5.0 on 64-bit architectures. This time it was found + * that OpenBSD <= 4.3 on AMD64 and SPARC64 needed the workaround for + * sending only. The following platforms worked with and without the + * workaround: OpenBSD 4.5 on AMD64 and SPARC64, FreeBSD 7.2 on AMD64, + * Solaris 8 on SPARC64, and Linux 2.6-11 on x86_64. + * + * As this appears to have been an OpenBSD-specific problem, we revert to + * the Postfix 2.2 behavior. Instead of hard-coding the workaround for + * all platforms, we now detect sendmsg() errors at run time and turn on + * the workaround dynamically. + * + * The workaround was made run-time configurable to investigate the problem + * on multiple platforms. Though set_unix_pass_fd_fix() is over-kill for + * this specific problem, it is left in place so that it can serve as an + * example of how to add run-time configurable workarounds to Postfix. + */ + if (sendmsg(fd, &msg, 0) >= 0) + return (0); + if (unix_pass_fd_fix == 0) { + if (msg_verbose) + msg_info("sendmsg error (%m). Trying CMSG_LEN workaround."); + unix_pass_fd_fix = UNIX_PASS_FD_FIX_CMSG_LEN; + return (unix_send_fd(fd, sendfd)); + } else { + return (-1); + } +#endif +} + +#ifdef TEST + + /* + * Proof-of-concept program. Open a file and send the descriptor, presumably + * to the unix_recv_fd test program. + */ +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <fcntl.h> +#include <split_at.h> +#include <connect.h> + +int main(int argc, char **argv) +{ + char *transport; + char *endpoint; + char *path; + int server_sock; + int client_fd; + + msg_verbose = 1; + + if (argc < 3 + || (endpoint = split_at(transport = argv[1], ':')) == 0 + || *endpoint == 0 || *transport == 0) + msg_fatal("usage: %s transport:endpoint file...", argv[0]); + + if (strcmp(transport, "unix") == 0) { + server_sock = unix_connect(endpoint, BLOCKING, 0); + } else { + msg_fatal("invalid transport name: %s", transport); + } + if (server_sock < 0) + msg_fatal("connect %s:%s: %m", transport, endpoint); + + argv += 2; + while ((path = *argv++) != 0) { + if ((client_fd = open(path, O_RDONLY, 0)) < 0) + msg_fatal("open %s: %m", path); + msg_info("path=%s fd=%d", path, client_fd); + if (unix_send_fd(server_sock, client_fd) < 0) + msg_fatal("send file descriptor: %m"); + if (close(client_fd) != 0) + msg_fatal("close(%d): %m", client_fd); + } + exit(0); +} + +#endif diff --git a/src/util/unix_trigger.c b/src/util/unix_trigger.c new file mode 100644 index 0000000..59a18ff --- /dev/null +++ b/src/util/unix_trigger.c @@ -0,0 +1,131 @@ +/*++ +/* NAME +/* unix_trigger 3 +/* SUMMARY +/* wakeup UNIX-domain server +/* SYNOPSIS +/* #include <trigger.h> +/* +/* int unix_trigger(service, buf, len, timeout) +/* const char *service; +/* const char *buf; +/* ssize_t len; +/* int timeout; +/* DESCRIPTION +/* unix_trigger() wakes up the named UNIX-domain server by making +/* a brief connection to it and writing the named buffer. +/* +/* The connection is closed by a background thread. Some kernels +/* cannot handle client-side disconnect before the server has +/* received the message. +/* +/* Arguments: +/* .IP service +/* Name of the communication endpoint. +/* .IP buf +/* Address of data to be written. +/* .IP len +/* Amount of data to be written. +/* .IP timeout +/* Deadline in seconds. Specify a value <= 0 to disable +/* the time limit. +/* DIAGNOSTICS +/* The result is zero in case of success, -1 in case of problems. +/* SEE ALSO +/* unix_connect(3), UNIX-domain client +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <unistd.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <connect.h> +#include <iostuff.h> +#include <mymalloc.h> +#include <events.h> +#include <trigger.h> + +struct unix_trigger { + int fd; + char *service; +}; + +/* unix_trigger_event - disconnect from peer */ + +static void unix_trigger_event(int event, void *context) +{ + struct unix_trigger *up = (struct unix_trigger *) context; + static const char *myname = "unix_trigger_event"; + + /* + * Disconnect. + */ + if (event == EVENT_TIME) + msg_warn("%s: read timeout for service %s", myname, up->service); + event_disable_readwrite(up->fd); + event_cancel_timer(unix_trigger_event, context); + if (close(up->fd) < 0) + msg_warn("%s: close %s: %m", myname, up->service); + myfree(up->service); + myfree((void *) up); +} + +/* unix_trigger - wakeup UNIX-domain server */ + +int unix_trigger(const char *service, const char *buf, ssize_t len, int timeout) +{ + const char *myname = "unix_trigger"; + struct unix_trigger *up; + int fd; + + if (msg_verbose > 1) + msg_info("%s: service %s", myname, service); + + /* + * Connect... + */ + if ((fd = unix_connect(service, BLOCKING, timeout)) < 0) { + if (msg_verbose) + msg_warn("%s: connect to %s: %m", myname, service); + return (-1); + } + close_on_exec(fd, CLOSE_ON_EXEC); + + /* + * Stash away context. + */ + up = (struct unix_trigger *) mymalloc(sizeof(*up)); + up->fd = fd; + up->service = mystrdup(service); + + /* + * Write the request... + */ + if (write_buf(fd, buf, len, timeout) < 0 + || write_buf(fd, "", 1, timeout) < 0) + if (msg_verbose) + msg_warn("%s: write to %s: %m", myname, service); + + /* + * Wakeup when the peer disconnects, or when we lose patience. + */ + if (timeout > 0) + event_request_timer(unix_trigger_event, (void *) up, timeout + 100); + event_enable_read(fd, unix_trigger_event, (void *) up); + return (0); +} diff --git a/src/util/unsafe.c b/src/util/unsafe.c new file mode 100644 index 0000000..5d307c9 --- /dev/null +++ b/src/util/unsafe.c @@ -0,0 +1,77 @@ +/*++ +/* NAME +/* unsafe 3 +/* SUMMARY +/* are we running at non-user privileges +/* SYNOPSIS +/* #include <safe.h> +/* +/* int unsafe() +/* DESCRIPTION +/* The \fBunsafe()\fR routine attempts to determine if the process +/* (runs with privileges or has access to information) that the +/* controlling user has no access to. The purpose is to prevent +/* misuse of privileges, including access to protected information. +/* +/* The result is always false when both of the following conditions +/* are true: +/* .IP \(bu +/* The real UID is zero. +/* .IP \(bu +/* The effective UID is zero. +/* .PP +/* Otherwise, the result is true if any of the following conditions +/* is true: +/* .IP \(bu +/* The issetuid kernel flag is non-zero (on systems that support +/* this concept). +/* .IP \(bu +/* The real and effective user id differ. +/* .IP \(bu +/* The real and effective group id differ. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <unistd.h> + +/* Utility library. */ + +#include "safe.h" + +/* unsafe - can we trust user-provided environment, working directory, etc. */ + +int unsafe(void) +{ + + /* + * The super-user is trusted. + */ + if (getuid() == 0 && geteuid() == 0) + return (0); + + /* + * Danger: don't trust inherited process attributes, and don't leak + * privileged info that the parent has no access to. + */ + return (geteuid() != getuid() +#ifdef HAS_ISSETUGID + || issetugid() +#endif + || getgid() != getegid()); +} diff --git a/src/util/uppercase.c b/src/util/uppercase.c new file mode 100644 index 0000000..9c61622 --- /dev/null +++ b/src/util/uppercase.c @@ -0,0 +1,43 @@ +/*++ +/* NAME +/* uppercase 3 +/* SUMMARY +/* map lowercase characters to uppercase +/* SYNOPSIS +/* #include <stringops.h> +/* +/* char *uppercase(buf) +/* char *buf; +/* DESCRIPTION +/* uppercase() replaces lowercase characters in its null-terminated +/* input by their uppercase equivalent. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <ctype.h> + +/* Utility library. */ + +#include "stringops.h" + +char *uppercase(char *string) +{ + char *cp; + int ch; + + for (cp = string; (ch = *cp) != 0; cp++) + if (ISLOWER(ch)) + *cp = TOUPPER(ch); + return (string); +} diff --git a/src/util/username.c b/src/util/username.c new file mode 100644 index 0000000..680161c --- /dev/null +++ b/src/util/username.c @@ -0,0 +1,47 @@ +/*++ +/* NAME +/* username 3 +/* SUMMARY +/* lookup name of real user +/* SYNOPSIS +/* #include <username.h> +/* +/* const char *username() +/* DESCRIPTION +/* username() jumps whatever system-specific hoops it takes to +/* get the name of the user who started the process. The result +/* is volatile. Make a copy if it is to be used for an appreciable +/* amount of time. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <unistd.h> +#include <pwd.h> + +/* Utility library. */ + +#include "username.h" + +/* username - get name of user */ + +const char *username(void) +{ + uid_t uid; + struct passwd *pwd; + + uid = getuid(); + if ((pwd = getpwuid(uid)) == 0) + return (0); + return (pwd->pw_name); +} diff --git a/src/util/username.h b/src/util/username.h new file mode 100644 index 0000000..648be45 --- /dev/null +++ b/src/util/username.h @@ -0,0 +1,29 @@ +#ifndef _USERNAME_H_INCLUDED_ +#define _USERNAME_H_INCLUDED_ + +/*++ +/* NAME +/* username 3h +/* SUMMARY +/* lookup name of real user +/* SYNOPSIS +/* #include <username.h> +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern const char *username(void); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/valid_hostname.c b/src/util/valid_hostname.c new file mode 100644 index 0000000..8b234c4 --- /dev/null +++ b/src/util/valid_hostname.c @@ -0,0 +1,412 @@ +/*++ +/* NAME +/* valid_hostname 3 +/* SUMMARY +/* network name validation +/* SYNOPSIS +/* #include <valid_hostname.h> +/* +/* int valid_hostname(name, gripe) +/* const char *name; +/* int gripe; +/* +/* int valid_hostaddr(addr, gripe) +/* const char *addr; +/* int gripe; +/* +/* int valid_ipv4_hostaddr(addr, gripe) +/* const char *addr; +/* int gripe; +/* +/* int valid_ipv6_hostaddr(addr, gripe) +/* const char *addr; +/* int gripe; +/* +/* int valid_hostport(port, gripe) +/* const char *port; +/* int gripe; +/* DESCRIPTION +/* valid_hostname() scrutinizes a hostname: the name should +/* be no longer than VALID_HOSTNAME_LEN characters, should +/* contain only letters, digits, dots and hyphens, no adjacent +/* dots, no leading or trailing dots or hyphens, no labels +/* longer than VALID_LABEL_LEN characters, and it should not +/* be all numeric. +/* +/* valid_hostaddr() requires that the input is a valid string +/* representation of an IPv4 or IPv6 network address as +/* described next. +/* +/* valid_ipv4_hostaddr() and valid_ipv6_hostaddr() implement +/* protocol-specific address syntax checks. A valid IPv4 +/* address is in dotted-quad decimal form. A valid IPv6 address +/* has 16-bit hexadecimal fields separated by ":", and does not +/* include the RFC 2821 style "IPv6:" prefix. +/* +/* These routines operate silently unless the gripe parameter +/* specifies a non-zero value. The macros DO_GRIPE and DONT_GRIPE +/* provide suitable constants. +/* +/* valid_hostport() requires that the input is a valid string +/* representation of a TCP or UDP port number. +/* BUGS +/* valid_hostmumble() does not guarantee that string lengths +/* fit the buffer sizes defined in myaddrinfo(3h). +/* DIAGNOSTICS +/* All functions return zero if they disagree with the input. +/* SEE ALSO +/* RFC 952, RFC 1123, RFC 1035, RFC 2373. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> +#include <ctype.h> +#include <stdlib.h> + +/* Utility library. */ + +#include "msg.h" +#include "mymalloc.h" +#include "stringops.h" +#include "valid_hostname.h" + +/* valid_hostname - screen out bad hostnames */ + +int valid_hostname(const char *name, int flags) +{ + const char *myname = "valid_hostname"; + const char *cp; + int label_length = 0; + int label_count = 0; + int non_numeric = 0; + int ch; + int gripe = flags & DO_GRIPE; + + /* + * Trivial cases first. + */ + if (*name == 0) { + if (gripe) + msg_warn("%s: empty hostname", myname); + return (0); + } + + /* + * Find bad characters or label lengths. Find adjacent delimiters. + */ + for (cp = name; (ch = *(unsigned char *) cp) != 0; cp++) { + if (ISALNUM(ch) || ch == '_') { /* grr.. */ + if (label_length == 0) + label_count++; + label_length++; + if (label_length > VALID_LABEL_LEN) { + if (gripe) + msg_warn("%s: hostname label too long: %.100s", myname, name); + return (0); + } + if (!ISDIGIT(ch)) + non_numeric = 1; + } else if ((flags & DO_WILDCARD) && ch == '*') { + if (label_length || label_count || (cp[1] && cp[1] != '.')) { + if (gripe) + msg_warn("%s: '*' can be the first label only: %.100s", myname, name); + return (0); + } + label_count++; + label_length++; + non_numeric = 1; + } else if (ch == '.') { + if (label_length == 0 || cp[1] == 0) { + if (gripe) + msg_warn("%s: misplaced delimiter: %.100s", myname, name); + return (0); + } + label_length = 0; + } else if (ch == '-') { + non_numeric = 1; + label_length++; + if (label_length == 1 || cp[1] == 0 || cp[1] == '.') { + if (gripe) + msg_warn("%s: misplaced hyphen: %.100s", myname, name); + return (0); + } + } +#ifdef SLOPPY_VALID_HOSTNAME + else if (ch == ':' && valid_ipv6_hostaddr(name, DONT_GRIPE)) { + non_numeric = 0; + break; + } +#endif + else { + if (gripe) + msg_warn("%s: invalid character %d(decimal): %.100s", + myname, ch, name); + return (0); + } + } + + if (non_numeric == 0) { + if (gripe) + msg_warn("%s: numeric hostname: %.100s", myname, name); +#ifndef SLOPPY_VALID_HOSTNAME + return (0); +#endif + } + if (cp - name > VALID_HOSTNAME_LEN) { + if (gripe) + msg_warn("%s: bad length %d for %.100s...", + myname, (int) (cp - name), name); + return (0); + } + return (1); +} + +/* valid_hostaddr - verify numerical address syntax */ + +int valid_hostaddr(const char *addr, int gripe) +{ + const char *myname = "valid_hostaddr"; + + /* + * Trivial cases first. + */ + if (*addr == 0) { + if (gripe) + msg_warn("%s: empty address", myname); + return (0); + } + + /* + * Protocol-dependent processing next. + */ + if (strchr(addr, ':') != 0) + return (valid_ipv6_hostaddr(addr, gripe)); + else + return (valid_ipv4_hostaddr(addr, gripe)); +} + +/* valid_ipv4_hostaddr - test dotted quad string for correctness */ + +int valid_ipv4_hostaddr(const char *addr, int gripe) +{ + const char *cp; + const char *myname = "valid_ipv4_hostaddr"; + int in_byte = 0; + int byte_count = 0; + int byte_val = 0; + int ch; + +#define BYTES_NEEDED 4 + + /* + * Scary code to avoid sscanf() overflow nasties. + * + * This routine is called by valid_ipv6_hostaddr(). It must not call that + * routine, to avoid deadly recursion. + */ + for (cp = addr; (ch = *(unsigned const char *) cp) != 0; cp++) { + if (ISDIGIT(ch)) { + if (in_byte == 0) { + in_byte = 1; + byte_val = 0; + byte_count++; + } + byte_val *= 10; + byte_val += ch - '0'; + if (byte_val > 255) { + if (gripe) + msg_warn("%s: invalid octet value: %.100s", myname, addr); + return (0); + } + } else if (ch == '.') { + if (in_byte == 0 || cp[1] == 0) { + if (gripe) + msg_warn("%s: misplaced dot: %.100s", myname, addr); + return (0); + } + /* XXX Allow 0.0.0.0 but not 0.1.2.3 */ + if (byte_count == 1 && byte_val == 0 && addr[strspn(addr, "0.")]) { + if (gripe) + msg_warn("%s: bad initial octet value: %.100s", myname, addr); + return (0); + } + in_byte = 0; + } else { + if (gripe) + msg_warn("%s: invalid character %d(decimal): %.100s", + myname, ch, addr); + return (0); + } + } + + if (byte_count != BYTES_NEEDED) { + if (gripe) + msg_warn("%s: invalid octet count: %.100s", myname, addr); + return (0); + } + return (1); +} + +/* valid_ipv6_hostaddr - validate IPv6 address syntax */ + +int valid_ipv6_hostaddr(const char *addr, int gripe) +{ + const char *myname = "valid_ipv6_hostaddr"; + int null_field = 0; + int field = 0; + unsigned char *cp = (unsigned char *) addr; + int len = 0; + + /* + * FIX 200501 The IPv6 patch validated syntax with getaddrinfo(), but I + * am not confident that everyone's system library routines are robust + * enough, like buffer overflow free. Remember, the valid_hostmumble() + * routines are meant to protect Postfix against malformed information in + * data received from the network. + * + * We require eight-field hex addresses of the form 0:1:2:3:4:5:6:7, + * 0:1:2:3:4:5:6a.6b.7c.7d, or some :: compressed version of the same. + * + * Note: the character position is advanced inside the loop. I have added + * comments to show why we can't get stuck. + */ + for (;;) { + switch (*cp) { + case 0: + /* Terminate the loop. */ + if (field < 2) { + if (gripe) + msg_warn("%s: too few `:' in IPv6 address: %.100s", + myname, addr); + return (0); + } else if (len == 0 && null_field != field - 1) { + if (gripe) + msg_warn("%s: bad null last field in IPv6 address: %.100s", + myname, addr); + return (0); + } else + return (1); + case '.': + /* Terminate the loop. */ + if (field < 2 || field > 6) { + if (gripe) + msg_warn("%s: malformed IPv4-in-IPv6 address: %.100s", + myname, addr); + return (0); + } else + /* NOT: valid_hostaddr(). Avoid recursion. */ + return (valid_ipv4_hostaddr((char *) cp - len, gripe)); + case ':': + /* Advance by exactly 1 character position or terminate. */ + if (field == 0 && len == 0 && ISALNUM(cp[1])) { + if (gripe) + msg_warn("%s: bad null first field in IPv6 address: %.100s", + myname, addr); + return (0); + } + field++; + if (field > 7) { + if (gripe) + msg_warn("%s: too many `:' in IPv6 address: %.100s", + myname, addr); + return (0); + } + cp++; + len = 0; + if (*cp == ':') { + if (null_field > 0) { + if (gripe) + msg_warn("%s: too many `::' in IPv6 address: %.100s", + myname, addr); + return (0); + } + null_field = field; + } + break; + default: + /* Advance by at least 1 character position or terminate. */ + len = strspn((char *) cp, "0123456789abcdefABCDEF"); + if (len /* - strspn((char *) cp, "0") */ > 4) { + if (gripe) + msg_warn("%s: malformed IPv6 address: %.100s", + myname, addr); + return (0); + } + if (len <= 0) { + if (gripe) + msg_warn("%s: invalid character %d(decimal) in IPv6 address: %.100s", + myname, *cp, addr); + return (0); + } + cp += len; + break; + } + } +} + +/* valid_hostport - validate numeric port */ + +int valid_hostport(const char *str, int gripe) +{ + const char *myname = "valid_hostport"; + int port; + + if (str[0] == '0' && str[1] != 0) { + if (gripe) + msg_warn("%s: leading zero in port number: %.100s", myname, str); + return (0); + } + if (alldig(str) == 0) { + if (gripe) + msg_warn("%s: non-numeric port number: %.100s", myname, str); + return (0); + } + if (strlen(str) > strlen("65535") + || (port = atoi(str)) > 65535 || port < 0) { + if (gripe) + msg_warn("%s: out-of-range port number: %.100s", myname, str); + return (0); + } + return (1); +} + +#ifdef TEST + + /* + * Test program - reads hostnames from stdin, reports invalid hostnames to + * stderr. + */ +#include <stdlib.h> + +#include "vstring.h" +#include "vstream.h" +#include "vstring_vstream.h" +#include "msg_vstream.h" + +int main(int unused_argc, char **argv) +{ + VSTRING *buffer = vstring_alloc(1); + + msg_vstream_init(argv[0], VSTREAM_ERR); + msg_verbose = 1; + + while (vstring_fgets_nonl(buffer, VSTREAM_IN)) { + msg_info("testing: \"%s\"", vstring_str(buffer)); + valid_hostname(vstring_str(buffer), DO_GRIPE); + valid_hostaddr(vstring_str(buffer), DO_GRIPE); + } + exit(0); +} + +#endif diff --git a/src/util/valid_hostname.h b/src/util/valid_hostname.h new file mode 100644 index 0000000..463bc6e --- /dev/null +++ b/src/util/valid_hostname.h @@ -0,0 +1,41 @@ +#ifndef _VALID_HOSTNAME_H_INCLUDED_ +#define _VALID_HOSTNAME_H_INCLUDED_ + +/*++ +/* NAME +/* valid_hostname 3h +/* SUMMARY +/* validate hostname +/* SYNOPSIS +/* #include <valid_hostname.h> +/* DESCRIPTION +/* .nf + + /* External interface */ + +#define VALID_HOSTNAME_LEN 255 /* RFC 1035 */ +#define VALID_LABEL_LEN 63 /* RFC 1035 */ + +#define DONT_GRIPE 0 +#define DO_GRIPE 1 +#define DONT_WILDCARD 0 +#define DO_WILDCARD (1<<1) + +extern int valid_hostname(const char *, int); +extern int valid_hostaddr(const char *, int); +extern int valid_ipv4_hostaddr(const char *, int); +extern int valid_ipv6_hostaddr(const char *, int); +extern int valid_hostport(const char *, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/valid_hostname.in b/src/util/valid_hostname.in new file mode 100644 index 0000000..608c0d1 --- /dev/null +++ b/src/util/valid_hostname.in @@ -0,0 +1,55 @@ +123456789012345678901234567890123456789012345678901234567890123 +1234567890123456789012345678901234567890123456789012345678901234 +a.123456789012345678901234567890123456789012345678901234567890123.b +a.1234567890123456789012345678901234567890123456789012345678901234.b +1.2.3.4 +321.255.255.255 +0.0.0.0 +255.255.255.255 +0.255.255.255 +1.2.3.321 +1.2.3 +1.2.3.4.5 +1..2.3.4 +.1.2.3.4 +1.2.3.4.5. +1 +. + +321 +f +f.2.3.4 +1f.2.3.4 +f1.2.3.4 +1.2f.3.4 +1.f2.3.4 +1.2.3.4f +1.2.3.f4 +1.2.3.f +-.a.b +a.-.b +a.b.- +-aa.b.b +aa-.b.b +a.-bb.b +a.bb-.b +a.b.-bb +a.b.bb- +a-a.b-b +: +:: +::: +a:: +::a +::1.2.3.4 +a:a:a:a:a:1.2.3.4 +a:a:a:a:a:a:1.2.3.4 +a:a:a:a:a:a:a:1.2.3.4 +a:a:a:a:a:a:1.2.3. +a:a:a:a:a:a:1.2.3 +a:a:a:a:a:a:a:a +a:a:a:a:a:a:a:a:a +g:a:a:a:a:a:a:a +a::b +:a::b +a::b: diff --git a/src/util/valid_hostname.ref b/src/util/valid_hostname.ref new file mode 100644 index 0000000..08b23b8 --- /dev/null +++ b/src/util/valid_hostname.ref @@ -0,0 +1,143 @@ +./valid_hostname: testing: "123456789012345678901234567890123456789012345678901234567890123" +./valid_hostname: warning: valid_hostname: numeric hostname: 123456789012345678901234567890123456789012345678901234567890123 +./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet value: 123456789012345678901234567890123456789012345678901234567890123 +./valid_hostname: testing: "1234567890123456789012345678901234567890123456789012345678901234" +./valid_hostname: warning: valid_hostname: hostname label too long: 1234567890123456789012345678901234567890123456789012345678901234 +./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet value: 1234567890123456789012345678901234567890123456789012345678901234 +./valid_hostname: testing: "a.123456789012345678901234567890123456789012345678901234567890123.b" +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.123456789012345678901234567890123456789012345678901234567890123.b +./valid_hostname: testing: "a.1234567890123456789012345678901234567890123456789012345678901234.b" +./valid_hostname: warning: valid_hostname: hostname label too long: a.1234567890123456789012345678901234567890123456789012345678901234.b +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.1234567890123456789012345678901234567890123456789012345678901234.b +./valid_hostname: testing: "1.2.3.4" +./valid_hostname: warning: valid_hostname: numeric hostname: 1.2.3.4 +./valid_hostname: testing: "321.255.255.255" +./valid_hostname: warning: valid_hostname: numeric hostname: 321.255.255.255 +./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet value: 321.255.255.255 +./valid_hostname: testing: "0.0.0.0" +./valid_hostname: warning: valid_hostname: numeric hostname: 0.0.0.0 +./valid_hostname: testing: "255.255.255.255" +./valid_hostname: warning: valid_hostname: numeric hostname: 255.255.255.255 +./valid_hostname: testing: "0.255.255.255" +./valid_hostname: warning: valid_hostname: numeric hostname: 0.255.255.255 +./valid_hostname: warning: valid_ipv4_hostaddr: bad initial octet value: 0.255.255.255 +./valid_hostname: testing: "1.2.3.321" +./valid_hostname: warning: valid_hostname: numeric hostname: 1.2.3.321 +./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet value: 1.2.3.321 +./valid_hostname: testing: "1.2.3" +./valid_hostname: warning: valid_hostname: numeric hostname: 1.2.3 +./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet count: 1.2.3 +./valid_hostname: testing: "1.2.3.4.5" +./valid_hostname: warning: valid_hostname: numeric hostname: 1.2.3.4.5 +./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet count: 1.2.3.4.5 +./valid_hostname: testing: "1..2.3.4" +./valid_hostname: warning: valid_hostname: misplaced delimiter: 1..2.3.4 +./valid_hostname: warning: valid_ipv4_hostaddr: misplaced dot: 1..2.3.4 +./valid_hostname: testing: ".1.2.3.4" +./valid_hostname: warning: valid_hostname: misplaced delimiter: .1.2.3.4 +./valid_hostname: warning: valid_ipv4_hostaddr: misplaced dot: .1.2.3.4 +./valid_hostname: testing: "1.2.3.4.5." +./valid_hostname: warning: valid_hostname: misplaced delimiter: 1.2.3.4.5. +./valid_hostname: warning: valid_ipv4_hostaddr: misplaced dot: 1.2.3.4.5. +./valid_hostname: testing: "1" +./valid_hostname: warning: valid_hostname: numeric hostname: 1 +./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet count: 1 +./valid_hostname: testing: "." +./valid_hostname: warning: valid_hostname: misplaced delimiter: . +./valid_hostname: warning: valid_ipv4_hostaddr: misplaced dot: . +./valid_hostname: testing: "" +./valid_hostname: warning: valid_hostname: empty hostname +./valid_hostname: warning: valid_hostaddr: empty address +./valid_hostname: testing: "321" +./valid_hostname: warning: valid_hostname: numeric hostname: 321 +./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet value: 321 +./valid_hostname: testing: "f" +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): f +./valid_hostname: testing: "f.2.3.4" +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): f.2.3.4 +./valid_hostname: testing: "1f.2.3.4" +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): 1f.2.3.4 +./valid_hostname: testing: "f1.2.3.4" +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): f1.2.3.4 +./valid_hostname: testing: "1.2f.3.4" +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): 1.2f.3.4 +./valid_hostname: testing: "1.f2.3.4" +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): 1.f2.3.4 +./valid_hostname: testing: "1.2.3.4f" +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): 1.2.3.4f +./valid_hostname: testing: "1.2.3.f4" +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): 1.2.3.f4 +./valid_hostname: testing: "1.2.3.f" +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): 1.2.3.f +./valid_hostname: testing: "-.a.b" +./valid_hostname: warning: valid_hostname: misplaced hyphen: -.a.b +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 45(decimal): -.a.b +./valid_hostname: testing: "a.-.b" +./valid_hostname: warning: valid_hostname: misplaced hyphen: a.-.b +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.-.b +./valid_hostname: testing: "a.b.-" +./valid_hostname: warning: valid_hostname: misplaced hyphen: a.b.- +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.b.- +./valid_hostname: testing: "-aa.b.b" +./valid_hostname: warning: valid_hostname: misplaced hyphen: -aa.b.b +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 45(decimal): -aa.b.b +./valid_hostname: testing: "aa-.b.b" +./valid_hostname: warning: valid_hostname: misplaced hyphen: aa-.b.b +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): aa-.b.b +./valid_hostname: testing: "a.-bb.b" +./valid_hostname: warning: valid_hostname: misplaced hyphen: a.-bb.b +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.-bb.b +./valid_hostname: testing: "a.bb-.b" +./valid_hostname: warning: valid_hostname: misplaced hyphen: a.bb-.b +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.bb-.b +./valid_hostname: testing: "a.b.-bb" +./valid_hostname: warning: valid_hostname: misplaced hyphen: a.b.-bb +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.b.-bb +./valid_hostname: testing: "a.b.bb-" +./valid_hostname: warning: valid_hostname: misplaced hyphen: a.b.bb- +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.b.bb- +./valid_hostname: testing: "a-a.b-b" +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a-a.b-b +./valid_hostname: testing: ":" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): : +./valid_hostname: warning: valid_ipv6_hostaddr: too few `:' in IPv6 address: : +./valid_hostname: testing: "::" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): :: +./valid_hostname: testing: ":::" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): ::: +./valid_hostname: warning: valid_ipv6_hostaddr: too many `::' in IPv6 address: ::: +./valid_hostname: testing: "a::" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:: +./valid_hostname: testing: "::a" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): ::a +./valid_hostname: testing: "::1.2.3.4" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): ::1.2.3.4 +./valid_hostname: testing: "a:a:a:a:a:1.2.3.4" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:a:a:a:a:1.2.3.4 +./valid_hostname: testing: "a:a:a:a:a:a:1.2.3.4" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:a:a:a:a:a:1.2.3.4 +./valid_hostname: testing: "a:a:a:a:a:a:a:1.2.3.4" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:a:a:a:a:a:a:1.2.3.4 +./valid_hostname: warning: valid_ipv6_hostaddr: malformed IPv4-in-IPv6 address: a:a:a:a:a:a:a:1.2.3.4 +./valid_hostname: testing: "a:a:a:a:a:a:1.2.3." +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:a:a:a:a:a:1.2.3. +./valid_hostname: warning: valid_ipv4_hostaddr: misplaced dot: 1.2.3. +./valid_hostname: testing: "a:a:a:a:a:a:1.2.3" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:a:a:a:a:a:1.2.3 +./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet count: 1.2.3 +./valid_hostname: testing: "a:a:a:a:a:a:a:a" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:a:a:a:a:a:a:a +./valid_hostname: testing: "a:a:a:a:a:a:a:a:a" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:a:a:a:a:a:a:a:a +./valid_hostname: warning: valid_ipv6_hostaddr: too many `:' in IPv6 address: a:a:a:a:a:a:a:a:a +./valid_hostname: testing: "g:a:a:a:a:a:a:a" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): g:a:a:a:a:a:a:a +./valid_hostname: warning: valid_ipv6_hostaddr: invalid character 103(decimal) in IPv6 address: g:a:a:a:a:a:a:a +./valid_hostname: testing: "a::b" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a::b +./valid_hostname: testing: ":a::b" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): :a::b +./valid_hostname: warning: valid_ipv6_hostaddr: bad null first field in IPv6 address: :a::b +./valid_hostname: testing: "a::b:" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a::b: +./valid_hostname: warning: valid_ipv6_hostaddr: bad null last field in IPv6 address: a::b: diff --git a/src/util/valid_utf8_hostname.c b/src/util/valid_utf8_hostname.c new file mode 100644 index 0000000..3d6922a --- /dev/null +++ b/src/util/valid_utf8_hostname.c @@ -0,0 +1,87 @@ +/*++ +/* NAME +/* valid_utf8_hostname 3 +/* SUMMARY +/* validate (maybe UTF-8) domain name +/* SYNOPSIS +/* #include <valid_utf8_hostname.h> +/* +/* int valid_utf8_hostname( +/* int enable_utf8, +/* const char *domain, +/* int gripe) +/* DESCRIPTION +/* valid_utf8_hostname() is a wrapper around valid_hostname(). +/* If EAI support is compiled in, and enable_utf8 is true, the +/* name is converted from UTF-8 to ASCII per IDNA rules, before +/* invoking valid_hostname(). +/* SEE ALSO +/* valid_hostname(3) STD3 hostname validation. +/* DIAGNOSTICS +/* Fatal errors: memory allocation problem. +/* Warnings: malformed domain name. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + + /* + * System library. + */ +#include <sys_defs.h> + + /* + * Utility library. + */ +#include <msg.h> +#include <mymalloc.h> +#include <stringops.h> +#include <valid_hostname.h> +#include <midna_domain.h> +#include <valid_utf8_hostname.h> + +/* valid_utf8_hostname - validate internationalized domain name */ + +int valid_utf8_hostname(int enable_utf8, const char *name, int gripe) +{ + static const char myname[] = "valid_utf8_hostname"; + + /* + * Trivial cases first. + */ + if (*name == 0) { + if (gripe) + msg_warn("%s: empty domain name", myname); + return (0); + } + + /* + * Convert non-ASCII domain name to ASCII and validate the result per + * STD3. midna_domain_to_ascii() applies valid_hostname() to the result. + * Propagate the gripe parameter for better diagnostics (note that + * midna_domain_to_ascii() logs a problem only when the result is not + * cached). + */ +#ifndef NO_EAI + if (enable_utf8 && !allascii(name)) { + if (midna_domain_to_ascii(name) == 0) { + if (gripe) + msg_warn("%s: malformed UTF-8 domain name", myname); + return (0); + } else { + return (1); + } + } +#endif + + /* + * Validate ASCII name per STD3. + */ + return (valid_hostname(name, gripe)); +} diff --git a/src/util/valid_utf8_hostname.h b/src/util/valid_utf8_hostname.h new file mode 100644 index 0000000..0c0b41f --- /dev/null +++ b/src/util/valid_utf8_hostname.h @@ -0,0 +1,35 @@ +#ifndef _VALID_UTF8_HOSTNAME_H_INCLUDED_ +#define _VALID_UTF8_HOSTNAME_H_INCLUDED_ + +/*++ +/* NAME +/* valid_utf8_hostname 3h +/* SUMMARY +/* validate (maybe UTF-8) domain name +/* SYNOPSIS +/* #include <valid_utf8_hostname.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <valid_hostname.h> + + /* + * External interface + */ +extern int valid_utf8_hostname(int, const char *, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/valid_utf8_string.c b/src/util/valid_utf8_string.c new file mode 100644 index 0000000..96b5b4d --- /dev/null +++ b/src/util/valid_utf8_string.c @@ -0,0 +1,139 @@ +/*++ +/* NAME +/* valid_utf8_string 3 +/* SUMMARY +/* predicate if string is valid UTF-8 +/* SYNOPSIS +/* #include <stringops.h> +/* +/* int valid_utf8_string(str, len) +/* const char *str; +/* ssize_t len; +/* DESCRIPTION +/* valid_utf8_string() determines if a string satisfies the UTF-8 +/* definition in RFC 3629. That is, it contains proper encodings +/* of code points U+0000..U+10FFFF, excluding over-long encodings +/* and excluding U+D800..U+DFFF surrogates. +/* +/* A zero-length string is considered valid. +/* DIAGNOSTICS +/* The result value is zero when the caller specifies a negative +/* length, or a string that violates RFC 3629, for example a +/* string that is truncated in the middle of a multi-byte +/* sequence. +/* BUGS +/* But wait, there is more. Code points in the range U+FDD0..U+FDEF +/* and ending in FFFE or FFFF are non-characters in UNICODE. This +/* function does not block these. +/* SEE ALSO +/* RFC 3629 +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +/* Utility library. */ + +#include <stringops.h> + +/* valid_utf8_string - validate string according to RFC 3629 */ + +int valid_utf8_string(const char *str, ssize_t len) +{ + const unsigned char *end = (const unsigned char *) str + len; + const unsigned char *cp; + unsigned char c0, ch; + + if (len < 0) + return (0); + if (len <= 0) + return (1); + + /* + * Optimized for correct input, time, space, and for CPUs that have a + * decent number of registers. + */ + for (cp = (const unsigned char *) str; cp < end; cp++) { + /* Single-byte encodings. */ + if (EXPECTED((c0 = *cp) <= 0x7f) /* we know that c0 >= 0x0 */ ) { + /* void */ ; + } + /* Two-byte encodings. */ + else if (EXPECTED(c0 <= 0xdf) /* we know that c0 >= 0x80 */ ) { + /* Exclude over-long encodings. */ + if (UNEXPECTED(c0 < 0xc2) + || UNEXPECTED(cp + 1 >= end) + /* Require UTF-8 tail byte. */ + || UNEXPECTED(((ch = *++cp) & 0xc0) != 0x80)) + return (0); + } + /* Three-byte encodings. */ + else if (EXPECTED(c0 <= 0xef) /* we know that c0 >= 0xe0 */ ) { + if (UNEXPECTED(cp + 2 >= end) + /* Exclude over-long encodings. */ + || UNEXPECTED((ch = *++cp) < (c0 == 0xe0 ? 0xa0 : 0x80)) + /* Exclude U+D800..U+DFFF. */ + || UNEXPECTED(ch > (c0 == 0xed ? 0x9f : 0xbf)) + /* Require UTF-8 tail byte. */ + || UNEXPECTED(((ch = *++cp) & 0xc0) != 0x80)) + return (0); + } + /* Four-byte encodings. */ + else if (EXPECTED(c0 <= 0xf4) /* we know that c0 >= 0xf0 */ ) { + if (UNEXPECTED(cp + 3 >= end) + /* Exclude over-long encodings. */ + || UNEXPECTED((ch = *++cp) < (c0 == 0xf0 ? 0x90 : 0x80)) + /* Exclude code points above U+10FFFF. */ + || UNEXPECTED(ch > (c0 == 0xf4 ? 0x8f : 0xbf)) + /* Require UTF-8 tail byte. */ + || UNEXPECTED(((ch = *++cp) & 0xc0) != 0x80) + /* Require UTF-8 tail byte. */ + || UNEXPECTED(((ch = *++cp) & 0xc0) != 0x80)) + return (0); + } + /* Invalid: c0 >= 0xf5 */ + else { + return (0); + } + } + return (1); +} + + /* + * Stand-alone test program. Each string is a line without line terminator. + */ +#ifdef TEST +#include <stdlib.h> +#include <vstream.h> +#include <vstring.h> +#include <vstring_vstream.h> + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +int main(void) +{ + VSTRING *buf = vstring_alloc(1); + + while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) { + vstream_printf("%c", (LEN(buf) && !valid_utf8_string(STR(buf), LEN(buf))) ? + '!' : ' '); + vstream_fwrite(VSTREAM_OUT, STR(buf), LEN(buf)); + vstream_printf("\n"); + } + vstream_fflush(VSTREAM_OUT); + vstring_free(buf); + exit(0); +} + +#endif diff --git a/src/util/vbuf.c b/src/util/vbuf.c new file mode 100644 index 0000000..924e230 --- /dev/null +++ b/src/util/vbuf.c @@ -0,0 +1,238 @@ +/*++ +/* NAME +/* vbuf 3 +/* SUMMARY +/* generic buffer package +/* SYNOPSIS +/* #include <vbuf.h> +/* +/* int VBUF_GET(bp) +/* VBUF *bp; +/* +/* int VBUF_PUT(bp, ch) +/* VBUF *bp; +/* int ch; +/* +/* int VBUF_SPACE(bp, len) +/* VBUF *bp; +/* ssize_t len; +/* +/* int vbuf_unget(bp, ch) +/* VBUF *bp; +/* int ch; +/* +/* ssize_t vbuf_read(bp, buf, len) +/* VBUF *bp; +/* void *buf; +/* ssize_t len; +/* +/* ssize_t vbuf_write(bp, buf, len) +/* VBUF *bp; +/* const void *buf; +/* ssize_t len; +/* +/* int vbuf_err(bp) +/* VBUF *bp; +/* +/* int vbuf_eof(bp) +/* VBUF *bp; +/* +/* int vbuf_timeout(bp) +/* VBUF *bp; +/* +/* int vbuf_clearerr(bp) +/* VBUF *bp; +/* +/* int vbuf_rd_err(bp) +/* VBUF *bp; +/* +/* int vbuf_wr_err(bp) +/* VBUF *bp; +/* +/* int vbuf_rd_timeout(bp) +/* VBUF *bp; +/* +/* int vbuf_wr_timeout(bp) +/* VBUF *bp; +/* DESCRIPTION +/* This module implements a buffer with read/write primitives that +/* automatically handle buffer-empty or buffer-full conditions. +/* The application is expected to provide callback routines that run +/* when the read-write primitives detect a buffer-empty/full condition. +/* +/* VBUF buffers provide primitives to store and retrieve characters, +/* and to look up buffer status information. +/* By design, VBUF buffers provide no explicit primitives for buffer +/* memory management. This is left to the application to avoid any bias +/* toward specific management models. The application is free to use +/* whatever strategy suits best: memory-resident buffer, memory mapped +/* file, or stdio-like window to an open file. +/* +/* VBUF_GET() returns the next character from the specified buffer, +/* or VBUF_EOF when none is available. VBUF_GET() is an unsafe macro +/* that evaluates its argument more than once. +/* +/* VBUF_PUT() stores one character into the specified buffer. The result +/* is the stored character, or VBUF_EOF in case of problems. VBUF_PUT() +/* is an unsafe macro that evaluates its arguments more than once. +/* +/* VBUF_SPACE() requests that the requested amount of buffer space be +/* made available, so that it can be accessed without using VBUF_PUT(). +/* The result value is 0 for success, VBUF_EOF for problems. +/* VBUF_SPACE() is an unsafe macro that evaluates its arguments more +/* than once. VBUF_SPACE() does not support read-only streams. +/* +/* vbuf_unget() provides at least one character of pushback, and returns +/* the pushed back character, or VBUF_EOF in case of problems. It is +/* an error to call vbuf_unget() on a buffer before reading any data +/* from it. vbuf_unget() clears the buffer's end-of-file indicator upon +/* success, and sets the buffer's error indicator when an attempt is +/* made to push back a non-character value. +/* +/* vbuf_read() and vbuf_write() do bulk I/O. The result value is the +/* number of bytes transferred. A short count is returned in case of +/* an error. +/* +/* vbuf_timeout() is a macro that returns non-zero if a timeout error +/* condition was detected while reading or writing the buffer. The +/* error status can be reset by calling vbuf_clearerr(). +/* +/* vbuf_err() is a macro that returns non-zero if a non-EOF error +/* (including timeout) condition was detected while reading or writing +/* the buffer. The error status can be reset by calling vbuf_clearerr(). +/* +/* The vbuf_rd_mumble() and vbuf_wr_mumble() macros report on +/* read and write error conditions, respectively. +/* +/* vbuf_eof() is a macro that returns non-zero if an end-of-file +/* condition was detected while reading or writing the buffer. The error +/* status can be reset by calling vbuf_clearerr(). +/* APPLICATION CALLBACK SYNOPSIS +/* int get_ready(bp) +/* VBUF *bp; +/* +/* int put_ready(bp) +/* VBUF *bp; +/* +/* int space(bp, len) +/* VBUF *bp; +/* ssize_t len; +/* APPLICATION CALLBACK DESCRIPTION +/* .ad +/* .fi +/* get_ready() is called when VBUF_GET() detects a buffer-empty condition. +/* The result is zero when more data could be read, VBUF_EOF otherwise. +/* +/* put_ready() is called when VBUF_PUT() detects a buffer-full condition. +/* The result is zero when the buffer could be flushed, VBUF_EOF otherwise. +/* +/* space() performs whatever magic necessary to make at least \fIlen\fR +/* bytes available for access without using VBUF_PUT(). The result is 0 +/* in case of success, VBUF_EOF otherwise. +/* SEE ALSO +/* vbuf(3h) layout of the VBUF data structure. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <string.h> + +/* Utility library. */ + +#include "vbuf.h" + +/* vbuf_unget - implement at least one character pushback */ + +int vbuf_unget(VBUF *bp, int ch) +{ + if ((ch & 0xff) != ch || -bp->cnt >= bp->len) { + bp->flags |= VBUF_FLAG_RD_ERR; /* This error affects reads! */ + return (VBUF_EOF); + } else { + bp->cnt--; + bp->flags &= ~VBUF_FLAG_EOF; + return (*--bp->ptr = ch); + } +} + +/* vbuf_get - handle read buffer empty condition */ + +int vbuf_get(VBUF *bp) +{ + return (bp->get_ready(bp) ? + ((bp->flags |= VBUF_FLAG_EOF), VBUF_EOF) : VBUF_GET(bp)); +} + +/* vbuf_put - handle write buffer full condition */ + +int vbuf_put(VBUF *bp, int ch) +{ + return (bp->put_ready(bp) ? VBUF_EOF : VBUF_PUT(bp, ch)); +} + +/* vbuf_read - bulk read from buffer */ + +ssize_t vbuf_read(VBUF *bp, void *buf, ssize_t len) +{ + ssize_t count; + void *cp; + ssize_t n; + +#if 0 + for (count = 0; count < len; count++) + if ((buf[count] = VBUF_GET(bp)) < 0) + break; + return (count); +#else + for (cp = buf, count = len; count > 0; cp += n, count -= n) { + if (bp->cnt >= 0 && bp->get_ready(bp)) + break; + n = (count < -bp->cnt ? count : -bp->cnt); + memcpy(cp, bp->ptr, n); + bp->ptr += n; + bp->cnt += n; + } + return (len - count); +#endif +} + +/* vbuf_write - bulk write to buffer */ + +ssize_t vbuf_write(VBUF *bp, const void *buf, ssize_t len) +{ + ssize_t count; + const void *cp; + ssize_t n; + +#if 0 + for (count = 0; count < len; count++) + if (VBUF_PUT(bp, buf[count]) < 0) + break; + return (count); +#else + for (cp = buf, count = len; count > 0; cp += n, count -= n) { + if (bp->cnt <= 0 && bp->put_ready(bp) != 0) + break; + n = (count < bp->cnt ? count : bp->cnt); + memcpy(bp->ptr, cp, n); + bp->ptr += n; + bp->cnt -= n; + } + return (len - count); +#endif +} diff --git a/src/util/vbuf.h b/src/util/vbuf.h new file mode 100644 index 0000000..149fe0c --- /dev/null +++ b/src/util/vbuf.h @@ -0,0 +1,110 @@ +#ifndef _VBUF_H_INCLUDED_ +#define _VBUF_H_INCLUDED_ + +/*++ +/* NAME +/* vbuf 3h +/* SUMMARY +/* generic buffer +/* SYNOPSIS +/* #include <vbuf.h> +/* DESCRIPTION +/* .nf + + /* + * The VBUF buffer is defined by 1) its structure, by 2) the VBUF_GET() and + * 3) VBUF_PUT() operations that automatically handle buffer empty and + * buffer full conditions, and 4) by the VBUF_SPACE() operation that allows + * the user to reserve buffer space ahead of time, to allow for situations + * where calling VBUF_PUT() is not possible or desirable. + * + * The VBUF buffer does not specify primitives for memory allocation or + * deallocation. The purpose is to allow different applications to have + * different strategies: a memory-resident buffer; a memory-mapped file; or + * a stdio-like window to an open file. Each application provides its own + * get(), put() and space() methods that perform the necessary magic. + * + * This interface is pretty normal. With one exception: the number of bytes + * left to read is negated. This is done so that we can change direction + * between reading and writing on the fly. The alternative would be to use + * separate read and write counters per buffer. + */ +typedef struct VBUF VBUF; +typedef int (*VBUF_GET_READY_FN) (VBUF *); +typedef int (*VBUF_PUT_READY_FN) (VBUF *); +typedef int (*VBUF_SPACE_FN) (VBUF *, ssize_t); + +struct VBUF { + int flags; /* status, see below */ + unsigned char *data; /* variable-length buffer */ + ssize_t len; /* buffer length */ + ssize_t cnt; /* bytes left to read/write */ + unsigned char *ptr; /* read/write position */ + VBUF_GET_READY_FN get_ready; /* read buffer empty action */ + VBUF_PUT_READY_FN put_ready; /* write buffer full action */ + VBUF_SPACE_FN space; /* request for buffer space */ +}; + + /* + * Typically, an application will embed a VBUF structure into a larger + * structure that also contains application-specific members. This approach + * gives us the best of both worlds. The application can still use the + * generic VBUF primitives for reading and writing VBUFs. The macro below + * transforms a pointer from VBUF structure to the structure that contains + * it. + */ +#define VBUF_TO_APPL(vbuf_ptr,app_type,vbuf_member) \ + ((app_type *) (((char *) (vbuf_ptr)) - offsetof(app_type,vbuf_member))) + + /* + * Buffer status management. + */ +#define VBUF_FLAG_RD_ERR (1<<0) /* read error */ +#define VBUF_FLAG_WR_ERR (1<<1) /* write error */ +#define VBUF_FLAG_ERR (VBUF_FLAG_RD_ERR | VBUF_FLAG_WR_ERR) +#define VBUF_FLAG_EOF (1<<2) /* end of data */ +#define VBUF_FLAG_RD_TIMEOUT (1<<3) /* read timeout */ +#define VBUF_FLAG_WR_TIMEOUT (1<<4) /* write timeout */ +#define VBUF_FLAG_TIMEOUT (VBUF_FLAG_RD_TIMEOUT | VBUF_FLAG_WR_TIMEOUT) +#define VBUF_FLAG_BAD (VBUF_FLAG_ERR | VBUF_FLAG_EOF | VBUF_FLAG_TIMEOUT) +#define VBUF_FLAG_FIXED (1<<5) /* fixed-size buffer */ + +#define vbuf_rd_error(v) ((v)->flags & (VBUF_FLAG_RD_ERR | VBUF_FLAG_RD_TIMEOUT)) +#define vbuf_wr_error(v) ((v)->flags & (VBUF_FLAG_WR_ERR | VBUF_FLAG_WR_TIMEOUT)) +#define vbuf_rd_timeout(v) ((v)->flags & VBUF_FLAG_RD_TIMEOUT) +#define vbuf_wr_timeout(v) ((v)->flags & VBUF_FLAG_WR_TIMEOUT) + +#define vbuf_error(v) ((v)->flags & (VBUF_FLAG_ERR | VBUF_FLAG_TIMEOUT)) +#define vbuf_eof(v) ((v)->flags & VBUF_FLAG_EOF) +#define vbuf_timeout(v) ((v)->flags & VBUF_FLAG_TIMEOUT) +#define vbuf_clearerr(v) ((v)->flags &= ~VBUF_FLAG_BAD) + + /* + * Buffer I/O-like operations and results. + */ +#define VBUF_GET(v) ((v)->cnt < 0 ? ++(v)->cnt, \ + (int) *(v)->ptr++ : vbuf_get(v)) +#define VBUF_PUT(v,c) ((v)->cnt > 0 ? --(v)->cnt, \ + (int) (*(v)->ptr++ = (c)) : vbuf_put((v),(c))) +#define VBUF_SPACE(v,n) ((v)->space((v),(n))) + +#define VBUF_EOF (-1) /* no more space or data */ + +extern int vbuf_get(VBUF *); +extern int vbuf_put(VBUF *, int); +extern int vbuf_unget(VBUF *, int); +extern ssize_t vbuf_read(VBUF *, void *, ssize_t); +extern ssize_t vbuf_write(VBUF *, const void *, ssize_t); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/vbuf_print.c b/src/util/vbuf_print.c new file mode 100644 index 0000000..d7a323f --- /dev/null +++ b/src/util/vbuf_print.c @@ -0,0 +1,385 @@ +/*++ +/* NAME +/* vbuf_print 3 +/* SUMMARY +/* formatted print to generic buffer +/* SYNOPSIS +/* #include <stdarg.h> +/* #include <vbuf_print.h> +/* +/* VBUF *vbuf_print(bp, format, ap) +/* VBUF *bp; +/* const char *format; +/* va_list ap; +/* DESCRIPTION +/* vbuf_print() appends data to the named buffer according to its +/* \fIformat\fR argument. It understands the s, c, d, u, o, x, X, p, e, +/* f and g format types, the l modifier, field width and precision, +/* sign, and padding with zeros or spaces. +/* +/* In addition, vbuf_print() recognizes the %m format specifier +/* and expands it to the error message corresponding to the current +/* value of the global \fIerrno\fR variable. +/* REENTRANCY +/* .ad +/* .fi +/* vbuf_print() allocates a static buffer. After completion +/* of the first vbuf_print() call, this buffer is safe for +/* reentrant vbuf_print() calls by (asynchronous) terminating +/* signal handlers or by (synchronous) terminating error +/* handlers. vbuf_print() initialization typically happens +/* upon the first formatted output to a VSTRING or VSTREAM. +/* +/* However, it is up to the caller to ensure that the destination +/* VSTREAM or VSTRING buffer is protected against reentrant usage. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <stdlib.h> /* 44BSD stdarg.h uses abort() */ +#include <stdarg.h> +#include <string.h> +#include <ctype.h> +#include <stdlib.h> /* 44bsd stdarg.h uses abort() */ +#include <stdio.h> /* sprintf() prototype */ +#include <float.h> /* range of doubles */ +#include <errno.h> +#include <limits.h> /* CHAR_BIT, INT_MAX */ + +/* Application-specific. */ + +#include "msg.h" +#include "mymalloc.h" +#include "vbuf.h" +#include "vstring.h" +#include "vbuf_print.h" + + /* + * What we need here is a *sprintf() routine that can ask for more room (as + * in 4.4 BSD). However, that functionality is not widely available, and I + * have no plans to maintain a complete 4.4 BSD *sprintf() alternative. + * + * Postfix vbuf_print() was implemented when many mainstream systems had no + * usable snprintf() implementation (usable means: return the length, + * excluding terminator, that the output would have if the buffer were large + * enough). For example, GLIBC before 2.1 (1999) snprintf() did not + * distinguish between formatting error and buffer size error, while SUN had + * no snprintf() implementation before Solaris 2.6 (1997). + * + * For the above reasons, vbuf_print() was implemented with sprintf() and a + * generously-sized output buffer. Current vbuf_print() implementations use + * snprintf(), and report an error if the output does not fit (in that case, + * the old sprintf()-based implementation would have had a buffer overflow + * vulnerability). The old implementation is still available for building + * Postfix on ancient systems. + * + * Guessing the output size of a string (%s) conversion is not hard. The + * problem is with numerical results. Instead of making an accurate guess we + * take a wide margin when reserving space. The INT_SPACE margin should be + * large enough to hold the result from any (octal, hex, decimal) integer + * conversion that has no explicit width or precision specifiers. With + * floating-point numbers, use a similar estimate, and add DBL_MAX_10_EXP + * just to be sure. + */ +#define INT_SPACE ((CHAR_BIT * sizeof(long)) / 2) +#define DBL_SPACE ((CHAR_BIT * sizeof(double)) / 2 + DBL_MAX_10_EXP) +#define PTR_SPACE ((CHAR_BIT * sizeof(char *)) / 2) + + /* + * Helper macros... Note that there is no need to check the result from + * VSTRING_SPACE() because that always succeeds or never returns. + */ +#ifndef NO_SNPRINTF +#define VBUF_SNPRINTF(bp, sz, fmt, arg) do { \ + ssize_t _ret; \ + if (VBUF_SPACE((bp), (sz)) != 0) \ + return (bp); \ + _ret = snprintf((char *) (bp)->ptr, (bp)->cnt, (fmt), (arg)); \ + if (_ret < 0) \ + msg_panic("%s: output error for '%s'", myname, mystrdup(fmt)); \ + if (_ret >= (bp)->cnt) \ + msg_panic("%s: output for '%s' exceeds space %ld", \ + myname, mystrdup(fmt), (long) (bp)->cnt); \ + VBUF_SKIP(bp); \ + } while (0) +#else +#define VBUF_SNPRINTF(bp, sz, fmt, arg) do { \ + if (VBUF_SPACE((bp), (sz)) != 0) \ + return (bp); \ + sprintf((char *) (bp)->ptr, (fmt), (arg)); \ + VBUF_SKIP(bp); \ + } while (0) +#endif + +#define VBUF_SKIP(bp) do { \ + while ((bp)->cnt > 0 && *(bp)->ptr) \ + (bp)->ptr++, (bp)->cnt--; \ + } while (0) + +#define VSTRING_ADDNUM(vp, n) do { \ + VBUF_SNPRINTF(&(vp)->vbuf, INT_SPACE, "%d", n); \ + } while (0) + +#define VBUF_STRCAT(bp, s) do { \ + unsigned char *_cp = (unsigned char *) (s); \ + int _ch; \ + while ((_ch = *_cp++) != 0) \ + VBUF_PUT((bp), _ch); \ + } while (0) + +/* vbuf_print - format string, vsprintf-like interface */ + +VBUF *vbuf_print(VBUF *bp, const char *format, va_list ap) +{ + const char *myname = "vbuf_print"; + static VSTRING *fmt; /* format specifier */ + unsigned char *cp; + int width; /* width and numerical precision */ + int prec; /* are signed for overflow defense */ + unsigned long_flag; /* long or plain integer */ + int ch; + char *s; + int saved_errno = errno; /* VBUF_SPACE() may clobber it */ + + /* + * Assume that format strings are short. + */ + if (fmt == 0) + fmt = vstring_alloc(INT_SPACE); + + /* + * Iterate over characters in the format string, picking up arguments + * when format specifiers are found. + */ + for (cp = (unsigned char *) format; *cp; cp++) { + if (*cp != '%') { + VBUF_PUT(bp, *cp); /* ordinary character */ + } else if (cp[1] == '%') { + VBUF_PUT(bp, *cp++); /* %% becomes % */ + } else { + + /* + * Handle format specifiers one at a time, since we can only deal + * with arguments one at a time. Try to determine the end of the + * format specifier. We do not attempt to fully parse format + * strings, since we are ging to let sprintf() do the hard work. + * In regular expression notation, we recognize: + * + * %-?+?0?([0-9]+|\*)?(\.([0-9]+|\*))?l?[a-zA-Z] + * + * which includes some combinations that do not make sense. Garbage + * in, garbage out. + */ + VSTRING_RESET(fmt); /* clear format string */ + VSTRING_ADDCH(fmt, *cp++); + if (*cp == '-') /* left-adjusted field? */ + VSTRING_ADDCH(fmt, *cp++); + if (*cp == '+') /* signed field? */ + VSTRING_ADDCH(fmt, *cp++); + if (*cp == '0') /* zero-padded field? */ + VSTRING_ADDCH(fmt, *cp++); + if (*cp == '*') { /* dynamic field width */ + width = va_arg(ap, int); + if (width < 0) { + msg_warn("%s: bad width %d in %.50s", + myname, width, format); + width = 0; + } else + VSTRING_ADDNUM(fmt, width); + cp++; + } else { /* hard-coded field width */ + for (width = 0; ch = *cp, ISDIGIT(ch); cp++) { + int digit = ch - '0'; + + if (width > INT_MAX / 10 + || (width *= 10) > INT_MAX - digit) + msg_panic("%s: bad width %d... in %.50s", + myname, width, format); + width += digit; + VSTRING_ADDCH(fmt, ch); + } + } + if (*cp == '.') { /* width/precision separator */ + VSTRING_ADDCH(fmt, *cp++); + if (*cp == '*') { /* dynamic precision */ + prec = va_arg(ap, int); + if (prec < 0) { + msg_warn("%s: bad precision %d in %.50s", + myname, prec, format); + prec = -1; + } else + VSTRING_ADDNUM(fmt, prec); + cp++; + } else { /* hard-coded precision */ + for (prec = 0; ch = *cp, ISDIGIT(ch); cp++) { + int digit = ch - '0'; + + if (prec > INT_MAX / 10 + || (prec *= 10) > INT_MAX - digit) + msg_panic("%s: bad precision %d... in %.50s", + myname, prec, format); + prec += digit; + VSTRING_ADDCH(fmt, ch); + } + } + } else { + prec = -1; + } + if ((long_flag = (*cp == 'l')) != 0)/* long whatever */ + VSTRING_ADDCH(fmt, *cp++); + if (*cp == 0) /* premature end, punt */ + break; + VSTRING_ADDCH(fmt, *cp); /* type (checked below) */ + VSTRING_TERMINATE(fmt); /* null terminate */ + + /* + * Execute the format string - let sprintf() do the hard work for + * non-trivial cases only. For simple string conversions and for + * long string conversions, do a direct copy to the output + * buffer. + */ + switch (*cp) { + case 's': /* string-valued argument */ + if (long_flag) + msg_panic("%s: %%l%c is not supported", myname, *cp); + s = va_arg(ap, char *); + if (prec >= 0 || (width > 0 && width > strlen(s))) { + VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE, + vstring_str(fmt), s); + } else { + VBUF_STRCAT(bp, s); + } + break; + case 'c': /* integral-valued argument */ + if (long_flag) + msg_panic("%s: %%l%c is not supported", myname, *cp); + /* FALLTHROUGH */ + case 'd': + case 'u': + case 'o': + case 'x': + case 'X': + if (long_flag) + VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE, + vstring_str(fmt), va_arg(ap, long)); + else + VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE, + vstring_str(fmt), va_arg(ap, int)); + break; + case 'e': /* float-valued argument */ + case 'f': + case 'g': + /* C99 *printf ignore the 'l' modifier. */ + VBUF_SNPRINTF(bp, (width > prec ? width : prec) + DBL_SPACE, + vstring_str(fmt), va_arg(ap, double)); + break; + case 'm': + /* Ignore the 'l' modifier, width and precision. */ + VBUF_STRCAT(bp, saved_errno ? + strerror(saved_errno) : "Application error"); + break; + case 'p': + if (long_flag) + msg_panic("%s: %%l%c is not supported", myname, *cp); + VBUF_SNPRINTF(bp, (width > prec ? width : prec) + PTR_SPACE, + vstring_str(fmt), va_arg(ap, char *)); + break; + default: /* anything else is bad */ + msg_panic("vbuf_print: unknown format type: %c", *cp); + /* NOTREACHED */ + break; + } + } + } + return (bp); +} + +#ifdef TEST +#include <argv.h> +#include <msg_vstream.h> +#include <vstring.h> +#include <vstring_vstream.h> + +int main(int argc, char **argv) +{ + VSTRING *ibuf = vstring_alloc(100); + + msg_vstream_init(argv[0], VSTREAM_ERR); + + while (vstring_fgets_nonl(ibuf, VSTREAM_IN)) { + ARGV *args = argv_split(vstring_str(ibuf), CHARS_SPACE); + char *cp; + + if (args->argc == 0 || *(cp = args->argv[0]) == '#') { + /* void */ ; + } else if (args->argc != 2 || *cp != '%') { + msg_warn("usage: format number"); + } else { + char *fmt = cp++; + int lflag; + + /* Determine the vstring_sprintf() argument type. */ + cp += strspn(cp, "+-*0123456789."); + if ((lflag = (*cp == 'l')) != 0) + cp++; + if (cp[1] != 0) { + msg_warn("bad format: \"%s\"", fmt); + } else { + VSTRING *obuf = vstring_alloc(1); + char *val = args->argv[1]; + + /* Test the worst-case memory allocation. */ +#ifdef CA_VSTRING_CTL_EXACT + vstring_ctl(obuf, CA_VSTRING_CTL_EXACT, CA_VSTRING_CTL_END); +#endif + switch (*cp) { + case 'c': + case 'd': + case 'o': + case 'u': + case 'x': + case 'X': + if (lflag) + vstring_sprintf(obuf, fmt, atol(val)); + else + vstring_sprintf(obuf, fmt, atoi(val)); + msg_info("\"%s\"", vstring_str(obuf)); + break; + case 's': + vstring_sprintf(obuf, fmt, val); + msg_info("\"%s\"", vstring_str(obuf)); + break; + case 'f': + case 'g': + vstring_sprintf(obuf, fmt, atof(val)); + msg_info("\"%s\"", vstring_str(obuf)); + break; + default: + msg_warn("bad format: \"%s\"", fmt); + break; + } + vstring_free(obuf); + } + } + argv_free(args); + } + vstring_free(ibuf); + return (0); +} + +#endif diff --git a/src/util/vbuf_print.h b/src/util/vbuf_print.h new file mode 100644 index 0000000..32549c1 --- /dev/null +++ b/src/util/vbuf_print.h @@ -0,0 +1,40 @@ +#ifndef _VBUF_PRINT_H_INCLUDED_ +#define _VBUF_PRINT_H_INCLUDED_ + +/*++ +/* NAME +/* vbuf_print 3h +/* SUMMARY +/* formatted print to generic buffer +/* SYNOPSIS +/* #include <vbuf_print.h> +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include <stdarg.h> + + /* + * Utility library. + */ +#include <vbuf.h> + + /* + * External interface. + */ +extern VBUF *vbuf_print(VBUF *, const char *, va_list); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/vbuf_print_test.in b/src/util/vbuf_print_test.in new file mode 100644 index 0000000..5ed13dc --- /dev/null +++ b/src/util/vbuf_print_test.in @@ -0,0 +1,33 @@ +# Check width and precision. +%-30.20s 123456789012345678901234567890 +%30.20s 123456789012345678901234567890 + +%d 123456789 +%+10d 123456789 +%-10d 123456789 +%10d 123456789 +%10.10d 123456789 + +%+ld 123456789 +%-ld 123456789 +%ld 123456789 +%10ld 123456789 +%10.10ld 123456789 + +%+lo 123456789 +%-lo 123456789 +%lo 123456789 +%10lo 123456789 +%10.10lo 123456789 + +%f 1e308 +%.100f 1e308 +%g 1e308 +%.309g 1e308 + +%s foo +%0s foo +%.0s foo +%10s foo +%+10s foo +%-10s foo diff --git a/src/util/vbuf_print_test.ref b/src/util/vbuf_print_test.ref new file mode 100644 index 0000000..346c919 --- /dev/null +++ b/src/util/vbuf_print_test.ref @@ -0,0 +1,27 @@ +./vbuf_print: "12345678901234567890 " +./vbuf_print: " 12345678901234567890" +./vbuf_print: "123456789" +./vbuf_print: "+123456789" +./vbuf_print: "123456789 " +./vbuf_print: " 123456789" +./vbuf_print: "0123456789" +./vbuf_print: "+123456789" +./vbuf_print: "123456789" +./vbuf_print: "123456789" +./vbuf_print: " 123456789" +./vbuf_print: "0123456789" +./vbuf_print: "726746425" +./vbuf_print: "726746425" +./vbuf_print: "726746425" +./vbuf_print: " 726746425" +./vbuf_print: "0726746425" +./vbuf_print: "100000000000000001097906362944045541740492309677311846336810682903157585404911491537163328978494688899061249669721172515611590283743140088328307009198146046031271664502933027185697489699588559043338384466165001178426897626212945177628091195786707458122783970171784415105291802893207873272974885715430223118336.000000" +./vbuf_print: "100000000000000001097906362944045541740492309677311846336810682903157585404911491537163328978494688899061249669721172515611590283743140088328307009198146046031271664502933027185697489699588559043338384466165001178426897626212945177628091195786707458122783970171784415105291802893207873272974885715430223118336.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +./vbuf_print: "1e+308" +./vbuf_print: "100000000000000001097906362944045541740492309677311846336810682903157585404911491537163328978494688899061249669721172515611590283743140088328307009198146046031271664502933027185697489699588559043338384466165001178426897626212945177628091195786707458122783970171784415105291802893207873272974885715430223118336" +./vbuf_print: "foo" +./vbuf_print: "foo" +./vbuf_print: "" +./vbuf_print: " foo" +./vbuf_print: " foo" +./vbuf_print: "foo " diff --git a/src/util/vstream.c b/src/util/vstream.c new file mode 100644 index 0000000..b4f9fbb --- /dev/null +++ b/src/util/vstream.c @@ -0,0 +1,2046 @@ +/*++ +/* NAME +/* vstream 3 +/* SUMMARY +/* light-weight buffered I/O package +/* SYNOPSIS +/* #include <vstream.h> +/* +/* VSTREAM *vstream_fopen(path, flags, mode) +/* const char *path; +/* int flags; +/* mode_t mode; +/* +/* VSTREAM *vstream_fdopen(fd, flags) +/* int fd; +/* int flags; +/* +/* VSTREAM *vstream_memopen(string, flags) +/* VSTRING *string; +/* int flags; +/* +/* VSTREAM *vstream_memreopen(stream, string, flags) +/* VSTREAM *stream; +/* VSTRING *string; +/* int flags; +/* +/* int vstream_fclose(stream) +/* VSTREAM *stream; +/* +/* int vstream_fdclose(stream) +/* VSTREAM *stream; +/* +/* VSTREAM *vstream_printf(format, ...) +/* const char *format; +/* +/* VSTREAM *vstream_fprintf(stream, format, ...) +/* VSTREAM *stream; +/* const char *format; +/* +/* int VSTREAM_GETC(stream) +/* VSTREAM *stream; +/* +/* int VSTREAM_PUTC(ch, stream) +/* int ch; +/* +/* int VSTREAM_GETCHAR(void) +/* +/* int VSTREAM_PUTCHAR(ch) +/* int ch; +/* +/* int vstream_ungetc(stream, ch) +/* VSTREAM *stream; +/* int ch; +/* +/* int vstream_fputs(str, stream) +/* const char *str; +/* VSTREAM *stream; +/* +/* off_t vstream_ftell(stream) +/* VSTREAM *stream; +/* +/* off_t vstream_fseek(stream, offset, whence) +/* VSTREAM *stream; +/* off_t offset; +/* int whence; +/* +/* int vstream_fflush(stream) +/* VSTREAM *stream; +/* +/* int vstream_fpurge(stream, direction) +/* VSTREAM *stream; +/* int direction; +/* +/* ssize_t vstream_fread(stream, buf, len) +/* VSTREAM *stream; +/* void *buf; +/* ssize_t len; +/* +/* ssize_t vstream_fwrite(stream, buf, len) +/* VSTREAM *stream; +/* void *buf; +/* ssize_t len; +/* +/* ssize_t vstream_fread_app(stream, buf, len) +/* VSTREAM *stream; +/* VSTRING *buf; +/* ssize_t len; +/* +/* ssize_t vstream_fread_buf(stream, buf, len) +/* VSTREAM *stream; +/* VSTRING *buf; +/* ssize_t len; +/* +/* void vstream_control(stream, name, ...) +/* VSTREAM *stream; +/* int name; +/* +/* int vstream_fileno(stream) +/* VSTREAM *stream; +/* +/* const ssize_t vstream_req_bufsize(stream) +/* VSTREAM *stream; +/* +/* void *vstream_context(stream) +/* VSTREAM *stream; +/* +/* int vstream_ferror(stream) +/* VSTREAM *stream; +/* +/* int vstream_ftimeout(stream) +/* VSTREAM *stream; +/* +/* int vstream_feof(stream) +/* VSTREAM *stream; +/* +/* int vstream_clearerr(stream) +/* VSTREAM *stream; +/* +/* const char *VSTREAM_PATH(stream) +/* VSTREAM *stream; +/* +/* char *vstream_vprintf(format, ap) +/* const char *format; +/* va_list *ap; +/* +/* char *vstream_vfprintf(stream, format, ap) +/* VSTREAM *stream; +/* const char *format; +/* va_list *ap; +/* +/* ssize_t vstream_bufstat(stream, command) +/* VSTREAM *stream; +/* int command; +/* +/* ssize_t vstream_peek(stream) +/* VSTREAM *stream; +/* +/* const char *vstream_peek_data(stream) +/* VSTREAM *stream; +/* +/* int vstream_setjmp(stream) +/* VSTREAM *stream; +/* +/* void vstream_longjmp(stream, val) +/* VSTREAM *stream; +/* int val; +/* +/* time_t vstream_ftime(stream) +/* VSTREAM *stream; +/* +/* struct timeval vstream_ftimeval(stream) +/* VSTREAM *stream; +/* +/* int vstream_rd_error(stream) +/* VSTREAM *stream; +/* +/* int vstream_wr_error(stream) +/* VSTREAM *stream; +/* +/* int vstream_rd_timeout(stream) +/* VSTREAM *stream; +/* +/* int vstream_wr_timeout(stream) +/* VSTREAM *stream; +/* +/* int vstream_fstat(stream, flags) +/* VSTREAM *stream; +/* int flags; +/* DESCRIPTION +/* The \fIvstream\fR module implements light-weight buffered I/O +/* similar to the standard I/O routines. +/* +/* The interface is implemented in terms of VSTREAM structure +/* pointers, also called streams. For convenience, three streams +/* are predefined: VSTREAM_IN, VSTREAM_OUT, and VSTREAM_ERR. These +/* streams are connected to the standard input, output and error +/* file descriptors, respectively. +/* +/* Although the interface is patterned after the standard I/O +/* library, there are some major differences: +/* .IP \(bu +/* File descriptors are not limited to the range 0..255. This +/* was reason #1 to write these routines in the first place. +/* .IP \(bu +/* The application can switch between reading and writing on +/* the same stream without having to perform a flush or seek +/* operation, and can change write position without having to +/* flush. This was reason #2. Upon position or direction change, +/* unread input is discarded, and unwritten output is flushed +/* automatically. Exception: with double-buffered streams, unread +/* input is not discarded upon change of I/O direction, and +/* output flushing is delayed until the read buffer must be refilled. +/* .IP \(bu +/* A bidirectional stream can read and write with the same buffer +/* and file descriptor, or it can have separate read/write +/* buffers and/or file descriptors. +/* .IP \(bu +/* No automatic flushing of VSTREAM_OUT upon program exit, or of +/* VSTREAM_ERR at any time. No unbuffered or line buffered modes. +/* This functionality may be added when it is really needed. +/* .PP +/* vstream_fopen() opens the named file and associates a buffered +/* stream with it. The \fIpath\fR, \fIflags\fR and \fImode\fR +/* arguments are passed on to the open(2) routine. The result is +/* a null pointer in case of problems. The \fIpath\fR argument is +/* copied and can be looked up with VSTREAM_PATH(). +/* +/* vstream_fdopen() takes an open file and associates a buffered +/* stream with it. The \fIflags\fR argument specifies how the file +/* was opened. vstream_fdopen() either succeeds or never returns. +/* +/* vstream_memopen() opens a VSTRING as a stream. The \fIflags\fR +/* argument must specify one of O_RDONLY, O_WRONLY, or O_APPEND. +/* vstream_memopen() either succeeds or never returns. Streams +/* opened with vstream_memopen() have limitations: they can't +/* be opened in read/write mode, they can't seek beyond the +/* end of the VSTRING, and they don't support vstream_control() +/* methods that manipulate buffers, file descriptors, or I/O +/* functions. After a VSTRING is opened for writing, its content +/* will be in an indeterminate state while the stream is open, +/* and will be null-terminated when the stream is closed. +/* +/* vstream_memreopen() reopens a memory stream. When the +/* \fIstream\fR argument is a null pointer, the behavior is that +/* of vstream_memopen(). +/* +/* vstream_fclose() closes the named buffered stream. The result +/* is 0 in case of success, VSTREAM_EOF in case of problems. +/* vstream_fclose() reports the same errors as vstream_ferror(). +/* +/* vstream_fdclose() leaves the file(s) open but is otherwise +/* identical to vstream_fclose(). +/* +/* vstream_fprintf() formats its arguments according to the +/* \fIformat\fR argument and writes the result to the named stream. +/* The result is the stream argument. It understands the s, c, d, u, +/* o, x, X, e, f and g format types, the l modifier, field width and +/* precision, sign, and padding with zeros or spaces. In addition, +/* vstream_fprintf() recognizes the %m format specifier and expands +/* it to the error message corresponding to the current value of the +/* global \fIerrno\fR variable. +/* +/* vstream_printf() performs formatted output to the standard output +/* stream. +/* +/* VSTREAM_GETC() reads the next character from the named stream. +/* The result is VSTREAM_EOF when end-of-file is reached or if a read +/* error was detected. VSTREAM_GETC() is an unsafe macro that +/* evaluates some arguments more than once. +/* +/* VSTREAM_GETCHAR() is an alias for VSTREAM_GETC(VSTREAM_IN). +/* +/* VSTREAM_PUTC() appends the specified character to the specified +/* stream. The result is the stored character, or VSTREAM_EOF in +/* case of problems. VSTREAM_PUTC() is an unsafe macro that +/* evaluates some arguments more than once. +/* +/* VSTREAM_PUTCHAR(c) is an alias for VSTREAM_PUTC(c, VSTREAM_OUT). +/* +/* vstream_ungetc() pushes back a character onto the specified stream +/* and returns the character, or VSTREAM_EOF in case of problems. +/* It is an error to push back before reading (or immediately after +/* changing the stream offset via vstream_fseek()). Upon successful +/* return, vstream_ungetc() clears the end-of-file stream flag. +/* +/* vstream_fputs() appends the given null-terminated string to the +/* specified buffered stream. The result is 0 in case of success, +/* VSTREAM_EOF in case of problems. +/* +/* vstream_ftell() returns the file offset for the specified stream, +/* -1 if the stream is connected to a non-seekable file. +/* +/* vstream_fseek() changes the file position for the next read or write +/* operation. Unwritten output is flushed. With unidirectional streams, +/* unread input is discarded. The \fIoffset\fR argument specifies the file +/* position from the beginning of the file (\fIwhence\fR is SEEK_SET), +/* from the current file position (\fIwhence\fR is SEEK_CUR), or from +/* the file end (SEEK_END). The result value is the file offset +/* from the beginning of the file, -1 in case of problems. +/* +/* vstream_fflush() flushes unwritten data to a file that was +/* opened in read-write or write-only mode. +/* vstream_fflush() returns 0 in case of success, VSTREAM_EOF in +/* case of problems. It is an error to flush a read-only stream. +/* vstream_fflush() reports the same errors as vstream_ferror(). +/* +/* vstream_fpurge() discards the contents of the stream buffer. +/* If direction is VSTREAM_PURGE_READ, it discards unread data, +/* else if direction is VSTREAM_PURGE_WRITE, it discards unwritten +/* data. In the case of a double-buffered stream, if direction is +/* VSTREAM_PURGE_BOTH, it discards the content of both the read +/* and write buffers. vstream_fpurge() returns 0 in case of success, +/* VSTREAM_EOF in case of problems. +/* +/* vstream_fread() and vstream_fwrite() perform unformatted I/O +/* on the named stream. The result value is the number of bytes +/* transferred. A short count is returned in case of end-of-file +/* or error conditions. +/* +/* vstream_fread_buf() resets the buffer write position, +/* allocates space for the specified number of bytes in the +/* buffer, reads the bytes from the specified VSTREAM, and +/* adjusts the buffer write position. The buffer is NOT +/* null-terminated. The result value is as with vstream_fread(). +/* NOTE: do not skip calling vstream_fread_buf() when len == 0. +/* This function has side effects including resetting the buffer +/* write position, and skipping the call would invalidate the +/* buffer state. +/* +/* vstream_fread_app() is like vstream_fread_buf() but appends +/* to existing buffer content, instead of writing over it. +/* +/* vstream_control() allows the user to fine tune the behavior of +/* the specified stream. The arguments are a list of macros with +/* zero or more arguments, terminated with CA_VSTREAM_CTL_END +/* which has none. The following lists the names and the types +/* of the corresponding value arguments. +/* .IP "CA_VSTREAM_CTL_READ_FN(ssize_t (*)(int, void *, size_t, int, void *))" +/* The argument specifies an alternative for the timed_read(3) function, +/* for example, a read function that performs decryption. +/* This function receives as arguments a file descriptor, buffer pointer, +/* buffer length, timeout value, and the VSTREAM's context value. +/* A timeout value <= 0 disables the time limit. +/* This function should return the positive number of bytes transferred, +/* 0 upon EOF, and -1 upon error with errno set appropriately. +/* .IP "CA_VSTREAM_CTL_WRITE_FN(ssize_t (*)(int, void *, size_t, int, void *))" +/* The argument specifies an alternative for the timed_write(3) function, +/* for example, a write function that performs encryption. +/* This function receives as arguments a file descriptor, buffer pointer, +/* buffer length, timeout value, and the VSTREAM's context value. +/* A timeout value <= 0 disables the time limit. +/* This function should return the positive number of bytes transferred, +/* and -1 upon error with errno set appropriately. Instead of -1 it may +/* also return 0, e.g., upon remote party-initiated protocol shutdown. +/* .IP "CA_VSTREAM_CTL_CONTEXT(void *)" +/* The argument specifies application context that is passed on to +/* the application-specified read/write routines. No copy is made. +/* .IP "CA_VSTREAM_CTL_PATH(const char *)" +/* Updates the stored pathname of the specified stream. The pathname +/* is copied. +/* .IP "CA_VSTREAM_CTL_DOUBLE (no arguments)" +/* Use separate buffers for reading and for writing. This prevents +/* unread input from being discarded upon change of I/O direction. +/* .IP "CA_VSTREAM_CTL_READ_FD(int)" +/* The argument specifies the file descriptor to be used for reading. +/* This feature is limited to double-buffered streams, and makes the +/* stream non-seekable. +/* .IP "CA_VSTREAM_CTL_WRITE_FD(int)" +/* The argument specifies the file descriptor to be used for writing. +/* This feature is limited to double-buffered streams, and makes the +/* stream non-seekable. +/* .IP "CA_VSTREAM_CTL_SWAP_FD(VSTREAM *)" +/* The argument specifies a VSTREAM pointer; the request swaps the +/* file descriptor members of the two streams. This feature is limited +/* to streams that are both double-buffered or both single-buffered. +/* .IP "CA_VSTREAM_CTL_DUPFD(int)" +/* The argument specifies a minimum file descriptor value. If +/* the actual stream's file descriptors are below the minimum, +/* reallocate the descriptors to the first free value greater +/* than or equal to the minimum. The VSTREAM_CTL_DUPFD macro +/* is defined only on systems with fcntl() F_DUPFD support. +/* .IP "CA_VSTREAM_CTL_WAITPID_FN(int (*)(pid_t, WAIT_STATUS_T *, int))" +/* A pointer to function that behaves like waitpid(). This information +/* is used by the vstream_pclose() routine. +/* .IP "CA_VSTREAM_CTL_TIMEOUT(int)" +/* The deadline for a descriptor to become readable in case of a read +/* request, or writable in case of a write request. Specify a value +/* of 0 to disable deadlines. +/* .IP "CA_VSTREAM_CTL_EXCEPT (no arguments)" +/* Enable exception handling with vstream_setjmp() and vstream_longjmp(). +/* This involves allocation of additional memory that normally isn't +/* used. +/* .IP "CA_VSTREAM_CTL_BUFSIZE(ssize_t)" +/* Specify a non-default buffer size for the next read(2) or +/* write(2) operation, or zero to implement a no-op. Requests +/* to reduce the buffer size are silently ignored (i.e. any +/* positive value <= vstream_req_bufsize()). To get a buffer +/* size smaller than VSTREAM_BUFSIZE, make the VSTREAM_CTL_BUFSIZE +/* request before the first stream read or write operation +/* (i.e., vstream_req_bufsize() returns zero). Requests to +/* change a fixed-size buffer (i.e., VSTREAM_ERR) are not +/* allowed. +/* +/* NOTE: the vstream_*printf() routines may silently expand a +/* buffer, so that the result of some %letter specifiers can +/* be written to contiguous memory. +/* .IP CA_VSTREAM_CTL_START_DEADLINE (no arguments) +/* Change the VSTREAM_CTL_TIMEOUT behavior, to a deadline for +/* the total amount of time for all subsequent file descriptor +/* read or write operations, and recharge the deadline timer. +/* .IP CA_VSTREAM_CTL_STOP_DEADLINE (no arguments) +/* Revert VSTREAM_CTL_TIMEOUT behavior to the default, i.e. +/* a time limit for individual file descriptor read or write +/* operations. +/* .IP CA_VSTREAM_CTL_MIN_DATA_RATE (int) +/* When the DEADLINE is enabled, the amount of data that must +/* be transferred to add 1 second to the deadline. However, +/* the deadline will never exceed the timeout specified with +/* VSTREAM_CTL_TIMEOUT. A zero value requests no update to the +/* deadline as data is transferred; that is appropriate for +/* request/reply interactions. +/* .IP CA_VSTREAM_CTL_OWN_VSTRING (no arguments) +/* Transfer ownership of the VSTRING that was opened with +/* vstream_memopen() etc. to the stream, so that the VSTRING +/* is automatically destroyed when the stream is closed. +/* .PP +/* vstream_fileno() gives access to the file handle associated with +/* a buffered stream. With streams that have separate read/write +/* file descriptors, the result is the current descriptor. +/* +/* vstream_req_bufsize() returns the buffer size that will be +/* used for the next read(2) or write(2) operation on the named +/* stream. A zero result means that the next read(2) or write(2) +/* operation will use the default buffer size (VSTREAM_BUFSIZE). +/* +/* vstream_context() returns the application context that is passed on to +/* the application-specified read/write routines. +/* +/* VSTREAM_PATH() is an unsafe macro that returns the name stored +/* with vstream_fopen() or with vstream_control(). The macro is +/* unsafe because it evaluates some arguments more than once. +/* +/* vstream_feof() returns non-zero when a previous operation on the +/* specified stream caused an end-of-file condition. +/* Although further read requests after EOF may complete +/* successfully, vstream_feof() will keep returning non-zero +/* until vstream_clearerr() is called for that stream. +/* +/* vstream_ferror() returns non-zero when a previous operation on the +/* specified stream caused a non-EOF error condition, including timeout. +/* After a non-EOF error on a stream, no I/O request will +/* complete until after vstream_clearerr() is called for that stream. +/* +/* vstream_ftimeout() returns non-zero when a previous operation on the +/* specified stream caused a timeout error condition. See +/* vstream_ferror() for error persistence details. +/* +/* vstream_clearerr() resets the timeout, error and end-of-file indication +/* of the specified stream, and returns no useful result. +/* +/* vstream_vfprintf() provides an alternate interface +/* for formatting an argument list according to a format string. +/* +/* vstream_vprintf() provides a similar alternative interface. +/* +/* vstream_bufstat() provides input and output buffer status +/* information. The command is one of the following: +/* .IP VSTREAM_BST_IN_PEND +/* Return the number of characters that can be read without +/* refilling the read buffer. +/* .IP VSTREAM_BST_OUT_PEND +/* Return the number of characters that are waiting in the +/* write buffer. +/* .PP +/* vstream_peek() returns the number of characters that can be +/* read from the named stream without refilling the read buffer. +/* This is an alias for vstream_bufstat(stream, VSTREAM_BST_IN_PEND). +/* +/* vstream_peek_data() returns a pointer to the unread bytes +/* that exist according to vstream_peek(), or null if no unread +/* bytes are available. +/* +/* vstream_setjmp() saves processing context and makes that context +/* available for use with vstream_longjmp(). Normally, vstream_setjmp() +/* returns zero. A non-zero result means that vstream_setjmp() returned +/* through a vstream_longjmp() call; the result is the \fIval\fR argument +/* given to vstream_longjmp(). +/* +/* NB: non-local jumps such as vstream_longjmp() are not safe +/* for jumping out of any routine that manipulates VSTREAM data. +/* longjmp() like calls are best avoided in signal handlers. +/* +/* vstream_ftime() returns the time of initialization, the last buffer +/* fill operation, or the last buffer flush operation for the specified +/* stream. This information is maintained only when stream timeouts are +/* enabled. +/* +/* vstream_ftimeval() is like vstream_ftime() but returns more +/* detail. +/* +/* vstream_rd_mumble() and vstream_wr_mumble() report on +/* read and write error conditions, respectively. +/* +/* vstream_fstat() queries stream status information about +/* user-requested features. The \fIflags\fR argument is the +/* bitwise OR of one or more of the following, and the result +/* value is the bitwise OR of the features that are activated. +/* .IP VSTREAM_FLAG_DEADLINE +/* The deadline feature is activated. +/* .IP VSTREAM_FLAG_DOUBLE +/* The double-buffering feature is activated. +/* .IP VSTREAM_FLAG_MEMORY +/* The stream is connected to a VSTRING buffer. +/* .IP VSTREAM_FLAG_OWN_VSTRING +/* The stream 'owns' the VSTRING buffer, and is responsible +/* for cleaning up when the stream is closed. +/* DIAGNOSTICS +/* Panics: interface violations. Fatal errors: out of memory. +/* SEE ALSO +/* timed_read(3) default read routine +/* timed_write(3) default write routine +/* vbuf_print(3) formatting engine +/* setjmp(3) non-local jumps +/* BUGS +/* Should use mmap() on reasonable systems. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdlib.h> /* 44BSD stdarg.h uses abort() */ +#include <stdarg.h> +#include <stddef.h> +#include <unistd.h> +#include <fcntl.h> +#include <time.h> +#include <errno.h> +#include <string.h> + +/* Utility library. */ + +#define VSTRING_INTERNAL + +#include "mymalloc.h" +#include "msg.h" +#include "vbuf_print.h" +#include "iostuff.h" +#include "vstring.h" +#include "vstream.h" + +/* Application-specific. */ + + /* + * Forward declarations. + */ +static int vstream_buf_get_ready(VBUF *); +static int vstream_buf_put_ready(VBUF *); +static int vstream_buf_space(VBUF *, ssize_t); + + /* + * Initialization of the three pre-defined streams. Pre-allocate a static + * I/O buffer for the standard error stream, so that the error handler can + * produce a diagnostic even when memory allocation fails. + */ +static unsigned char vstream_fstd_buf[VSTREAM_BUFSIZE]; + +VSTREAM vstream_fstd[] = { + {{ + 0, /* flags */ + 0, 0, 0, 0, /* buffer */ + vstream_buf_get_ready, vstream_buf_put_ready, vstream_buf_space, + }, STDIN_FILENO, (VSTREAM_RW_FN) timed_read, (VSTREAM_RW_FN) timed_write, + 0,}, + {{ + 0, /* flags */ + 0, 0, 0, 0, /* buffer */ + vstream_buf_get_ready, vstream_buf_put_ready, vstream_buf_space, + }, STDOUT_FILENO, (VSTREAM_RW_FN) timed_read, (VSTREAM_RW_FN) timed_write, + 0,}, + {{ + VBUF_FLAG_FIXED | VSTREAM_FLAG_WRITE, + vstream_fstd_buf, VSTREAM_BUFSIZE, VSTREAM_BUFSIZE, vstream_fstd_buf, + vstream_buf_get_ready, vstream_buf_put_ready, vstream_buf_space, + }, STDERR_FILENO, (VSTREAM_RW_FN) timed_read, (VSTREAM_RW_FN) timed_write, + VSTREAM_BUFSIZE,}, +}; + +#define VSTREAM_STATIC(v) ((v) >= VSTREAM_IN && (v) <= VSTREAM_ERR) + + /* + * A bunch of macros to make some expressions more readable. XXX We're + * assuming that O_RDONLY == 0, O_WRONLY == 1, O_RDWR == 2. + */ +#define VSTREAM_ACC_MASK(f) ((f) & (O_APPEND | O_WRONLY | O_RDWR)) + +#define VSTREAM_CAN_READ(f) (VSTREAM_ACC_MASK(f) == O_RDONLY \ + || VSTREAM_ACC_MASK(f) == O_RDWR) +#define VSTREAM_CAN_WRITE(f) (VSTREAM_ACC_MASK(f) & O_WRONLY \ + || VSTREAM_ACC_MASK(f) & O_RDWR \ + || VSTREAM_ACC_MASK(f) & O_APPEND) + +#define VSTREAM_BUF_COUNT(bp, n) \ + ((bp)->flags & VSTREAM_FLAG_READ ? -(n) : (n)) + +#define VSTREAM_BUF_AT_START(bp) { \ + (bp)->cnt = VSTREAM_BUF_COUNT((bp), (bp)->len); \ + (bp)->ptr = (bp)->data; \ + } + +#define VSTREAM_BUF_AT_OFFSET(bp, offset) { \ + (bp)->ptr = (bp)->data + (offset); \ + (bp)->cnt = VSTREAM_BUF_COUNT(bp, (bp)->len - (offset)); \ + } + +#define VSTREAM_BUF_AT_END(bp) { \ + (bp)->cnt = 0; \ + (bp)->ptr = (bp)->data + (bp)->len; \ + } + +#define VSTREAM_BUF_ZERO(bp) { \ + (bp)->flags = 0; \ + (bp)->data = (bp)->ptr = 0; \ + (bp)->len = (bp)->cnt = 0; \ + } + +#define VSTREAM_BUF_ACTIONS(bp, get_action, put_action, space_action) { \ + (bp)->get_ready = (get_action); \ + (bp)->put_ready = (put_action); \ + (bp)->space = (space_action); \ + } + +#define VSTREAM_SAVE_STATE(stream, buffer, filedes) { \ + stream->buffer = stream->buf; \ + stream->filedes = stream->fd; \ + } + +#define VSTREAM_RESTORE_STATE(stream, buffer, filedes) do { \ + stream->buffer.flags = stream->buf.flags; \ + stream->buf = stream->buffer; \ + stream->fd = stream->filedes; \ + } while(0) + +#define VSTREAM_FORK_STATE(stream, buffer, filedes) { \ + stream->buffer = stream->buf; \ + stream->filedes = stream->fd; \ + stream->buffer.data = stream->buffer.ptr = 0; \ + stream->buffer.len = stream->buffer.cnt = 0; \ + stream->buffer.flags &= ~VSTREAM_FLAG_FIXED; \ + }; + +#define VSTREAM_FLAG_READ_DOUBLE (VSTREAM_FLAG_READ | VSTREAM_FLAG_DOUBLE) +#define VSTREAM_FLAG_WRITE_DOUBLE (VSTREAM_FLAG_WRITE | VSTREAM_FLAG_DOUBLE) + +#define VSTREAM_FFLUSH_SOME(stream) \ + vstream_fflush_some((stream), (stream)->buf.len - (stream)->buf.cnt) + +/* Note: this does not change a negative result into a zero result. */ +#define VSTREAM_SUB_TIME(x, y, z) \ + do { \ + (x).tv_sec = (y).tv_sec - (z).tv_sec; \ + (x).tv_usec = (y).tv_usec - (z).tv_usec; \ + while ((x).tv_usec < 0) { \ + (x).tv_usec += 1000000; \ + (x).tv_sec -= 1; \ + } \ + while ((x).tv_usec >= 1000000) { \ + (x).tv_usec -= 1000000; \ + (x).tv_sec += 1; \ + } \ + } while (0) + +#define VSTREAM_ADD_TIME(x, y, z) \ + do { \ + (x).tv_sec = (y).tv_sec + (z).tv_sec; \ + (x).tv_usec = (y).tv_usec + (z).tv_usec; \ + while ((x).tv_usec >= 1000000) { \ + (x).tv_usec -= 1000000; \ + (x).tv_sec += 1; \ + } \ + } while (0) + +/* vstream_buf_init - initialize buffer */ + +static void vstream_buf_init(VBUF *bp, int flags) +{ + + /* + * Initialize the buffer such that the first data access triggers a + * buffer boundary action. + */ + VSTREAM_BUF_ZERO(bp); + VSTREAM_BUF_ACTIONS(bp, + VSTREAM_CAN_READ(flags) ? vstream_buf_get_ready : 0, + VSTREAM_CAN_WRITE(flags) ? vstream_buf_put_ready : 0, + vstream_buf_space); +} + +/* vstream_buf_alloc - allocate buffer memory */ + +static void vstream_buf_alloc(VBUF *bp, ssize_t len) +{ + VSTREAM *stream = VBUF_TO_APPL(bp, VSTREAM, buf); + ssize_t used = bp->ptr - bp->data; + const char *myname = "vstream_buf_alloc"; + + if (len < bp->len) + msg_panic("%s: attempt to shrink buffer", myname); + if (bp->flags & VSTREAM_FLAG_FIXED) + msg_panic("%s: unable to extend fixed-size buffer", myname); + + /* + * Late buffer allocation allows the user to override the default policy. + * If a buffer already exists, allow for the presence of (output) data. + */ + bp->data = (unsigned char *) + (bp->data ? myrealloc((void *) bp->data, len) : mymalloc(len)); + if (bp->flags & VSTREAM_FLAG_MEMORY) + memset(bp->data + bp->len, 0, len - bp->len); + bp->len = len; + if (bp->flags & VSTREAM_FLAG_READ) { + bp->ptr = bp->data + used; + if (bp->flags & VSTREAM_FLAG_DOUBLE) + VSTREAM_SAVE_STATE(stream, read_buf, read_fd); + } else { + VSTREAM_BUF_AT_OFFSET(bp, used); + if (bp->flags & VSTREAM_FLAG_DOUBLE) + VSTREAM_SAVE_STATE(stream, write_buf, write_fd); + } +} + +/* vstream_buf_wipe - reset buffer to initial state */ + +static void vstream_buf_wipe(VBUF *bp) +{ + if ((bp->flags & VBUF_FLAG_FIXED) == 0 && bp->data) + myfree((void *) bp->data); + VSTREAM_BUF_ZERO(bp); + VSTREAM_BUF_ACTIONS(bp, 0, 0, 0); +} + +/* vstream_fflush_some - flush some buffered data */ + +static int vstream_fflush_some(VSTREAM *stream, ssize_t to_flush) +{ + const char *myname = "vstream_fflush_some"; + VBUF *bp = &stream->buf; + ssize_t used; + ssize_t left_over; + void *data; + ssize_t len; + ssize_t n; + int timeout; + struct timeval before; + struct timeval elapsed; + struct timeval bonus; + + /* + * Sanity checks. It is illegal to flush a read-only stream. Otherwise, + * if there is buffered input, discard the input. If there is buffered + * output, require that the amount to flush is larger than the amount to + * keep, so that we can memcpy() the residue. + */ + if (bp->put_ready == 0) + msg_panic("%s: read-only stream", myname); + switch (bp->flags & (VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ)) { + case VSTREAM_FLAG_READ: /* discard input */ + VSTREAM_BUF_AT_END(bp); + /* FALLTHROUGH */ + case 0: /* flush after seek? */ + return ((bp->flags & VSTREAM_FLAG_ERR) ? VSTREAM_EOF : 0); + case VSTREAM_FLAG_WRITE: /* output buffered */ + break; + case VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ: + msg_panic("%s: read/write stream", myname); + } + used = bp->len - bp->cnt; + left_over = used - to_flush; + + if (msg_verbose > 2 && stream != VSTREAM_ERR) + msg_info("%s: fd %d flush %ld", myname, stream->fd, (long) to_flush); + if (to_flush < 0 || left_over < 0) + msg_panic("%s: bad to_flush %ld", myname, (long) to_flush); + if (to_flush < left_over) + msg_panic("%s: to_flush < left_over", myname); + if (to_flush == 0) + return ((bp->flags & VSTREAM_FLAG_ERR) ? VSTREAM_EOF : 0); + if (bp->flags & VSTREAM_FLAG_ERR) + return (VSTREAM_EOF); + + /* + * When flushing a buffer, allow for partial writes. These can happen + * while talking to a network. Update the cached file seek position, if + * any. + * + * When deadlines are enabled, we count the elapsed time for each write + * operation instead of simply comparing the time-of-day clock with a + * per-stream deadline. The latter could result in anomalies when an + * application does lengthy processing between write operations. Keep in + * mind that a receiver may not be able to keep up when a sender suddenly + * floods it with a lot of data as it tries to catch up with a deadline. + */ + for (data = (void *) bp->data, len = to_flush; len > 0; len -= n, data += n) { + if (bp->flags & VSTREAM_FLAG_DEADLINE) { + timeout = stream->time_limit.tv_sec + (stream->time_limit.tv_usec > 0); + if (timeout <= 0) { + bp->flags |= (VSTREAM_FLAG_WR_ERR | VSTREAM_FLAG_WR_TIMEOUT); + errno = ETIMEDOUT; + return (VSTREAM_EOF); + } + if (len == to_flush) + GETTIMEOFDAY(&before); + else + before = stream->iotime; + } else + timeout = stream->timeout; + if ((n = stream->write_fn(stream->fd, data, len, timeout, stream->context)) <= 0) { + bp->flags |= VSTREAM_FLAG_WR_ERR; + if (errno == ETIMEDOUT) { + bp->flags |= VSTREAM_FLAG_WR_TIMEOUT; + stream->time_limit.tv_sec = stream->time_limit.tv_usec = 0; + } + return (VSTREAM_EOF); + } + if (timeout) { + GETTIMEOFDAY(&stream->iotime); + if (bp->flags & VSTREAM_FLAG_DEADLINE) { + VSTREAM_SUB_TIME(elapsed, stream->iotime, before); + VSTREAM_SUB_TIME(stream->time_limit, stream->time_limit, elapsed); + if (stream->min_data_rate > 0) { + bonus.tv_sec = n / stream->min_data_rate; + bonus.tv_usec = (n % stream->min_data_rate) * 1000000; + bonus.tv_usec /= stream->min_data_rate; + VSTREAM_ADD_TIME(stream->time_limit, stream->time_limit, + bonus); + if (stream->time_limit.tv_sec >= stream->timeout) { + stream->time_limit.tv_sec = stream->timeout; + stream->time_limit.tv_usec = 0; + } + } + } + } + if (msg_verbose > 2 && stream != VSTREAM_ERR && n != to_flush) + msg_info("%s: %d flushed %ld/%ld", myname, stream->fd, + (long) n, (long) to_flush); + } + if (bp->flags & VSTREAM_FLAG_SEEK) + stream->offset += to_flush; + + /* + * Allow for partial buffer flush requests. We use memcpy() for reasons + * of portability to pre-ANSI environments (SunOS 4.x or Ultrix 4.x :-). + * This is OK because we have already verified that the to_flush count is + * larger than the left_over count. + */ + if (left_over > 0) + memcpy(bp->data, bp->data + to_flush, left_over); + bp->cnt += to_flush; + bp->ptr -= to_flush; + return ((bp->flags & VSTREAM_FLAG_ERR) ? VSTREAM_EOF : 0); +} + +/* vstream_fflush_delayed - delayed stream flush for double-buffered stream */ + +static int vstream_fflush_delayed(VSTREAM *stream) +{ + int status; + + /* + * Sanity check. + */ + if ((stream->buf.flags & VSTREAM_FLAG_READ_DOUBLE) != VSTREAM_FLAG_READ_DOUBLE) + msg_panic("vstream_fflush_delayed: bad flags"); + + /* + * Temporarily swap buffers and flush unwritten data. This may seem like + * a lot of work, but it's peanuts compared to the write(2) call that we + * already have avoided. For example, delayed flush is never used on a + * non-pipelined SMTP connection. + */ + stream->buf.flags &= ~VSTREAM_FLAG_READ; + VSTREAM_SAVE_STATE(stream, read_buf, read_fd); + stream->buf.flags |= VSTREAM_FLAG_WRITE; + VSTREAM_RESTORE_STATE(stream, write_buf, write_fd); + + status = VSTREAM_FFLUSH_SOME(stream); + + stream->buf.flags &= ~VSTREAM_FLAG_WRITE; + VSTREAM_SAVE_STATE(stream, write_buf, write_fd); + stream->buf.flags |= VSTREAM_FLAG_READ; + VSTREAM_RESTORE_STATE(stream, read_buf, read_fd); + + return (status); +} + +/* vstream_buf_get_ready - vbuf callback to make buffer ready for reading */ + +static int vstream_buf_get_ready(VBUF *bp) +{ + VSTREAM *stream = VBUF_TO_APPL(bp, VSTREAM, buf); + const char *myname = "vstream_buf_get_ready"; + ssize_t n; + struct timeval before; + struct timeval elapsed; + struct timeval bonus; + int timeout; + + /* + * Detect a change of I/O direction or position. If so, flush any + * unwritten output immediately when the stream is single-buffered, or + * when the stream is double-buffered and the read buffer is empty. + */ + switch (bp->flags & (VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ)) { + case VSTREAM_FLAG_WRITE: /* change direction */ + if (bp->ptr > bp->data) + if ((bp->flags & VSTREAM_FLAG_DOUBLE) == 0 + || stream->read_buf.cnt >= 0) + if (VSTREAM_FFLUSH_SOME(stream)) + return (VSTREAM_EOF); + bp->flags &= ~VSTREAM_FLAG_WRITE; + if (bp->flags & VSTREAM_FLAG_DOUBLE) + VSTREAM_SAVE_STATE(stream, write_buf, write_fd); + /* FALLTHROUGH */ + case 0: /* change position */ + bp->flags |= VSTREAM_FLAG_READ; + if (bp->flags & VSTREAM_FLAG_DOUBLE) { + VSTREAM_RESTORE_STATE(stream, read_buf, read_fd); + if (bp->cnt < 0) + return (0); + } + /* FALLTHROUGH */ + case VSTREAM_FLAG_READ: /* no change */ + break; + case VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ: + msg_panic("%s: read/write stream", myname); + } + + /* + * If this is the first GET operation, allocate a buffer. Late buffer + * allocation gives the application a chance to override the default + * buffering policy. + * + * XXX Subtle code to set the preferred buffer size as late as possible. + */ + if (stream->req_bufsize == 0) + stream->req_bufsize = VSTREAM_BUFSIZE; + if (bp->len < stream->req_bufsize) + vstream_buf_alloc(bp, stream->req_bufsize); + + /* + * If the stream is double-buffered and the write buffer is not empty, + * this is the time to flush the write buffer. Delayed flushes reduce + * system call overhead, and on TCP sockets, avoid triggering Nagle's + * algorithm. + */ + if ((bp->flags & VSTREAM_FLAG_DOUBLE) + && stream->write_buf.len > stream->write_buf.cnt) + if (vstream_fflush_delayed(stream)) + return (VSTREAM_EOF); + + /* + * Did we receive an EOF indication? + */ + if (bp->flags & VSTREAM_FLAG_EOF) + return (VSTREAM_EOF); + + /* + * Fill the buffer with as much data as we can handle, or with as much + * data as is available right now, whichever is less. Update the cached + * file seek position, if any. + * + * When deadlines are enabled, we count the elapsed time for each read + * operation instead of simply comparing the time-of-day clock with a + * per-stream deadline. The latter could result in anomalies when an + * application does lengthy processing between read operations. Keep in + * mind that a sender may get blocked, and may not be able to keep up + * when a receiver suddenly wants to read a lot of data as it tries to + * catch up with a deadline. + */ + if (bp->flags & VSTREAM_FLAG_DEADLINE) { + timeout = stream->time_limit.tv_sec + (stream->time_limit.tv_usec > 0); + if (timeout <= 0) { + bp->flags |= (VSTREAM_FLAG_RD_ERR | VSTREAM_FLAG_RD_TIMEOUT); + errno = ETIMEDOUT; + return (VSTREAM_EOF); + } + GETTIMEOFDAY(&before); + } else + timeout = stream->timeout; + switch (n = stream->read_fn(stream->fd, bp->data, bp->len, timeout, stream->context)) { + case -1: + bp->flags |= VSTREAM_FLAG_RD_ERR; + if (errno == ETIMEDOUT) { + bp->flags |= VSTREAM_FLAG_RD_TIMEOUT; + stream->time_limit.tv_sec = stream->time_limit.tv_usec = 0; + } + return (VSTREAM_EOF); + case 0: + bp->flags |= VSTREAM_FLAG_EOF; + return (VSTREAM_EOF); + default: + if (timeout) { + GETTIMEOFDAY(&stream->iotime); + if (bp->flags & VSTREAM_FLAG_DEADLINE) { + VSTREAM_SUB_TIME(elapsed, stream->iotime, before); + VSTREAM_SUB_TIME(stream->time_limit, stream->time_limit, elapsed); + if (stream->min_data_rate > 0) { + bonus.tv_sec = n / stream->min_data_rate; + bonus.tv_usec = (n % stream->min_data_rate) * 1000000; + bonus.tv_usec /= stream->min_data_rate; + VSTREAM_ADD_TIME(stream->time_limit, stream->time_limit, + bonus); + if (stream->time_limit.tv_sec >= stream->timeout) { + stream->time_limit.tv_sec = stream->timeout; + stream->time_limit.tv_usec = 0; + } + } + } + } + if (msg_verbose > 2) + msg_info("%s: fd %d got %ld", myname, stream->fd, (long) n); + bp->cnt = -n; + bp->ptr = bp->data; + if (bp->flags & VSTREAM_FLAG_SEEK) + stream->offset += n; + return (0); + } +} + +/* vstream_buf_put_ready - vbuf callback to make buffer ready for writing */ + +static int vstream_buf_put_ready(VBUF *bp) +{ + VSTREAM *stream = VBUF_TO_APPL(bp, VSTREAM, buf); + const char *myname = "vstream_buf_put_ready"; + + /* + * Sanity checks. Detect a change of I/O direction or position. If so, + * discard unread input, and reset the buffer to the beginning. + */ + switch (bp->flags & (VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ)) { + case VSTREAM_FLAG_READ: /* change direction */ + bp->flags &= ~VSTREAM_FLAG_READ; + if (bp->flags & VSTREAM_FLAG_DOUBLE) + VSTREAM_SAVE_STATE(stream, read_buf, read_fd); + /* FALLTHROUGH */ + case 0: /* change position */ + bp->flags |= VSTREAM_FLAG_WRITE; + if (bp->flags & VSTREAM_FLAG_DOUBLE) + VSTREAM_RESTORE_STATE(stream, write_buf, write_fd); + else + VSTREAM_BUF_AT_START(bp); + /* FALLTHROUGH */ + case VSTREAM_FLAG_WRITE: /* no change */ + break; + case VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ: + msg_panic("%s: read/write stream", myname); + } + + /* + * Remember the direction. If this is the first PUT operation for this + * stream or if the buffer is smaller than the requested size, allocate a + * new buffer; obviously there is no data to be flushed yet. Otherwise, + * flush the buffer. + * + * XXX Subtle code to set the preferred buffer size as late as possible. + */ + if (stream->req_bufsize == 0) + stream->req_bufsize = VSTREAM_BUFSIZE; + if (bp->len < stream->req_bufsize) { + vstream_buf_alloc(bp, stream->req_bufsize); + } else if (bp->cnt <= 0) { + if (VSTREAM_FFLUSH_SOME(stream)) + return (VSTREAM_EOF); + } + return (0); +} + +/* vstream_buf_space - reserve space ahead of time */ + +static int vstream_buf_space(VBUF *bp, ssize_t want) +{ + VSTREAM *stream = VBUF_TO_APPL(bp, VSTREAM, buf); + ssize_t used; + ssize_t incr; + ssize_t shortage; + const char *myname = "vstream_buf_space"; + + /* + * Sanity checks. Reserving space implies writing. It is illegal to write + * to a read-only stream. Detect a change of I/O direction or position. + * If so, reset the buffer to the beginning. + */ + if (bp->put_ready == 0) + msg_panic("%s: read-only stream", myname); + if (want < 0) + msg_panic("%s: bad length %ld", myname, (long) want); + switch (bp->flags & (VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE)) { + case VSTREAM_FLAG_READ: /* change direction */ + bp->flags &= ~VSTREAM_FLAG_READ; + if (bp->flags & VSTREAM_FLAG_DOUBLE) + VSTREAM_SAVE_STATE(stream, read_buf, read_fd); + /* FALLTHROUGH */ + case 0: /* change position */ + bp->flags |= VSTREAM_FLAG_WRITE; + if (bp->flags & VSTREAM_FLAG_DOUBLE) + VSTREAM_RESTORE_STATE(stream, write_buf, write_fd); + else + VSTREAM_BUF_AT_START(bp); + /* FALLTHROUGH */ + case VSTREAM_FLAG_WRITE: /* no change */ + break; + case VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE: + msg_panic("%s: read/write stream", myname); + } + + /* + * See if enough space is available. If not, flush a multiple of + * VSTREAM_BUFSIZE bytes and resize the buffer to a multiple of + * VSTREAM_BUFSIZE. We flush multiples of VSTREAM_BUFSIZE in an attempt + * to keep file updates block-aligned for better performance. + * + * XXX Subtle code to set the preferred buffer size as late as possible. + */ +#define VSTREAM_TRUNCATE(count, base) (((count) / (base)) * (base)) +#define VSTREAM_ROUNDUP(count, base) VSTREAM_TRUNCATE(count + base - 1, base) + + if (stream->req_bufsize == 0) + stream->req_bufsize = VSTREAM_BUFSIZE; + if (want > bp->cnt) { + if ((used = bp->len - bp->cnt) > stream->req_bufsize) + if (vstream_fflush_some(stream, VSTREAM_TRUNCATE(used, stream->req_bufsize))) + return (VSTREAM_EOF); + if ((shortage = (want - bp->cnt)) > 0) { + if ((bp->flags & VSTREAM_FLAG_FIXED) + || shortage > __MAXINT__(ssize_t) -bp->len - stream->req_bufsize) { + bp->flags |= VSTREAM_FLAG_WR_ERR; + } else { + incr = VSTREAM_ROUNDUP(shortage, stream->req_bufsize); + vstream_buf_alloc(bp, bp->len + incr); + } + } + } + return (vstream_ferror(stream) ? VSTREAM_EOF : 0); /* mmap() may fail */ +} + +/* vstream_fpurge - discard unread or unwritten content */ + +int vstream_fpurge(VSTREAM *stream, int direction) +{ + const char *myname = "vstream_fpurge"; + VBUF *bp = &stream->buf; + +#define VSTREAM_MAYBE_PURGE_WRITE(d, b) if ((d) & VSTREAM_PURGE_WRITE) \ + VSTREAM_BUF_AT_START((b)) +#define VSTREAM_MAYBE_PURGE_READ(d, b) if ((d) & VSTREAM_PURGE_READ) \ + VSTREAM_BUF_AT_END((b)) + + /* + * To discard all unread contents, position the read buffer at its end, + * so that we skip over any unread data, and so that the next read + * operation will refill the buffer. + * + * To discard all unwritten content, position the write buffer at its + * beginning, so that the next write operation clobbers any unwritten + * data. + */ + switch (bp->flags & (VSTREAM_FLAG_READ_DOUBLE | VSTREAM_FLAG_WRITE)) { + case VSTREAM_FLAG_READ_DOUBLE: + VSTREAM_MAYBE_PURGE_WRITE(direction, &stream->write_buf); + /* FALLTHROUGH */ + case VSTREAM_FLAG_READ: + VSTREAM_MAYBE_PURGE_READ(direction, bp); + break; + case VSTREAM_FLAG_DOUBLE: + VSTREAM_MAYBE_PURGE_WRITE(direction, &stream->write_buf); + VSTREAM_MAYBE_PURGE_READ(direction, &stream->read_buf); + break; + case VSTREAM_FLAG_WRITE_DOUBLE: + VSTREAM_MAYBE_PURGE_READ(direction, &stream->read_buf); + /* FALLTHROUGH */ + case VSTREAM_FLAG_WRITE: + VSTREAM_MAYBE_PURGE_WRITE(direction, bp); + break; + case VSTREAM_FLAG_READ_DOUBLE | VSTREAM_FLAG_WRITE: + case VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE: + msg_panic("%s: read/write stream", myname); + } + + /* + * Invalidate the cached file seek position. + */ + bp->flags &= ~VSTREAM_FLAG_SEEK; + stream->offset = 0; + + return (0); +} + +/* vstream_fseek - change I/O position */ + +off_t vstream_fseek(VSTREAM *stream, off_t offset, int whence) +{ + const char *myname = "vstream_fseek"; + VBUF *bp = &stream->buf; + + /* + * TODO: support data length (data length != buffer length). Without data + * length information, Without explicit data length information, + * vstream_memopen(O_RDONLY) has to set the VSTREAM buffer length to the + * vstring payload length to avoid accessing unwritten data after + * vstream_fseek(), because for lseek() compatibility, vstream_fseek() + * must allow seeking past the end of a file. + */ + if (stream->buf.flags & VSTREAM_FLAG_MEMORY) { + if (whence == SEEK_CUR) + offset += (bp->ptr - bp->data); + else if (whence == SEEK_END) + offset += bp->len; + if (offset < 0) { + errno = EINVAL; + return (-1); + } + if (offset > bp->len && (bp->flags & VSTREAM_FLAG_WRITE)) + vstream_buf_space(bp, offset - bp->len); + VSTREAM_BUF_AT_OFFSET(bp, offset); + return (offset); + } + + /* + * Flush any unwritten output. Discard any unread input. Position the + * buffer at the end, so that the next GET or PUT operation triggers a + * buffer boundary action. + */ + switch (bp->flags & (VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE)) { + case VSTREAM_FLAG_WRITE: + if (bp->ptr > bp->data) { + if (whence == SEEK_CUR) + offset += (bp->ptr - bp->data); /* add unwritten data */ + else if (whence == SEEK_END) + bp->flags &= ~VSTREAM_FLAG_SEEK; + if (VSTREAM_FFLUSH_SOME(stream)) + return (-1); + } + VSTREAM_BUF_AT_END(bp); + break; + case VSTREAM_FLAG_READ: + if (whence == SEEK_CUR) + offset += bp->cnt; /* subtract unread data */ + else if (whence == SEEK_END) + bp->flags &= ~VSTREAM_FLAG_SEEK; + /* FALLTHROUGH */ + case 0: + VSTREAM_BUF_AT_END(bp); + break; + case VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE: + msg_panic("%s: read/write stream", myname); + } + + /* + * Clear the read/write flags to inform the buffer boundary action + * routines that we may have changed I/O position. + */ + bp->flags &= ~(VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE); + + /* + * Shave an unnecessary system call. + */ + if (bp->flags & VSTREAM_FLAG_NSEEK) { + errno = ESPIPE; + return (-1); + } + + /* + * Update the cached file seek position. + */ + if ((stream->offset = lseek(stream->fd, offset, whence)) < 0) { + if (errno == ESPIPE) + bp->flags |= VSTREAM_FLAG_NSEEK; + } else { + bp->flags |= VSTREAM_FLAG_SEEK; + } + bp->flags &= ~VSTREAM_FLAG_EOF; + return (stream->offset); +} + +/* vstream_ftell - return file offset */ + +off_t vstream_ftell(VSTREAM *stream) +{ + VBUF *bp = &stream->buf; + + /* + * Special case for memory buffer. + */ + if (stream->buf.flags & VSTREAM_FLAG_MEMORY) + return (bp->ptr - bp->data); + + /* + * Shave an unnecessary syscall. + */ + if (bp->flags & VSTREAM_FLAG_NSEEK) { + errno = ESPIPE; + return (-1); + } + + /* + * Use the cached file offset when available. This is the offset after + * the last read, write or seek operation. + */ + if ((bp->flags & VSTREAM_FLAG_SEEK) == 0) { + if ((stream->offset = lseek(stream->fd, (off_t) 0, SEEK_CUR)) < 0) { + bp->flags |= VSTREAM_FLAG_NSEEK; + return (-1); + } + bp->flags |= VSTREAM_FLAG_SEEK; + } + + /* + * If this is a read buffer, subtract the number of unread bytes from the + * cached offset. Remember that read counts are negative. + */ + if (bp->flags & VSTREAM_FLAG_READ) + return (stream->offset + bp->cnt); + + /* + * If this is a write buffer, add the number of unwritten bytes to the + * cached offset. + */ + if (bp->flags & VSTREAM_FLAG_WRITE) + return (stream->offset + (bp->ptr - bp->data)); + + /* + * Apparently, this is a new buffer, or a buffer after seek, so there is + * no need to account for unread or unwritten data. + */ + return (stream->offset); +} + +/* vstream_subopen - initialize everything except buffers and I/O handlers */ + +static VSTREAM *vstream_subopen(void) +{ + VSTREAM *stream; + + /* Note: memset() is not a portable way to initialize non-integer types. */ + stream = (VSTREAM *) mymalloc(sizeof(*stream)); + stream->offset = 0; + stream->path = 0; + stream->pid = 0; + stream->waitpid_fn = 0; + stream->timeout = 0; + stream->context = 0; + stream->jbuf = 0; + stream->iotime.tv_sec = stream->iotime.tv_usec = 0; + stream->time_limit.tv_sec = stream->time_limit.tv_usec = 0; + stream->req_bufsize = 0; + stream->vstring = 0; + stream->min_data_rate = 0; + return (stream); +} + +/* vstream_fdopen - add buffering to pre-opened stream */ + +VSTREAM *vstream_fdopen(int fd, int flags) +{ + VSTREAM *stream; + + /* + * Sanity check. + */ + if (fd < 0) + msg_panic("vstream_fdopen: bad file %d", fd); + + /* + * Initialize buffers etc. but do as little as possible. Late buffer + * allocation etc. gives the application a chance to override default + * policies. Either this, or the vstream*open() routines would have to + * have a really ugly interface with lots of mostly-unused arguments (can + * you say VMS?). + */ + stream = vstream_subopen(); + stream->fd = fd; + stream->read_fn = VSTREAM_CAN_READ(flags) ? (VSTREAM_RW_FN) timed_read : 0; + stream->write_fn = VSTREAM_CAN_WRITE(flags) ? (VSTREAM_RW_FN) timed_write : 0; + vstream_buf_init(&stream->buf, flags); + return (stream); +} + +/* vstream_fopen - open buffered file stream */ + +VSTREAM *vstream_fopen(const char *path, int flags, mode_t mode) +{ + VSTREAM *stream; + int fd; + + if ((fd = open(path, flags, mode)) < 0) { + return (0); + } else { + stream = vstream_fdopen(fd, flags); + stream->path = mystrdup(path); + return (stream); + } +} + +/* vstream_fflush - flush write buffer */ + +int vstream_fflush(VSTREAM *stream) +{ + + /* + * With VSTRING, the write pointer must be positioned behind the end of + * data. But vstream_fseek() changes the write position, and causes the + * data length to be forgotten. Before flushing to vstream, remember the + * current write position, move the write pointer and do what needs to be + * done, then move the write pointer back to the saved location. + */ + if (stream->buf.flags & VSTREAM_FLAG_MEMORY) { + if (stream->buf.flags & VSTREAM_FLAG_WRITE) { + VSTRING *string = stream->vstring; + +#ifdef PENDING_VSTREAM_FSEEK_FOR_MEMORY + VSTREAM_BUF_AT_OFFSET(&stream->buf, stream->buf.data_len); +#endif + memcpy(&string->vbuf, &stream->buf, sizeof(stream->buf)); + string->vbuf.flags &= VSTRING_FLAG_MASK; + VSTRING_TERMINATE(string); + } + return (0); + } + if ((stream->buf.flags & VSTREAM_FLAG_READ_DOUBLE) + == VSTREAM_FLAG_READ_DOUBLE + && stream->write_buf.len > stream->write_buf.cnt) + vstream_fflush_delayed(stream); + return (VSTREAM_FFLUSH_SOME(stream)); +} + +/* vstream_fclose - close buffered stream */ + +int vstream_fclose(VSTREAM *stream) +{ + int err; + + /* + * NOTE: Negative file descriptors are not part of the external + * interface. They are for internal use only, in order to support + * vstream_fdclose() without a lot of code duplication. Applications that + * rely on negative VSTREAM file descriptors will break without warning. + */ + if (stream->pid != 0) + msg_panic("vstream_fclose: stream has process"); + if ((stream->buf.flags & VSTREAM_FLAG_MEMORY) + || ((stream->buf.flags & VSTREAM_FLAG_WRITE_DOUBLE) != 0 + && stream->fd >= 0)) + vstream_fflush(stream); + /* Do not remove: vstream_fdclose() depends on this error test. */ + err = vstream_ferror(stream); + if (stream->buf.flags & VSTREAM_FLAG_DOUBLE) { + if (stream->read_fd >= 0) + err |= close(stream->read_fd); + if (stream->write_fd != stream->read_fd) + if (stream->write_fd >= 0) + err |= close(stream->write_fd); + vstream_buf_wipe(&stream->read_buf); + vstream_buf_wipe(&stream->write_buf); + stream->buf = stream->read_buf; + } else { + if (stream->fd >= 0) + err |= close(stream->fd); + if ((stream->buf.flags & VSTREAM_FLAG_MEMORY) == 0) + vstream_buf_wipe(&stream->buf); + } + if (stream->path) + myfree(stream->path); + if (stream->jbuf) + myfree((void *) stream->jbuf); + if (stream->vstring && (stream->buf.flags & VSTREAM_FLAG_OWN_VSTRING)) + vstring_free(stream->vstring); + if (!VSTREAM_STATIC(stream)) + myfree((void *) stream); + return (err ? VSTREAM_EOF : 0); +} + +/* vstream_fdclose - close stream, leave file(s) open */ + +int vstream_fdclose(VSTREAM *stream) +{ + + /* + * Flush unwritten output, just like vstream_fclose(). Errors are + * reported by vstream_fclose(). + */ + if ((stream->buf.flags & VSTREAM_FLAG_WRITE_DOUBLE) != 0) + (void) vstream_fflush(stream); + + /* + * NOTE: Negative file descriptors are not part of the external + * interface. They are for internal use only, in order to support + * vstream_fdclose() without a lot of code duplication. Applications that + * rely on negative VSTREAM file descriptors will break without warning. + */ + if (stream->buf.flags & VSTREAM_FLAG_DOUBLE) { + stream->fd = stream->read_fd = stream->write_fd = -1; + } else { + stream->fd = -1; + } + return (vstream_fclose(stream)); +} + +/* vstream_printf - formatted print to stdout */ + +VSTREAM *vstream_printf(const char *fmt,...) +{ + VSTREAM *stream = VSTREAM_OUT; + va_list ap; + + va_start(ap, fmt); + vbuf_print(&stream->buf, fmt, ap); + va_end(ap); + return (stream); +} + +/* vstream_fprintf - formatted print to buffered stream */ + +VSTREAM *vstream_fprintf(VSTREAM *stream, const char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + vbuf_print(&stream->buf, fmt, ap); + va_end(ap); + return (stream); +} + +/* vstream_fputs - write string to stream */ + +int vstream_fputs(const char *str, VSTREAM *stream) +{ + int ch; + + while ((ch = *str++) != 0) + if (VSTREAM_PUTC(ch, stream) == VSTREAM_EOF) + return (VSTREAM_EOF); + return (0); +} + +/* vstream_fread_buf - unformatted read to VSTRING */ + +ssize_t vstream_fread_buf(VSTREAM *fp, VSTRING *vp, ssize_t len) +{ + ssize_t ret; + + VSTRING_RESET(vp); + VSTRING_SPACE(vp, len); + ret = vstream_fread(fp, vstring_str(vp), len); + if (ret > 0) + VSTRING_AT_OFFSET(vp, ret); + return (ret); +} + +/* vstream_fread_app - unformatted read to VSTRING */ + +ssize_t vstream_fread_app(VSTREAM *fp, VSTRING *vp, ssize_t len) +{ + ssize_t ret; + + VSTRING_SPACE(vp, len); + ret = vstream_fread(fp, vstring_end(vp), len); + if (ret > 0) + VSTRING_AT_OFFSET(vp, VSTRING_LEN(vp) + ret); + return (ret); +} + +/* vstream_control - fine control */ + +void vstream_control(VSTREAM *stream, int name,...) +{ + const char *myname = "vstream_control"; + va_list ap; + int floor; + int old_fd; + ssize_t req_bufsize = 0; + VSTREAM *stream2; + int min_data_rate; + +#define SWAP(type,a,b) do { type temp = (a); (a) = (b); (b) = (temp); } while (0) + + /* + * A crude 'allow' filter for memory streams. + */ + int memory_ops = + ((1 << VSTREAM_CTL_END) | (1 << VSTREAM_CTL_CONTEXT) + | (1 << VSTREAM_CTL_PATH) | (1 << VSTREAM_CTL_EXCEPT) + | (1 << VSTREAM_CTL_OWN_VSTRING)); + + for (va_start(ap, name); name != VSTREAM_CTL_END; name = va_arg(ap, int)) { + if ((stream->buf.flags & VSTREAM_FLAG_MEMORY) + && (memory_ops & (1 << name)) == 0) + msg_panic("%s: memory stream does not support VSTREAM_CTL_%d", + VSTREAM_PATH(stream), name); + switch (name) { + case VSTREAM_CTL_READ_FN: + stream->read_fn = va_arg(ap, VSTREAM_RW_FN); + break; + case VSTREAM_CTL_WRITE_FN: + stream->write_fn = va_arg(ap, VSTREAM_RW_FN); + break; + case VSTREAM_CTL_CONTEXT: + stream->context = va_arg(ap, void *); + break; + case VSTREAM_CTL_PATH: + if (stream->path) + myfree(stream->path); + stream->path = mystrdup(va_arg(ap, char *)); + break; + case VSTREAM_CTL_DOUBLE: + if ((stream->buf.flags & VSTREAM_FLAG_DOUBLE) == 0) { + stream->buf.flags |= VSTREAM_FLAG_DOUBLE; + if (stream->buf.flags & VSTREAM_FLAG_READ) { + VSTREAM_SAVE_STATE(stream, read_buf, read_fd); + VSTREAM_FORK_STATE(stream, write_buf, write_fd); + } else { + VSTREAM_SAVE_STATE(stream, write_buf, write_fd); + VSTREAM_FORK_STATE(stream, read_buf, read_fd); + } + } + break; + case VSTREAM_CTL_READ_FD: + if ((stream->buf.flags & VSTREAM_FLAG_DOUBLE) == 0) + msg_panic("VSTREAM_CTL_READ_FD requires double buffering"); + stream->read_fd = va_arg(ap, int); + stream->buf.flags |= VSTREAM_FLAG_NSEEK; + break; + case VSTREAM_CTL_WRITE_FD: + if ((stream->buf.flags & VSTREAM_FLAG_DOUBLE) == 0) + msg_panic("VSTREAM_CTL_WRITE_FD requires double buffering"); + stream->write_fd = va_arg(ap, int); + stream->buf.flags |= VSTREAM_FLAG_NSEEK; + break; + case VSTREAM_CTL_SWAP_FD: + stream2 = va_arg(ap, VSTREAM *); + if ((stream->buf.flags & VSTREAM_FLAG_DOUBLE) + != (stream2->buf.flags & VSTREAM_FLAG_DOUBLE)) + msg_panic("VSTREAM_CTL_SWAP_FD can't swap descriptors between " + "single-buffered and double-buffered streams"); + if (stream->buf.flags & VSTREAM_FLAG_DOUBLE) { + SWAP(int, stream->read_fd, stream2->read_fd); + SWAP(int, stream->write_fd, stream2->write_fd); + stream->fd = ((stream->buf.flags & VSTREAM_FLAG_WRITE) ? + stream->write_fd : stream->read_fd); + } else { + SWAP(int, stream->fd, stream2->fd); + } + break; + case VSTREAM_CTL_TIMEOUT: + if (stream->timeout == 0) + GETTIMEOFDAY(&stream->iotime); + stream->timeout = va_arg(ap, int); + if (stream->timeout < 0) + msg_panic("%s: bad timeout %d", myname, stream->timeout); + break; + case VSTREAM_CTL_EXCEPT: + if (stream->jbuf == 0) + stream->jbuf = + (VSTREAM_JMP_BUF *) mymalloc(sizeof(VSTREAM_JMP_BUF)); + break; + +#ifdef VSTREAM_CTL_DUPFD + +#define VSTREAM_TRY_DUPFD(backup, fd, floor) do { \ + if (((backup) = (fd)) < floor) { \ + if (((fd) = fcntl((backup), F_DUPFD, (floor))) < 0) \ + msg_fatal("fcntl F_DUPFD %d: %m", (floor)); \ + (void) close(backup); \ + } \ + } while (0) + + case VSTREAM_CTL_DUPFD: + floor = va_arg(ap, int); + if (stream->buf.flags & VSTREAM_FLAG_DOUBLE) { + VSTREAM_TRY_DUPFD(old_fd, stream->read_fd, floor); + if (stream->write_fd == old_fd) + stream->write_fd = stream->read_fd; + else + VSTREAM_TRY_DUPFD(old_fd, stream->write_fd, floor); + stream->fd = (stream->buf.flags & VSTREAM_FLAG_READ) ? + stream->read_fd : stream->write_fd; + } else { + VSTREAM_TRY_DUPFD(old_fd, stream->fd, floor); + } + break; +#endif + + /* + * Postpone memory (re)allocation until the space is needed. + */ + case VSTREAM_CTL_BUFSIZE: + req_bufsize = va_arg(ap, ssize_t); + /* Heuristic to detect missing (ssize_t) type cast on LP64 hosts. */ + if (req_bufsize < 0 || req_bufsize > INT_MAX) + msg_panic("unreasonable VSTREAM_CTL_BUFSIZE request: %ld", + (long) req_bufsize); + if ((stream->buf.flags & VSTREAM_FLAG_FIXED) == 0 + && req_bufsize > stream->req_bufsize) { + if (msg_verbose) + msg_info("fd=%d: stream buffer size old=%ld new=%ld", + vstream_fileno(stream), + (long) stream->req_bufsize, + (long) req_bufsize); + stream->req_bufsize = req_bufsize; + } + break; + + /* + * Make no gettimeofday() etc. system call until we really know + * that we need to do I/O. This avoids a performance hit when + * sending or receiving body content one line at a time. + */ + case VSTREAM_CTL_STOP_DEADLINE: + stream->buf.flags &= ~VSTREAM_FLAG_DEADLINE; + break; + case VSTREAM_CTL_START_DEADLINE: + if (stream->timeout <= 0) + msg_panic("%s: bad timeout %d", myname, stream->timeout); + stream->buf.flags |= VSTREAM_FLAG_DEADLINE; + stream->time_limit.tv_sec = stream->timeout; + stream->time_limit.tv_usec = 0; + break; + case VSTREAM_CTL_MIN_DATA_RATE: + min_data_rate = va_arg(ap, int); + if (min_data_rate < 0) + msg_panic("%s: bad min_data_rate %d", myname, min_data_rate); + stream->min_data_rate = min_data_rate; + break; + case VSTREAM_CTL_OWN_VSTRING: + if ((stream->buf.flags |= VSTREAM_FLAG_MEMORY) == 0) + msg_panic("%s: operation on non-VSTRING stream", myname); + stream->buf.flags |= VSTREAM_FLAG_OWN_VSTRING; + break; + default: + msg_panic("%s: bad name %d", myname, name); + } + } + va_end(ap); +} + +/* vstream_vprintf - formatted print to stdout */ + +VSTREAM *vstream_vprintf(const char *format, va_list ap) +{ + VSTREAM *vp = VSTREAM_OUT; + + vbuf_print(&vp->buf, format, ap); + return (vp); +} + +/* vstream_vfprintf - formatted print engine */ + +VSTREAM *vstream_vfprintf(VSTREAM *vp, const char *format, va_list ap) +{ + vbuf_print(&vp->buf, format, ap); + return (vp); +} + +/* vstream_bufstat - get stream buffer status */ + +ssize_t vstream_bufstat(VSTREAM *vp, int command) +{ + VBUF *bp; + + switch (command & VSTREAM_BST_MASK_DIR) { + case VSTREAM_BST_FLAG_IN: + if (vp->buf.flags & VSTREAM_FLAG_READ) { + bp = &vp->buf; + } else if (vp->buf.flags & VSTREAM_FLAG_DOUBLE) { + bp = &vp->read_buf; + } else { + bp = 0; + } + switch (command & ~VSTREAM_BST_MASK_DIR) { + case VSTREAM_BST_FLAG_PEND: + return (bp ? -bp->cnt : 0); + /* Add other requests below. */ + } + break; + case VSTREAM_BST_FLAG_OUT: + if (vp->buf.flags & VSTREAM_FLAG_WRITE) { + bp = &vp->buf; + } else if (vp->buf.flags & VSTREAM_FLAG_DOUBLE) { + bp = &vp->write_buf; + } else { + bp = 0; + } + switch (command & ~VSTREAM_BST_MASK_DIR) { + case VSTREAM_BST_FLAG_PEND: + return (bp ? bp->len - bp->cnt : 0); + /* Add other requests below. */ + } + break; + } + msg_panic("vstream_bufstat: unknown command: %d", command); +} + +#undef vstream_peek /* API binary compatibility. */ + +/* vstream_peek - peek at a stream */ + +ssize_t vstream_peek(VSTREAM *vp) +{ + if (vp->buf.flags & VSTREAM_FLAG_READ) { + return (-vp->buf.cnt); + } else if (vp->buf.flags & VSTREAM_FLAG_DOUBLE) { + return (-vp->read_buf.cnt); + } else { + return (0); + } +} + +/* vstream_peek_data - peek at unread data */ + +const char *vstream_peek_data(VSTREAM *vp) +{ + if (vp->buf.flags & VSTREAM_FLAG_READ) { + return ((const char *) vp->buf.ptr); + } else if (vp->buf.flags & VSTREAM_FLAG_DOUBLE) { + return ((const char *) vp->read_buf.ptr); + } else { + return (0); + } +} + +/* vstream_memopen - open a VSTRING */ + +VSTREAM *vstream_memreopen(VSTREAM *stream, VSTRING *string, int flags) +{ + if (stream == 0) + stream = vstream_subopen(); + else if ((stream->buf.flags & VSTREAM_FLAG_MEMORY) == 0) + msg_panic("vstream_memreopen: cannot reopen non-memory stream"); + stream->fd = -1; + stream->read_fn = 0; + stream->write_fn = 0; + stream->vstring = string; + memcpy(&stream->buf, &stream->vstring->vbuf, sizeof(stream->buf)); + stream->buf.flags |= VSTREAM_FLAG_MEMORY; + switch (VSTREAM_ACC_MASK(flags)) { + case O_RDONLY: + stream->buf.flags |= VSTREAM_FLAG_READ; + /* Prevent reading unwritten data after vstream_fseek(). */ + stream->buf.len = stream->buf.ptr - stream->buf.data; + VSTREAM_BUF_AT_OFFSET(&stream->buf, 0); + break; + case O_WRONLY: + stream->buf.flags |= VSTREAM_FLAG_WRITE; + VSTREAM_BUF_AT_OFFSET(&stream->buf, 0); + break; + case O_APPEND: + stream->buf.flags |= VSTREAM_FLAG_WRITE; + VSTREAM_BUF_AT_OFFSET(&stream->buf, + stream->buf.ptr - stream->buf.data); + break; + default: + msg_panic("vstream_memopen: flags must be one of " + "O_RDONLY, O_WRONLY, or O_APPEND"); + } + return (stream); +} + +#ifdef TEST + +static void copy_line(ssize_t bufsize) +{ + int c; + + /* + * Demonstrates that VSTREAM_CTL_BUFSIZE increases the buffer size, but + * does not decrease it. Uses VSTREAM_ERR for non-test output to avoid + * interfering with the test. + */ + vstream_fprintf(VSTREAM_ERR, "buffer size test: copy text with %ld buffer size, ignore requests to shrink\n", + (long) bufsize); + vstream_fflush(VSTREAM_ERR); + vstream_control(VSTREAM_IN, CA_VSTREAM_CTL_BUFSIZE(bufsize), VSTREAM_CTL_END); + vstream_control(VSTREAM_OUT, CA_VSTREAM_CTL_BUFSIZE(bufsize), VSTREAM_CTL_END); + while ((c = VSTREAM_GETC(VSTREAM_IN)) != VSTREAM_EOF) { + VSTREAM_PUTC(c, VSTREAM_OUT); + if (c == '\n') + break; + } + vstream_fflush(VSTREAM_OUT); + vstream_fprintf(VSTREAM_ERR, "actual read/write buffer sizes: %ld/%ld\n\n", + (long) VSTREAM_IN->buf.len, (long) VSTREAM_OUT->buf.len); + vstream_fflush(VSTREAM_ERR); +} + +static void printf_number(void) +{ + + /* + * Demonstrates that vstream_printf() use vbuf_print(). + */ + vstream_printf("formatting test: print a number\n"); + vstream_printf("%d\n\n", 1234567890); + vstream_fflush(VSTREAM_OUT); +} + +static void do_memory_stream(void) +{ + VSTRING *buf = vstring_alloc(1); + VSTREAM *fp; + off_t offset; + int ch; + + /* + * Preload the string. + */ + vstream_printf("memory stream test prep: prefill the VSTRING\n"); + vstring_strcpy(buf, "01234567"); + vstream_printf("VSTRING content length: %ld/%ld, content: %s\n", + (long) VSTRING_LEN(buf), (long) buf->vbuf.len, + vstring_str(buf)); + VSTREAM_PUTCHAR('\n'); + vstream_fflush(VSTREAM_OUT); + + /* + * Test: open the memory VSTREAM in write-only mode, and clobber it. + */ + vstream_printf("memory stream test: open the VSTRING for writing, overwrite, close\n"); + fp = vstream_memopen(buf, O_WRONLY); + vstream_printf("initial memory VSTREAM write offset: %ld/%ld\n", + (long) vstream_ftell(fp), (long) fp->buf.len); + vstream_fprintf(fp, "hallo"); + vstream_printf("final memory VSTREAM write offset: %ld/%ld\n", + (long) vstream_ftell(fp), (long) fp->buf.len); + vstream_fclose(fp); + vstream_printf("VSTRING content length: %ld/%ld, content: %s\n", + (long) VSTRING_LEN(buf), (long) buf->vbuf.len, + vstring_str(buf)); + VSTREAM_PUTCHAR('\n'); + vstream_fflush(VSTREAM_OUT); + + /* + * Test: open the memory VSTREAM for append. vstream_memopen() sets the + * buffer length to the VSTRING buffer length, and positions the write + * pointer at the VSTRING write position. Write some content, then + * overwrite one character. + */ + vstream_printf("memory stream test: open the VSTRING for append, write multiple, then overwrite 1\n"); + fp = vstream_memopen(buf, O_APPEND); + vstream_printf("initial memory VSTREAM write offset: %ld/%ld\n", + (long) vstream_ftell(fp), (long) fp->buf.len); + vstream_fprintf(fp, " world"); + vstream_printf("final memory VSTREAM write offset: %ld/%ld\n", + (long) vstream_ftell(fp), (long) fp->buf.len); + if (vstream_fflush(fp)) + msg_fatal("vstream_fflush: %m"); + vstream_printf("VSTRING content length: %ld/%ld, content: %s\n", + (long) VSTRING_LEN(buf), (long) buf->vbuf.len, + vstring_str(buf)); + VSTREAM_PUTCHAR('\n'); + + /* + * While the stream is still open, replace the second character. + */ + vstream_printf("replace second character and close\n"); + if ((offset = vstream_fseek(fp, 1, SEEK_SET)) != 1) + msg_panic("unexpected vstream_fseek return: %ld, expected: %ld", + (long) offset, (long) 1); + VSTREAM_PUTC('e', fp); + + /* + * Skip to the end of the content, so that vstream_fflush() will update + * the VSTRING with the right content length. + */ + if ((offset = vstream_fseek(fp, VSTRING_LEN(buf), SEEK_SET)) != VSTRING_LEN(buf)) + msg_panic("unexpected vstream_fseek return: %ld, expected: %ld", + (long) offset, (long) VSTRING_LEN(buf)); + vstream_fclose(fp); + + vstream_printf("VSTRING content length: %ld/%ld, content: %s\n", + (long) VSTRING_LEN(buf), (long) buf->vbuf.len, + vstring_str(buf)); + VSTREAM_PUTCHAR('\n'); + vstream_fflush(VSTREAM_OUT); + + /* + * TODO: test that in write/append mode, seek past the end of data will + * result in zero-filled space. + */ + + /* + * Test: Open the VSTRING for reading. This time, vstream_memopen() will + * set the VSTREAM buffer length to the content length of the VSTRING, so + * that it won't attempt to read past the end of the content. + */ + vstream_printf("memory stream test: open VSTRING for reading, then read\n"); + fp = vstream_memopen(buf, O_RDONLY); + vstream_printf("initial memory VSTREAM read offset: %ld/%ld\n", + (long) vstream_ftell(fp), (long) fp->buf.len); + vstream_printf("reading memory VSTREAM: "); + while ((ch = VSTREAM_GETC(fp)) != VSTREAM_EOF) + VSTREAM_PUTCHAR(ch); + VSTREAM_PUTCHAR('\n'); + vstream_printf("final memory VSTREAM read offset: %ld/%ld\n", + (long) vstream_ftell(fp), (long) fp->buf.len); + vstream_printf("seeking to offset %ld should work: ", + (long) fp->buf.len + 1); + vstream_fflush(VSTREAM_OUT); + if ((offset = vstream_fseek(fp, fp->buf.len + 1, SEEK_SET)) != fp->buf.len + 1) + msg_panic("unexpected vstream_fseek return: %ld, expected: %ld", + (long) offset, (long) fp->buf.len + 1); + vstream_printf("PASS\n"); + vstream_fflush(VSTREAM_OUT); + vstream_printf("VSTREAM_GETC should return VSTREAM_EOF\n"); + ch = VSTREAM_GETC(fp); + if (ch != VSTREAM_EOF) + msg_panic("unexpected vstream_fseek VSTREAM_GETC return: %d, expected: %d", + ch, VSTREAM_EOF); + vstream_printf("PASS\n"); + vstream_printf("final memory VSTREAM read offset: %ld/%ld\n", + (long) vstream_ftell(fp), (long) fp->buf.len); + vstream_printf("VSTRING content length: %ld/%ld, content: %s\n", + (long) VSTRING_LEN(buf), (long) buf->vbuf.len, + vstring_str(buf)); + VSTREAM_PUTCHAR('\n'); + vstream_fflush(VSTREAM_OUT); + vstream_fclose(fp); + vstring_free(buf); +} + + /* + * Exercise some of the features. + */ + +#include <msg_vstream.h> + +int main(int argc, char **argv) +{ + msg_vstream_init(argv[0], VSTREAM_ERR); + + /* + * Test buffer expansion and shrinking. Formatted print may silently + * expand the write buffer and cause multiple bytes to be written. + */ + copy_line(1); /* one-byte read/write */ + copy_line(2); /* two-byte read/write */ + copy_line(1); /* two-byte read/write */ + printf_number(); /* multi-byte write */ + do_memory_stream(); + + exit(0); +} + +#endif diff --git a/src/util/vstream.h b/src/util/vstream.h new file mode 100644 index 0000000..23688c7 --- /dev/null +++ b/src/util/vstream.h @@ -0,0 +1,293 @@ +#ifndef _VSTREAM_H_INCLUDED_ +#define _VSTREAM_H_INCLUDED_ + +/*++ +/* NAME +/* vstream 3h +/* SUMMARY +/* simple buffered I/O package +/* SYNOPSIS +/* #include <vstream.h> +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include <sys/time.h> +#include <time.h> +#include <fcntl.h> +#include <stdarg.h> +#include <setjmp.h> +#include <unistd.h> + + /* + * Utility library. + */ +#include <vbuf.h> +#include <check_arg.h> + + /* + * Simple buffered stream. The members of this structure are not part of the + * official interface and can change without prior notice. + */ +typedef ssize_t (*VSTREAM_RW_FN) (int, void *, size_t, int, void *); +typedef pid_t(*VSTREAM_WAITPID_FN) (pid_t, WAIT_STATUS_T *, int); + +#ifdef NO_SIGSETJMP +#define VSTREAM_JMP_BUF jmp_buf +#else +#define VSTREAM_JMP_BUF sigjmp_buf +#endif + +typedef struct VSTREAM { + VBUF buf; /* generic intelligent buffer */ + int fd; /* file handle, no 256 limit */ + VSTREAM_RW_FN read_fn; /* buffer fill action */ + VSTREAM_RW_FN write_fn; /* buffer flush action */ + ssize_t req_bufsize; /* requested read/write buffer size */ + void *context; /* application context */ + off_t offset; /* cached seek info */ + char *path; /* give it at least try */ + int read_fd; /* read channel (double-buffered) */ + int write_fd; /* write channel (double-buffered) */ + VBUF read_buf; /* read buffer (double-buffered) */ + VBUF write_buf; /* write buffer (double-buffered) */ + pid_t pid; /* vstream_popen/close() */ + VSTREAM_WAITPID_FN waitpid_fn; /* vstream_popen/close() */ + int timeout; /* read/write timeout */ + VSTREAM_JMP_BUF *jbuf; /* exception handling */ + struct timeval iotime; /* time of last fill/flush */ + struct timeval time_limit; /* read/write time limit */ + int min_data_rate; /* min data rate for time limit */ + struct VSTRING *vstring; /* memory-backed stream */ +} VSTREAM; + +extern VSTREAM vstream_fstd[]; /* pre-defined streams */ + +#define VSTREAM_IN (&vstream_fstd[0]) +#define VSTREAM_OUT (&vstream_fstd[1]) +#define VSTREAM_ERR (&vstream_fstd[2]) + +#define VSTREAM_FLAG_RD_ERR VBUF_FLAG_RD_ERR /* read error */ +#define VSTREAM_FLAG_WR_ERR VBUF_FLAG_WR_ERR /* write error */ +#define VSTREAM_FLAG_RD_TIMEOUT VBUF_FLAG_RD_TIMEOUT /* read timeout */ +#define VSTREAM_FLAG_WR_TIMEOUT VBUF_FLAG_WR_TIMEOUT /* write timeout */ + +#define VSTREAM_FLAG_ERR VBUF_FLAG_ERR /* some I/O error */ +#define VSTREAM_FLAG_EOF VBUF_FLAG_EOF /* end of file */ +#define VSTREAM_FLAG_TIMEOUT VBUF_FLAG_TIMEOUT /* timeout error */ +#define VSTREAM_FLAG_FIXED VBUF_FLAG_FIXED /* fixed-size buffer */ +#define VSTREAM_FLAG_BAD VBUF_FLAG_BAD + +/* Flags 1<<24 and above are reserved for VSTRING. */ +#define VSTREAM_FLAG_READ (1<<8) /* read buffer */ +#define VSTREAM_FLAG_WRITE (1<<9) /* write buffer */ +#define VSTREAM_FLAG_SEEK (1<<10) /* seek info valid */ +#define VSTREAM_FLAG_NSEEK (1<<11) /* can't seek this file */ +#define VSTREAM_FLAG_DOUBLE (1<<12) /* double buffer */ +#define VSTREAM_FLAG_DEADLINE (1<<13) /* deadline active */ +#define VSTREAM_FLAG_MEMORY (1<<14) /* internal stream */ +#define VSTREAM_FLAG_OWN_VSTRING (1<<15)/* owns VSTRING resource */ + +#define VSTREAM_PURGE_READ (1<<0) /* flush unread data */ +#define VSTREAM_PURGE_WRITE (1<<1) /* flush unwritten data */ +#define VSTREAM_PURGE_BOTH (VSTREAM_PURGE_READ|VSTREAM_PURGE_WRITE) + +#define VSTREAM_BUFSIZE 4096 + +extern VSTREAM *vstream_fopen(const char *, int, mode_t); +extern int vstream_fclose(VSTREAM *); +extern off_t WARN_UNUSED_RESULT vstream_fseek(VSTREAM *, off_t, int); +extern off_t vstream_ftell(VSTREAM *); +extern int vstream_fpurge(VSTREAM *, int); +extern int vstream_fflush(VSTREAM *); +extern int vstream_fputs(const char *, VSTREAM *); +extern VSTREAM *vstream_fdopen(int, int); +extern int vstream_fdclose(VSTREAM *); + +#define vstream_fread(v, b, n) vbuf_read(&(v)->buf, (b), (n)) +#define vstream_fwrite(v, b, n) vbuf_write(&(v)->buf, (b), (n)) + +#define VSTREAM_PUTC(ch, vp) VBUF_PUT(&(vp)->buf, (ch)) +#define VSTREAM_GETC(vp) VBUF_GET(&(vp)->buf) +#define vstream_ungetc(vp, ch) vbuf_unget(&(vp)->buf, (ch)) +#define VSTREAM_EOF VBUF_EOF + +#define VSTREAM_PUTCHAR(ch) VSTREAM_PUTC((ch), VSTREAM_OUT) +#define VSTREAM_GETCHAR() VSTREAM_GETC(VSTREAM_IN) + +#define vstream_fileno(vp) ((vp)->fd) +#define vstream_req_bufsize(vp) ((const ssize_t) ((vp)->req_bufsize)) +#define vstream_context(vp) ((vp)->context) +#define vstream_rd_error(vp) vbuf_rd_error(&(vp)->buf) +#define vstream_wr_error(vp) vbuf_wr_error(&(vp)->buf) +#define vstream_ferror(vp) vbuf_error(&(vp)->buf) +#define vstream_feof(vp) vbuf_eof(&(vp)->buf) +#define vstream_rd_timeout(vp) vbuf_rd_timeout(&(vp)->buf) +#define vstream_wr_timeout(vp) vbuf_wr_timeout(&(vp)->buf) +#define vstream_ftimeout(vp) vbuf_timeout(&(vp)->buf) +#define vstream_clearerr(vp) vbuf_clearerr(&(vp)->buf) +#define VSTREAM_PATH(vp) ((vp)->path ? (const char *) (vp)->path : "unknown_stream") +#define vstream_ftime(vp) ((time_t) ((vp)->iotime.tv_sec)) +#define vstream_ftimeval(vp) ((vp)->iotime) + +#define vstream_fstat(vp, fl) ((vp)->buf.flags & (fl)) + +extern ssize_t vstream_fread_buf(VSTREAM *, struct VSTRING *, ssize_t); +extern ssize_t vstream_fread_app(VSTREAM *, struct VSTRING *, ssize_t); +extern void vstream_control(VSTREAM *, int,...); + +/* Legacy API: type-unchecked arguments, internal use. */ +#define VSTREAM_CTL_END 0 +#define VSTREAM_CTL_READ_FN 1 +#define VSTREAM_CTL_WRITE_FN 2 +#define VSTREAM_CTL_PATH 3 +#define VSTREAM_CTL_DOUBLE 4 +#define VSTREAM_CTL_READ_FD 5 +#define VSTREAM_CTL_WRITE_FD 6 +#define VSTREAM_CTL_WAITPID_FN 7 +#define VSTREAM_CTL_TIMEOUT 8 +#define VSTREAM_CTL_EXCEPT 9 +#define VSTREAM_CTL_CONTEXT 10 +#ifdef F_DUPFD +#define VSTREAM_CTL_DUPFD 11 +#endif +#define VSTREAM_CTL_BUFSIZE 12 +#define VSTREAM_CTL_SWAP_FD 13 +#define VSTREAM_CTL_START_DEADLINE 14 +#define VSTREAM_CTL_STOP_DEADLINE 15 +#define VSTREAM_CTL_OWN_VSTRING 16 +#define VSTREAM_CTL_MIN_DATA_RATE 17 + +/* Safer API: type-checked arguments, external use. */ +#define CA_VSTREAM_CTL_END VSTREAM_CTL_END +#define CA_VSTREAM_CTL_READ_FN(v) VSTREAM_CTL_READ_FN, CHECK_VAL(VSTREAM_CTL, VSTREAM_RW_FN, (v)) +#define CA_VSTREAM_CTL_WRITE_FN(v) VSTREAM_CTL_WRITE_FN, CHECK_VAL(VSTREAM_CTL, VSTREAM_RW_FN, (v)) +#define CA_VSTREAM_CTL_PATH(v) VSTREAM_CTL_PATH, CHECK_CPTR(VSTREAM_CTL, char, (v)) +#define CA_VSTREAM_CTL_DOUBLE VSTREAM_CTL_DOUBLE +#define CA_VSTREAM_CTL_READ_FD(v) VSTREAM_CTL_READ_FD, CHECK_VAL(VSTREAM_CTL, int, (v)) +#define CA_VSTREAM_CTL_WRITE_FD(v) VSTREAM_CTL_WRITE_FD, CHECK_VAL(VSTREAM_CTL, int, (v)) +#define CA_VSTREAM_CTL_WAITPID_FN(v) VSTREAM_CTL_WAITPID_FN, CHECK_VAL(VSTREAM_CTL, VSTREAM_WAITPID_FN, (v)) +#define CA_VSTREAM_CTL_TIMEOUT(v) VSTREAM_CTL_TIMEOUT, CHECK_VAL(VSTREAM_CTL, int, (v)) +#define CA_VSTREAM_CTL_EXCEPT VSTREAM_CTL_EXCEPT +#define CA_VSTREAM_CTL_CONTEXT(v) VSTREAM_CTL_CONTEXT, CHECK_PTR(VSTREAM_CTL, void, (v)) +#ifdef F_DUPFD +#define CA_VSTREAM_CTL_DUPFD(v) VSTREAM_CTL_DUPFD, CHECK_VAL(VSTREAM_CTL, int, (v)) +#endif +#define CA_VSTREAM_CTL_BUFSIZE(v) VSTREAM_CTL_BUFSIZE, CHECK_VAL(VSTREAM_CTL, ssize_t, (v)) +#define CA_VSTREAM_CTL_SWAP_FD(v) VSTREAM_CTL_SWAP_FD, CHECK_PTR(VSTREAM_CTL, VSTREAM, (v)) +#define CA_VSTREAM_CTL_START_DEADLINE VSTREAM_CTL_START_DEADLINE +#define CA_VSTREAM_CTL_STOP_DEADLINE VSTREAM_CTL_STOP_DEADLINE +#define CA_VSTREAM_CTL_MIN_DATA_RATE(v) VSTREAM_CTL_MIN_DATA_RATE, CHECK_VAL(VSTREAM_CTL, int, (v)) + +CHECK_VAL_HELPER_DCL(VSTREAM_CTL, ssize_t); +CHECK_VAL_HELPER_DCL(VSTREAM_CTL, int); +CHECK_VAL_HELPER_DCL(VSTREAM_CTL, VSTREAM_WAITPID_FN); +CHECK_VAL_HELPER_DCL(VSTREAM_CTL, VSTREAM_RW_FN); +CHECK_PTR_HELPER_DCL(VSTREAM_CTL, void); +CHECK_PTR_HELPER_DCL(VSTREAM_CTL, VSTREAM); +CHECK_CPTR_HELPER_DCL(VSTREAM_CTL, char); + +extern VSTREAM *PRINTFLIKE(1, 2) vstream_printf(const char *,...); +extern VSTREAM *PRINTFLIKE(2, 3) vstream_fprintf(VSTREAM *, const char *,...); + +extern VSTREAM *vstream_popen(int,...); +extern int vstream_pclose(VSTREAM *); + +#define vstream_ispipe(vp) ((vp)->pid != 0) + +/* Legacy API: type-unchecked arguments, internal use. */ +#define VSTREAM_POPEN_END 0 /* terminator */ +#define VSTREAM_POPEN_COMMAND 1 /* command is string */ +#define VSTREAM_POPEN_ARGV 2 /* command is array */ +#define VSTREAM_POPEN_UID 3 /* privileges */ +#define VSTREAM_POPEN_GID 4 /* privileges */ +#define VSTREAM_POPEN_ENV 5 /* extra environment */ +#define VSTREAM_POPEN_SHELL 6 /* alternative shell */ +#define VSTREAM_POPEN_WAITPID_FN 7 /* child catcher, waitpid() compat. */ +#define VSTREAM_POPEN_EXPORT 8 /* exportable environment */ + +/* Safer API: type-checked arguments, external use. */ +#define CA_VSTREAM_POPEN_END VSTREAM_POPEN_END +#define CA_VSTREAM_POPEN_COMMAND(v) VSTREAM_POPEN_COMMAND, CHECK_CPTR(VSTREAM_PPN, char, (v)) +#define CA_VSTREAM_POPEN_ARGV(v) VSTREAM_POPEN_ARGV, CHECK_PPTR(VSTREAM_PPN, char, (v)) +#define CA_VSTREAM_POPEN_UID(v) VSTREAM_POPEN_UID, CHECK_VAL(VSTREAM_PPN, uid_t, (v)) +#define CA_VSTREAM_POPEN_GID(v) VSTREAM_POPEN_GID, CHECK_VAL(VSTREAM_PPN, gid_t, (v)) +#define CA_VSTREAM_POPEN_ENV(v) VSTREAM_POPEN_ENV, CHECK_PPTR(VSTREAM_PPN, char, (v)) +#define CA_VSTREAM_POPEN_SHELL(v) VSTREAM_POPEN_SHELL, CHECK_CPTR(VSTREAM_PPN, char, (v)) +#define CA_VSTREAM_POPEN_WAITPID_FN(v) VSTREAM_POPEN_WAITPID_FN, CHECK_VAL(VSTREAM_PPN, VSTREAM_WAITPID_FN, (v)) +#define CA_VSTREAM_POPEN_EXPORT(v) VSTREAM_POPEN_EXPORT, CHECK_PPTR(VSTREAM_PPN, char, (v)) + +CHECK_VAL_HELPER_DCL(VSTREAM_PPN, uid_t); +CHECK_VAL_HELPER_DCL(VSTREAM_PPN, gid_t); +CHECK_VAL_HELPER_DCL(VSTREAM_PPN, VSTREAM_WAITPID_FN); +CHECK_PPTR_HELPER_DCL(VSTREAM_PPN, char); +CHECK_CPTR_HELPER_DCL(VSTREAM_PPN, char); + +extern VSTREAM *vstream_vprintf(const char *, va_list); +extern VSTREAM *vstream_vfprintf(VSTREAM *, const char *, va_list); + +extern ssize_t vstream_peek(VSTREAM *); +extern ssize_t vstream_bufstat(VSTREAM *, int); + +#define VSTREAM_BST_FLAG_IN (1<<0) +#define VSTREAM_BST_FLAG_OUT (1<<1) +#define VSTREAM_BST_FLAG_PEND (1<<2) + +#define VSTREAM_BST_MASK_DIR (VSTREAM_BST_FLAG_IN | VSTREAM_BST_FLAG_OUT) +#define VSTREAM_BST_IN_PEND (VSTREAM_BST_FLAG_IN | VSTREAM_BST_FLAG_PEND) +#define VSTREAM_BST_OUT_PEND (VSTREAM_BST_FLAG_OUT | VSTREAM_BST_FLAG_PEND) + +#define vstream_peek(vp) vstream_bufstat((vp), VSTREAM_BST_IN_PEND) + +extern const char *vstream_peek_data(VSTREAM *); + + /* + * Exception handling. We use pointer to jmp_buf to avoid a lot of unused + * baggage for streams that don't need this functionality. + * + * XXX sigsetjmp()/siglongjmp() save and restore the signal mask which can + * avoid surprises in code that manipulates signals, but unfortunately some + * systems have bugs in their implementation. + */ +#ifdef NO_SIGSETJMP +#define vstream_setjmp(stream) setjmp((stream)->jbuf[0]) +#define vstream_longjmp(stream, val) longjmp((stream)->jbuf[0], (val)) +#else +#define vstream_setjmp(stream) sigsetjmp((stream)->jbuf[0], 1) +#define vstream_longjmp(stream, val) siglongjmp((stream)->jbuf[0], (val)) +#endif + + /* + * Tweaks and workarounds. + */ +extern int vstream_tweak_sock(VSTREAM *); +extern int vstream_tweak_tcp(VSTREAM *); + +#define vstream_flags(stream) ((const int) (stream)->buf.flags) + + /* + * Read/write VSTRING memory. + */ +#define vstream_memopen(string, flags) \ + vstream_memreopen((VSTREAM *) 0, (string), (flags)) +VSTREAM *vstream_memreopen(VSTREAM *, struct VSTRING *, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/vstream_popen.c b/src/util/vstream_popen.c new file mode 100644 index 0000000..d00d49e --- /dev/null +++ b/src/util/vstream_popen.c @@ -0,0 +1,363 @@ +/*++ +/* NAME +/* vstream_popen 3 +/* SUMMARY +/* open stream to child process +/* SYNOPSIS +/* #include <vstream.h> +/* +/* VSTREAM *vstream_popen(flags, key, value, ...) +/* int flags; +/* int key; +/* +/* int vstream_pclose(stream) +/* VSTREAM *stream; +/* DESCRIPTION +/* vstream_popen() opens a one-way or two-way stream to a user-specified +/* command, which is executed by a child process. The \fIflags\fR +/* argument is as with vstream_fopen(). The child's standard input and +/* standard output are redirected to the stream, which is based on a +/* socketpair or other suitable local IPC. vstream_popen() takes a list +/* of macros with zero or more arguments, terminated by +/* CA_VSTREAM_POPEN_END. The following is a listing of macros +/* with the expected argument type. +/* .RS +/* .IP "CA_VSTREAM_POPEN_COMMAND(const char *)" +/* Specifies the command to execute as a string. The string is +/* passed to the shell when it contains shell meta characters +/* or when it appears to be a shell built-in command, otherwise +/* the command is executed without invoking a shell. +/* One of CA_VSTREAM_POPEN_COMMAND or VSTREAM_POPEN_ARGV must be specified. +/* .IP "CA_VSTREAM_POPEN_ARGV(char **)" +/* The command is specified as an argument vector. This vector is +/* passed without further inspection to the \fIexecvp\fR() routine. +/* One of CA_VSTREAM_POPEN_COMMAND or VSTREAM_POPEN_ARGV must be specified. +/* See also the CA_VSTREAM_POPEN_SHELL attribute below. +/* .IP "CA_VSTREAM_POPEN_ENV(char **)" +/* Additional environment information, in the form of a null-terminated +/* list of name, value, name, value, ... elements. By default only the +/* command search path is initialized to _PATH_DEFPATH. +/* .IP "CA_VSTREAM_POPEN_EXPORT(char **)" +/* This argument is passed to clean_env(). +/* Null-terminated array of names of environment parameters +/* that can be exported. By default, everything is exported. +/* .IP "CA_VSTREAM_POPEN_UID(uid_t)" +/* The user ID to execute the command as. The user ID must be non-zero. +/* .IP "CA_VSTREAM_POPEN_GID(gid_t)" +/* The group ID to execute the command as. The group ID must be non-zero. +/* .IP "CA_VSTREAM_POPEN_SHELL(const char *)" +/* The shell to use when executing the command specified with +/* CA_VSTREAM_POPEN_COMMAND. This shell is invoked regardless of the +/* command content. +/* .IP "CA_VSTREAM_POPEN_WAITPID_FN(pid_t (*)(pid_t, WAIT_STATUS_T *, int))" +/* waitpid()-like function to reap the child exit status when +/* vstream_pclose() is called. +/* .RE +/* .PP +/* vstream_pclose() closes the named stream and returns the child +/* exit status. It is an error to specify a stream that was not +/* returned by vstream_popen() or that is no longer open. +/* DIAGNOSTICS +/* Panics: interface violations. Fatal errors: out of memory. +/* +/* vstream_popen() returns a null pointer in case of trouble. +/* The nature of the problem is specified via the \fIerrno\fR +/* global variable. +/* +/* vstream_pclose() returns -1 in case of trouble. +/* The nature of the problem is specified via the \fIerrno\fR +/* global variable. +/* SEE ALSO +/* vstream(3) light-weight buffered I/O +/* BUGS +/* The interface, stolen from popen()/pclose(), ignores errors +/* returned when the stream is closed, and does not distinguish +/* between exit status codes and kill signals. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/wait.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#ifdef USE_PATHS_H +#include <paths.h> +#endif +#include <syslog.h> + +/* Utility library. */ + +#include <msg.h> +#include <exec_command.h> +#include <vstream.h> +#include <argv.h> +#include <set_ugid.h> +#include <clean_env.h> +#include <iostuff.h> + +/* Application-specific. */ + +typedef struct VSTREAM_POPEN_ARGS { + char **argv; + char *command; + uid_t uid; + gid_t gid; + int privileged; + char **env; + char **export; + char *shell; + VSTREAM_WAITPID_FN waitpid_fn; +} VSTREAM_POPEN_ARGS; + +/* vstream_parse_args - get arguments from variadic list */ + +static void vstream_parse_args(VSTREAM_POPEN_ARGS *args, va_list ap) +{ + const char *myname = "vstream_parse_args"; + int key; + + /* + * First, set the default values (on all non-zero entries) + */ + args->argv = 0; + args->command = 0; + args->uid = 0; + args->gid = 0; + args->privileged = 0; + args->env = 0; + args->export = 0; + args->shell = 0; + args->waitpid_fn = 0; + + /* + * Then, override the defaults with user-supplied inputs. + */ + while ((key = va_arg(ap, int)) != VSTREAM_POPEN_END) { + switch (key) { + case VSTREAM_POPEN_ARGV: + if (args->command != 0) + msg_panic("%s: got VSTREAM_POPEN_ARGV and VSTREAM_POPEN_COMMAND", myname); + args->argv = va_arg(ap, char **); + break; + case VSTREAM_POPEN_COMMAND: + if (args->argv != 0) + msg_panic("%s: got VSTREAM_POPEN_ARGV and VSTREAM_POPEN_COMMAND", myname); + args->command = va_arg(ap, char *); + break; + case VSTREAM_POPEN_UID: + args->privileged = 1; + args->uid = va_arg(ap, uid_t); + break; + case VSTREAM_POPEN_GID: + args->privileged = 1; + args->gid = va_arg(ap, gid_t); + break; + case VSTREAM_POPEN_ENV: + args->env = va_arg(ap, char **); + break; + case VSTREAM_POPEN_EXPORT: + args->export = va_arg(ap, char **); + break; + case VSTREAM_POPEN_SHELL: + args->shell = va_arg(ap, char *); + break; + case VSTREAM_POPEN_WAITPID_FN: + args->waitpid_fn = va_arg(ap, VSTREAM_WAITPID_FN); + break; + default: + msg_panic("%s: unknown key: %d", myname, key); + } + } + + if (args->command == 0 && args->argv == 0) + msg_panic("%s: missing VSTREAM_POPEN_ARGV or VSTREAM_POPEN_COMMAND", myname); + if (args->privileged != 0 && args->uid == 0) + msg_panic("%s: privileged uid", myname); + if (args->privileged != 0 && args->gid == 0) + msg_panic("%s: privileged gid", myname); +} + +/* vstream_popen - open stream to child process */ + +VSTREAM *vstream_popen(int flags,...) +{ + const char *myname = "vstream_popen"; + VSTREAM_POPEN_ARGS args; + va_list ap; + VSTREAM *stream; + int sockfd[2]; + int pid; + int fd; + ARGV *argv; + char **cpp; + + va_start(ap, flags); + vstream_parse_args(&args, ap); + va_end(ap); + + if (args.command == 0) + args.command = args.argv[0]; + + if (duplex_pipe(sockfd) < 0) + return (0); + + switch (pid = fork()) { + case -1: /* error */ + (void) close(sockfd[0]); + (void) close(sockfd[1]); + return (0); + case 0: /* child */ + (void) msg_cleanup((MSG_CLEANUP_FN) 0); + if (close(sockfd[1])) + msg_warn("close: %m"); + for (fd = 0; fd < 2; fd++) + if (sockfd[0] != fd) + if (DUP2(sockfd[0], fd) < 0) + msg_fatal("dup2: %m"); + if (sockfd[0] >= 2 && close(sockfd[0])) + msg_warn("close: %m"); + + /* + * Don't try to become someone else unless the user specified it. + */ + if (args.privileged) + set_ugid(args.uid, args.gid); + + /* + * Environment plumbing. Always reset the command search path. XXX + * That should probably be done by clean_env(). + */ + if (args.export) + clean_env(args.export); + if (setenv("PATH", _PATH_DEFPATH, 1)) + msg_fatal("%s: setenv: %m", myname); + if (args.env) + for (cpp = args.env; *cpp; cpp += 2) + if (setenv(cpp[0], cpp[1], 1)) + msg_fatal("setenv: %m"); + + /* + * Process plumbing. If possible, avoid running a shell. + */ + closelog(); + if (args.argv) { + execvp(args.argv[0], args.argv); + msg_fatal("%s: execvp %s: %m", myname, args.argv[0]); + } else if (args.shell && *args.shell) { + argv = argv_split(args.shell, CHARS_SPACE); + argv_add(argv, args.command, (char *) 0); + argv_terminate(argv); + execvp(argv->argv[0], argv->argv); + msg_fatal("%s: execvp %s: %m", myname, argv->argv[0]); + } else { + exec_command(args.command); + } + /* NOTREACHED */ + default: /* parent */ + if (close(sockfd[0])) + msg_warn("close: %m"); + stream = vstream_fdopen(sockfd[1], flags); + stream->waitpid_fn = args.waitpid_fn; + stream->pid = pid; + return (stream); + } +} + +/* vstream_pclose - close stream to child process */ + +int vstream_pclose(VSTREAM *stream) +{ + pid_t saved_pid = stream->pid; + VSTREAM_WAITPID_FN saved_waitpid_fn = stream->waitpid_fn; + pid_t pid; + WAIT_STATUS_T wait_status; + + /* + * Close the pipe. Don't trigger an alarm in vstream_fclose(). + */ + if (saved_pid == 0) + msg_panic("vstream_pclose: stream has no process"); + stream->pid = 0; + vstream_fclose(stream); + + /* + * Reap the child exit status. + */ + do { + if (saved_waitpid_fn != 0) + pid = saved_waitpid_fn(saved_pid, &wait_status, 0); + else + pid = waitpid(saved_pid, &wait_status, 0); + } while (pid == -1 && errno == EINTR); + return (pid == -1 ? -1 : + WIFSIGNALED(wait_status) ? WTERMSIG(wait_status) : + WEXITSTATUS(wait_status)); +} + +#ifdef TEST + +#include <fcntl.h> +#include <vstring.h> +#include <vstring_vstream.h> + + /* + * Test program. Run a command and copy lines one by one. + */ +int main(int argc, char **argv) +{ + VSTRING *buf = vstring_alloc(100); + VSTREAM *stream; + int status; + + /* + * Sanity check. + */ + if (argc < 2) + msg_fatal("usage: %s 'command'", argv[0]); + + /* + * Open stream to child process. + */ + if ((stream = vstream_popen(O_RDWR, + VSTREAM_POPEN_ARGV, argv + 1, + VSTREAM_POPEN_END)) == 0) + msg_fatal("vstream_popen: %m"); + + /* + * Copy loop, one line at a time. + */ + while (vstring_fgets(buf, stream) != 0) { + if (vstream_fwrite(VSTREAM_OUT, vstring_str(buf), VSTRING_LEN(buf)) + != VSTRING_LEN(buf)) + msg_fatal("vstream_fwrite: %m"); + if (vstream_fflush(VSTREAM_OUT) != 0) + msg_fatal("vstream_fflush: %m"); + if (vstring_fgets(buf, VSTREAM_IN) == 0) + break; + if (vstream_fwrite(stream, vstring_str(buf), VSTRING_LEN(buf)) + != VSTRING_LEN(buf)) + msg_fatal("vstream_fwrite: %m"); + } + + /* + * Cleanup. + */ + vstring_free(buf); + if ((status = vstream_pclose(stream)) != 0) + msg_warn("exit status: %d", status); + + exit(status); +} + +#endif diff --git a/src/util/vstream_test.in b/src/util/vstream_test.in new file mode 100644 index 0000000..b6687c8 --- /dev/null +++ b/src/util/vstream_test.in @@ -0,0 +1,3 @@ +abcdef +ghijkl +mnopqr diff --git a/src/util/vstream_test.ref b/src/util/vstream_test.ref new file mode 100644 index 0000000..eaa9952 --- /dev/null +++ b/src/util/vstream_test.ref @@ -0,0 +1,41 @@ +buffer size test: copy text with 1 buffer size, ignore requests to shrink +abcdef +actual read/write buffer sizes: 1/1 + +buffer size test: copy text with 2 buffer size, ignore requests to shrink +ghijkl +actual read/write buffer sizes: 2/2 + +buffer size test: copy text with 1 buffer size, ignore requests to shrink +mnopqr +actual read/write buffer sizes: 2/2 + +formatting test: print a number +1234567890 + +memory stream test prep: prefill the VSTRING +VSTRING content length: 8/8, content: 01234567 + +memory stream test: open the VSTRING for writing, overwrite, close +initial memory VSTREAM write offset: 0/8 +final memory VSTREAM write offset: 5/8 +VSTRING content length: 5/8, content: hallo + +memory stream test: open the VSTRING for append, write multiple, then overwrite 1 +initial memory VSTREAM write offset: 5/8 +final memory VSTREAM write offset: 11/16 +VSTRING content length: 11/16, content: hallo world + +replace second character and close +VSTRING content length: 11/16, content: hello world + +memory stream test: open VSTRING for reading, then read +initial memory VSTREAM read offset: 0/11 +reading memory VSTREAM: hello world +final memory VSTREAM read offset: 11/11 +seeking to offset 12 should work: PASS +VSTREAM_GETC should return VSTREAM_EOF +PASS +final memory VSTREAM read offset: 12/11 +VSTRING content length: 11/16, content: hello world + diff --git a/src/util/vstream_tweak.c b/src/util/vstream_tweak.c new file mode 100644 index 0000000..7100bc6 --- /dev/null +++ b/src/util/vstream_tweak.c @@ -0,0 +1,168 @@ +/*++ +/* NAME +/* vstream_tweak 3 +/* SUMMARY +/* performance tweaks +/* SYNOPSIS +/* #include <vstream.h> +/* +/* VSTREAM *vstream_tweak_sock(stream) +/* VSTREAM *stream; +/* +/* VSTREAM *vstream_tweak_tcp(stream) +/* VSTREAM *stream; +/* DESCRIPTION +/* vstream_tweak_sock() does a best effort to boost your +/* network performance on the specified generic stream. +/* +/* vstream_tweak_tcp() does a best effort to boost your +/* Internet performance on the specified TCP stream. +/* +/* Arguments: +/* .IP stream +/* The stream being boosted. +/* DIAGNOSTICS +/* Panics: interface violations. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <errno.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstream.h> + +/* Application-specific. */ + +#ifdef HAS_IPV6 +#define SOCKADDR_STORAGE struct sockaddr_storage +#else +#define SOCKADDR_STORAGE struct sockaddr +#endif + +/* vstream_tweak_sock - boost your generic network performance */ + +int vstream_tweak_sock(VSTREAM *fp) +{ + SOCKADDR_STORAGE ss; + struct sockaddr *sa = (struct sockaddr *) &ss; + SOCKADDR_SIZE sa_length = sizeof(ss); + int ret; + + /* + * If the caller doesn't know if this socket is AF_LOCAL, AF_INET, etc., + * figure it out for them. + */ + if ((ret = getsockname(vstream_fileno(fp), sa, &sa_length)) >= 0) { + switch (sa->sa_family) { +#ifdef AF_INET6 + case AF_INET6: +#endif + case AF_INET: + ret = vstream_tweak_tcp(fp); + break; + } + } + return (ret); +} + +/* vstream_tweak_tcp - boost your TCP performance */ + +int vstream_tweak_tcp(VSTREAM *fp) +{ + const char *myname = "vstream_tweak_tcp"; + int mss = 0; + SOCKOPT_SIZE mss_len = sizeof(mss); + int err; + + /* + * Avoid Nagle delays when VSTREAM buffers are smaller than the MSS. + * + * Forcing TCP_NODELAY to be "always on" would hurt performance in the + * common case where VSTREAM buffers are larger than the MSS. + * + * Instead we ask the kernel what the current MSS is, and take appropriate + * action. Linux <= 2.2 getsockopt(TCP_MAXSEG) always returns zero (or + * whatever value was stored last with setsockopt()). + * + * Some ancient FreeBSD kernels don't report 'host unreachable' errors with + * getsockopt(SO_ERROR), and then treat getsockopt(TCP_MAXSEG) as a NOOP, + * leaving the mss parameter value unchanged. To work around these two + * getsockopt() bugs we set mss = 0, which is a harmless value. + */ + if ((err = getsockopt(vstream_fileno(fp), IPPROTO_TCP, TCP_MAXSEG, + (void *) &mss, &mss_len)) < 0 + && errno != ECONNRESET) { + msg_warn("%s: getsockopt TCP_MAXSEG: %m", myname); + return (err); + } + if (msg_verbose) + msg_info("%s: TCP_MAXSEG %d", myname, mss); + + /* + * Fix for recent Postfix versions: increase the VSTREAM buffer size if + * it is smaller than the MSS. Note: the MSS may change when the route + * changes and IP path MTU discovery is turned on, so we choose a + * somewhat larger buffer. + * + * Note: as of 20120527, the CA_VSTREAM_CTL_BUFSIZE request can reduce the + * stream buffer size to less than VSTREAM_BUFSIZE, when the request is + * made before the first stream read or write operation. We don't want to + * reduce the buffer size. + * + * As of 20190820 we increase the mss size multiplier from 2x to 4x, because + * some LINUX loopback TCP stacks report an MSS of 21845 which is 3x + * smaller than the MTU of 65536. Even with a VSTREAM buffer 2x the + * reported MSS size, performance would suck due to Nagle or delayed ACK + * delays. + */ +#define EFF_BUFFER_SIZE(fp) (vstream_req_bufsize(fp) ? \ + vstream_req_bufsize(fp) : VSTREAM_BUFSIZE) + +#ifdef CA_VSTREAM_CTL_BUFSIZE + if (mss > EFF_BUFFER_SIZE(fp) / 4) { + if (mss < INT_MAX / 2) + mss *= 2; + if (mss < INT_MAX / 2) + mss *= 2; + vstream_control(fp, + CA_VSTREAM_CTL_BUFSIZE(mss), + CA_VSTREAM_CTL_END); + } + + /* + * Workaround for older Postfix versions: turn on TCP_NODELAY if the + * VSTREAM buffer size is smaller than the MSS. + */ +#else + if (mss > VSTREAM_BUFSIZE) { + int nodelay = 1; + + if ((err = setsockopt(vstream_fileno(fp), IPPROTO_TCP, TCP_NODELAY, + (void *) &nodelay, sizeof(nodelay))) < 0 + && errno != ECONNRESET) + msg_warn("%s: setsockopt TCP_NODELAY: %m", myname); + } +#endif + return (err); +} diff --git a/src/util/vstring.c b/src/util/vstring.c new file mode 100644 index 0000000..43897eb --- /dev/null +++ b/src/util/vstring.c @@ -0,0 +1,719 @@ +/*++ +/* NAME +/* vstring 3 +/* SUMMARY +/* arbitrary-length string manager +/* SYNOPSIS +/* #include <vstring.h> +/* +/* VSTRING *vstring_alloc(len) +/* ssize_t len; +/* +/* vstring_ctl(vp, type, value, ..., VSTRING_CTL_END) +/* VSTRING *vp; +/* int type; +/* +/* VSTRING *vstring_free(vp) +/* VSTRING *vp; +/* +/* char *vstring_str(vp) +/* VSTRING *vp; +/* +/* ssize_t VSTRING_LEN(vp) +/* VSTRING *vp; +/* +/* char *vstring_end(vp) +/* VSTRING *vp; +/* +/* void VSTRING_ADDCH(vp, ch) +/* VSTRING *vp; +/* int ch; +/* +/* int VSTRING_SPACE(vp, len) +/* VSTRING *vp; +/* ssize_t len; +/* +/* ssize_t vstring_avail(vp) +/* VSTRING *vp; +/* +/* VSTRING *vstring_truncate(vp, len) +/* VSTRING *vp; +/* ssize_t len; +/* +/* VSTRING *vstring_set_payload_size(vp, len) +/* VSTRING *vp; +/* ssize_t len; +/* +/* void VSTRING_RESET(vp) +/* VSTRING *vp; +/* +/* void VSTRING_TERMINATE(vp) +/* VSTRING *vp; +/* +/* void VSTRING_SKIP(vp) +/* VSTRING *vp; +/* +/* VSTRING *vstring_strcpy(vp, src) +/* VSTRING *vp; +/* const char *src; +/* +/* VSTRING *vstring_strncpy(vp, src, len) +/* VSTRING *vp; +/* const char *src; +/* ssize_t len; +/* +/* VSTRING *vstring_strcat(vp, src) +/* VSTRING *vp; +/* const char *src; +/* +/* VSTRING *vstring_strncat(vp, src, len) +/* VSTRING *vp; +/* const char *src; +/* ssize_t len; +/* +/* VSTRING *vstring_memcpy(vp, src, len) +/* VSTRING *vp; +/* const char *src; +/* ssize_t len; +/* +/* VSTRING *vstring_memcat(vp, src, len) +/* VSTRING *vp; +/* const char *src; +/* ssize_t len; +/* +/* char *vstring_memchr(vp, ch) +/* VSTRING *vp; +/* int ch; +/* +/* VSTRING *vstring_insert(vp, start, src, len) +/* VSTRING *vp; +/* ssize_t start; +/* const char *src; +/* ssize_t len; +/* +/* VSTRING *vstring_prepend(vp, src, len) +/* VSTRING *vp; +/* const char *src; +/* ssize_t len; +/* +/* VSTRING *vstring_sprintf(vp, format, ...) +/* VSTRING *vp; +/* const char *format; +/* +/* VSTRING *vstring_sprintf_append(vp, format, ...) +/* VSTRING *vp; +/* const char *format; +/* +/* VSTRING *vstring_sprintf_prepend(vp, format, ...) +/* VSTRING *vp; +/* const char *format; +/* +/* VSTRING *vstring_vsprintf(vp, format, ap) +/* VSTRING *vp; +/* const char *format; +/* va_list ap; +/* +/* VSTRING *vstring_vsprintf_append(vp, format, ap) +/* VSTRING *vp; +/* const char *format; +/* va_list ap; +/* AUXILIARY FUNCTIONS +/* char *vstring_export(vp) +/* VSTRING *vp; +/* +/* VSTRING *vstring_import(str) +/* char *str; +/* DESCRIPTION +/* The functions and macros in this module implement arbitrary-length +/* strings and common operations on those strings. The strings do not +/* need to be null terminated and may contain arbitrary binary data. +/* The strings manage their own memory and grow automatically when full. +/* The optional string null terminator does not add to the string length. +/* +/* vstring_alloc() allocates storage for a variable-length string +/* of at least "len" bytes. The minimal length is 1. The result +/* is a null-terminated string of length zero. +/* +/* vstring_ctl() gives additional control over VSTRING behavior. +/* The function takes a VSTRING pointer and a list of zero or +/* more macros with zer or more arguments, terminated with +/* CA_VSTRING_CTL_END which has none. +/* .IP "CA_VSTRING_CTL_MAXLEN(ssize_t len)" +/* Specifies a hard upper limit on a string's length. When the +/* length would be exceeded, the program simulates a memory +/* allocation problem (i.e. it terminates through msg_fatal()). +/* This functionality is currently unimplemented. +/* .IP "CA_VSTRING_CTL_EXACT (no argument)" +/* Allocate the requested amounts, instead of rounding up. +/* This should be used for tests only. +/* .IP "CA_VSTRING_CTL_END (no argument)" +/* Specifies the end of the argument list. Forgetting to terminate +/* the argument list may cause the program to crash. +/* .PP +/* VSTRING_SPACE() ensures that the named string has room for +/* "len" more characters. VSTRING_SPACE() is an unsafe macro +/* that either returns zero or never returns. +/* +/* vstring_avail() returns the number of bytes that can be placed +/* into the buffer before the buffer would need to grow. +/* +/* vstring_free() reclaims storage for a variable-length string. +/* It conveniently returns a null pointer. +/* +/* vstring_str() is a macro that returns the string value +/* of a variable-length string. It is a safe macro that +/* evaluates its argument only once. +/* +/* VSTRING_LEN() is a macro that returns the current length of +/* its argument (i.e. the distance from the start of the string +/* to the current write position). VSTRING_LEN() is an unsafe macro +/* that evaluates its argument more than once. +/* +/* vstring_end() is a macro that returns the current write position of +/* its argument. It is a safe macro that evaluates its argument only once. +/* +/* VSTRING_ADDCH() adds a character to a variable-length string +/* and extends the string if it fills up. \fIvs\fP is a pointer +/* to a VSTRING structure; \fIch\fP the character value to be written. +/* The result is the written character. +/* Note that VSTRING_ADDCH() is an unsafe macro that evaluates some +/* arguments more than once. The result is NOT null-terminated. +/* +/* vstring_truncate() truncates the named string to the specified +/* length. If length is negative, the trailing portion is kept. +/* The operation has no effect when the string is shorter. +/* The string is not null-terminated. +/* +/* vstring_set_payload_size() sets the number of 'used' bytes +/* in the named buffer's metadata. This determines the buffer +/* write position and the VSTRING_LEN() result. The payload +/* size must be within the closed range [0, number of allocated +/* bytes]. The typical usage is to request buffer space with +/* VSTRING_SPACE(), to use some non-VSTRING operations to write +/* to the buffer, and to call vstring_set_payload_size() to +/* update buffer metadata, perhaps followed by VSTRING_TERMINATE(). +/* +/* VSTRING_RESET() is a macro that resets the write position of its +/* string argument to the very beginning. Note that VSTRING_RESET() +/* is an unsafe macro that evaluates some arguments more than once. +/* The result is NOT null-terminated. +/* +/* VSTRING_TERMINATE() null-terminates its string argument. +/* VSTRING_TERMINATE() is an unsafe macro that evaluates some +/* arguments more than once. +/* VSTRING_TERMINATE() does not return an interesting result. +/* +/* VSTRING_SKIP() is a macro that moves the write position to the first +/* null byte after the current write position. VSTRING_SKIP() is an unsafe +/* macro that evaluates some arguments more than once. +/* +/* vstring_strcpy() copies a null-terminated string to a variable-length +/* string. \fIsrc\fP provides the data to be copied; \fIvp\fP is the +/* target and result value. The result is null-terminated. +/* +/* vstring_strncpy() copies at most \fIlen\fR characters. Otherwise it is +/* identical to vstring_strcpy(). +/* +/* vstring_strcat() appends a null-terminated string to a variable-length +/* string. \fIsrc\fP provides the data to be copied; \fIvp\fP is the +/* target and result value. The result is null-terminated. +/* +/* vstring_strncat() copies at most \fIlen\fR characters. Otherwise it is +/* identical to vstring_strcat(). +/* +/* vstring_memcpy() copies \fIlen\fR bytes to a variable-length string. +/* \fIsrc\fP provides the data to be copied; \fIvp\fP is the +/* target and result value. The result is not null-terminated. +/* +/* vstring_memcat() appends \fIlen\fR bytes to a variable-length string. +/* \fIsrc\fP provides the data to be copied; \fIvp\fP is the +/* target and result value. The result is not null-terminated. +/* +/* vstring_memchr() locates a byte in a variable-length string. +/* +/* vstring_insert() inserts a buffer content into a variable-length +/* string at the specified start position. The result is +/* null-terminated. +/* +/* vstring_prepend() prepends a buffer content to a variable-length +/* string. The result is null-terminated. +/* +/* vstring_sprintf() produces a formatted string according to its +/* \fIformat\fR argument. See vstring_vsprintf() for details. +/* +/* vstring_sprintf_append() is like vstring_sprintf(), but appends +/* to the end of the result buffer. +/* +/* vstring_sprintf_append() is like vstring_sprintf(), but prepends +/* to the beginning of the result buffer. +/* +/* vstring_vsprintf() returns a null-terminated string according to +/* the \fIformat\fR argument. It understands the s, c, d, u, +/* o, x, X, p, e, f and g format types, the l modifier, field width +/* and precision, sign, and null or space padding. This module +/* can format strings as large as available memory permits. +/* +/* vstring_vsprintf_append() is like vstring_vsprintf(), but appends +/* to the end of the result buffer. +/* +/* In addition to stdio-like format specifiers, vstring_vsprintf() +/* recognizes %m and expands it to the corresponding errno text. +/* +/* vstring_export() extracts the string value from a VSTRING. +/* The VSTRING is destroyed. The result should be passed to myfree(). +/* +/* vstring_import() takes a `bare' string and converts it to +/* a VSTRING. The string argument must be obtained from mymalloc(). +/* The string argument is not copied. +/* DIAGNOSTICS +/* Fatal errors: memory allocation failure. +/* BUGS +/* Auto-resizing may change the address of the string data in +/* a vstring structure. Beware of dangling pointers. +/* HISTORY +/* .ad +/* .fi +/* A vstring module appears in the UNPROTO software by Wietse Venema. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System libraries. */ + +#include <sys_defs.h> +#include <stddef.h> +#include <stdlib.h> /* 44BSD stdarg.h uses abort() */ +#include <stdarg.h> +#include <string.h> + +/* Utility library. */ + +#define VSTRING_INTERNAL + +#include "mymalloc.h" +#include "msg.h" +#include "vbuf_print.h" +#include "vstring.h" + +/* vstring_extend - variable-length string buffer extension policy */ + +static void vstring_extend(VBUF *bp, ssize_t incr) +{ + size_t used = bp->ptr - bp->data; + ssize_t new_len; + + /* + * Note: vp->vbuf.len is the current buffer size (both on entry and on + * exit of this routine). We round up the increment size to the buffer + * size to avoid silly little buffer increments. With really large + * strings we might want to abandon the length doubling strategy, and go + * to fixed increments. + * + * The length overflow tests here and in vstring_alloc() should protect us + * against all length overflow problems within vstring library routines. + * + * Safety net: add a gratuitous null terminator so that C-style string + * operations won't scribble past the end. + */ + if ((bp->flags & VSTRING_FLAG_EXACT) == 0 && bp->len > incr) + incr = bp->len; + if (bp->len > SSIZE_T_MAX - incr - 1) + msg_fatal("vstring_extend: length overflow"); + new_len = bp->len + incr; + bp->data = (unsigned char *) myrealloc((void *) bp->data, new_len + 1); + bp->data[new_len] = 0; + bp->len = new_len; + bp->ptr = bp->data + used; + bp->cnt = bp->len - used; +} + +/* vstring_buf_get_ready - vbuf callback for read buffer empty condition */ + +static int vstring_buf_get_ready(VBUF *unused_buf) +{ + return (VBUF_EOF); /* be VSTREAM-friendly */ +} + +/* vstring_buf_put_ready - vbuf callback for write buffer full condition */ + +static int vstring_buf_put_ready(VBUF *bp) +{ + vstring_extend(bp, 1); + return (0); +} + +/* vstring_buf_space - vbuf callback to reserve space */ + +static int vstring_buf_space(VBUF *bp, ssize_t len) +{ + ssize_t need; + + if (len < 0) + msg_panic("vstring_buf_space: bad length %ld", (long) len); + if ((need = len - bp->cnt) > 0) + vstring_extend(bp, need); + return (0); +} + +/* vstring_alloc - create variable-length string */ + +VSTRING *vstring_alloc(ssize_t len) +{ + VSTRING *vp; + + /* + * Safety net: add a gratuitous null terminator so that C-style string + * operations won't scribble past the end. + */ + if (len < 1 || len > SSIZE_T_MAX - 1) + msg_panic("vstring_alloc: bad length %ld", (long) len); + vp = (VSTRING *) mymalloc(sizeof(*vp)); + vp->vbuf.flags = 0; + vp->vbuf.len = 0; + vp->vbuf.data = (unsigned char *) mymalloc(len + 1); + vp->vbuf.data[len] = 0; + vp->vbuf.len = len; + VSTRING_RESET(vp); + vp->vbuf.data[0] = 0; + vp->vbuf.get_ready = vstring_buf_get_ready; + vp->vbuf.put_ready = vstring_buf_put_ready; + vp->vbuf.space = vstring_buf_space; + return (vp); +} + +/* vstring_free - destroy variable-length string */ + +VSTRING *vstring_free(VSTRING *vp) +{ + if (vp->vbuf.data) + myfree((void *) vp->vbuf.data); + myfree((void *) vp); + return (0); +} + +/* vstring_ctl - modify memory management policy */ + +void vstring_ctl(VSTRING *vp,...) +{ + va_list ap; + int code; + + va_start(ap, vp); + while ((code = va_arg(ap, int)) != VSTRING_CTL_END) { + switch (code) { + default: + msg_panic("vstring_ctl: unknown code: %d", code); + case VSTRING_CTL_EXACT: + vp->vbuf.flags |= VSTRING_FLAG_EXACT; + break; + } + } + va_end(ap); +} + +/* vstring_truncate - truncate string */ + +VSTRING *vstring_truncate(VSTRING *vp, ssize_t len) +{ + ssize_t move; + + if (len < 0) { + len = (-len); + if ((move = VSTRING_LEN(vp) - len) > 0) + memmove(vstring_str(vp), vstring_str(vp) + move, len); + } + if (len < VSTRING_LEN(vp)) + VSTRING_AT_OFFSET(vp, len); + return (vp); +} + +/* vstring_set_payload_size - public version of VSTRING_AT_OFFSET */ + +VSTRING *vstring_set_payload_size(VSTRING *vp, ssize_t len) +{ + if (len < 0 || len > vp->vbuf.len) + msg_panic("vstring_set_payload_size: invalid offset: %ld", (long) len); + if (vp->vbuf.data[vp->vbuf.len] != 0) + msg_panic("vstring_set_payload_size: no safety null byte"); + VSTRING_AT_OFFSET(vp, len); + return (vp); +} + +/* vstring_strcpy - copy string */ + +VSTRING *vstring_strcpy(VSTRING *vp, const char *src) +{ + VSTRING_RESET(vp); + + while (*src) { + VSTRING_ADDCH(vp, *src); + src++; + } + VSTRING_TERMINATE(vp); + return (vp); +} + +/* vstring_strncpy - copy string of limited length */ + +VSTRING *vstring_strncpy(VSTRING *vp, const char *src, ssize_t len) +{ + VSTRING_RESET(vp); + + while (len-- > 0 && *src) { + VSTRING_ADDCH(vp, *src); + src++; + } + VSTRING_TERMINATE(vp); + return (vp); +} + +/* vstring_strcat - append string */ + +VSTRING *vstring_strcat(VSTRING *vp, const char *src) +{ + while (*src) { + VSTRING_ADDCH(vp, *src); + src++; + } + VSTRING_TERMINATE(vp); + return (vp); +} + +/* vstring_strncat - append string of limited length */ + +VSTRING *vstring_strncat(VSTRING *vp, const char *src, ssize_t len) +{ + while (len-- > 0 && *src) { + VSTRING_ADDCH(vp, *src); + src++; + } + VSTRING_TERMINATE(vp); + return (vp); +} + +/* vstring_memcpy - copy buffer of limited length */ + +VSTRING *vstring_memcpy(VSTRING *vp, const char *src, ssize_t len) +{ + VSTRING_RESET(vp); + + VSTRING_SPACE(vp, len); + memcpy(vstring_str(vp), src, len); + VSTRING_AT_OFFSET(vp, len); + return (vp); +} + +/* vstring_memcat - append buffer of limited length */ + +VSTRING *vstring_memcat(VSTRING *vp, const char *src, ssize_t len) +{ + VSTRING_SPACE(vp, len); + memcpy(vstring_end(vp), src, len); + len += VSTRING_LEN(vp); + VSTRING_AT_OFFSET(vp, len); + return (vp); +} + +/* vstring_memchr - locate byte in buffer */ + +char *vstring_memchr(VSTRING *vp, int ch) +{ + unsigned char *cp; + + for (cp = (unsigned char *) vstring_str(vp); cp < (unsigned char *) vstring_end(vp); cp++) + if (*cp == ch) + return ((char *) cp); + return (0); +} + +/* vstring_insert - insert text into string */ + +VSTRING *vstring_insert(VSTRING *vp, ssize_t start, const char *buf, ssize_t len) +{ + ssize_t new_len; + + /* + * Sanity check. + */ + if (start < 0 || start >= VSTRING_LEN(vp)) + msg_panic("vstring_insert: bad start %ld", (long) start); + if (len < 0) + msg_panic("vstring_insert: bad length %ld", (long) len); + + /* + * Move the existing content and copy the new content. + */ + new_len = VSTRING_LEN(vp) + len; + VSTRING_SPACE(vp, len); + memmove(vstring_str(vp) + start + len, vstring_str(vp) + start, + VSTRING_LEN(vp) - start); + memcpy(vstring_str(vp) + start, buf, len); + VSTRING_AT_OFFSET(vp, new_len); + VSTRING_TERMINATE(vp); + return (vp); +} + +/* vstring_prepend - prepend text to string */ + +VSTRING *vstring_prepend(VSTRING *vp, const char *buf, ssize_t len) +{ + ssize_t new_len; + + /* + * Sanity check. + */ + if (len < 0) + msg_panic("vstring_prepend: bad length %ld", (long) len); + + /* + * Move the existing content and copy the new content. + */ + new_len = VSTRING_LEN(vp) + len; + VSTRING_SPACE(vp, len); + memmove(vstring_str(vp) + len, vstring_str(vp), VSTRING_LEN(vp)); + memcpy(vstring_str(vp), buf, len); + VSTRING_AT_OFFSET(vp, new_len); + VSTRING_TERMINATE(vp); + return (vp); +} + +/* vstring_export - VSTRING to bare string */ + +char *vstring_export(VSTRING *vp) +{ + char *cp; + + cp = (char *) vp->vbuf.data; + vp->vbuf.data = 0; + myfree((void *) vp); + return (cp); +} + +/* vstring_import - bare string to vstring */ + +VSTRING *vstring_import(char *str) +{ + VSTRING *vp; + ssize_t len; + + vp = (VSTRING *) mymalloc(sizeof(*vp)); + len = strlen(str); + vp->vbuf.flags = 0; + vp->vbuf.len = 0; + vp->vbuf.data = (unsigned char *) str; + vp->vbuf.len = len + 1; + VSTRING_AT_OFFSET(vp, len); + vp->vbuf.get_ready = vstring_buf_get_ready; + vp->vbuf.put_ready = vstring_buf_put_ready; + vp->vbuf.space = vstring_buf_space; + return (vp); +} + +/* vstring_sprintf - formatted string */ + +VSTRING *vstring_sprintf(VSTRING *vp, const char *format,...) +{ + va_list ap; + + va_start(ap, format); + vp = vstring_vsprintf(vp, format, ap); + va_end(ap); + return (vp); +} + +/* vstring_vsprintf - format string, vsprintf-like interface */ + +VSTRING *vstring_vsprintf(VSTRING *vp, const char *format, va_list ap) +{ + VSTRING_RESET(vp); + vbuf_print(&vp->vbuf, format, ap); + VSTRING_TERMINATE(vp); + return (vp); +} + +/* vstring_sprintf_append - append formatted string */ + +VSTRING *vstring_sprintf_append(VSTRING *vp, const char *format,...) +{ + va_list ap; + + va_start(ap, format); + vp = vstring_vsprintf_append(vp, format, ap); + va_end(ap); + return (vp); +} + +/* vstring_vsprintf_append - format + append string, vsprintf-like interface */ + +VSTRING *vstring_vsprintf_append(VSTRING *vp, const char *format, va_list ap) +{ + vbuf_print(&vp->vbuf, format, ap); + VSTRING_TERMINATE(vp); + return (vp); +} + +/* vstring_sprintf_prepend - format + prepend string, vsprintf-like interface */ + +VSTRING *vstring_sprintf_prepend(VSTRING *vp, const char *format,...) +{ + va_list ap; + ssize_t old_len = VSTRING_LEN(vp); + ssize_t result_len; + + /* Construct: old|new|free */ + va_start(ap, format); + vp = vstring_vsprintf_append(vp, format, ap); + va_end(ap); + result_len = VSTRING_LEN(vp); + + /* Construct: old|new|old|free */ + VSTRING_SPACE(vp, old_len); + vstring_memcat(vp, vstring_str(vp), old_len); + + /* Construct: new|old|free */ + memmove(vstring_str(vp), vstring_str(vp) + old_len, result_len); + VSTRING_AT_OFFSET(vp, result_len); + VSTRING_TERMINATE(vp); + return (vp); +} + +#ifdef TEST + + /* + * Test program - concatenate all command-line arguments into one string. + */ +#include <stdio.h> + +int main(int argc, char **argv) +{ + VSTRING *vp = vstring_alloc(1); + int n; + + /* + * Report the location of the gratuitous null terminator. + */ + for (n = 1; n <= 5; n++) { + VSTRING_ADDCH(vp, 'x'); + printf("payload/buffer size %d/%ld, strlen() %ld\n", + n, (long) (vp)->vbuf.len, (long) strlen(vstring_str(vp))); + } + + VSTRING_RESET(vp); + while (argc-- > 0) { + vstring_strcat(vp, *argv++); + vstring_strcat(vp, "."); + } + printf("argv concatenated: %s\n", vstring_str(vp)); + vstring_free(vp); + return (0); +} + +#endif diff --git a/src/util/vstring.h b/src/util/vstring.h new file mode 100644 index 0000000..49fd960 --- /dev/null +++ b/src/util/vstring.h @@ -0,0 +1,125 @@ +#ifndef _VSTRING_H_INCLUDED_ +#define _VSTRING_H_INCLUDED_ + +/*++ +/* NAME +/* vstring 3h +/* SUMMARY +/* arbitrary-length string manager +/* SYNOPSIS +/* #include "vstring.h" +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include <stdarg.h> + + /* + * Utility library. + */ +#include <vbuf.h> +#include <check_arg.h> + + /* + * We can't allow bare VBUFs in the interface, because VSTRINGs have a + * specific initialization and destruction sequence. + */ +typedef struct VSTRING { + VBUF vbuf; +} VSTRING; + +extern VSTRING *vstring_alloc(ssize_t); +extern void vstring_ctl(VSTRING *,...); +extern VSTRING *vstring_truncate(VSTRING *, ssize_t); +extern VSTRING *vstring_set_payload_size(VSTRING *, ssize_t); +extern VSTRING *vstring_free(VSTRING *); +extern VSTRING *vstring_strcpy(VSTRING *, const char *); +extern VSTRING *vstring_strncpy(VSTRING *, const char *, ssize_t); +extern VSTRING *vstring_strcat(VSTRING *, const char *); +extern VSTRING *vstring_strncat(VSTRING *, const char *, ssize_t); +extern VSTRING *vstring_memcpy(VSTRING *, const char *, ssize_t); +extern VSTRING *vstring_memcat(VSTRING *, const char *, ssize_t); +extern char *vstring_memchr(VSTRING *, int); +extern VSTRING *vstring_insert(VSTRING *, ssize_t, const char *, ssize_t); +extern VSTRING *vstring_prepend(VSTRING *, const char *, ssize_t); +extern VSTRING *PRINTFLIKE(2, 3) vstring_sprintf(VSTRING *, const char *,...); +extern VSTRING *PRINTFLIKE(2, 3) vstring_sprintf_append(VSTRING *, const char *,...); +extern VSTRING *PRINTFLIKE(2, 3) vstring_sprintf_prepend(VSTRING *, const char *,...); +extern char *vstring_export(VSTRING *); +extern VSTRING *vstring_import(char *); + +/* Legacy API: constant plus type-unchecked argument. */ +#define VSTRING_CTL_EXACT 2 +#define VSTRING_CTL_END 0 + +/* Safer API: type-checked arguments. */ +#define CA_VSTRING_CTL_END VSTRING_CTL_END +#define CA_VSTRING_CTL_EXACT VSTRING_CTL_EXACT + +CHECK_VAL_HELPER_DCL(VSTRING_CTL, ssize_t); + +/* Flags 24..31 are reserved for VSTRING. */ +#define VSTRING_FLAG_EXACT (1<<24) /* exact allocation for tests */ +#define VSTRING_FLAG_MASK (255 << 24) + + /* + * Macros. Unsafe macros have UPPERCASE names. + */ +#define VSTRING_SPACE(vp, len) ((vp)->vbuf.space(&(vp)->vbuf, (len))) +#define vstring_str(vp) ((char *) (vp)->vbuf.data) +#define VSTRING_LEN(vp) ((ssize_t) ((vp)->vbuf.ptr - (vp)->vbuf.data)) +#define vstring_end(vp) ((char *) (vp)->vbuf.ptr) +#define VSTRING_TERMINATE(vp) do { \ + *(vp)->vbuf.ptr = 0; \ + } while (0) +#define VSTRING_RESET(vp) do { \ + (vp)->vbuf.ptr = (vp)->vbuf.data; \ + (vp)->vbuf.cnt = (vp)->vbuf.len; \ + } while (0) +#define VSTRING_ADDCH(vp, ch) VBUF_PUT(&(vp)->vbuf, ch) +#define VSTRING_SKIP(vp) do { \ + while ((vp)->vbuf.cnt > 0 && *(vp)->vbuf.ptr) \ + (vp)->vbuf.ptr++, (vp)->vbuf.cnt--; \ + } while (0) +#define vstring_avail(vp) ((vp)->vbuf.cnt) + + /* + * The following macro is not part of the public interface, because it can + * really screw up a buffer by positioning past allocated memory. + */ +#ifdef VSTRING_INTERNAL +#define VSTRING_AT_OFFSET(vp, offset) do { \ + (vp)->vbuf.ptr = (vp)->vbuf.data + (offset); \ + (vp)->vbuf.cnt = (vp)->vbuf.len - (offset); \ + } while (0) +#endif + +extern VSTRING *vstring_vsprintf(VSTRING *, const char *, va_list); +extern VSTRING *vstring_vsprintf_append(VSTRING *, const char *, va_list); + +/* BUGS +/* Auto-resizing may change the address of the string data in +/* a vstring structure. Beware of dangling pointers. +/* HISTORY +/* .ad +/* .fi +/* A vstring module appears in the UNPROTO software by Wietse Venema. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/vstring_test.ref b/src/util/vstring_test.ref new file mode 100644 index 0000000..54c54cf --- /dev/null +++ b/src/util/vstring_test.ref @@ -0,0 +1,6 @@ +payload/buffer size 1/1, strlen() 1 +payload/buffer size 2/2, strlen() 2 +payload/buffer size 3/4, strlen() 4 +payload/buffer size 4/4, strlen() 4 +payload/buffer size 5/8, strlen() 8 +argv concatenated: ./vstring.one.two.three. diff --git a/src/util/vstring_vstream.c b/src/util/vstring_vstream.c new file mode 100644 index 0000000..451cc50 --- /dev/null +++ b/src/util/vstring_vstream.c @@ -0,0 +1,267 @@ +/*++ +/* NAME +/* vstring_vstream 3 +/* SUMMARY +/* auto-resizing string library, standard I/O interface +/* SYNOPSIS +/* #include <vstring_vstream.h> +/* +/* int vstring_get_flags(vp, fp, flags) +/* VSTRING *vp; +/* VSTREAM *fp; +/* int flags +/* +/* int vstring_get_flags_nonl(vp, fp, flags) +/* VSTRING *vp; +/* VSTREAM *fp; +/* int flags +/* +/* int vstring_get_flags_null(vp, fp, flags) +/* VSTRING *vp; +/* VSTREAM *fp; +/* int flags +/* +/* int vstring_get_flags_bound(vp, fp, flags, bound) +/* VSTRING *vp; +/* VSTREAM *fp; +/* ssize_t bound; +/* int flags +/* +/* int vstring_get_flags_nonl_bound(vp, fp, flags, bound) +/* VSTRING *vp; +/* VSTREAM *fp; +/* ssize_t bound; +/* int flags +/* +/* int vstring_get_flags_null_bound(vp, fp, flags, bound) +/* VSTRING *vp; +/* VSTREAM *fp; +/* ssize_t bound; +/* int flags +/* CONVENIENCE API +/* int vstring_get(vp, fp) +/* VSTRING *vp; +/* VSTREAM *fp; +/* +/* int vstring_get_nonl(vp, fp) +/* VSTRING *vp; +/* VSTREAM *fp; +/* +/* int vstring_get_null(vp, fp) +/* VSTRING *vp; +/* VSTREAM *fp; +/* +/* int vstring_get_bound(vp, fp, bound) +/* VSTRING *vp; +/* VSTREAM *fp; +/* ssize_t bound; +/* +/* int vstring_get_nonl_bound(vp, fp, bound) +/* VSTRING *vp; +/* VSTREAM *fp; +/* ssize_t bound; +/* +/* int vstring_get_null_bound(vp, fp, bound) +/* VSTRING *vp; +/* VSTREAM *fp; +/* ssize_t bound; +/* DESCRIPTION +/* The routines in this module each read one newline or null-terminated +/* string from an input stream. In all cases the result is either the +/* last character read, typically the record terminator, or VSTREAM_EOF. +/* The flags argument is VSTRING_GET_FLAG_NONE (default) or +/* VSTRING_GET_FLAG_APPEND (append instead of overwrite). +/* +/* vstring_get_flags() reads one line from the named stream, including the +/* terminating newline character if present. +/* +/* vstring_get_flags_nonl() reads a line from the named stream and strips +/* the trailing newline character. +/* +/* vstring_get_flags_null() reads a null-terminated string from the named +/* stream. +/* +/* the vstring_get_flags<whatever>_bound() routines read no more +/* than \fIbound\fR characters. Otherwise they behave like the +/* unbounded versions documented above. +/* +/* The functions without _flags in their name accept the same +/* arguments except flags. These functions use the default +/* flags value. +/* DIAGNOSTICS +/* Fatal errors: memory allocation failure. +/* Panic: improper string bound. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <stdio.h> +#include <string.h> + +/* Application-specific. */ + +#include "msg.h" +#include "vstring.h" +#include "vstream.h" +#include "vstring_vstream.h" + + /* + * Macro to return the last character added to a VSTRING, for consistency. + */ +#define VSTRING_GET_RESULT(vp, baselen) \ + (VSTRING_LEN(vp) > (base_len) ? vstring_end(vp)[-1] : VSTREAM_EOF) + +/* vstring_get_flags - read line from file, keep newline */ + +int vstring_get_flags(VSTRING *vp, VSTREAM *fp, int flags) +{ + int c; + ssize_t base_len; + + if ((flags & VSTRING_GET_FLAG_APPEND) == 0) + VSTRING_RESET(vp); + base_len = VSTRING_LEN(vp); + while ((c = VSTREAM_GETC(fp)) != VSTREAM_EOF) { + VSTRING_ADDCH(vp, c); + if (c == '\n') + break; + } + VSTRING_TERMINATE(vp); + return (VSTRING_GET_RESULT(vp, baselen)); +} + +/* vstring_get_flags_nonl - read line from file, strip newline */ + +int vstring_get_flags_nonl(VSTRING *vp, VSTREAM *fp, int flags) +{ + int c; + ssize_t base_len; + + if ((flags & VSTRING_GET_FLAG_APPEND) == 0) + VSTRING_RESET(vp); + base_len = VSTRING_LEN(vp); + while ((c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != '\n') + VSTRING_ADDCH(vp, c); + VSTRING_TERMINATE(vp); + return (c == '\n' ? c : VSTRING_GET_RESULT(vp, baselen)); +} + +/* vstring_get_flags_null - read null-terminated string from file */ + +int vstring_get_flags_null(VSTRING *vp, VSTREAM *fp, int flags) +{ + int c; + ssize_t base_len; + + if ((flags & VSTRING_GET_FLAG_APPEND) == 0) + VSTRING_RESET(vp); + base_len = VSTRING_LEN(vp); + while ((c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != 0) + VSTRING_ADDCH(vp, c); + VSTRING_TERMINATE(vp); + return (c == 0 ? c : VSTRING_GET_RESULT(vp, baselen)); +} + +/* vstring_get_flags_bound - read line from file, keep newline, up to bound */ + +int vstring_get_flags_bound(VSTRING *vp, VSTREAM *fp, int flags, + ssize_t bound) +{ + int c; + ssize_t base_len; + + if (bound <= 0) + msg_panic("vstring_get_bound: invalid bound %ld", (long) bound); + + if ((flags & VSTRING_GET_FLAG_APPEND) == 0) + VSTRING_RESET(vp); + base_len = VSTRING_LEN(vp); + while (bound-- > 0 && (c = VSTREAM_GETC(fp)) != VSTREAM_EOF) { + VSTRING_ADDCH(vp, c); + if (c == '\n') + break; + } + VSTRING_TERMINATE(vp); + return (VSTRING_GET_RESULT(vp, baselen)); +} + +/* vstring_get_flags_nonl_bound - read line from file, strip newline, up to bound */ + +int vstring_get_flags_nonl_bound(VSTRING *vp, VSTREAM *fp, int flags, + ssize_t bound) +{ + int c; + ssize_t base_len; + + if (bound <= 0) + msg_panic("vstring_get_nonl_bound: invalid bound %ld", (long) bound); + + if ((flags & VSTRING_GET_FLAG_APPEND) == 0) + VSTRING_RESET(vp); + base_len = VSTRING_LEN(vp); + while (bound-- > 0 && (c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != '\n') + VSTRING_ADDCH(vp, c); + VSTRING_TERMINATE(vp); + return (c == '\n' ? c : VSTRING_GET_RESULT(vp, baselen)); +} + +/* vstring_get_flags_null_bound - read null-terminated string from file */ + +int vstring_get_flags_null_bound(VSTRING *vp, VSTREAM *fp, int flags, + ssize_t bound) +{ + int c; + ssize_t base_len; + + if (bound <= 0) + msg_panic("vstring_get_null_bound: invalid bound %ld", (long) bound); + + if ((flags & VSTRING_GET_FLAG_APPEND) == 0) + VSTRING_RESET(vp); + base_len = VSTRING_LEN(vp); + while (bound-- > 0 && (c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != 0) + VSTRING_ADDCH(vp, c); + VSTRING_TERMINATE(vp); + return (c == 0 ? c : VSTRING_GET_RESULT(vp, baselen)); +} + +#ifdef TEST + + /* + * Proof-of-concept test program: copy the source to this module to stdout. + */ +#include <fcntl.h> + +#define TEXT_VSTREAM "vstring_vstream.c" + +int main(void) +{ + VSTRING *vp = vstring_alloc(1); + VSTREAM *fp; + + if ((fp = vstream_fopen(TEXT_VSTREAM, O_RDONLY, 0)) == 0) + msg_fatal("open %s: %m", TEXT_VSTREAM); + while (vstring_fgets(vp, fp)) + vstream_fprintf(VSTREAM_OUT, "%s", vstring_str(vp)); + vstream_fclose(fp); + vstream_fflush(VSTREAM_OUT); + vstring_free(vp); + return (0); +} + +#endif diff --git a/src/util/vstring_vstream.h b/src/util/vstring_vstream.h new file mode 100644 index 0000000..e0dc801 --- /dev/null +++ b/src/util/vstring_vstream.h @@ -0,0 +1,82 @@ +#ifndef _VSTRING_VSTREAM_H_INCLUDED_ +#define _VSTRING_VSTREAM_H_INCLUDED_ + +/*++ +/* NAME +/* vstring_vstream 3h +/* SUMMARY +/* auto-resizing string library +/* SYNOPSIS +/* #include <vstring_vstream.h> +/* DESCRIPTION + + /* + * Utility library. + */ +#include <vstream.h> +#include <vstring.h> + + /* + * External interface. + */ +#define VSTRING_GET_FLAG_NONE (0) +#define VSTRING_GET_FLAG_APPEND (1<<1) /* append instead of overwrite */ + +extern int WARN_UNUSED_RESULT vstring_get_flags(VSTRING *, VSTREAM *, int); +extern int WARN_UNUSED_RESULT vstring_get_flags_nonl(VSTRING *, VSTREAM *, int); +extern int WARN_UNUSED_RESULT vstring_get_flags_null(VSTRING *, VSTREAM *, int); +extern int WARN_UNUSED_RESULT vstring_get_flags_bound(VSTRING *, VSTREAM *, int, ssize_t); +extern int WARN_UNUSED_RESULT vstring_get_flags_nonl_bound(VSTRING *, VSTREAM *, int, ssize_t); +extern int WARN_UNUSED_RESULT vstring_get_flags_null_bound(VSTRING *, VSTREAM *, int, ssize_t); + + /* + * Convenience aliases for most use cases. + */ +#define vstring_get(string, stream) \ + vstring_get_flags((string), (stream), VSTRING_GET_FLAG_NONE) +#define vstring_get_nonl(string, stream) \ + vstring_get_flags_nonl((string), (stream), VSTRING_GET_FLAG_NONE) +#define vstring_get_null(string, stream) \ + vstring_get_flags_null((string), (stream), VSTRING_GET_FLAG_NONE) + +#define vstring_get_bound(string, stream, size) \ + vstring_get_flags_bound((string), (stream), VSTRING_GET_FLAG_NONE, size) +#define vstring_get_nonl_bound(string, stream, size) \ + vstring_get_flags_nonl_bound((string), (stream), \ + VSTRING_GET_FLAG_NONE, size) +#define vstring_get_null_bound(string, stream, size) \ + vstring_get_flags_null_bound((string), (stream), \ + VSTRING_GET_FLAG_NONE, size) + + /* + * Backwards compatibility for code that still uses the vstring_fgets() + * interface. Unfortunately we can't change the macro name to upper case. + */ +#define vstring_fgets(s, p) \ + (vstring_get((s), (p)) == VSTREAM_EOF ? 0 : (s)) +#define vstring_fgets_nonl(s, p) \ + (vstring_get_nonl((s), (p)) == VSTREAM_EOF ? 0 : (s)) +#define vstring_fgets_null(s, p) \ + (vstring_get_null((s), (p)) == VSTREAM_EOF ? 0 : (s)) +#define vstring_fgets_bound(s, p, l) \ + (vstring_get_bound((s), (p), (l)) == VSTREAM_EOF ? 0 : (s)) +#define vstring_fgets_nonl_bound(s, p, l) \ + (vstring_get_nonl_bound((s), (p), (l)) == VSTREAM_EOF ? 0 : (s)) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/warn_stat.c b/src/util/warn_stat.c new file mode 100644 index 0000000..fd885d9 --- /dev/null +++ b/src/util/warn_stat.c @@ -0,0 +1,101 @@ +/*++ +/* NAME +/* warn_stat 3 +/* SUMMARY +/* baby-sit stat() error returns +/* SYNOPSIS +/* #include <warn_stat.h> +/* +/* int warn_stat(path, st) +/* const char *path; +/* struct stat *st; +/* +/* int warn_lstat(path, st) +/* const char *path; +/* struct stat *st; +/* +/* int warn_fstat(fd, st) +/* int fd; +/* struct stat *st; +/* DESCRIPTION +/* warn_stat(), warn_fstat() and warn_lstat() wrap the stat(), +/* fstat() and lstat() system calls with code that logs a +/* diagnosis for common error cases. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/stat.h> +#include <errno.h> + +/* Utility library. */ + +#include <msg.h> +#define WARN_STAT_INTERNAL +#include <warn_stat.h> + +/* diagnose_stat - log stat warning */ + +static void diagnose_stat(void) +{ + struct stat st; + + /* + * When *stat() fails with EOVERFLOW, and the interface uses 32-bit data + * types, suggest that the program be recompiled with larger data types. + */ +#ifdef EOVERFLOW + if (errno == EOVERFLOW && sizeof(st.st_size) == 4) { + msg_warn("this program was built for 32-bit file handles, " + "but some number does not fit in 32 bits"); + msg_warn("possible solution: recompile in 64-bit mode, or " + "recompile in 32-bit mode with 'large file' support"); + } +#endif +} + +/* warn_stat - stat with warning */ + +int warn_stat(const char *path, struct stat * st) +{ + int ret; + + ret = stat(path, st); + if (ret < 0) + diagnose_stat(); + return (ret); +} + +/* warn_lstat - lstat with warning */ + +int warn_lstat(const char *path, struct stat * st) +{ + int ret; + + ret = lstat(path, st); + if (ret < 0) + diagnose_stat(); + return (ret); +} + +/* warn_fstat - fstat with warning */ + +int warn_fstat(int fd, struct stat * st) +{ + int ret; + + ret = fstat(fd, st); + if (ret < 0) + diagnose_stat(); + return (ret); +} diff --git a/src/util/warn_stat.h b/src/util/warn_stat.h new file mode 100644 index 0000000..92ddce0 --- /dev/null +++ b/src/util/warn_stat.h @@ -0,0 +1,38 @@ +#ifndef _WARN_STAT_H_ +#define _WARN_STAT_H_ + +/*++ +/* NAME +/* warn_stat 3h +/* SUMMARY +/* baby-sit stat() error returns +/* SYNOPSIS +/* #include <warn_stat.h> +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +#ifndef WARN_STAT_INTERNAL +#define stat(p, s) warn_stat((p), (s)) +#define lstat(p, s) warn_lstat((p), (s)) +#define fstat(f, s) warn_fstat((f), (s)) +#endif + +extern int warn_stat(const char *path, struct stat *); +extern int warn_lstat(const char *path, struct stat *); +extern int warn_fstat(int, struct stat *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/watchdog.c b/src/util/watchdog.c new file mode 100644 index 0000000..3ec1fbc --- /dev/null +++ b/src/util/watchdog.c @@ -0,0 +1,314 @@ +/*++ +/* NAME +/* watchdog 3 +/* SUMMARY +/* watchdog timer +/* SYNOPSIS +/* #include <watchdog.h> +/* +/* WATCHDOG *watchdog_create(timeout, action, context) +/* unsigned timeout; +/* void (*action)(WATCHDOG *watchdog, char *context); +/* char *context; +/* +/* void watchdog_start(watchdog) +/* WATCHDOG *watchdog; +/* +/* void watchdog_stop(watchdog) +/* WATCHDOG *watchdog; +/* +/* void watchdog_destroy(watchdog) +/* WATCHDOG *watchdog; +/* +/* void watchdog_pat() +/* DESCRIPTION +/* This module implements watchdog timers that are based on ugly +/* UNIX alarm timers. The module is designed to survive systems +/* with clocks that jump occasionally. +/* +/* Watchdog timers can be stacked. Only one watchdog timer can be +/* active at a time. Only the last created watchdog timer can be +/* manipulated. Watchdog timers must be destroyed in reverse order +/* of creation. +/* +/* watchdog_create() suspends the current watchdog timer, if any, +/* and instantiates a new watchdog timer. +/* +/* watchdog_start() starts or restarts the watchdog timer. +/* +/* watchdog_stop() stops the watchdog timer. +/* +/* watchdog_destroy() stops the watchdog timer, and resumes the +/* watchdog timer instance that was suspended by watchdog_create(). +/* +/* watchdog_pat() pats the watchdog, so it stays quiet. +/* +/* Arguments: +/* .IP timeout +/* The watchdog time limit. When the watchdog timer runs, the +/* process must invoke watchdog_start(), watchdog_stop() or +/* watchdog_destroy() before the time limit is reached. +/* .IP action +/* A null pointer, or pointer to function that is called when the +/* watchdog alarm goes off. The default action is to terminate +/* the process with a fatal error. +/* .IP context +/* Application context that is passed to the action routine. +/* .IP watchdog +/* Must be a pointer to the most recently created watchdog instance. +/* This argument is checked upon each call. +/* BUGS +/* UNIX alarm timers are not stackable, so there can be at most one +/* watchdog instance active at any given time. +/* SEE ALSO +/* msg(3) diagnostics interface +/* DIAGNOSTICS +/* Fatal errors: memory allocation problem, system call failure. +/* Panics: interface violations. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <unistd.h> +#include <signal.h> +#include <posix_signals.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <killme_after.h> +#include <watchdog.h> + +/* Application-specific. */ + + /* + * Rather than having one timer that goes off when it is too late, we break + * up the time limit into smaller intervals so that we can deal with clocks + * that jump occasionally. + */ +#define WATCHDOG_STEPS 3 + + /* + * UNIX alarms are not stackable, but we can save and restore state, so that + * watchdogs can at least be nested, sort of. + */ +struct WATCHDOG { + unsigned timeout; /* our time resolution */ + WATCHDOG_FN action; /* application routine */ + char *context; /* application context */ + int trip_run; /* number of successive timeouts */ + WATCHDOG *saved_watchdog; /* saved state */ + struct sigaction saved_action; /* saved state */ + unsigned saved_time; /* saved state */ +}; + + /* + * However, only one watchdog instance can be current, and the caller has to + * restore state before a prior watchdog instance can be manipulated. + */ +static WATCHDOG *watchdog_curr; + + /* + * Workaround for systems where the alarm signal does not wakeup the event + * machinery, and therefore does not restart the watchdog timer in the + * single_server etc. skeletons. The symptom is that programs abort when the + * watchdog timeout is less than the max_idle time. + */ +#ifdef USE_WATCHDOG_PIPE +#include <errno.h> +#include <iostuff.h> +#include <events.h> + +static int watchdog_pipe[2]; + +/* watchdog_read - read event pipe */ + +static void watchdog_read(int unused_event, void *unused_context) +{ + char ch; + + while (read(watchdog_pipe[0], &ch, 1) > 0) + /* void */ ; +} + +#endif /* USE_WATCHDOG_PIPE */ + +/* watchdog_event - handle timeout event */ + +static void watchdog_event(int unused_sig) +{ + const char *myname = "watchdog_event"; + WATCHDOG *wp; + + /* + * This routine runs as a signal handler. We should not do anything that + * could involve memory allocation/deallocation, but exiting without + * proper explanation would be unacceptable. For this reason, msg(3) was + * made safe for usage by signal handlers that terminate the process. + */ + if ((wp = watchdog_curr) == 0) + msg_panic("%s: no instance", myname); + if (msg_verbose > 1) + msg_info("%s: %p %d", myname, (void *) wp, wp->trip_run); + if (++(wp->trip_run) < WATCHDOG_STEPS) { +#ifdef USE_WATCHDOG_PIPE + int saved_errno = errno; + + /* Wake up the events(3) engine. */ + if (write(watchdog_pipe[1], "", 1) != 1) + msg_warn("%s: write watchdog_pipe: %m", myname); + errno = saved_errno; +#endif + alarm(wp->timeout); + } else { + if (wp->action) + wp->action(wp, wp->context); + else { + killme_after(5); +#ifdef TEST + pause(); +#endif + msg_fatal("watchdog timeout"); + } + } +} + +/* watchdog_create - create watchdog instance */ + +WATCHDOG *watchdog_create(unsigned timeout, WATCHDOG_FN action, char *context) +{ + const char *myname = "watchdog_create"; + struct sigaction sig_action; + WATCHDOG *wp; + + wp = (WATCHDOG *) mymalloc(sizeof(*wp)); + if ((wp->timeout = timeout / WATCHDOG_STEPS) == 0) + msg_panic("%s: timeout %d is too small", myname, timeout); + wp->action = action; + wp->context = context; + wp->saved_watchdog = watchdog_curr; + wp->saved_time = alarm(0); + sigemptyset(&sig_action.sa_mask); +#ifdef SA_RESTART + sig_action.sa_flags = SA_RESTART; +#else + sig_action.sa_flags = 0; +#endif + sig_action.sa_handler = watchdog_event; + if (sigaction(SIGALRM, &sig_action, &wp->saved_action) < 0) + msg_fatal("%s: sigaction(SIGALRM): %m", myname); + if (msg_verbose > 1) + msg_info("%s: %p %d", myname, (void *) wp, timeout); +#ifdef USE_WATCHDOG_PIPE + if (watchdog_curr == 0) { + if (pipe(watchdog_pipe) < 0) + msg_fatal("%s: pipe: %m", myname); + non_blocking(watchdog_pipe[0], NON_BLOCKING); + non_blocking(watchdog_pipe[1], NON_BLOCKING); + close_on_exec(watchdog_pipe[0], CLOSE_ON_EXEC); /* Fix 20190126 */ + close_on_exec(watchdog_pipe[1], CLOSE_ON_EXEC); /* Fix 20190126 */ + event_enable_read(watchdog_pipe[0], watchdog_read, (void *) 0); + } +#endif + return (watchdog_curr = wp); +} + +/* watchdog_destroy - destroy watchdog instance, restore state */ + +void watchdog_destroy(WATCHDOG *wp) +{ + const char *myname = "watchdog_destroy"; + + watchdog_stop(wp); + watchdog_curr = wp->saved_watchdog; + if (sigaction(SIGALRM, &wp->saved_action, (struct sigaction *) 0) < 0) + msg_fatal("%s: sigaction(SIGALRM): %m", myname); + if (wp->saved_time) + alarm(wp->saved_time); + myfree((void *) wp); +#ifdef USE_WATCHDOG_PIPE + if (watchdog_curr == 0) { + event_disable_readwrite(watchdog_pipe[0]); + (void) close(watchdog_pipe[0]); + (void) close(watchdog_pipe[1]); + } +#endif + if (msg_verbose > 1) + msg_info("%s: %p", myname, (void *) wp); +} + +/* watchdog_start - enable watchdog timer */ + +void watchdog_start(WATCHDOG *wp) +{ + const char *myname = "watchdog_start"; + + if (wp != watchdog_curr) + msg_panic("%s: wrong watchdog instance", myname); + wp->trip_run = 0; + alarm(wp->timeout); + if (msg_verbose > 1) + msg_info("%s: %p", myname, (void *) wp); +} + +/* watchdog_stop - disable watchdog timer */ + +void watchdog_stop(WATCHDOG *wp) +{ + const char *myname = "watchdog_stop"; + + if (wp != watchdog_curr) + msg_panic("%s: wrong watchdog instance", myname); + alarm(0); + if (msg_verbose > 1) + msg_info("%s: %p", myname, (void *) wp); +} + +/* watchdog_pat - pat the dog so it stays quiet */ + +void watchdog_pat(void) +{ + const char *myname = "watchdog_pat"; + + if (watchdog_curr) + watchdog_curr->trip_run = 0; + if (msg_verbose > 1) + msg_info("%s: %p", myname, (void *) watchdog_curr); +} + +#ifdef TEST + +#include <vstream.h> + +int main(int unused_argc, char **unused_argv) +{ + WATCHDOG *wp; + + msg_verbose = 2; + + wp = watchdog_create(10, (WATCHDOG_FN) 0, (void *) 0); + watchdog_start(wp); + do { + watchdog_pat(); + } while (VSTREAM_GETCHAR() != VSTREAM_EOF); + watchdog_destroy(wp); + return (0); +} + +#endif diff --git a/src/util/watchdog.h b/src/util/watchdog.h new file mode 100644 index 0000000..ee01faf --- /dev/null +++ b/src/util/watchdog.h @@ -0,0 +1,36 @@ +#ifndef _WATCHDOG_H_INCLUDED_ +#define _WATCHDOG_H_INCLUDED_ + +/*++ +/* NAME +/* watchdog 3h +/* SUMMARY +/* watchdog timer +/* SYNOPSIS +/* #include "watchdog.h" + DESCRIPTION + .nf + + /* + * External interface. + */ +typedef struct WATCHDOG WATCHDOG; +typedef void (*WATCHDOG_FN) (WATCHDOG *, char *); +extern WATCHDOG *watchdog_create(unsigned, WATCHDOG_FN, char *); +extern void watchdog_start(WATCHDOG *); +extern void watchdog_stop(WATCHDOG *); +extern void watchdog_destroy(WATCHDOG *); +extern void watchdog_pat(void); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/write_buf.c b/src/util/write_buf.c new file mode 100644 index 0000000..968a468 --- /dev/null +++ b/src/util/write_buf.c @@ -0,0 +1,89 @@ +/*++ +/* NAME +/* write_buf 3 +/* SUMMARY +/* write buffer or bust +/* SYNOPSIS +/* #include <iostuff.h> +/* +/* ssize_t write_buf(fd, buf, len, timeout) +/* int fd; +/* const char *buf; +/* ssize_t len; +/* int timeout; +/* DESCRIPTION +/* write_buf() writes a buffer to the named stream in as many +/* fragments as needed, and returns the number of bytes written, +/* which is always the number requested or an error indication. +/* +/* Arguments: +/* .IP fd +/* File descriptor in the range 0..FD_SETSIZE. +/* .IP buf +/* Address of data to be written. +/* .IP len +/* Amount of data to be written. +/* .IP timeout +/* Bounds the time in seconds to wait until \fIfd\fD becomes writable. +/* A value <= 0 means do not wait; this is useful only when \fIfd\fR +/* uses blocking I/O. +/* DIAGNOSTICS +/* write_buf() returns -1 in case of trouble. The global \fIerrno\fR +/* variable reflects the nature of the problem. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <unistd.h> +#include <errno.h> +#include <time.h> + +/* Utility library. */ + +#include <msg.h> +#include <iostuff.h> + +/* write_buf - write buffer or bust */ + +ssize_t write_buf(int fd, const char *buf, ssize_t len, int timeout) +{ + const char *start = buf; + ssize_t count; + time_t expire; + int time_left = timeout; + + if (time_left > 0) + expire = time((time_t *) 0) + time_left; + + while (len > 0) { + if (time_left > 0 && write_wait(fd, time_left) < 0) + return (-1); + if ((count = write(fd, buf, len)) < 0) { + if ((errno == EAGAIN && time_left > 0) || errno == EINTR) + /* void */ ; + else + return (-1); + } else { + buf += count; + len -= count; + } + if (len > 0 && time_left > 0) { + time_left = expire - time((time_t *) 0); + if (time_left <= 0) { + errno = ETIMEDOUT; + return (-1); + } + } + } + return (buf - start); +} |