diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:46:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:46:30 +0000 |
commit | b5896ba9f6047e7031e2bdee0622d543e11a6734 (patch) | |
tree | fd7b460593a2fee1be579bec5697e6d887ea3421 /src/master | |
parent | Initial commit. (diff) | |
download | postfix-2f07919848e7bd4a084699d26e8821e3a00696d9.tar.xz postfix-2f07919848e7bd4a084699d26e8821e3a00696d9.zip |
Adding upstream version 3.4.23.upstream/3.4.23upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/master')
28 files changed, 8781 insertions, 0 deletions
diff --git a/src/master/.indent.pro b/src/master/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/master/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro
\ No newline at end of file diff --git a/src/master/.printfck b/src/master/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/master/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/master/Makefile.in b/src/master/Makefile.in new file mode 100644 index 0000000..1dd334d --- /dev/null +++ b/src/master/Makefile.in @@ -0,0 +1,476 @@ +SHELL = /bin/sh +SRCS = master.c master_conf.c master_ent.c master_sig.c master_avail.c \ + master_spawn.c master_service.c master_status.c master_listen.c \ + master_proto.c single_server.c multi_server.c master_vars.c \ + master_wakeup.c master_flow.c master_watch.c mail_flow.c \ + master_monitor.c dgram_server.c +OBJS = master.o master_conf.o master_ent.o master_sig.o master_avail.o \ + master_spawn.o master_service.o master_status.o master_listen.o \ + master_vars.o master_wakeup.o master_watch.o master_flow.o \ + master_monitor.o +LIB_OBJ = single_server.o multi_server.o trigger_server.o master_proto.o \ + mail_flow.o event_server.o dgram_server.o +HDRS = mail_server.h master_proto.h mail_flow.h +INT_HDR = master.h +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +LIB = lib$(LIB_PREFIX)master$(LIB_SUFFIX) +PROG = master +TESTPROG= +LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) +LIB_DIR = ../../lib +INC_DIR = ../../include +BIN_DIR = ../../libexec + +.c.o:; $(CC) `for i in $(LIB_OBJ); do if [ $$i = $@ ]; then echo $(SHLIB_CFLAGS); else true; fi; done` $(CFLAGS) -c $*.c + +all: $(PROG) $(LIB) + +$(OBJS) $(LIB_OBJ): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +test: $(TESTPROG) + +tests: + +root_tests: + +$(LIB): $(LIB_OBJ) + $(AR) $(ARFL) $(LIB) $? + $(RANLIB) $(LIB) + $(SHLIB_LD) $(SHLIB_RPATH) -o $(LIB) $(LIB_OBJ) $(SHLIB_SYSLIBS) + +$(LIB_DIR)/$(LIB): $(LIB) + cp $(LIB) $(LIB_DIR)/$(LIB) + $(RANLIB) $(LIB_DIR)/$(LIB) + +$(BIN_DIR)/$(PROG): $(PROG) + cp $(PROG) $(BIN_DIR) + +update: $(LIB_DIR)/$(LIB) $(BIN_DIR)/$(PROG) $(HDRS) + -for i in $(HDRS); \ + do \ + cmp -s $$i $(INC_DIR)/$$i 2>/dev/null || cp $$i $(INC_DIR); \ + done + cd $(INC_DIR); chmod 644 $(HDRS) + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) junk $(LIB) + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +dgram_server.o: ../../include/argv.h +dgram_server.o: ../../include/attr.h +dgram_server.o: ../../include/bounce.h +dgram_server.o: ../../include/check_arg.h +dgram_server.o: ../../include/chroot_uid.h +dgram_server.o: ../../include/debug_process.h +dgram_server.o: ../../include/deliver_request.h +dgram_server.o: ../../include/dict.h +dgram_server.o: ../../include/dsn.h +dgram_server.o: ../../include/dsn_buf.h +dgram_server.o: ../../include/events.h +dgram_server.o: ../../include/htable.h +dgram_server.o: ../../include/iostuff.h +dgram_server.o: ../../include/listen.h +dgram_server.o: ../../include/mail_conf.h +dgram_server.o: ../../include/mail_dict.h +dgram_server.o: ../../include/mail_params.h +dgram_server.o: ../../include/mail_task.h +dgram_server.o: ../../include/mail_version.h +dgram_server.o: ../../include/maillog_client.h +dgram_server.o: ../../include/msg.h +dgram_server.o: ../../include/msg_stats.h +dgram_server.o: ../../include/msg_vstream.h +dgram_server.o: ../../include/myflock.h +dgram_server.o: ../../include/mymalloc.h +dgram_server.o: ../../include/nvtable.h +dgram_server.o: ../../include/recipient_list.h +dgram_server.o: ../../include/resolve_local.h +dgram_server.o: ../../include/safe_open.h +dgram_server.o: ../../include/sane_accept.h +dgram_server.o: ../../include/split_at.h +dgram_server.o: ../../include/stringops.h +dgram_server.o: ../../include/sys_defs.h +dgram_server.o: ../../include/vbuf.h +dgram_server.o: ../../include/vstream.h +dgram_server.o: ../../include/vstring.h +dgram_server.o: ../../include/watchdog.h +dgram_server.o: dgram_server.c +dgram_server.o: mail_flow.h +dgram_server.o: mail_server.h +dgram_server.o: master_proto.h +event_server.o: ../../include/argv.h +event_server.o: ../../include/attr.h +event_server.o: ../../include/bounce.h +event_server.o: ../../include/check_arg.h +event_server.o: ../../include/chroot_uid.h +event_server.o: ../../include/debug_process.h +event_server.o: ../../include/deliver_request.h +event_server.o: ../../include/dict.h +event_server.o: ../../include/dsn.h +event_server.o: ../../include/dsn_buf.h +event_server.o: ../../include/events.h +event_server.o: ../../include/htable.h +event_server.o: ../../include/iostuff.h +event_server.o: ../../include/listen.h +event_server.o: ../../include/mail_conf.h +event_server.o: ../../include/mail_dict.h +event_server.o: ../../include/mail_params.h +event_server.o: ../../include/mail_task.h +event_server.o: ../../include/mail_version.h +event_server.o: ../../include/maillog_client.h +event_server.o: ../../include/msg.h +event_server.o: ../../include/msg_stats.h +event_server.o: ../../include/msg_vstream.h +event_server.o: ../../include/myflock.h +event_server.o: ../../include/mymalloc.h +event_server.o: ../../include/nvtable.h +event_server.o: ../../include/recipient_list.h +event_server.o: ../../include/resolve_local.h +event_server.o: ../../include/safe_open.h +event_server.o: ../../include/sane_accept.h +event_server.o: ../../include/split_at.h +event_server.o: ../../include/stringops.h +event_server.o: ../../include/sys_defs.h +event_server.o: ../../include/timed_ipc.h +event_server.o: ../../include/vbuf.h +event_server.o: ../../include/vstream.h +event_server.o: ../../include/vstring.h +event_server.o: ../../include/watchdog.h +event_server.o: event_server.c +event_server.o: mail_flow.h +event_server.o: mail_server.h +event_server.o: master_proto.h +mail_flow.o: ../../include/iostuff.h +mail_flow.o: ../../include/msg.h +mail_flow.o: ../../include/sys_defs.h +mail_flow.o: ../../include/warn_stat.h +mail_flow.o: mail_flow.c +mail_flow.o: mail_flow.h +mail_flow.o: master_proto.h +master.o: ../../include/argv.h +master.o: ../../include/check_arg.h +master.o: ../../include/clean_env.h +master.o: ../../include/debug_process.h +master.o: ../../include/events.h +master.o: ../../include/inet_proto.h +master.o: ../../include/iostuff.h +master.o: ../../include/mail_conf.h +master.o: ../../include/mail_params.h +master.o: ../../include/mail_parm_split.h +master.o: ../../include/mail_task.h +master.o: ../../include/mail_version.h +master.o: ../../include/maillog_client.h +master.o: ../../include/msg.h +master.o: ../../include/myflock.h +master.o: ../../include/mymalloc.h +master.o: ../../include/open_lock.h +master.o: ../../include/safe.h +master.o: ../../include/set_eugid.h +master.o: ../../include/set_ugid.h +master.o: ../../include/stringops.h +master.o: ../../include/sys_defs.h +master.o: ../../include/vbuf.h +master.o: ../../include/vstream.h +master.o: ../../include/vstring.h +master.o: ../../include/watchdog.h +master.o: master.c +master.o: master.h +master_avail.o: ../../include/events.h +master_avail.o: ../../include/msg.h +master_avail.o: ../../include/sys_defs.h +master_avail.o: master.h +master_avail.o: master_avail.c +master_avail.o: master_proto.h +master_conf.o: ../../include/argv.h +master_conf.o: ../../include/msg.h +master_conf.o: ../../include/sys_defs.h +master_conf.o: master.h +master_conf.o: master_conf.c +master_ent.o: ../../include/argv.h +master_ent.o: ../../include/attr.h +master_ent.o: ../../include/check_arg.h +master_ent.o: ../../include/host_port.h +master_ent.o: ../../include/htable.h +master_ent.o: ../../include/inet_addr_host.h +master_ent.o: ../../include/inet_addr_list.h +master_ent.o: ../../include/inet_proto.h +master_ent.o: ../../include/iostuff.h +master_ent.o: ../../include/mail_conf.h +master_ent.o: ../../include/mail_params.h +master_ent.o: ../../include/mail_proto.h +master_ent.o: ../../include/match_service.h +master_ent.o: ../../include/msg.h +master_ent.o: ../../include/myaddrinfo.h +master_ent.o: ../../include/mymalloc.h +master_ent.o: ../../include/nvtable.h +master_ent.o: ../../include/own_inet_addr.h +master_ent.o: ../../include/readlline.h +master_ent.o: ../../include/sock_addr.h +master_ent.o: ../../include/stringops.h +master_ent.o: ../../include/sys_defs.h +master_ent.o: ../../include/vbuf.h +master_ent.o: ../../include/vstream.h +master_ent.o: ../../include/vstring.h +master_ent.o: ../../include/wildcard_inet_addr.h +master_ent.o: master.h +master_ent.o: master_ent.c +master_ent.o: master_proto.h +master_flow.o: ../../include/iostuff.h +master_flow.o: ../../include/msg.h +master_flow.o: ../../include/sys_defs.h +master_flow.o: master.h +master_flow.o: master_flow.c +master_flow.o: master_proto.h +master_listen.o: ../../include/check_arg.h +master_listen.o: ../../include/htable.h +master_listen.o: ../../include/inet_addr_list.h +master_listen.o: ../../include/iostuff.h +master_listen.o: ../../include/listen.h +master_listen.o: ../../include/mail_params.h +master_listen.o: ../../include/msg.h +master_listen.o: ../../include/myaddrinfo.h +master_listen.o: ../../include/mymalloc.h +master_listen.o: ../../include/set_eugid.h +master_listen.o: ../../include/set_ugid.h +master_listen.o: ../../include/sock_addr.h +master_listen.o: ../../include/stringops.h +master_listen.o: ../../include/sys_defs.h +master_listen.o: ../../include/vbuf.h +master_listen.o: ../../include/vstring.h +master_listen.o: master.h +master_listen.o: master_listen.c +master_monitor.o: ../../include/iostuff.h +master_monitor.o: ../../include/msg.h +master_monitor.o: ../../include/sys_defs.h +master_monitor.o: master.h +master_monitor.o: master_monitor.c +master_proto.o: ../../include/msg.h +master_proto.o: ../../include/sys_defs.h +master_proto.o: master_proto.c +master_proto.o: master_proto.h +master_service.o: ../../include/msg.h +master_service.o: ../../include/mymalloc.h +master_service.o: ../../include/sys_defs.h +master_service.o: master.h +master_service.o: master_service.c +master_sig.o: ../../include/events.h +master_sig.o: ../../include/iostuff.h +master_sig.o: ../../include/killme_after.h +master_sig.o: ../../include/msg.h +master_sig.o: ../../include/posix_signals.h +master_sig.o: ../../include/sys_defs.h +master_sig.o: master.h +master_sig.o: master_sig.c +master_spawn.o: ../../include/argv.h +master_spawn.o: ../../include/binhash.h +master_spawn.o: ../../include/check_arg.h +master_spawn.o: ../../include/events.h +master_spawn.o: ../../include/mail_conf.h +master_spawn.o: ../../include/msg.h +master_spawn.o: ../../include/mymalloc.h +master_spawn.o: ../../include/sys_defs.h +master_spawn.o: ../../include/vbuf.h +master_spawn.o: ../../include/vstring.h +master_spawn.o: master.h +master_spawn.o: master_proto.h +master_spawn.o: master_spawn.c +master_status.o: ../../include/binhash.h +master_status.o: ../../include/events.h +master_status.o: ../../include/iostuff.h +master_status.o: ../../include/msg.h +master_status.o: ../../include/sys_defs.h +master_status.o: master.h +master_status.o: master_proto.h +master_status.o: master_status.c +master_vars.o: ../../include/check_arg.h +master_vars.o: ../../include/mail_conf.h +master_vars.o: ../../include/mail_params.h +master_vars.o: ../../include/msg.h +master_vars.o: ../../include/mymalloc.h +master_vars.o: ../../include/stringops.h +master_vars.o: ../../include/sys_defs.h +master_vars.o: ../../include/vbuf.h +master_vars.o: ../../include/vstring.h +master_vars.o: master.h +master_vars.o: master_vars.c +master_wakeup.o: ../../include/attr.h +master_wakeup.o: ../../include/check_arg.h +master_wakeup.o: ../../include/events.h +master_wakeup.o: ../../include/htable.h +master_wakeup.o: ../../include/iostuff.h +master_wakeup.o: ../../include/mail_conf.h +master_wakeup.o: ../../include/mail_params.h +master_wakeup.o: ../../include/mail_proto.h +master_wakeup.o: ../../include/msg.h +master_wakeup.o: ../../include/mymalloc.h +master_wakeup.o: ../../include/nvtable.h +master_wakeup.o: ../../include/set_eugid.h +master_wakeup.o: ../../include/set_ugid.h +master_wakeup.o: ../../include/sys_defs.h +master_wakeup.o: ../../include/trigger.h +master_wakeup.o: ../../include/vbuf.h +master_wakeup.o: ../../include/vstream.h +master_wakeup.o: ../../include/vstring.h +master_wakeup.o: mail_server.h +master_wakeup.o: master.h +master_wakeup.o: master_wakeup.c +master_watch.o: ../../include/msg.h +master_watch.o: ../../include/mymalloc.h +master_watch.o: ../../include/sys_defs.h +master_watch.o: master.h +master_watch.o: master_watch.c +multi_server.o: ../../include/argv.h +multi_server.o: ../../include/attr.h +multi_server.o: ../../include/bounce.h +multi_server.o: ../../include/check_arg.h +multi_server.o: ../../include/chroot_uid.h +multi_server.o: ../../include/debug_process.h +multi_server.o: ../../include/deliver_request.h +multi_server.o: ../../include/dict.h +multi_server.o: ../../include/dsn.h +multi_server.o: ../../include/dsn_buf.h +multi_server.o: ../../include/events.h +multi_server.o: ../../include/htable.h +multi_server.o: ../../include/iostuff.h +multi_server.o: ../../include/listen.h +multi_server.o: ../../include/mail_conf.h +multi_server.o: ../../include/mail_dict.h +multi_server.o: ../../include/mail_params.h +multi_server.o: ../../include/mail_task.h +multi_server.o: ../../include/mail_version.h +multi_server.o: ../../include/maillog_client.h +multi_server.o: ../../include/msg.h +multi_server.o: ../../include/msg_stats.h +multi_server.o: ../../include/msg_vstream.h +multi_server.o: ../../include/myflock.h +multi_server.o: ../../include/mymalloc.h +multi_server.o: ../../include/nvtable.h +multi_server.o: ../../include/recipient_list.h +multi_server.o: ../../include/resolve_local.h +multi_server.o: ../../include/safe_open.h +multi_server.o: ../../include/sane_accept.h +multi_server.o: ../../include/split_at.h +multi_server.o: ../../include/stringops.h +multi_server.o: ../../include/sys_defs.h +multi_server.o: ../../include/timed_ipc.h +multi_server.o: ../../include/vbuf.h +multi_server.o: ../../include/vstream.h +multi_server.o: ../../include/vstring.h +multi_server.o: ../../include/watchdog.h +multi_server.o: mail_flow.h +multi_server.o: mail_server.h +multi_server.o: master_proto.h +multi_server.o: multi_server.c +single_server.o: ../../include/argv.h +single_server.o: ../../include/attr.h +single_server.o: ../../include/bounce.h +single_server.o: ../../include/check_arg.h +single_server.o: ../../include/chroot_uid.h +single_server.o: ../../include/debug_process.h +single_server.o: ../../include/deliver_request.h +single_server.o: ../../include/dict.h +single_server.o: ../../include/dsn.h +single_server.o: ../../include/dsn_buf.h +single_server.o: ../../include/events.h +single_server.o: ../../include/htable.h +single_server.o: ../../include/iostuff.h +single_server.o: ../../include/listen.h +single_server.o: ../../include/mail_conf.h +single_server.o: ../../include/mail_dict.h +single_server.o: ../../include/mail_params.h +single_server.o: ../../include/mail_task.h +single_server.o: ../../include/mail_version.h +single_server.o: ../../include/maillog_client.h +single_server.o: ../../include/msg.h +single_server.o: ../../include/msg_stats.h +single_server.o: ../../include/msg_vstream.h +single_server.o: ../../include/myflock.h +single_server.o: ../../include/mymalloc.h +single_server.o: ../../include/nvtable.h +single_server.o: ../../include/recipient_list.h +single_server.o: ../../include/resolve_local.h +single_server.o: ../../include/safe_open.h +single_server.o: ../../include/sane_accept.h +single_server.o: ../../include/split_at.h +single_server.o: ../../include/stringops.h +single_server.o: ../../include/sys_defs.h +single_server.o: ../../include/timed_ipc.h +single_server.o: ../../include/vbuf.h +single_server.o: ../../include/vstream.h +single_server.o: ../../include/vstring.h +single_server.o: ../../include/watchdog.h +single_server.o: mail_flow.h +single_server.o: mail_server.h +single_server.o: master_proto.h +single_server.o: single_server.c +trigger_server.o: ../../include/argv.h +trigger_server.o: ../../include/attr.h +trigger_server.o: ../../include/bounce.h +trigger_server.o: ../../include/check_arg.h +trigger_server.o: ../../include/chroot_uid.h +trigger_server.o: ../../include/debug_process.h +trigger_server.o: ../../include/deliver_request.h +trigger_server.o: ../../include/dict.h +trigger_server.o: ../../include/dsn.h +trigger_server.o: ../../include/dsn_buf.h +trigger_server.o: ../../include/events.h +trigger_server.o: ../../include/htable.h +trigger_server.o: ../../include/iostuff.h +trigger_server.o: ../../include/listen.h +trigger_server.o: ../../include/mail_conf.h +trigger_server.o: ../../include/mail_dict.h +trigger_server.o: ../../include/mail_params.h +trigger_server.o: ../../include/mail_task.h +trigger_server.o: ../../include/mail_version.h +trigger_server.o: ../../include/maillog_client.h +trigger_server.o: ../../include/msg.h +trigger_server.o: ../../include/msg_stats.h +trigger_server.o: ../../include/msg_vstream.h +trigger_server.o: ../../include/myflock.h +trigger_server.o: ../../include/mymalloc.h +trigger_server.o: ../../include/nvtable.h +trigger_server.o: ../../include/recipient_list.h +trigger_server.o: ../../include/resolve_local.h +trigger_server.o: ../../include/safe_open.h +trigger_server.o: ../../include/sane_accept.h +trigger_server.o: ../../include/split_at.h +trigger_server.o: ../../include/stringops.h +trigger_server.o: ../../include/sys_defs.h +trigger_server.o: ../../include/vbuf.h +trigger_server.o: ../../include/vstream.h +trigger_server.o: ../../include/vstring.h +trigger_server.o: ../../include/watchdog.h +trigger_server.o: mail_flow.h +trigger_server.o: mail_server.h +trigger_server.o: master_proto.h +trigger_server.o: trigger_server.c diff --git a/src/master/dgram_server.c b/src/master/dgram_server.c new file mode 100644 index 0000000..2ef3a5a --- /dev/null +++ b/src/master/dgram_server.c @@ -0,0 +1,665 @@ +/*++ +/* NAME +/* dgram_server 3 +/* SUMMARY +/* skeleton datagram server subsystem +/* SYNOPSIS +/* #include <mail_server.h> +/* +/* NORETURN dgram_server_main(argc, argv, service, key, value, ...) +/* int argc; +/* char **argv; +/* void (*service)(char *buf, int len, char *service_name, char **argv); +/* int key; +/* DESCRIPTION +/* This module implements a skeleton for mail subsystem programs +/* that wake up on client request and perform some activity +/* without further client interaction. This module supports +/* local IPC via a UNIX-domain datagram socket. The resulting +/* program expects to be run from the \fBmaster\fR process. +/* +/* dgram_server_main() is the skeleton entry point. It should +/* be called from the application main program. The skeleton +/* does the generic command-line options processing, initialization +/* of configurable parameters, and receiving datagrams. The +/* skeleton never returns. +/* +/* Arguments: +/* .IP "void (*service)(char *buf, int len, char *service_name, char **argv)" +/* A pointer to a function that is called by the skeleton each +/* time a client sends a datagram to the program's service +/* port. The function is run after the program has irrevocably +/* dropped its privileges. The buffer argument specifies the +/* data read from the datagram port; this data corresponds to +/* request. The len argument specifies how much client data +/* is available. The maximal size of the buffer is specified +/* via the DGRAM_BUF_SIZE manifest constant. The service name +/* argument corresponds to the service name in the master.cf +/* file. The argv argument specifies command-line arguments +/* left over after options processing. +/* .PP +/* Optional arguments are specified as a null-terminated list +/* with macros that have zero or more arguments: +/* .IP "CA_MAIL_SERVER_INT_TABLE(CONFIG_INT_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_LONG_TABLE(CONFIG_LONG_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_STR_TABLE(CONFIG_STR_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_BOOL_TABLE(CONFIG_BOOL_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_TIME_TABLE(CONFIG_TIME_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_RAW_TABLE(CONFIG_RAW_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. Raw parameters are not subjected to $name +/* evaluation. +/* .IP "CA_MAIL_SERVER_NINT_TABLE(CONFIG_NINT_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_NBOOL_TABLE(CONFIG_NBOOL_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_PRE_INIT(void *(char *service_name, char **argv))" +/* A pointer to a function that is called once +/* by the skeleton after it has read the global configuration file +/* and after it has processed command-line arguments, but before +/* the skeleton has optionally relinquished the process privileges. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_POST_INIT(void *(char *service_name, char **argv))" +/* A pointer to a function that is called once +/* by the skeleton after it has optionally relinquished the process +/* privileges, but before servicing client connection requests. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_LOOP(int *(char *service_name, char **argv))" +/* A pointer to function that is executed from +/* within the event loop, whenever an I/O or timer event has happened, +/* or whenever nothing has happened for a specified amount of time. +/* The result value of the function specifies how long to wait until +/* the next event. Specify -1 to wait for "as long as it takes". +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_EXIT(void *(char *service_name, char **argv))" +/* A pointer to function that is executed immediately before normal +/* process termination. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_PRE_ACCEPT(void *(char *service_name, char **argv))" +/* Function to be executed prior to accepting a new request. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_IN_FLOW_DELAY(none)" +/* Pause $in_flow_delay seconds when no "mail flow control token" +/* is available. A token is consumed for each connection request. +/* .IP CA_MAIL_SERVER_SOLITARY +/* This service must be configured with process limit of 1. +/* .IP CA_MAIL_SERVER_UNLIMITED +/* This service must be configured with process limit of 0. +/* .IP CA_MAIL_SERVER_PRIVILEGED +/* This service must be configured as privileged. +/* .IP "CA_MAIL_SERVER_WATCHDOG(int *)" +/* Override the default 1000s watchdog timeout. The value is +/* used after command-line and main.cf file processing. +/* .IP "CA_MAIL_SERVER_BOUNCE_INIT(const char *, const char **)" +/* Initialize the DSN filter for the bounce/defer service +/* clients with the specified map source and map names. +/* .PP +/* The var_use_limit variable limits the number of clients that +/* a server can service before it commits suicide. +/* This value is taken from the global \fBmain.cf\fR configuration +/* file. Setting \fBvar_use_limit\fR to zero disables the client limit. +/* +/* The var_idle_limit variable limits the time that a service +/* receives no client connection requests before it commits suicide. +/* This value is taken from the global \fBmain.cf\fR configuration +/* file. Setting \fBvar_use_limit\fR to zero disables the idle limit. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* SEE ALSO +/* master(8), master process +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <unistd.h> +#include <signal.h> +#include <stdlib.h> +#include <limits.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <stdarg.h> +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif +#include <time.h> + +/* Utility library. */ + +#include <msg.h> +#include <msg_vstream.h> +#include <chroot_uid.h> +#include <vstring.h> +#include <vstream.h> +#include <msg_vstream.h> +#include <mymalloc.h> +#include <events.h> +#include <iostuff.h> +#include <stringops.h> +#include <sane_accept.h> +#include <myflock.h> +#include <safe_open.h> +#include <listen.h> +#include <watchdog.h> +#include <split_at.h> + +/* Global library. */ + +#include <mail_params.h> +#include <mail_task.h> +#include <debug_process.h> +#include <mail_conf.h> +#include <mail_dict.h> +#include <resolve_local.h> +#include <mail_flow.h> +#include <mail_version.h> +#include <bounce.h> +#include <maillog_client.h> + +/* Process manager. */ + +#include "master_proto.h" + +/* Application-specific */ + +#include "mail_server.h" + + /* + * Global state. + */ +static int use_count; + +static DGRAM_SERVER_FN dgram_server_service; +static char *dgram_server_name; +static char **dgram_server_argv; +static void (*dgram_server_accept) (int, void *); +static void (*dgram_server_onexit) (char *, char **); +static void (*dgram_server_pre_accept) (char *, char **); +static VSTREAM *dgram_server_lock; +static int dgram_server_in_flow_delay; +static unsigned dgram_server_generation; +static int dgram_server_watchdog = 1000; + +/* dgram_server_exit - normal termination */ + +static NORETURN dgram_server_exit(void) +{ + if (dgram_server_onexit) + dgram_server_onexit(dgram_server_name, dgram_server_argv); + exit(0); +} + +/* dgram_server_abort - terminate after abnormal master exit */ + +static void dgram_server_abort(int unused_event, void *unused_context) +{ + if (msg_verbose) + msg_info("master disconnect -- exiting"); + dgram_server_exit(); +} + +/* dgram_server_timeout - idle time exceeded */ + +static void dgram_server_timeout(int unused_event, void *unused_context) +{ + if (msg_verbose) + msg_info("idle timeout -- exiting"); + dgram_server_exit(); +} + +/* dgram_server_wakeup - wake up application */ + +static void dgram_server_wakeup(int fd) +{ + char buf[DGRAM_BUF_SIZE]; + ssize_t len; + + /* + * Commit suicide when the master process disconnected from us, after + * handling the client request. + */ + if (master_notify(var_pid, dgram_server_generation, MASTER_STAT_TAKEN) < 0) + /* void */ ; + if (dgram_server_in_flow_delay && mail_flow_get(1) < 0) + doze(var_in_flow_delay * 1000000); + if ((len = recv(fd, buf, sizeof(buf), 0)) >= 0) + dgram_server_service(buf, len, dgram_server_name, dgram_server_argv); + if (master_notify(var_pid, dgram_server_generation, MASTER_STAT_AVAIL) < 0) + dgram_server_abort(EVENT_NULL_TYPE, EVENT_NULL_CONTEXT); + if (var_idle_limit > 0) + event_request_timer(dgram_server_timeout, (void *) 0, var_idle_limit); + /* Avoid integer wrap-around in a persistent process. */ + if (use_count < INT_MAX) + use_count++; +} + +/* dgram_server_accept_unix - handle UNIX-domain socket event */ + +static void dgram_server_accept_unix(int unused_event, void *context) +{ + const char *myname = "dgram_server_accept"; + int listen_fd = CAST_ANY_PTR_TO_INT(context); + + if (dgram_server_lock != 0 + && myflock(vstream_fileno(dgram_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + + if (msg_verbose) + msg_info("%s: request arrived", myname); + + /* + * Read whatever the other side wrote. The socket is non-blocking so we + * won't get stuck when multiple processes wake up. + */ + if (dgram_server_pre_accept) + dgram_server_pre_accept(dgram_server_name, dgram_server_argv); + dgram_server_wakeup(listen_fd); +} + +/* dgram_server_main - the real main program */ + +NORETURN dgram_server_main(int argc, char **argv, DGRAM_SERVER_FN service,...) +{ + const char *myname = "dgram_server_main"; + char *root_dir = 0; + char *user_name = 0; + int debug_me = 0; + int daemon_mode = 1; + char *service_name = basename(argv[0]); + int delay; + int c; + int socket_count = 1; + int fd; + va_list ap; + MAIL_SERVER_INIT_FN pre_init = 0; + MAIL_SERVER_INIT_FN post_init = 0; + MAIL_SERVER_LOOP_FN loop = 0; + int key; + char *transport = 0; + char *lock_path; + VSTRING *why; + int alone = 0; + int zerolimit = 0; + WATCHDOG *watchdog; + char *oname_val; + char *oname; + char *oval; + const char *err; + char *generation; + int msg_vstream_needed = 0; + const char *dsn_filter_title; + const char **dsn_filter_maps; + + /* + * Process environment options as early as we can. + */ + if (getenv(CONF_ENV_VERB)) + msg_verbose = 1; + if (getenv(CONF_ENV_DEBUG)) + debug_me = 1; + + /* + * Don't die when a process goes away unexpectedly. + */ + signal(SIGPIPE, SIG_IGN); + + /* + * Don't die for frivolous reasons. + */ +#ifdef SIGXFSZ + signal(SIGXFSZ, SIG_IGN); +#endif + + /* + * May need this every now and then. + */ + var_procname = mystrdup(basename(argv[0])); + set_mail_conf_str(VAR_PROCNAME, var_procname); + + /* + * Initialize logging and exit handler. Do the syslog first, so that its + * initialization completes before we enter the optional chroot jail. + */ + maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE); + if (msg_verbose) + msg_info("daemon started"); + + /* + * Check the Postfix library version as soon as we enable logging. + */ + MAIL_VERSION_CHECK; + + /* + * Initialize from the configuration file. Allow command-line options to + * override compiled-in defaults or configured parameter values. + */ + mail_conf_suck(); + + /* + * After database open error, continue execution with reduced + * functionality. + */ + dict_allow_surrogate = 1; + + /* + * Pick up policy settings from master process. Shut up error messages to + * stderr, because no-one is going to see them. + */ + opterr = 0; + while ((c = GETOPT(argc, argv, "cdDi:lm:n:o:s:t:uvVz")) > 0) { + switch (c) { + case 'c': + root_dir = "setme"; + break; + case 'd': + daemon_mode = 0; + break; + case 'D': + debug_me = 1; + break; + case 'i': + mail_conf_update(VAR_MAX_IDLE, optarg); + break; + case 'l': + alone = 1; + break; + case 'm': + mail_conf_update(VAR_MAX_USE, optarg); + break; + case 'n': + service_name = optarg; + break; + case 'o': + oname_val = mystrdup(optarg); + if ((err = split_nameval(oname_val, &oname, &oval)) != 0) + msg_fatal("invalid \"-o %s\" option value: %s", optarg, err); + mail_conf_update(oname, oval); + myfree(oname_val); + break; + case 's': + if ((socket_count = atoi(optarg)) <= 0) + msg_fatal("invalid socket_count: %s", optarg); + break; + case 't': + transport = optarg; + break; + case 'u': + user_name = "setme"; + break; + case 'v': + msg_verbose++; + break; + case 'V': + if (++msg_vstream_needed == 1) + msg_vstream_init(mail_task(var_procname), VSTREAM_ERR); + break; + case 'z': + zerolimit = 1; + break; + default: + msg_fatal("invalid option: %c", optopt); + break; + } + } + set_mail_conf_str(VAR_SERVNAME, service_name); + + /* + * Initialize generic parameters and re-initialize logging in case of a + * non-default program name or logging destination. + */ + mail_params_init(); + maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE); + + /* + * Register higher-level dictionaries and initialize the support for + * dynamically-loaded dictionarles. + */ + mail_dict_init(); + + /* + * If not connected to stdin, stdin must not be a terminal. + */ + if (daemon_mode && isatty(STDIN_FILENO)) { + msg_vstream_init(var_procname, VSTREAM_ERR); + msg_fatal("do not run this command by hand"); + } + + /* + * Application-specific initialization. + */ + va_start(ap, service); + while ((key = va_arg(ap, int)) != 0) { + switch (key) { + case MAIL_SERVER_INT_TABLE: + get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *)); + break; + case MAIL_SERVER_LONG_TABLE: + get_mail_conf_long_table(va_arg(ap, CONFIG_LONG_TABLE *)); + break; + case MAIL_SERVER_STR_TABLE: + get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *)); + break; + case MAIL_SERVER_BOOL_TABLE: + get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *)); + break; + case MAIL_SERVER_TIME_TABLE: + get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *)); + break; + case MAIL_SERVER_RAW_TABLE: + get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *)); + break; + case MAIL_SERVER_NINT_TABLE: + get_mail_conf_nint_table(va_arg(ap, CONFIG_NINT_TABLE *)); + break; + case MAIL_SERVER_NBOOL_TABLE: + get_mail_conf_nbool_table(va_arg(ap, CONFIG_NBOOL_TABLE *)); + break; + case MAIL_SERVER_PRE_INIT: + pre_init = va_arg(ap, MAIL_SERVER_INIT_FN); + break; + case MAIL_SERVER_POST_INIT: + post_init = va_arg(ap, MAIL_SERVER_INIT_FN); + break; + case MAIL_SERVER_LOOP: + loop = va_arg(ap, MAIL_SERVER_LOOP_FN); + break; + case MAIL_SERVER_EXIT: + dgram_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN); + break; + case MAIL_SERVER_PRE_ACCEPT: + dgram_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN); + break; + case MAIL_SERVER_IN_FLOW_DELAY: + dgram_server_in_flow_delay = 1; + break; + case MAIL_SERVER_SOLITARY: + if (!alone) + msg_fatal("service %s requires a process limit of 1", + service_name); + break; + case MAIL_SERVER_UNLIMITED: + if (!zerolimit) + msg_fatal("service %s requires a process limit of 0", + service_name); + break; + case MAIL_SERVER_PRIVILEGED: + if (user_name) + msg_fatal("service %s requires privileged operation", + service_name); + break; + case MAIL_SERVER_WATCHDOG: + dgram_server_watchdog = *va_arg(ap, int *); + break; + case MAIL_SERVER_BOUNCE_INIT: + dsn_filter_title = va_arg(ap, const char *); + dsn_filter_maps = va_arg(ap, const char **); + bounce_client_init(dsn_filter_title, *dsn_filter_maps); + break; + default: + msg_panic("%s: unknown argument type: %d", myname, key); + } + } + va_end(ap); + + if (root_dir) + root_dir = var_queue_dir; + if (user_name) + user_name = var_mail_owner; + + /* + * Can options be required? + */ + if (transport == 0) + msg_fatal("no transport type specified"); + else if (strcasecmp(transport, MASTER_XPORT_NAME_UXDG) == 0) + dgram_server_accept = dgram_server_accept_unix; + else + msg_fatal("unsupported transport type: %s", transport); + + /* + * Retrieve process generation from environment. + */ + if ((generation = getenv(MASTER_GEN_NAME)) != 0) { + if (!alldig(generation)) + msg_fatal("bad generation: %s", generation); + OCTAL_TO_UNSIGNED(dgram_server_generation, generation); + if (msg_verbose) + msg_info("process generation: %s (%o)", + generation, dgram_server_generation); + } + + /* + * Optionally start the debugger on ourself. + */ + if (debug_me) + debug_process(); + + /* + * Traditionally, BSD select() can't handle multiple processes selecting + * on the same socket, and wakes up every process in select(). See TCP/IP + * Illustrated volume 2 page 532. We avoid select() collisions with an + * external lock file. + */ + if (!alone) { + lock_path = concatenate(DEF_PID_DIR, "/", transport, + ".", service_name, (char *) 0); + why = vstring_alloc(1); + if ((dgram_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600, + (struct stat *) 0, -1, -1, why)) == 0) + msg_fatal("open lock file %s: %s", lock_path, vstring_str(why)); + close_on_exec(vstream_fileno(dgram_server_lock), CLOSE_ON_EXEC); + myfree(lock_path); + vstring_free(why); + } + + /* + * Set up call-back info. + */ + dgram_server_service = service; + dgram_server_name = service_name; + dgram_server_argv = argv + optind; + + /* + * Run pre-jail initialization. + */ + if (chdir(var_queue_dir) < 0) + msg_fatal("chdir(\"%s\"): %m", var_queue_dir); + if (pre_init) + pre_init(dgram_server_name, dgram_server_argv); + + /* + * Optionally, restrict the damage that this process can do. + */ + resolve_local_init(); + tzset(); + chroot_uid(root_dir, user_name); + + /* + * Run post-jail initialization. + */ + if (post_init) + post_init(dgram_server_name, dgram_server_argv); + + /* + * Running as a semi-resident server. Service requests. Terminate when we + * have serviced a sufficient number of requests, when no-one has been + * talking to us for a configurable amount of time, or when the master + * process terminated abnormally. + */ + if (var_idle_limit > 0) + event_request_timer(dgram_server_timeout, (void *) 0, var_idle_limit); + for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) { + event_enable_read(fd, dgram_server_accept, CAST_INT_TO_VOID_PTR(fd)); + close_on_exec(fd, CLOSE_ON_EXEC); + } + event_enable_read(MASTER_STATUS_FD, dgram_server_abort, (void *) 0); + close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC); + close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC); + close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC); + watchdog = watchdog_create(dgram_server_watchdog, + (WATCHDOG_FN) 0, (void *) 0); + + /* + * The event loop, at last. + */ + while (var_use_limit == 0 || use_count < var_use_limit) { + if (dgram_server_lock != 0) { + watchdog_stop(watchdog); + if (myflock(vstream_fileno(dgram_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("select lock: %m"); + } + watchdog_start(watchdog); + delay = loop ? loop(dgram_server_name, dgram_server_argv) : -1; + event_loop(delay); + } + dgram_server_exit(); +} diff --git a/src/master/event_server.c b/src/master/event_server.c new file mode 100644 index 0000000..0e750c0 --- /dev/null +++ b/src/master/event_server.c @@ -0,0 +1,971 @@ +/*++ +/* NAME +/* event_server 3 +/* SUMMARY +/* skeleton multi-threaded mail subsystem +/* SYNOPSIS +/* #include <mail_server.h> +/* +/* NORETURN event_server_main(argc, argv, service, key, value, ...) +/* int argc; +/* char **argv; +/* void (*service)(VSTREAM *stream, char *service_name, char **argv); +/* int key; +/* +/* void event_server_disconnect(fd) +/* VSTREAM *stream; +/* +/* void event_server_drain() +/* DESCRIPTION +/* This module implements a skeleton for event-driven +/* mail subsystems: mail subsystem programs that service multiple +/* clients at the same time. The resulting program expects to be run +/* from the \fBmaster\fR process. +/* +/* event_server_main() is the skeleton entry point. It should be +/* called from the application main program. The skeleton does all +/* the generic command-line processing, initialization of +/* configurable parameters, and connection management. +/* Unlike multi_server, this skeleton does not attempt to manage +/* all the events on a client connection. +/* The skeleton never returns. +/* +/* Arguments: +/* .IP "void (*service)(VSTREAM *stream, char *service_name, char **argv)" +/* A pointer to a function that is called by the skeleton each +/* time a client connects to the program's service port. The +/* function is run after the program has optionally dropped +/* its privileges. The application is responsible for managing +/* subsequent I/O events on the stream, and is responsible for +/* calling event_server_disconnect() when the stream is closed. +/* The stream initial state is non-blocking mode. +/* Optional connection attributes are provided as a hash that +/* is attached as stream context. NOTE: the attributes are +/* destroyed after this function is called. The service +/* name argument corresponds to the service name in the master.cf +/* file. The argv argument specifies command-line arguments +/* left over after options processing. +/* .PP +/* Optional arguments are specified as a null-terminated list +/* with macros that have zero or more arguments: +/* .IP "CA_MAIL_SERVER_REQ_INT_TABLE(CONFIG_INT_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_REQ_LONG_TABLE(CONFIG_LONG_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_REQ_STR_TABLE(CONFIG_STR_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_REQ_BOOL_TABLE(CONFIG_BOOL_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_REQ_TIME_TABLE(CONFIG_TIME_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_REQ_RAW_TABLE(CONFIG_RAW_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. Raw parameters are not subjected to $name +/* evaluation. +/* .IP "CA_MAIL_SERVER_REQ_NINT_TABLE(CONFIG_NINT_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_REQ_NBOOL_TABLE(CONFIG_NBOOL_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_REQ_PRE_INIT(void *(char *service_name, char **argv))" +/* A pointer to a function that is called once +/* by the skeleton after it has read the global configuration file +/* and after it has processed command-line arguments, but before +/* the skeleton has optionally relinquished the process privileges. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_REQ_POST_INIT(void *(char *service_name, char **argv))" +/* A pointer to a function that is called once +/* by the skeleton after it has optionally relinquished the process +/* privileges, but before servicing client connection requests. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_REQ_LOOP(int *(char *service_name, char **argv))" +/* A pointer to function that is executed from +/* within the event loop, whenever an I/O or timer event has happened, +/* or whenever nothing has happened for a specified amount of time. +/* The result value of the function specifies how long to wait until +/* the next event. Specify -1 to wait for "as long as it takes". +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_EXIT(void *(char *service_name, char **argv))" +/* A pointer to function that is executed immediately before normal +/* process termination. +/* .IP "CA_MAIL_SERVER_PRE_ACCEPT(void *(char *service_name, char **argv))" +/* Function to be executed prior to accepting a new connection. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_PRE_DISCONN(VSTREAM *, char *service_name, char **argv)" +/* A pointer to a function that is called +/* by the event_server_disconnect() function (see below). +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP CA_MAIL_SERVER_IN_FLOW_DELAY +/* Pause $in_flow_delay seconds when no "mail flow control token" +/* is available. A token is consumed for each connection request. +/* .IP CA_MAIL_SERVER_SOLITARY +/* This service must be configured with process limit of 1. +/* .IP CA_MAIL_SERVER_UNLIMITED +/* This service must be configured with process limit of 0. +/* .IP CA_MAIL_SERVER_PRIVILEGED +/* This service must be configured as privileged. +/* .IP "CA_MAIL_SERVER_SLOW_EXIT(void *(char *service_name, char **argv))" +/* A pointer to a function that is called after "postfix reload" +/* or "master exit". The application can call event_server_drain() +/* (see below) to finish ongoing activities in the background. +/* .IP "CA_MAIL_SERVER_WATCHDOG(int *)" +/* Override the default 1000s watchdog timeout. The value is +/* used after command-line and main.cf file processing. +/* .IP "CA_MAIL_SERVER_BOUNCE_INIT(const char *, const char **)" +/* Initialize the DSN filter for the bounce/defer service +/* clients with the specified map source and map names. +/* .IP "CA_MAIL_SERVER_RETIRE_ME" +/* Prevent a process from being reused indefinitely. After +/* (var_max_use * var_max_idle) seconds or some sane constant, +/* stop accepting new connections and terminate voluntarily +/* when the process becomes idle. +/* .PP +/* event_server_disconnect() should be called by the application +/* to close a client connection. +/* +/* event_server_drain() should be called when the application +/* no longer wishes to accept new client connections. Existing +/* clients are handled in a background process, and the process +/* terminates when the last client is disconnected. A non-zero +/* result means this call should be tried again later. +/* +/* The var_use_limit variable limits the number of clients +/* that a server can service before it commits suicide. This +/* value is taken from the global \fBmain.cf\fR configuration +/* file. Setting \fBvar_use_limit\fR to zero disables the +/* client limit. +/* +/* The var_idle_limit variable limits the time that a service +/* receives no client connection requests before it commits +/* suicide. This value is taken from the global \fBmain.cf\fR +/* configuration file. Setting \fBvar_idle_limit\fR to zero +/* disables the idle limit. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* SEE ALSO +/* master(8), master process +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <sys/time.h> /* select() */ +#include <unistd.h> +#include <signal.h> +#include <stdlib.h> +#include <limits.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <stdarg.h> +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif +#include <time.h> + +#ifdef USE_SYS_SELECT_H +#include <sys/select.h> /* select() */ +#endif + +/* Utility library. */ + +#include <msg.h> +#include <msg_vstream.h> +#include <chroot_uid.h> +#include <listen.h> +#include <events.h> +#include <vstring.h> +#include <vstream.h> +#include <msg_vstream.h> +#include <mymalloc.h> +#include <iostuff.h> +#include <stringops.h> +#include <sane_accept.h> +#include <myflock.h> +#include <safe_open.h> +#include <listen.h> +#include <watchdog.h> +#include <split_at.h> + +/* Global library. */ + +#include <mail_task.h> +#include <debug_process.h> +#include <mail_params.h> +#include <mail_conf.h> +#include <mail_dict.h> +#include <timed_ipc.h> +#include <resolve_local.h> +#include <mail_flow.h> +#include <mail_version.h> +#include <bounce.h> +#include <maillog_client.h> + +/* Process manager. */ + +#include "master_proto.h" + +/* Application-specific */ + +#include "mail_server.h" + + /* + * Global state. + */ +static int client_count; +static int use_count; +static int socket_count = 1; + +static void (*event_server_service) (VSTREAM *, char *, char **); +static char *event_server_name; +static char **event_server_argv; +static void (*event_server_accept) (int, void *); +static void (*event_server_onexit) (char *, char **); +static void (*event_server_pre_accept) (char *, char **); +static VSTREAM *event_server_lock; +static int event_server_in_flow_delay; +static unsigned event_server_generation; +static void (*event_server_pre_disconn) (VSTREAM *, char *, char **); +static void (*event_server_slow_exit) (char *, char **); +static int event_server_watchdog = 1000; +static int event_server_saved_flags; + +/* event_server_exit - normal termination */ + +static NORETURN event_server_exit(void) +{ + if (event_server_onexit) + event_server_onexit(event_server_name, event_server_argv); + exit(0); +} + +/* event_server_retire - retire when idle */ + +static void event_server_retire(int unused_event, void *unused_context) +{ + if (msg_verbose) + msg_info("time to retire -- %s", event_server_slow_exit ? + "draining" : "exiting"); + event_disable_readwrite(MASTER_STATUS_FD); + if (event_server_slow_exit) + event_server_slow_exit(event_server_name, event_server_argv); + else + event_server_exit(); +} + +/* event_server_abort - terminate after abnormal master exit */ + +static void event_server_abort(int unused_event, void *unused_context) +{ + if (msg_verbose) + msg_info("master disconnect -- %s", event_server_slow_exit ? + "draining" : "exiting"); + event_disable_readwrite(MASTER_STATUS_FD); + if (event_server_slow_exit) + event_server_slow_exit(event_server_name, event_server_argv); + else + event_server_exit(); +} + +/* event_server_timeout - idle time exceeded */ + +static void event_server_timeout(int unused_event, void *unused_context) +{ + if (msg_verbose) + msg_info("idle timeout -- exiting"); + event_server_exit(); +} + +/* event_server_drain - stop accepting new clients */ + +int event_server_drain(void) +{ + const char *myname = "event_server_drain"; + int fd; + + switch (fork()) { + /* Try again later. */ + case -1: + return (-1); + /* Finish existing clients in the background, then terminate. */ + case 0: + (void) msg_cleanup((MSG_CLEANUP_FN) 0); + event_fork(); + for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) { + event_disable_readwrite(fd); + (void) close(fd); + /* Play safe - don't reuse this file number. */ + if (DUP2(STDIN_FILENO, fd) < 0) + msg_warn("%s: dup2(%d, %d): %m", myname, STDIN_FILENO, fd); + } + var_use_limit = 1; + return (0); + /* Let the master start a new process. */ + default: + exit(0); + } +} + +/* event_server_disconnect - terminate client session */ + +void event_server_disconnect(VSTREAM *stream) +{ + if (msg_verbose) + msg_info("connection closed fd %d", vstream_fileno(stream)); + if (event_server_pre_disconn) + event_server_pre_disconn(stream, event_server_name, event_server_argv); + (void) vstream_fclose(stream); + client_count--; + /* Avoid integer wrap-around in a persistent process. */ + if (use_count < INT_MAX) + use_count++; + if (client_count == 0 && var_idle_limit > 0) + event_request_timer(event_server_timeout, (void *) 0, var_idle_limit); +} + +/* event_server_execute - in case (char *) != (struct *) */ + +static void event_server_execute(int unused_event, void *context) +{ + VSTREAM *stream = (VSTREAM *) context; + HTABLE *attr = (vstream_flags(stream) == event_server_saved_flags ? + (HTABLE *) vstream_context(stream) : 0); + + if (event_server_lock != 0 + && myflock(vstream_fileno(event_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + + /* + * Do bother the application when the client disconnected. Don't drop the + * already accepted client request after "postfix reload"; that would be + * rude. + */ + if (master_notify(var_pid, event_server_generation, MASTER_STAT_TAKEN) < 0) + /* void */ ; + event_server_service(stream, event_server_name, event_server_argv); + if (master_notify(var_pid, event_server_generation, MASTER_STAT_AVAIL) < 0) + event_server_abort(EVENT_NULL_TYPE, EVENT_NULL_CONTEXT); + if (attr) + htable_free(attr, myfree); +} + +/* event_server_wakeup - wake up application */ + +static void event_server_wakeup(int fd, HTABLE *attr) +{ + VSTREAM *stream; + char *tmp; + +#if defined(F_DUPFD) && (EVENTS_STYLE != EVENTS_STYLE_SELECT) +#ifndef THRESHOLD_FD_WORKAROUND +#define THRESHOLD_FD_WORKAROUND 128 +#endif + int new_fd; + + /* + * Leave some handles < FD_SETSIZE for DBMS libraries, in the unlikely + * case of a multi-server with a thousand clients. + */ + if (fd < THRESHOLD_FD_WORKAROUND) { + if ((new_fd = fcntl(fd, F_DUPFD, THRESHOLD_FD_WORKAROUND)) < 0) + msg_fatal("fcntl F_DUPFD: %m"); + (void) close(fd); + fd = new_fd; + } +#endif + if (msg_verbose) + msg_info("connection established fd %d", fd); + non_blocking(fd, BLOCKING); + close_on_exec(fd, CLOSE_ON_EXEC); + client_count++; + stream = vstream_fdopen(fd, O_RDWR); + tmp = concatenate(event_server_name, " socket", (char *) 0); + vstream_control(stream, + CA_VSTREAM_CTL_PATH(tmp), + CA_VSTREAM_CTL_CONTEXT((void *) attr), + CA_VSTREAM_CTL_END); + myfree(tmp); + timed_ipc_setup(stream); + event_server_saved_flags = vstream_flags(stream); + if (event_server_in_flow_delay && mail_flow_get(1) < 0) + event_request_timer(event_server_execute, (void *) stream, + var_in_flow_delay); + else + event_server_execute(0, (void *) stream); +} + +/* event_server_accept_local - accept client connection request */ + +static void event_server_accept_local(int unused_event, void *context) +{ + int listen_fd = CAST_ANY_PTR_TO_INT(context); + int time_left = -1; + int fd; + + /* + * Be prepared for accept() to fail because some other process already + * got the connection (the number of processes competing for clients is + * kept small, so this is not a "thundering herd" problem). If the + * accept() succeeds, be sure to disable non-blocking I/O, in order to + * minimize confusion. + */ + if (client_count == 0 && var_idle_limit > 0) + time_left = event_cancel_timer(event_server_timeout, (void *) 0); + + if (event_server_pre_accept) + event_server_pre_accept(event_server_name, event_server_argv); + fd = LOCAL_ACCEPT(listen_fd); + if (event_server_lock != 0 + && myflock(vstream_fileno(event_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + if (fd < 0) { + if (errno != EAGAIN) + msg_error("accept connection: %m"); + if (time_left >= 0) + event_request_timer(event_server_timeout, (void *) 0, time_left); + return; + } + event_server_wakeup(fd, (HTABLE *) 0); +} + +#ifdef MASTER_XPORT_NAME_PASS + +/* event_server_accept_pass - accept descriptor */ + +static void event_server_accept_pass(int unused_event, void *context) +{ + int listen_fd = CAST_ANY_PTR_TO_INT(context); + int time_left = -1; + int fd; + HTABLE *attr = 0; + + /* + * Be prepared for accept() to fail because some other process already + * got the connection (the number of processes competing for clients is + * kept small, so this is not a "thundering herd" problem). If the + * accept() succeeds, be sure to disable non-blocking I/O, in order to + * minimize confusion. + */ + if (client_count == 0 && var_idle_limit > 0) + time_left = event_cancel_timer(event_server_timeout, (void *) 0); + + if (event_server_pre_accept) + event_server_pre_accept(event_server_name, event_server_argv); + fd = pass_accept_attr(listen_fd, &attr); + if (event_server_lock != 0 + && myflock(vstream_fileno(event_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + if (fd < 0) { + if (errno != EAGAIN) + msg_error("accept connection: %m"); + if (time_left >= 0) + event_request_timer(event_server_timeout, (void *) 0, time_left); + return; + } + event_server_wakeup(fd, attr); +} + +#endif + +/* event_server_accept_inet - accept client connection request */ + +static void event_server_accept_inet(int unused_event, void *context) +{ + int listen_fd = CAST_ANY_PTR_TO_INT(context); + int time_left = -1; + int fd; + + /* + * Be prepared for accept() to fail because some other process already + * got the connection (the number of processes competing for clients is + * kept small, so this is not a "thundering herd" problem). If the + * accept() succeeds, be sure to disable non-blocking I/O, in order to + * minimize confusion. + */ + if (client_count == 0 && var_idle_limit > 0) + time_left = event_cancel_timer(event_server_timeout, (void *) 0); + + if (event_server_pre_accept) + event_server_pre_accept(event_server_name, event_server_argv); + fd = inet_accept(listen_fd); + if (event_server_lock != 0 + && myflock(vstream_fileno(event_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + if (fd < 0) { + if (errno != EAGAIN) + msg_error("accept connection: %m"); + if (time_left >= 0) + event_request_timer(event_server_timeout, (void *) 0, time_left); + return; + } + event_server_wakeup(fd, (HTABLE *) 0); +} + +/* event_server_main - the real main program */ + +NORETURN event_server_main(int argc, char **argv, MULTI_SERVER_FN service,...) +{ + const char *myname = "event_server_main"; + VSTREAM *stream = 0; + char *root_dir = 0; + char *user_name = 0; + int debug_me = 0; + int daemon_mode = 1; + char *service_name = basename(argv[0]); + int delay; + int c; + int fd; + va_list ap; + MAIL_SERVER_INIT_FN pre_init = 0; + MAIL_SERVER_INIT_FN post_init = 0; + MAIL_SERVER_LOOP_FN loop = 0; + int key; + char *transport = 0; + +#if 0 + char *lock_path; + VSTRING *why; + +#endif + int alone = 0; + int zerolimit = 0; + WATCHDOG *watchdog; + char *oname_val; + char *oname; + char *oval; + const char *err; + char *generation; + int msg_vstream_needed = 0; + const char *dsn_filter_title; + const char **dsn_filter_maps; + int retire_me_from_flags = 0; + int retire_me = 0; + + /* + * Process environment options as early as we can. + */ + if (getenv(CONF_ENV_VERB)) + msg_verbose = 1; + if (getenv(CONF_ENV_DEBUG)) + debug_me = 1; + + /* + * Don't die when a process goes away unexpectedly. + */ + signal(SIGPIPE, SIG_IGN); + + /* + * Don't die for frivolous reasons. + */ +#ifdef SIGXFSZ + signal(SIGXFSZ, SIG_IGN); +#endif + + /* + * May need this every now and then. + */ + var_procname = mystrdup(basename(argv[0])); + set_mail_conf_str(VAR_PROCNAME, var_procname); + + /* + * Initialize logging and exit handler. Do the syslog first, so that its + * initialization completes before we enter the optional chroot jail. + */ + maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE); + if (msg_verbose) + msg_info("daemon started"); + + /* + * Check the Postfix library version as soon as we enable logging. + */ + MAIL_VERSION_CHECK; + + /* + * Initialize from the configuration file. Allow command-line options to + * override compiled-in defaults or configured parameter values. + */ + mail_conf_suck(); + + /* + * After database open error, continue execution with reduced + * functionality. + */ + dict_allow_surrogate = 1; + + /* + * Pick up policy settings from master process. Shut up error messages to + * stderr, because no-one is going to see them. + */ + opterr = 0; + while ((c = GETOPT(argc, argv, "cdDi:lm:n:o:r:s:St:uvVz")) > 0) { + switch (c) { + case 'c': + root_dir = "setme"; + break; + case 'd': + daemon_mode = 0; + break; + case 'D': + debug_me = 1; + break; + case 'i': + mail_conf_update(VAR_MAX_IDLE, optarg); + break; + case 'l': + alone = 1; + break; + case 'm': + mail_conf_update(VAR_MAX_USE, optarg); + break; + case 'n': + service_name = optarg; + break; + case 'o': + oname_val = mystrdup(optarg); + if ((err = split_nameval(oname_val, &oname, &oval)) != 0) + msg_fatal("invalid \"-o %s\" option value: %s", optarg, err); + mail_conf_update(oname, oval); + myfree(oname_val); + break; + case 'r': + if ((retire_me_from_flags = atoi(optarg)) <= 0) + msg_fatal("invalid retirement time: %s", optarg); + break; + case 's': + if ((socket_count = atoi(optarg)) <= 0) + msg_fatal("invalid socket_count: %s", optarg); + break; + case 'S': + stream = VSTREAM_IN; + break; + case 'u': + user_name = "setme"; + break; + case 't': + transport = optarg; + break; + case 'v': + msg_verbose++; + break; + case 'V': + if (++msg_vstream_needed == 1) + msg_vstream_init(mail_task(var_procname), VSTREAM_ERR); + break; + case 'z': + zerolimit = 1; + break; + default: + msg_fatal("invalid option: %c", optopt); + break; + } + } + set_mail_conf_str(VAR_SERVNAME, service_name); + + /* + * Initialize generic parameters and re-initialize logging in case of a + * non-default program name or logging destination. + */ + mail_params_init(); + maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE); + + /* + * Register higher-level dictionaries and initialize the support for + * dynamically-loaded dictionarles. + */ + mail_dict_init(); + + /* + * If not connected to stdin, stdin must not be a terminal. + */ + if (daemon_mode && stream == 0 && isatty(STDIN_FILENO)) { + msg_vstream_init(var_procname, VSTREAM_ERR); + msg_fatal("do not run this command by hand"); + } + + /* + * Application-specific initialization. + */ + va_start(ap, service); + while ((key = va_arg(ap, int)) != 0) { + switch (key) { + case MAIL_SERVER_INT_TABLE: + get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *)); + break; + case MAIL_SERVER_LONG_TABLE: + get_mail_conf_long_table(va_arg(ap, CONFIG_LONG_TABLE *)); + break; + case MAIL_SERVER_STR_TABLE: + get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *)); + break; + case MAIL_SERVER_BOOL_TABLE: + get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *)); + break; + case MAIL_SERVER_TIME_TABLE: + get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *)); + break; + case MAIL_SERVER_RAW_TABLE: + get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *)); + break; + case MAIL_SERVER_NINT_TABLE: + get_mail_conf_nint_table(va_arg(ap, CONFIG_NINT_TABLE *)); + break; + case MAIL_SERVER_NBOOL_TABLE: + get_mail_conf_nbool_table(va_arg(ap, CONFIG_NBOOL_TABLE *)); + break; + case MAIL_SERVER_PRE_INIT: + pre_init = va_arg(ap, MAIL_SERVER_INIT_FN); + break; + case MAIL_SERVER_POST_INIT: + post_init = va_arg(ap, MAIL_SERVER_INIT_FN); + break; + case MAIL_SERVER_LOOP: + loop = va_arg(ap, MAIL_SERVER_LOOP_FN); + break; + case MAIL_SERVER_EXIT: + event_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN); + break; + case MAIL_SERVER_PRE_ACCEPT: + event_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN); + break; + case MAIL_SERVER_PRE_DISCONN: + event_server_pre_disconn = va_arg(ap, MAIL_SERVER_DISCONN_FN); + break; + case MAIL_SERVER_IN_FLOW_DELAY: + event_server_in_flow_delay = 1; + break; + case MAIL_SERVER_SOLITARY: + if (stream == 0 && !alone) + msg_fatal("service %s requires a process limit of 1", + service_name); + break; + case MAIL_SERVER_UNLIMITED: + if (stream == 0 && !zerolimit) + msg_fatal("service %s requires a process limit of 0", + service_name); + break; + case MAIL_SERVER_PRIVILEGED: + if (user_name) + msg_fatal("service %s requires privileged operation", + service_name); + break; + case MAIL_SERVER_WATCHDOG: + event_server_watchdog = *va_arg(ap, int *); + break; + case MAIL_SERVER_SLOW_EXIT: + event_server_slow_exit = va_arg(ap, MAIL_SERVER_SLOW_EXIT_FN); + break; + case MAIL_SERVER_BOUNCE_INIT: + dsn_filter_title = va_arg(ap, const char *); + dsn_filter_maps = va_arg(ap, const char **); + bounce_client_init(dsn_filter_title, *dsn_filter_maps); + break; + case MAIL_SERVER_RETIRE_ME: + if (retire_me_from_flags > 0) + retire_me = retire_me_from_flags; + else if (var_idle_limit == 0 || var_use_limit == 0 + || var_idle_limit > 18000 / var_use_limit) + retire_me = 18000; + else + retire_me = var_idle_limit * var_use_limit; + break; + default: + msg_panic("%s: unknown argument type: %d", myname, key); + } + } + va_end(ap); + + if (root_dir) + root_dir = var_queue_dir; + if (user_name) + user_name = var_mail_owner; + + /* + * Can options be required? + */ + if (stream == 0) { + if (transport == 0) + msg_fatal("no transport type specified"); + if (strcasecmp(transport, MASTER_XPORT_NAME_INET) == 0) + event_server_accept = event_server_accept_inet; + else if (strcasecmp(transport, MASTER_XPORT_NAME_UNIX) == 0) + event_server_accept = event_server_accept_local; +#ifdef MASTER_XPORT_NAME_PASS + else if (strcasecmp(transport, MASTER_XPORT_NAME_PASS) == 0) + event_server_accept = event_server_accept_pass; +#endif + else + msg_fatal("unsupported transport type: %s", transport); + } + + /* + * Retrieve process generation from environment. + */ + if ((generation = getenv(MASTER_GEN_NAME)) != 0) { + if (!alldig(generation)) + msg_fatal("bad generation: %s", generation); + OCTAL_TO_UNSIGNED(event_server_generation, generation); + if (msg_verbose) + msg_info("process generation: %s (%o)", + generation, event_server_generation); + } + + /* + * Optionally start the debugger on ourself. + */ + if (debug_me) + debug_process(); + + /* + * Traditionally, BSD select() can't handle multiple processes selecting + * on the same socket, and wakes up every process in select(). See TCP/IP + * Illustrated volume 2 page 532. We avoid select() collisions with an + * external lock file. + */ + + /* + * XXX Can't compete for exclusive access to the listen socket because we + * also have to monitor existing client connections for service requests. + */ +#if 0 + if (stream == 0 && !alone) { + lock_path = concatenate(DEF_PID_DIR, "/", transport, + ".", service_name, (char *) 0); + why = vstring_alloc(1); + if ((event_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600, + (struct stat *) 0, -1, -1, why)) == 0) + msg_fatal("open lock file %s: %s", lock_path, vstring_str(why)); + close_on_exec(vstream_fileno(event_server_lock), CLOSE_ON_EXEC); + myfree(lock_path); + vstring_free(why); + } +#endif + + /* + * Set up call-back info. + */ + event_server_service = service; + event_server_name = service_name; + event_server_argv = argv + optind; + + /* + * Run pre-jail initialization. + */ + if (chdir(var_queue_dir) < 0) + msg_fatal("chdir(\"%s\"): %m", var_queue_dir); + if (pre_init) + pre_init(event_server_name, event_server_argv); + + /* + * Optionally, restrict the damage that this process can do. + */ + resolve_local_init(); + tzset(); + chroot_uid(root_dir, user_name); + + /* + * Run post-jail initialization. + */ + if (post_init) + post_init(event_server_name, event_server_argv); + + /* + * Are we running as a one-shot server with the client connection on + * standard input? If so, make sure the output is written to stdout so as + * to satisfy common expectation. + */ + if (stream != 0) { + vstream_control(stream, + CA_VSTREAM_CTL_DOUBLE, + CA_VSTREAM_CTL_WRITE_FD(STDOUT_FILENO), + CA_VSTREAM_CTL_END); + service(stream, event_server_name, event_server_argv); + vstream_fflush(stream); + event_server_exit(); + } + + /* + * Running as a semi-resident server. Service connection requests. + * Terminate when we have serviced a sufficient number of clients, when + * no-one has been talking to us for a configurable amount of time, or + * when the master process terminated abnormally. + */ + if (var_idle_limit > 0) + event_request_timer(event_server_timeout, (void *) 0, var_idle_limit); + if (retire_me) + event_request_timer(event_server_retire, (void *) 0, retire_me); + for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) { + event_enable_read(fd, event_server_accept, CAST_INT_TO_VOID_PTR(fd)); + close_on_exec(fd, CLOSE_ON_EXEC); + } + event_enable_read(MASTER_STATUS_FD, event_server_abort, (void *) 0); + close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC); + close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC); + close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC); + watchdog = watchdog_create(event_server_watchdog, + (WATCHDOG_FN) 0, (void *) 0); + + /* + * The event loop, at last. + */ + while (var_use_limit == 0 || use_count < var_use_limit || client_count > 0) { + if (event_server_lock != 0) { + watchdog_stop(watchdog); + if (myflock(vstream_fileno(event_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("select lock: %m"); + } + watchdog_start(watchdog); + delay = loop ? loop(event_server_name, event_server_argv) : -1; + event_loop(delay); + } + event_server_exit(); +} diff --git a/src/master/mail_flow.c b/src/master/mail_flow.c new file mode 100644 index 0000000..2958500 --- /dev/null +++ b/src/master/mail_flow.c @@ -0,0 +1,142 @@ +/*++ +/* NAME +/* mail_flow 3 +/* SUMMARY +/* global mail flow control +/* SYNOPSIS +/* #include <mail_flow.h> +/* +/* ssize_t mail_flow_get(count) +/* ssize_t count; +/* +/* ssize_t mail_flow_put(count) +/* ssize_t count; +/* +/* ssize_t mail_flow_count() +/* DESCRIPTION +/* This module implements a simple flow control mechanism that +/* is based on tokens that are consumed by mail receiving processes +/* and that are produced by mail sending processes. +/* +/* mail_flow_get() attempts to read specified number of tokens. The +/* result is > 0 for success, < 0 for failure. In the latter case, +/* the process is expected to slow down a little. +/* +/* mail_flow_put() produces the specified number of tokens. The +/* token producing process is expected to produce new tokens +/* whenever it falls idle and no more tokens are available. +/* +/* mail_flow_count() returns the number of available tokens. +/* BUGS +/* The producer needs to wake up periodically to ensure that +/* tokens are not lost due to leakage. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <iostuff.h> +#include <warn_stat.h> + +/* Global library. */ + +#include <mail_flow.h> + +/* Master library. */ + +#include <master_proto.h> + +#define BUFFER_SIZE 1024 + +/* mail_flow_get - read N tokens */ + +ssize_t mail_flow_get(ssize_t len) +{ + const char *myname = "mail_flow_get"; + char buf[BUFFER_SIZE]; + struct stat st; + ssize_t count; + ssize_t n = 0; + + /* + * Sanity check. + */ + if (len <= 0) + msg_panic("%s: bad length %ld", myname, (long) len); + + /* + * Silence some wild claims. + */ + if (fstat(MASTER_FLOW_WRITE, &st) < 0) + msg_fatal("fstat flow pipe write descriptor: %m"); + + /* + * Read and discard N bytes. XXX AIX read() can return 0 when an open + * pipe is empty. + */ + for (count = len; count > 0; count -= n) + if ((n = read(MASTER_FLOW_READ, buf, count > BUFFER_SIZE ? + BUFFER_SIZE : count)) <= 0) + return (-1); + if (msg_verbose) + msg_info("%s: %ld %ld", myname, (long) len, (long) (len - count)); + return (len - count); +} + +/* mail_flow_put - put N tokens */ + +ssize_t mail_flow_put(ssize_t len) +{ + const char *myname = "mail_flow_put"; + char buf[BUFFER_SIZE]; + ssize_t count; + ssize_t n = 0; + + /* + * Sanity check. + */ + if (len <= 0) + msg_panic("%s: bad length %ld", myname, (long) len); + + /* + * Write or discard N bytes. + */ + memset(buf, 0, len > BUFFER_SIZE ? BUFFER_SIZE : len); + + for (count = len; count > 0; count -= n) + if ((n = write(MASTER_FLOW_WRITE, buf, count > BUFFER_SIZE ? + BUFFER_SIZE : count)) < 0) + return (-1); + if (msg_verbose) + msg_info("%s: %ld %ld", myname, (long) len, (long) (len - count)); + return (len - count); +} + +/* mail_flow_count - return number of available tokens */ + +ssize_t mail_flow_count(void) +{ + const char *myname = "mail_flow_count"; + ssize_t count; + + if ((count = peekfd(MASTER_FLOW_READ)) < 0) + msg_warn("%s: %m", myname); + return (count); +} diff --git a/src/master/mail_flow.h b/src/master/mail_flow.h new file mode 100644 index 0000000..3f7f7bd --- /dev/null +++ b/src/master/mail_flow.h @@ -0,0 +1,32 @@ +#ifndef _MAIL_FLOW_H_INCLUDED_ +#define _MAIL_FLOW_H_INCLUDED_ + +/*++ +/* NAME +/* mail_flow 3h +/* SUMMARY +/* global mail flow control +/* SYNOPSIS +/* #include <mail_flow.h> +/* DESCRIPTION +/* .nf + + /* + * Functional interface. + */ +extern ssize_t mail_flow_get(ssize_t); +extern ssize_t mail_flow_put(ssize_t); +extern ssize_t mail_flow_count(void); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/master/mail_server.h b/src/master/mail_server.h new file mode 100644 index 0000000..7300b3f --- /dev/null +++ b/src/master/mail_server.h @@ -0,0 +1,150 @@ +/*++ +/* NAME +/* mail_server 3h +/* SUMMARY +/* skeleton servers +/* SYNOPSIS +/* #include <mail_server.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <vstream.h> + + /* + * Global library. + */ +#include <mail_conf.h> + + /* + * External interface. Tables are defined in mail_conf.h. + */ +#define MAIL_SERVER_INT_TABLE 1 +#define MAIL_SERVER_STR_TABLE 2 +#define MAIL_SERVER_BOOL_TABLE 3 +#define MAIL_SERVER_TIME_TABLE 4 +#define MAIL_SERVER_RAW_TABLE 5 +#define MAIL_SERVER_NINT_TABLE 6 +#define MAIL_SERVER_NBOOL_TABLE 7 +#define MAIL_SERVER_LONG_TABLE 8 + +#define MAIL_SERVER_PRE_INIT 10 +#define MAIL_SERVER_POST_INIT 11 +#define MAIL_SERVER_LOOP 12 +#define MAIL_SERVER_EXIT 13 +#define MAIL_SERVER_PRE_ACCEPT 14 +#define MAIL_SERVER_SOLITARY 15 +#define MAIL_SERVER_UNLIMITED 16 +#define MAIL_SERVER_PRE_DISCONN 17 +#define MAIL_SERVER_PRIVILEGED 18 +#define MAIL_SERVER_WATCHDOG 19 + +#define MAIL_SERVER_IN_FLOW_DELAY 20 +#define MAIL_SERVER_SLOW_EXIT 21 +#define MAIL_SERVER_BOUNCE_INIT 22 +#define MAIL_SERVER_RETIRE_ME 23 + +typedef void (*MAIL_SERVER_INIT_FN) (char *, char **); +typedef int (*MAIL_SERVER_LOOP_FN) (char *, char **); +typedef void (*MAIL_SERVER_EXIT_FN) (char *, char **); +typedef void (*MAIL_SERVER_ACCEPT_FN) (char *, char **); +typedef void (*MAIL_SERVER_DISCONN_FN) (VSTREAM *, char *, char **); +typedef void (*MAIL_SERVER_SLOW_EXIT_FN) (char *, char **); + +/* Type-checked API for external use. */ +#define CA_MAIL_SERVER_INT_TABLE(v) MAIL_SERVER_INT_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_INT_TABLE, (v)) +#define CA_MAIL_SERVER_STR_TABLE(v) MAIL_SERVER_STR_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_STR_TABLE, (v)) +#define CA_MAIL_SERVER_BOOL_TABLE(v) MAIL_SERVER_BOOL_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_BOOL_TABLE, (v)) +#define CA_MAIL_SERVER_TIME_TABLE(v) MAIL_SERVER_TIME_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_TIME_TABLE, (v)) +#define CA_MAIL_SERVER_RAW_TABLE(v) MAIL_SERVER_RAW_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_RAW_TABLE, (v)) +#define CA_MAIL_SERVER_NINT_TABLE(v) MAIL_SERVER_NINT_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_NINT_TABLE, (v)) +#define CA_MAIL_SERVER_NBOOL_TABLE(v) MAIL_SERVER_NBOOL_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_NBOOL_TABLE, (v)) +#define CA_MAIL_SERVER_LONG_TABLE(v) MAIL_SERVER_LONG_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_LONG_TABLE, (v)) +#define CA_MAIL_SERVER_PRE_INIT(v) MAIL_SERVER_PRE_INIT, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_INIT_FN, (v)) +#define CA_MAIL_SERVER_POST_INIT(v) MAIL_SERVER_POST_INIT, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_INIT_FN, (v)) +#define CA_MAIL_SERVER_LOOP(v) MAIL_SERVER_LOOP, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_LOOP_FN, (v)) +#define CA_MAIL_SERVER_EXIT(v) MAIL_SERVER_EXIT, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_EXIT_FN, (v)) +#define CA_MAIL_SERVER_PRE_ACCEPT(v) MAIL_SERVER_PRE_ACCEPT, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_ACCEPT_FN, (v)) +#define CA_MAIL_SERVER_SOLITARY MAIL_SERVER_SOLITARY +#define CA_MAIL_SERVER_UNLIMITED MAIL_SERVER_UNLIMITED +#define CA_MAIL_SERVER_PRE_DISCONN(v) MAIL_SERVER_PRE_DISCONN, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_DISCONN_FN, (v)) +#define CA_MAIL_SERVER_PRIVILEGED MAIL_SERVER_PRIVILEGED +#define CA_MAIL_SERVER_WATCHDOG(v) MAIL_SERVER_WATCHDOG, CHECK_PTR(MAIL_SERVER, int, (v)) +#define CA_MAIL_SERVER_IN_FLOW_DELAY MAIL_SERVER_IN_FLOW_DELAY +#define CA_MAIL_SERVER_SLOW_EXIT(v) MAIL_SERVER_SLOW_EXIT, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_SLOW_EXIT_FN, (v)) +#define CA_MAIL_SERVER_BOUNCE_INIT(v, w) MAIL_SERVER_BOUNCE_INIT, CHECK_PTR(MAIL_SERVER, char, (v)), CHECK_PPTR(MAIL_SERVER, char, (w)) +#define CA_MAIL_SERVER_RETIRE_ME MAIL_SERVER_RETIRE_ME + +CHECK_VAL_HELPER_DCL(MAIL_SERVER, MAIL_SERVER_SLOW_EXIT_FN); +CHECK_VAL_HELPER_DCL(MAIL_SERVER, MAIL_SERVER_LOOP_FN); +CHECK_VAL_HELPER_DCL(MAIL_SERVER, MAIL_SERVER_INIT_FN); +CHECK_VAL_HELPER_DCL(MAIL_SERVER, MAIL_SERVER_EXIT_FN); +CHECK_VAL_HELPER_DCL(MAIL_SERVER, MAIL_SERVER_DISCONN_FN); +CHECK_VAL_HELPER_DCL(MAIL_SERVER, MAIL_SERVER_ACCEPT_FN); +CHECK_PTR_HELPER_DCL(MAIL_SERVER, int); +CHECK_PTR_HELPER_DCL(MAIL_SERVER, char); +CHECK_PPTR_HELPER_DCL(MAIL_SERVER, char); +CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_TIME_TABLE); +CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_STR_TABLE); +CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_RAW_TABLE); +CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_NINT_TABLE); +CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_NBOOL_TABLE); +CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_LONG_TABLE); +CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_INT_TABLE); +CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_BOOL_TABLE); + + /* + * single_server.c + */ +typedef void (*SINGLE_SERVER_FN) (VSTREAM *, char *, char **); +extern NORETURN single_server_main(int, char **, SINGLE_SERVER_FN,...); + + /* + * multi_server.c + */ +typedef void (*MULTI_SERVER_FN) (VSTREAM *, char *, char **); +extern NORETURN multi_server_main(int, char **, MULTI_SERVER_FN,...); +extern void multi_server_disconnect(VSTREAM *); +extern int multi_server_drain(void); + + /* + * event_server.c + */ +typedef void (*EVENT_SERVER_FN) (VSTREAM *, char *, char **); +extern NORETURN event_server_main(int, char **, EVENT_SERVER_FN,...); +extern void event_server_disconnect(VSTREAM *); +extern int event_server_drain(void); + + /* + * trigger_server.c + */ +typedef void (*TRIGGER_SERVER_FN) (char *, ssize_t, char *, char **); +extern NORETURN trigger_server_main(int, char **, TRIGGER_SERVER_FN,...); + +#define TRIGGER_BUF_SIZE 1024 + + /* + * dgram_server.c + */ +typedef void (*DGRAM_SERVER_FN) (char *, ssize_t, char *, char **); +extern NORETURN dgram_server_main(int, char **, DGRAM_SERVER_FN,...); + +#define DGRAM_BUF_SIZE 4096 + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ diff --git a/src/master/master.c b/src/master/master.c new file mode 100644 index 0000000..d901778 --- /dev/null +++ b/src/master/master.c @@ -0,0 +1,594 @@ +/*++ +/* NAME +/* master 8 +/* SUMMARY +/* Postfix master process +/* SYNOPSIS +/* \fBmaster\fR [\fB-Dditvw\fR] [\fB-c \fIconfig_dir\fR] [\fB-e \fIexit_time\fR] +/* DESCRIPTION +/* The \fBmaster\fR(8) daemon is the resident process that runs Postfix +/* daemons on demand: daemons to send or receive messages via the +/* network, daemons to deliver mail locally, etc. These daemons are +/* created on demand up to a configurable maximum number per service. +/* +/* Postfix daemons terminate voluntarily, either after being idle for +/* a configurable amount of time, or after having serviced a +/* configurable number of requests. Exceptions to this rule are the +/* resident queue manager, address verification server, and the TLS +/* session cache and pseudo-random number server. +/* +/* The behavior of the \fBmaster\fR(8) daemon is controlled by the +/* \fBmaster.cf\fR configuration file, as described in \fBmaster\fR(5). +/* +/* Options: +/* .IP "\fB-c \fIconfig_dir\fR" +/* Read the \fBmain.cf\fR and \fBmaster.cf\fR configuration files in +/* the named directory instead of the default configuration directory. +/* This also overrides the configuration files for other Postfix +/* daemon processes. +/* .IP \fB-D\fR +/* After initialization, run a debugger on the master process. The +/* debugging command is specified with the \fBdebugger_command\fR in +/* the \fBmain.cf\fR global configuration file. +/* .IP \fB-d\fR +/* Do not redirect stdin, stdout or stderr to /dev/null, and +/* do not discard the controlling terminal. This must be used +/* for debugging only. +/* .IP "\fB-e \fIexit_time\fR" +/* Terminate the master process after \fIexit_time\fR seconds. Child +/* processes terminate at their convenience. +/* .IP \fB-i\fR +/* Enable \fBinit\fR mode: do not become a session or process +/* group leader; and similar to \fB-s\fR, do not redirect stdout +/* to /dev/null, so that "maillog_file = /dev/stdout" works. +/* This mode is allowed only if the process ID equals 1. +/* .sp +/* This feature is available in Postfix 3.3 and later. +/* .IP \fB-s\fR +/* Do not redirect stdout to /dev/null, so that "maillog_file +/* = /dev/stdout" works. +/* .sp +/* This feature is available in Postfix 3.4 and later. +/* .IP \fB-t\fR +/* Test mode. Return a zero exit status when the \fBmaster.pid\fR lock +/* file does not exist or when that file is not locked. This is evidence +/* that the \fBmaster\fR(8) daemon is not running. +/* .IP \fB-v\fR +/* Enable verbose logging for debugging purposes. This option +/* is passed on to child processes. Multiple \fB-v\fR options +/* make the software increasingly verbose. +/* .IP \fB-w\fR +/* Wait in a dummy foreground process, while the real master +/* daemon initializes in a background process. The dummy +/* foreground process returns a zero exit status only if the +/* master daemon initialization is successful, and if it +/* completes in a reasonable amount of time. +/* .sp +/* This feature is available in Postfix 2.10 and later. +/* .PP +/* Signals: +/* .IP \fBSIGHUP\fR +/* Upon receipt of a \fBHUP\fR signal (e.g., after "\fBpostfix reload\fR"), +/* the master process re-reads its configuration files. If a service has +/* been removed from the \fBmaster.cf\fR file, its running processes +/* are terminated immediately. +/* Otherwise, running processes are allowed to terminate as soon +/* as is convenient, so that changes in configuration settings +/* affect only new service requests. +/* .IP \fBSIGTERM\fR +/* Upon receipt of a \fBTERM\fR signal (e.g., after "\fBpostfix abort\fR"), +/* the master process passes the signal on to its child processes and +/* terminates. +/* This is useful for an emergency shutdown. Normally one would +/* terminate only the master ("\fBpostfix stop\fR") and allow running +/* processes to finish what they are doing. +/* DIAGNOSTICS +/* Problems are reported to \fBsyslogd\fR(8) or \fBpostlogd\fR(8). +/* The exit status +/* is non-zero in case of problems, including problems while +/* initializing as a master daemon process in the background. +/* ENVIRONMENT +/* .ad +/* .fi +/* .IP \fBMAIL_DEBUG\fR +/* After initialization, start a debugger as specified with the +/* \fBdebugger_command\fR configuration parameter in the \fBmain.cf\fR +/* configuration file. +/* .IP \fBMAIL_CONFIG\fR +/* Directory with Postfix configuration files. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Unlike most Postfix daemon processes, the \fBmaster\fR(8) server does +/* not automatically pick up changes to \fBmain.cf\fR. Changes +/* to \fBmaster.cf\fR are never picked up automatically. +/* Use the "\fBpostfix reload\fR" command after a configuration change. +/* RESOURCE AND RATE CONTROLS +/* .ad +/* .fi +/* .IP "\fBdefault_process_limit (100)\fR" +/* The default maximal number of Postfix child processes that provide +/* a given service. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBmax_use (100)\fR" +/* The maximal number of incoming connections that a Postfix daemon +/* process will service before terminating voluntarily. +/* .IP "\fBservice_throttle_time (60s)\fR" +/* How long the Postfix \fBmaster\fR(8) waits before forking a server that +/* appears to be malfunctioning. +/* .PP +/* Available in Postfix version 2.6 and later: +/* .IP "\fBmaster_service_disable (empty)\fR" +/* Selectively disable \fBmaster\fR(8) listener ports by service type +/* or by service name and type. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_directory (see 'postconf -d' output)\fR" +/* The directory with Postfix support programs and daemon programs. +/* .IP "\fBdebugger_command (empty)\fR" +/* The external command to execute when a Postfix daemon program is +/* invoked with the -D option. +/* .IP "\fBinet_interfaces (all)\fR" +/* The network interface addresses that this mail system receives +/* mail on. +/* .IP "\fBinet_protocols (all)\fR" +/* The Internet protocols Postfix will attempt to use when making +/* or accepting connections. +/* .IP "\fBimport_environment (see 'postconf -d' output)\fR" +/* The list of environment parameters that a privileged Postfix +/* process will import from a non-Postfix parent process, or name=value +/* environment overrides. +/* .IP "\fBmail_owner (postfix)\fR" +/* The UNIX system account that owns the Postfix queue and most Postfix +/* daemon processes. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* FILES +/* .ad +/* .fi +/* To expand the directory names below into their actual values, +/* use the command "\fBpostconf config_directory\fR" etc. +/* .na +/* .nf +/* +/* $config_directory/main.cf, global configuration file. +/* $config_directory/master.cf, master server configuration file. +/* $queue_directory/pid/master.pid, master lock file. +/* $data_directory/master.lock, master lock file. +/* SEE ALSO +/* qmgr(8), queue manager +/* verify(8), address verification +/* master(5), master.cf configuration file syntax +/* postconf(5), main.cf configuration file syntax +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System libraries. */ + +#include <sys_defs.h> +#include <sys/stat.h> +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <limits.h> + +/* Utility library. */ + +#include <events.h> +#include <msg.h> +#include <vstring.h> +#include <mymalloc.h> +#include <iostuff.h> +#include <vstream.h> +#include <stringops.h> +#include <myflock.h> +#include <watchdog.h> +#include <clean_env.h> +#include <argv.h> +#include <safe.h> +#include <set_eugid.h> +#include <set_ugid.h> + +/* Global library. */ + +#include <mail_params.h> +#include <mail_version.h> +#include <debug_process.h> +#include <mail_task.h> +#include <mail_conf.h> +#include <open_lock.h> +#include <inet_proto.h> +#include <mail_parm_split.h> +#include <maillog_client.h> + +/* Application-specific. */ + +#include "master.h" + +int master_detach = 1; +int init_mode = 0; + +/* master_exit_event - exit for memory leak testing purposes */ + +static void master_exit_event(int unused_event, void *unused_context) +{ + msg_info("master exit time has arrived"); + exit(0); +} + +/* usage - show hint and terminate */ + +static NORETURN usage(const char *me) +{ + msg_fatal("usage: %s [-c config_dir] [-D (debug)] [-d (don't detach from terminal)] [-e exit_time] [-t (test)] [-v] [-w (wait for initialization)]", me); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - main program */ + +int main(int argc, char **argv) +{ + static VSTREAM *lock_fp; + static VSTREAM *data_lock_fp; + VSTRING *lock_path; + VSTRING *data_lock_path; + off_t inherited_limit; + int debug_me = 0; + int keep_stdout = 0; + int ch; + int fd; + int n; + int test_lock = 0; + VSTRING *why; + WATCHDOG *watchdog; + ARGV *import_env; + int wait_flag = 0; + int monitor_fd = -1; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Initialize. + */ + umask(077); /* never fails! */ + + /* + * Process environment options as early as we can. + */ + if (getenv(CONF_ENV_VERB)) + msg_verbose = 1; + if (getenv(CONF_ENV_DEBUG)) + debug_me = 1; + + /* + * Don't die when a process goes away unexpectedly. + */ + signal(SIGPIPE, SIG_IGN); + + /* + * Strip and save the process name for diagnostics etc. + */ + var_procname = mystrdup(basename(argv[0])); + + /* + * When running a child process, don't leak any open files that were + * leaked to us by our own (privileged) parent process. Descriptors 0-2 + * are taken care of after we have initialized error logging. + * + * Some systems such as AIX have a huge per-process open file limit. In + * those cases, limit the search for potential file descriptor leaks to + * just the first couple hundred. + * + * The Debian post-installation script passes an open file descriptor into + * the master process and waits forever for someone to close it. Because + * of this we have to close descriptors > 2, and pray that doing so does + * not break things. + */ + closefrom(3); + + /* + * Initialize logging and exit handler. + */ + maillog_client_init(mail_task(var_procname), + MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK); + + /* + * Check the Postfix library version as soon as we enable logging. + */ + MAIL_VERSION_CHECK; + + /* + * The mail system must be run by the superuser so it can revoke + * privileges for selected operations. That's right - it takes privileges + * to toss privileges. + */ + if (getuid() != 0) + msg_fatal("the master command is reserved for the superuser"); + if (unsafe() != 0) + msg_fatal("the master command must not run as a set-uid process"); + + /* + * Process JCL. + */ + while ((ch = GETOPT(argc, argv, "c:Dde:istvw")) > 0) { + switch (ch) { + case 'c': + if (setenv(CONF_ENV_PATH, optarg, 1) < 0) + msg_fatal("out of memory"); + break; + case 'd': + master_detach = 0; + break; + case 'e': + event_request_timer(master_exit_event, (void *) 0, atoi(optarg)); + break; + case 'i': + if (getpid() != 1) + msg_fatal("-i is allowed only for PID 1 process"); + init_mode = 1; + keep_stdout = 1; + break; + case 'D': + debug_me = 1; + break; + case 's': + keep_stdout = 1; + break; + case 't': + test_lock = 1; + break; + case 'v': + msg_verbose++; + break; + case 'w': + wait_flag = 1; + break; + default: + usage(argv[0]); + /* NOTREACHED */ + } + } + + /* + * This program takes no other arguments. + */ + if (argc > optind) + usage(argv[0]); + + /* + * Sanity check. + */ + if (test_lock && wait_flag) + msg_fatal("the -t and -w options cannot be used together"); + if (init_mode && (debug_me || !master_detach || wait_flag)) + msg_fatal("the -i option cannot be used with -D, -d, or -w"); + + /* + * Run a foreground monitor process that returns an exit status of 0 when + * the child background process reports successful initialization as a + * daemon process. We use a generous limit in case main/master.cf specify + * symbolic hosts/ports and the naming service is slow. + */ +#define MASTER_INIT_TIMEOUT 100 /* keep this limit generous */ + + if (wait_flag) + monitor_fd = master_monitor(MASTER_INIT_TIMEOUT); + + /* + * If started from a terminal, get rid of any tty association. This also + * means that all errors and warnings must go to the syslog daemon. + * Some new world has no terminals and prefers logging to stdout. + */ + if (master_detach) + for (fd = 0; fd < 3; fd++) { + if (fd == STDOUT_FILENO && keep_stdout) + continue; + (void) close(fd); + if (open("/dev/null", O_RDWR, 0) != fd) + msg_fatal("open /dev/null: %m"); + } + + /* + * Run in a separate process group, so that "postfix stop" can terminate + * all MTA processes cleanly. Give up if we can't separate from our + * parent process. We're not supposed to blow away the parent. + */ + if (init_mode == 0 && debug_me == 0 && master_detach != 0 + && setsid() == -1 && getsid(0) != getpid()) + msg_fatal("unable to set session and process group ID: %m"); + + /* + * Make some room for plumbing with file descriptors. XXX This breaks + * when a service listens on many ports. In order to do this right we + * must change the master-child interface so that descriptors do not need + * to have fixed numbers. + * + * In a child we need two descriptors for the flow control pipe, one for + * child->master status updates and at least one for listening. + */ + for (n = 0; n < 5; n++) { + if (close_on_exec(dup(0), CLOSE_ON_EXEC) < 0) + msg_fatal("dup(0): %m"); + } + + /* + * Final initializations. Unfortunately, we must read the global Postfix + * configuration file after doing command-line processing, so that we get + * consistent results when we SIGHUP the server to reload configuration + * files. + */ + master_vars_init(); + + /* + * In case of multi-protocol support. This needs to be done because + * master does not invoke mail_params_init() (it was written before that + * code existed). + */ + (void) inet_proto_init(VAR_INET_PROTOCOLS, var_inet_protocols); + + /* + * Environment import filter, to enforce consistent behavior whether + * Postfix is started by hand, or at system boot time. + */ + import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ); + clean_env(import_env->argv); + argv_free(import_env); + + if ((inherited_limit = get_file_limit()) < 0) + set_file_limit(OFF_T_MAX); + + if (chdir(var_queue_dir)) + msg_fatal("chdir %s: %m", var_queue_dir); + + /* + * Lock down the master.pid file. In test mode, no file means that it + * isn't locked. + */ + lock_path = vstring_alloc(10); + data_lock_path = vstring_alloc(10); + why = vstring_alloc(10); + + vstring_sprintf(lock_path, "%s/%s.pid", DEF_PID_DIR, var_procname); + if (test_lock && access(vstring_str(lock_path), F_OK) < 0) + exit(0); + lock_fp = open_lock(vstring_str(lock_path), O_RDWR | O_CREAT, 0644, why); + if (test_lock) + exit(lock_fp ? 0 : 1); + if (lock_fp == 0) + msg_fatal("open lock file %s: %s", + vstring_str(lock_path), vstring_str(why)); + vstream_fprintf(lock_fp, "%*lu\n", (int) sizeof(unsigned long) * 4, + (unsigned long) var_pid); + if (vstream_fflush(lock_fp)) + msg_fatal("cannot update lock file %s: %m", vstring_str(lock_path)); + close_on_exec(vstream_fileno(lock_fp), CLOSE_ON_EXEC); + + /* + * Lock down the Postfix-writable data directory. + */ + vstring_sprintf(data_lock_path, "%s/%s.lock", var_data_dir, var_procname); + set_eugid(var_owner_uid, var_owner_gid); + data_lock_fp = + open_lock(vstring_str(data_lock_path), O_RDWR | O_CREAT, 0644, why); + set_ugid(getuid(), getgid()); + if (data_lock_fp == 0) + msg_fatal("open lock file %s: %s", + vstring_str(data_lock_path), vstring_str(why)); + vstream_fprintf(data_lock_fp, "%*lu\n", (int) sizeof(unsigned long) * 4, + (unsigned long) var_pid); + if (vstream_fflush(data_lock_fp)) + msg_fatal("cannot update lock file %s: %m", vstring_str(data_lock_path)); + close_on_exec(vstream_fileno(data_lock_fp), CLOSE_ON_EXEC); + + /* + * Clean up. + */ + vstring_free(why); + vstring_free(lock_path); + vstring_free(data_lock_path); + + /* + * Optionally start the debugger on ourself. + */ + if (debug_me) + debug_process(); + + /* + * Finish initialization, last part. We must process configuration files + * after processing command-line parameters, so that we get consistent + * results when we SIGHUP the server to reload configuration files. + */ + master_config(); + master_sigsetup(); + master_flow_init(); + maillog_client_init(mail_task(var_procname), + MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK); + msg_info("daemon started -- version %s, configuration %s", + var_mail_version, var_config_dir); + + /* + * Report successful initialization to the foreground monitor process. + */ + if (monitor_fd >= 0) { + write(monitor_fd, "", 1); + (void) close(monitor_fd); + } + + /* + * Process events. The event handler will execute the read/write/timer + * action routines. Whenever something has happened, see if we received + * any signal in the mean time. Although the master process appears to do + * multiple things at the same time, it really is all a single thread, so + * that there are no concurrency conflicts within the master process. + */ +#define MASTER_WATCHDOG_TIME 1000 + + watchdog = watchdog_create(MASTER_WATCHDOG_TIME, (WATCHDOG_FN) 0, (void *) 0); + for (;;) { +#ifdef HAS_VOLATILE_LOCKS + if (myflock(vstream_fileno(lock_fp), INTERNAL_LOCK, + MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("refresh exclusive lock: %m"); + if (myflock(vstream_fileno(data_lock_fp), INTERNAL_LOCK, + MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("refresh exclusive lock: %m"); +#endif + watchdog_start(watchdog); /* same as trigger servers */ + event_loop(MASTER_WATCHDOG_TIME / 2); + if (master_gotsighup) { + msg_info("reload -- version %s, configuration %s", + var_mail_version, var_config_dir); + master_gotsighup = 0; /* this first */ + master_vars_init(); /* then this */ + master_refresh(); /* then this */ + maillog_client_init(mail_task(var_procname), + MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK); + } + if (master_gotsigchld) { + if (msg_verbose) + msg_info("got sigchld"); + master_gotsigchld = 0; /* this first */ + master_reap_child(); /* then this */ + } + } +} diff --git a/src/master/master.h b/src/master/master.h new file mode 100644 index 0000000..ce07ab7 --- /dev/null +++ b/src/master/master.h @@ -0,0 +1,246 @@ +/*++ +/* NAME +/* master 3h +/* SUMMARY +/* Postfix master - data structures and prototypes +/* SYNOPSIS +/* #include "master.h" +/* DESCRIPTION +/* .nf + + /* + * Server processes that provide the same service share a common "listen" + * socket to accept connection requests, and share a common pipe to the + * master process to send status reports. Server processes die voluntarily + * when idle for a configurable amount of time, or after servicing a + * configurable number of requests; the master process spawns new processes + * on demand up to a configurable concurrency limit and/or periodically. + * + * The canonical service name is what we use internally, so that we correctly + * handle a request to "reload" after someone changes "smtp" into "25". + * + * We use the external service name from master.cf when reporting problems, so + * that the user can figure out what we are talking about. Of course we also + * include the canonical service name so that the UNIX-domain smtp service + * can be distinguished from the Internet smtp service. + */ +typedef struct MASTER_SERV { + int flags; /* status, features, etc. */ + char *ext_name; /* service endpoint name (master.cf) */ + char *name; /* service endpoint name (canonical) */ + int type; /* UNIX-domain, INET, etc. */ + time_t busy_warn_time; /* limit "all servers busy" warning */ + int wakeup_time; /* wakeup interval */ + int *listen_fd; /* incoming requests */ + int listen_fd_count; /* nr of descriptors */ + union { + struct { + char *port; /* inet listen port */ + struct INET_ADDR_LIST *addr;/* inet listen address */ + } inet_ep; +#define MASTER_INET_ADDRLIST(s) ((s)->endpoint.inet_ep.addr) +#define MASTER_INET_PORT(s) ((s)->endpoint.inet_ep.port) + } endpoint; + int max_proc; /* upper bound on # processes */ + char *path; /* command pathname */ + struct ARGV *args; /* argument vector */ + char *stress_param_val; /* stress value: "yes" or empty */ + time_t stress_expire_time; /* stress pulse stretcher */ + int avail_proc; /* idle processes */ + int total_proc; /* number of processes */ + int throttle_delay; /* failure recovery parameter */ + int status_fd[2]; /* child status reports */ + struct BINHASH *children; /* linkage */ + struct MASTER_SERV *next; /* linkage */ +} MASTER_SERV; + + /* + * Per-service flag bits. We assume trouble when a child process terminates + * before completing its first request: either the program is defective, + * some configuration is wrong, or the system is out of resources. + */ +#define MASTER_FLAG_THROTTLE (1<<0) /* we're having trouble */ +#define MASTER_FLAG_MARK (1<<1) /* garbage collection support */ +#define MASTER_FLAG_CONDWAKE (1<<2) /* wake up if actually used */ +#define MASTER_FLAG_INETHOST (1<<3) /* endpoint name specifies host */ +#define MASTER_FLAG_LOCAL_ONLY (1<<4) /* no remote clients */ +#define MASTER_FLAG_LISTEN (1<<5) /* monitor this port */ + +#define MASTER_THROTTLED(f) ((f)->flags & MASTER_FLAG_THROTTLE) +#define MASTER_MARKED_FOR_DELETION(f) ((f)->flags & MASTER_FLAG_MARK) +#define MASTER_LISTENING(f) ((f)->flags & MASTER_FLAG_LISTEN) + +#define MASTER_LIMIT_OK(limit, count) ((limit) == 0 || ((count) < (limit))) + + /* + * Service types, stream sockets unless indicated otherwise. + */ +#define MASTER_SERV_TYPE_UNIX 1 /* AF_UNIX domain socket */ +#define MASTER_SERV_TYPE_INET 2 /* AF_INET domain socket */ +#define MASTER_SERV_TYPE_FIFO 3 /* fifo (named pipe) */ +#define MASTER_SERV_TYPE_PASS 4 /* AF_UNIX domain socket */ +#define MASTER_SERV_TYPE_UXDG 5 /* AF_UNIX domain datagram socket */ + + /* + * Default process management policy values. This is only the bare minimum. + * Most policy management is delegated to child processes. The process + * manager runs at high privilege level and has to be kept simple. + */ +#define MASTER_DEF_MIN_IDLE 1 /* preferred # of idle processes */ + + /* + * Structure of child process. + */ +typedef int MASTER_PID; /* pid is key into binhash table */ + +typedef struct MASTER_PROC { + MASTER_PID pid; /* child process id */ + unsigned gen; /* child generation number */ + int avail; /* availability */ + MASTER_SERV *serv; /* parent linkage */ + int use_count; /* number of service requests */ +} MASTER_PROC; + + /* + * Other manifest constants. + */ +#define MASTER_BUF_LEN 2048 /* logical config line length */ + + /* + * master.c + */ +extern int master_detach; +extern int init_mode; + + /* + * master_ent.c + */ +extern void fset_master_ent(char *); +extern void set_master_ent(void); +extern void end_master_ent(void); +extern void print_master_ent(MASTER_SERV *); +extern MASTER_SERV *get_master_ent(void); +extern void free_master_ent(MASTER_SERV *); + + /* + * master_conf.c + */ +extern void master_config(void); +extern void master_refresh(void); + + /* + * master_vars.c + */ +extern void master_vars_init(void); + + /* + * master_service.c + */ +extern MASTER_SERV *master_head; +extern void master_start_service(MASTER_SERV *); +extern void master_stop_service(MASTER_SERV *); +extern void master_restart_service(MASTER_SERV *, int); + +#define DO_CONF_RELOAD 1 /* config files were reloaded */ +#define NO_CONF_RELOAD 0 /* no config file was reloaded */ + + /* + * master_events.c + */ +extern int master_gotsighup; +extern int master_gotsigchld; +extern void master_sigsetup(void); + + /* + * master_status.c + */ +extern void master_status_init(MASTER_SERV *); +extern void master_status_cleanup(MASTER_SERV *); + + /* + * master_wakeup.c + */ +extern void master_wakeup_init(MASTER_SERV *); +extern void master_wakeup_cleanup(MASTER_SERV *); + + + /* + * master_listen.c + */ +extern void master_listen_init(MASTER_SERV *); +extern void master_listen_cleanup(MASTER_SERV *); + + /* + * master_avail.c + */ +extern void master_avail_listen(MASTER_SERV *); +extern void master_avail_cleanup(MASTER_SERV *); +extern void master_avail_more(MASTER_SERV *, MASTER_PROC *); +extern void master_avail_less(MASTER_SERV *, MASTER_PROC *); + + /* + * master_spawn.c + */ +extern struct BINHASH *master_child_table; +extern void master_spawn(MASTER_SERV *); +extern void master_reap_child(void); +extern void master_delete_children(MASTER_SERV *); + + /* + * master_flow.c + */ +extern void master_flow_init(void); +extern int master_flow_pipe[2]; + + /* + * master_watch.c + * + * Support to warn about main.cf parameters that can only be initialized but + * not updated, and to initialize or update data structures that derive + * values from main.cf parameters. + */ +typedef struct { + const char *name; /* parameter name */ + char **value; /* current main.cf value */ + char **backup; /* actual value that is being used */ + int flags; /* see below */ + void (*notify) (void); /* init or update data structure */ +} MASTER_STR_WATCH; + +typedef struct { + const char *name; /* parameter name */ + int *value; /* current main.cf value */ + int backup; /* actual value that is being used */ + int flags; /* see below */ + void (*notify) (void); /* init or update data structure */ +} MASTER_INT_WATCH; + +#define MASTER_WATCH_FLAG_UPDATABLE (1<<0) /* support update after init */ +#define MASTER_WATCH_FLAG_ISSET (1<<1) /* backup is initialized */ + +extern void master_str_watch(const MASTER_STR_WATCH *); +extern void master_int_watch(MASTER_INT_WATCH *); + + /* + * master_monitor.c + */ +extern int master_monitor(int); + +/* DIAGNOSTICS +/* BUGS +/* SEE ALSO +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ diff --git a/src/master/master_avail.c b/src/master/master_avail.c new file mode 100644 index 0000000..046503d --- /dev/null +++ b/src/master/master_avail.c @@ -0,0 +1,251 @@ +/*++ +/* NAME +/* master_avail 3 +/* SUMMARY +/* Postfix master - process creation policy +/* SYNOPSIS +/* #include "master.h" +/* +/* void master_avail_listen(serv) +/* MASTER_SERV *serv; +/* +/* void master_avail_cleanup(serv) +/* MASTER_SERV *serv; +/* +/* void master_avail_more(serv, proc) +/* MASTER_SERV *serv; +/* MASTER_PROC *proc; +/* +/* void master_avail_less(serv, proc) +/* MASTER_SERV *serv; +/* MASTER_PROC *proc; +/* DESCRIPTION +/* This module implements the process creation policy. As long as +/* the allowed number of processes for the given service is not +/* exceeded, a connection request is either handled by an existing +/* available process, or this module causes a new process to be +/* created to service the request. +/* +/* When the service runs out of process slots, and the service +/* is eligible for stress-mode operation, a warning is logged, +/* servers are asked to restart at their convenience, and new +/* servers are created with stress mode enabled. +/* +/* master_avail_listen() ensures that someone monitors the service's +/* listen socket for connection requests (as long as resources +/* to handle connection requests are available). This function may +/* be called at random times, but it must be called after each status +/* change of a service (throttled, process limit, etc.) or child +/* process (taken, available, dead, etc.). +/* +/* master_avail_cleanup() should be called when the named service +/* is taken out of operation. It terminates child processes by +/* sending SIGTERM. +/* +/* master_avail_more() should be called when the named process +/* has become available for servicing new connection requests. +/* This function updates the process availability status and +/* counter, and implicitly calls master_avail_listen(). +/* +/* master_avail_less() should be called when the named process +/* has become unavailable for servicing new connection requests. +/* This function updates the process availability status and +/* counter, and implicitly calls master_avail_listen(). +/* DIAGNOSTICS +/* Panic: internal inconsistencies. +/* BUGS +/* SEE ALSO +/* master_spawn(3), child process birth and death +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include <sys_defs.h> + +/* Utility library. */ + +#include <events.h> +#include <msg.h> + +/* Application-specific. */ + +#include "master_proto.h" +#include "master.h" + +/* master_avail_event - create child process to handle connection request */ + +static void master_avail_event(int event, void *context) +{ + MASTER_SERV *serv = (MASTER_SERV *) context; + time_t now; + + if (event == 0) /* XXX Can this happen? */ + msg_panic("master_avail_event: null event"); + else { + + /* + * When all servers for a public internet service are busy, we start + * creating server processes with "-o stress=yes" on the command + * line, and keep creating such processes until the process count is + * below the limit for at least 1000 seconds. This provides a minimal + * solution that can be adopted into legacy and stable Postfix + * releases. + * + * This is not the right place to update serv->stress_param_val in + * response to stress level changes. Doing so would would contaminate + * the "postfix reload" code with stress management implementation + * details, creating a source of future bugs. Instead, we update + * simple counters or flags here, and use their values to determine + * the proper serv->stress_param_val value when exec-ing a server + * process. + */ + if (serv->stress_param_val != 0 + && !MASTER_LIMIT_OK(serv->max_proc, serv->total_proc + 1)) { + now = event_time(); + if (serv->stress_expire_time < now) + master_restart_service(serv, NO_CONF_RELOAD); + serv->stress_expire_time = now + 1000; + } + master_spawn(serv); + } +} + +/* master_avail_listen - enforce the socket monitoring policy */ + +void master_avail_listen(MASTER_SERV *serv) +{ + const char *myname = "master_avail_listen"; + int listen_flag; + time_t now; + int n; + + /* + * Caution: several other master_XXX modules call master_avail_listen(), + * master_avail_more() or master_avail_less(). To avoid mutual dependency + * problems, the code below invokes no code in other master_XXX modules, + * and modifies no data that is maintained by other master_XXX modules. + * + * When no-one else is monitoring the service's listen socket, start + * monitoring the socket for connection requests. All this under the + * restriction that we have sufficient resources to service a connection + * request. + */ + if (msg_verbose) + msg_info("%s: %s avail %d total %d max %d", myname, serv->name, + serv->avail_proc, serv->total_proc, serv->max_proc); + if (MASTER_THROTTLED(serv) || serv->avail_proc > 0) { + listen_flag = 0; + } else if (MASTER_LIMIT_OK(serv->max_proc, serv->total_proc)) { + listen_flag = 1; + } else { + listen_flag = 0; + if (serv->stress_param_val != 0) { + now = event_time(); + if (serv->busy_warn_time < now - 1000) { + serv->busy_warn_time = now; + msg_warn("service \"%s\" (%s) has reached its process limit \"%d\": " + "new clients may experience noticeable delays", + serv->ext_name, serv->name, serv->max_proc); + msg_warn("to avoid this condition, increase the process count " + "in master.cf or reduce the service time per client"); + msg_warn("see http://www.postfix.org/STRESS_README.html for " + "examples of stress-adapting configuration settings"); + } + } + } + if (listen_flag && !MASTER_LISTENING(serv)) { + if (msg_verbose) + msg_info("%s: enable events %s", myname, serv->name); + for (n = 0; n < serv->listen_fd_count; n++) + event_enable_read(serv->listen_fd[n], master_avail_event, + (void *) serv); + serv->flags |= MASTER_FLAG_LISTEN; + } else if (!listen_flag && MASTER_LISTENING(serv)) { + if (msg_verbose) + msg_info("%s: disable events %s", myname, serv->name); + for (n = 0; n < serv->listen_fd_count; n++) + event_disable_readwrite(serv->listen_fd[n]); + serv->flags &= ~MASTER_FLAG_LISTEN; + } +} + +/* master_avail_cleanup - cleanup */ + +void master_avail_cleanup(MASTER_SERV *serv) +{ + int n; + + master_delete_children(serv); /* XXX calls + * master_avail_listen */ + + /* + * This code is redundant because master_delete_children() throttles the + * service temporarily before calling master_avail_listen/less(), which + * then turn off read events. This temporary throttling is not documented + * (it is only an optimization), and therefore we must not depend on it. + */ + if (MASTER_LISTENING(serv)) { + for (n = 0; n < serv->listen_fd_count; n++) + event_disable_readwrite(serv->listen_fd[n]); + serv->flags &= ~MASTER_FLAG_LISTEN; + } +} + +/* master_avail_more - one more available child process */ + +void master_avail_more(MASTER_SERV *serv, MASTER_PROC *proc) +{ + const char *myname = "master_avail_more"; + + /* + * Caution: several other master_XXX modules call master_avail_listen(), + * master_avail_more() or master_avail_less(). To avoid mutual dependency + * problems, the code below invokes no code in other master_XXX modules, + * and modifies no data that is maintained by other master_XXX modules. + * + * This child process has become available for servicing connection + * requests, so we can stop monitoring the service's listen socket. The + * child will do it for us. + */ + if (msg_verbose) + msg_info("%s: pid %d (%s)", myname, proc->pid, proc->serv->name); + if (proc->avail == MASTER_STAT_AVAIL) + msg_panic("%s: process already available", myname); + serv->avail_proc++; + proc->avail = MASTER_STAT_AVAIL; + master_avail_listen(serv); +} + +/* master_avail_less - one less available child process */ + +void master_avail_less(MASTER_SERV *serv, MASTER_PROC *proc) +{ + const char *myname = "master_avail_less"; + + /* + * Caution: several other master_XXX modules call master_avail_listen(), + * master_avail_more() or master_avail_less(). To avoid mutual dependency + * problems, the code below invokes no code in other master_XXX modules, + * and modifies no data that is maintained by other master_XXX modules. + * + * This child is no longer available for servicing connection requests. When + * no child processes are available, start monitoring the service's + * listen socket for new connection requests. + */ + if (msg_verbose) + msg_info("%s: pid %d (%s)", myname, proc->pid, proc->serv->name); + if (proc->avail != MASTER_STAT_AVAIL) + msg_panic("%s: process not available", myname); + serv->avail_proc--; + proc->avail = MASTER_STAT_TAKEN; + master_avail_listen(serv); +} diff --git a/src/master/master_conf.c b/src/master/master_conf.c new file mode 100644 index 0000000..37cad2a --- /dev/null +++ b/src/master/master_conf.c @@ -0,0 +1,152 @@ +/*++ +/* NAME +/* master_conf 3 +/* SUMMARY +/* Postfix master - master.cf file processing +/* SYNOPSIS +/* #include "master.h" +/* +/* void master_config(serv) +/* MASTER_SERV *serv; +/* +/* void master_refresh(serv) +/* MASTER_SERV *serv; +/* DESCRIPTION +/* Use master_config() to read the master.cf configuration file +/* during program initialization. +/* +/* Use master_refresh() to re-read the master.cf configuration file +/* when the process is already running. +/* DIAGNOSTICS +/* BUGS +/* SEE ALSO +/* master_ent(3), configuration file programmatic interface. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System libraries. */ + +#include <sys_defs.h> +#include <unistd.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <argv.h> + +/* Application-specific. */ + +#include "master.h" + +/* master_refresh - re-read configuration table */ + +void master_refresh(void) +{ + MASTER_SERV *serv; + MASTER_SERV **servp; + + /* + * Mark all existing services. + */ + for (serv = master_head; serv != 0; serv = serv->next) + serv->flags |= MASTER_FLAG_MARK; + + /* + * Read the master.cf configuration file. The master_conf() routine + * unmarks services upon update. New services are born with the mark bit + * off. After this, anything with the mark bit on should be removed. + */ + master_config(); + + /* + * Delete all services that are still marked - they disappeared from the + * configuration file and are therefore no longer needed. + */ + for (servp = &master_head; (serv = *servp) != 0; /* void */ ) { + if ((serv->flags & MASTER_FLAG_MARK) != 0) { + *servp = serv->next; + master_stop_service(serv); + free_master_ent(serv); + } else { + servp = &serv->next; + } + } +} + +/* master_config - read config file */ + +void master_config(void) +{ + MASTER_SERV *entry; + MASTER_SERV *serv; + +#define STR_DIFF strcmp +#define STR_SAME !strcmp +#define SWAP(type,a,b) { type temp = a; a = b; b = temp; } + + /* + * A service is identified by its endpoint name AND by its transport + * type, not just by its name alone. The name is unique within its + * transport type. XXX Service privacy is encoded in the service name. + */ + set_master_ent(); + while ((entry = get_master_ent()) != 0) { + if (msg_verbose) + print_master_ent(entry); + for (serv = master_head; serv != 0; serv = serv->next) + if (STR_SAME(serv->name, entry->name) && serv->type == entry->type) + break; + + /* + * Add a new service entry. We do not really care in what order the + * service entries are kept in memory. + */ + if (serv == 0) { + entry->next = master_head; + master_head = entry; + master_start_service(entry); + } + + /* + * Update an existing service entry. Make the current generation of + * child processes commit suicide whenever it is convenient. The next + * generation of child processes will run with the new configuration + * settings. + */ + else { + if ((serv->flags & MASTER_FLAG_MARK) == 0) + msg_warn("duplicate master.cf entry for service \"%s\" (%s) " + "-- using the last entry", serv->ext_name, serv->name); + else + serv->flags &= ~MASTER_FLAG_MARK; + if (entry->flags & MASTER_FLAG_CONDWAKE) + serv->flags |= MASTER_FLAG_CONDWAKE; + else + serv->flags &= ~MASTER_FLAG_CONDWAKE; + serv->wakeup_time = entry->wakeup_time; + serv->max_proc = entry->max_proc; + serv->throttle_delay = entry->throttle_delay; + SWAP(char *, serv->ext_name, entry->ext_name); + SWAP(char *, serv->path, entry->path); + SWAP(ARGV *, serv->args, entry->args); + SWAP(char *, serv->stress_param_val, entry->stress_param_val); + master_restart_service(serv, DO_CONF_RELOAD); + free_master_ent(entry); + } + } + end_master_ent(); +} diff --git a/src/master/master_ent.c b/src/master/master_ent.c new file mode 100644 index 0000000..9f0f34c --- /dev/null +++ b/src/master/master_ent.c @@ -0,0 +1,645 @@ +/*++ +/* NAME +/* master_ent 3 +/* SUMMARY +/* Postfix master - config file access +/* SYNOPSIS +/* #include "master.h" +/* +/* void fset_master_ent(path) +/* char *path; +/* +/* void set_master_ent() +/* +/* MASTER_SERV *get_master_ent() +/* +/* void end_master_ent() +/* +/* void print_master_ent(entry) +/* MASTER_SERV *entry; +/* +/* void free_master_ent(entry) +/* MASTER_SERV *entry; +/* DESCRIPTION +/* This module implements a simple programmatic interface +/* for accessing Postfix master process configuration files. +/* +/* fset_master_ent() specifies the location of the master process +/* configuration file. The pathname is copied. +/* +/* set_master_ent() opens the configuration file. It is an error +/* to call this routine while the configuration file is still open. +/* It is an error to open a configuration file without specifying +/* its name to fset_master_ent(). +/* +/* get_master_ent() reads the next entry from an open configuration +/* file and returns the parsed result. A null result means the end +/* of file was reached. +/* +/* print_master_ent() prints the specified service entry. +/* +/* end_master_ent() closes an open configuration file. It is an error +/* to call this routine when the configuration file is not open. +/* +/* free_master_ent() destroys the memory used for a parsed configuration +/* file entry. +/* DIAGNOSTICS +/* Panics: interface violations. Fatal errors: memory allocation +/* failure. +/* BUGS +/* SEE ALSO +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System libraries. */ + +#include <sys_defs.h> +#include <netinet/in.h> +#include <stdarg.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <ctype.h> +#include <fcntl.h> + +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif + +/* Utility libraries. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstring.h> +#include <vstream.h> +#include <argv.h> +#include <stringops.h> +#include <readlline.h> +#include <inet_addr_list.h> +#include <host_port.h> +#include <inet_addr_host.h> +#include <sock_addr.h> +#include <inet_proto.h> + +/* Global library. */ + +#include <match_service.h> +#include <mail_proto.h> +#include <mail_params.h> +#include <own_inet_addr.h> +#include <wildcard_inet_addr.h> +#include <mail_conf.h> + +/* Local stuff. */ + +#include "master_proto.h" +#include "master.h" + +static char *master_path; /* config file name */ +static VSTREAM *master_fp; /* config file pointer */ +static int master_line_last; /* config file line number */ +static int master_line; /* config file line number */ +static ARGV *master_disable; /* disabled service patterns */ + +static char master_blanks[] = CHARS_SPACE; /* field delimiters */ + +/* fset_master_ent - specify configuration file pathname */ + +void fset_master_ent(char *path) +{ + if (master_path != 0) + myfree(master_path); + master_path = mystrdup(path); +} + +/* set_master_ent - open configuration file */ + +void set_master_ent() +{ + const char *myname = "set_master_ent"; + char *disable; + + if (master_fp != 0) + msg_panic("%s: configuration file still open", myname); + if (master_path == 0) + msg_panic("%s: no configuration file specified", myname); + if ((master_fp = vstream_fopen(master_path, O_RDONLY, 0)) == 0) + msg_fatal("open %s: %m", master_path); + master_line_last = 0; + if (master_disable != 0) + msg_panic("%s: service disable list still exists", myname); + if (inet_proto_info()->ai_family_list[0] == 0) { + msg_warn("all network protocols are disabled (%s = %s)", + VAR_INET_PROTOCOLS, var_inet_protocols); + msg_warn("disabling all type \"inet\" services in master.cf"); + disable = concatenate(MASTER_XPORT_NAME_INET, ",", + var_master_disable, (char *) 0); + master_disable = match_service_init(disable); + myfree(disable); + } else + master_disable = match_service_init(var_master_disable); +} + +/* end_master_ent - close configuration file */ + +void end_master_ent() +{ + const char *myname = "end_master_ent"; + + if (master_fp == 0) + msg_panic("%s: configuration file not open", myname); + if (vstream_fclose(master_fp) != 0) + msg_fatal("%s: close configuration file: %m", myname); + master_fp = 0; + if (master_disable == 0) + msg_panic("%s: no service disable list", myname); + match_service_free(master_disable); + master_disable = 0; +} + +/* master_conf_context - plot the target range */ + +static const char *master_conf_context(void) +{ + static VSTRING *context_buf = 0; + + if (context_buf == 0) + context_buf = vstring_alloc(100); + vstring_sprintf(context_buf, "%s: line %d", master_path, master_line); + return (vstring_str(context_buf)); +} + +/* fatal_with_context - print fatal error with file/line context */ + +static NORETURN PRINTFLIKE(1, 2) fatal_with_context(char *format,...) +{ + const char *myname = "fatal_with_context"; + VSTRING *vp = vstring_alloc(100); + va_list ap; + + if (master_path == 0) + msg_panic("%s: no configuration file specified", myname); + + va_start(ap, format); + vstring_vsprintf(vp, format, ap); + va_end(ap); + msg_fatal("%s: %s", master_conf_context(), vstring_str(vp)); +} + +/* fatal_invalid_field - report invalid field value */ + +static NORETURN fatal_invalid_field(char *name, char *value) +{ + fatal_with_context("field \"%s\": bad value: \"%s\"", name, value); +} + +/* get_str_ent - extract string field */ + +static char *get_str_ent(char **bufp, char *name, char *def_val) +{ + char *value; + + if ((value = mystrtok(bufp, master_blanks)) == 0) + fatal_with_context("missing \"%s\" field", name); + if (strcmp(value, "-") == 0) { + if (def_val == 0) + fatal_with_context("field \"%s\" has no default value", name); + if (warn_compat_break_chroot && strcmp(name, "chroot") == 0) + msg_info("%s: using backwards-compatible default setting " + "%s=%s", master_conf_context(), name, def_val); + return (def_val); + } else { + return (value); + } +} + +/* get_bool_ent - extract boolean field */ + +static int get_bool_ent(char **bufp, char *name, char *def_val) +{ + char *value; + + value = get_str_ent(bufp, name, def_val); + if (strcmp("y", value) == 0) { + return (1); + } else if (strcmp("n", value) == 0) { + return (0); + } else { + fatal_invalid_field(name, value); + } + /* NOTREACHED */ +} + +/* get_int_ent - extract integer field */ + +static int get_int_ent(char **bufp, char *name, char *def_val, int min_val) +{ + char *value; + int n; + + value = get_str_ent(bufp, name, def_val); + if (!ISDIGIT(*value) || (n = atoi(value)) < min_val) + fatal_invalid_field(name, value); + return (n); +} + +/* get_master_ent - read entry from configuration file */ + +MASTER_SERV *get_master_ent() +{ + VSTRING *buf = vstring_alloc(100); + VSTRING *junk = vstring_alloc(100); + MASTER_SERV *serv; + char *cp; + char *name; + char *host = 0; + char *port = 0; + char *transport; + int private; + int unprivileged; /* passed on to child */ + int chroot; /* passed on to child */ + char *command; + int n; + char *bufp; + char *atmp; + const char *parse_err; + static char *saved_interfaces = 0; + char *err; + + if (master_fp == 0) + msg_panic("get_master_ent: config file not open"); + if (master_disable == 0) + msg_panic("get_master_ent: no service disable list"); + + /* + * XXX We cannot change the inet_interfaces setting for a running master + * process. Listening sockets are inherited by child processes so that + * closing and reopening those sockets in the master does not work. + * + * Another problem is that library routines still cache results that are + * based on the old inet_interfaces setting. It is too much trouble to + * recompute everything. + * + * In order to keep our data structures consistent we ignore changes in + * inet_interfaces settings, and issue a warning instead. + */ + if (saved_interfaces == 0) + saved_interfaces = mystrdup(var_inet_interfaces); + + /* + * Skip blank lines and comment lines. + */ + for (;;) { + if (readllines(buf, master_fp, &master_line_last, &master_line) == 0) { + vstring_free(buf); + vstring_free(junk); + return (0); + } + bufp = vstring_str(buf); + if ((cp = mystrtok(&bufp, master_blanks)) == 0) + continue; + name = cp; + transport = get_str_ent(&bufp, "transport type", (char *) 0); + vstring_sprintf(junk, "%s/%s", name, transport); + if (match_service_match(master_disable, vstring_str(junk)) == 0) + break; + } + + /* + * Parse one logical line from the configuration file. Initialize service + * structure members in order. + */ + serv = (MASTER_SERV *) mymalloc(sizeof(MASTER_SERV)); + serv->next = 0; + + /* + * Flags member. + */ + serv->flags = 0; + + /* + * All servers busy warning timer. + */ + serv->busy_warn_time = 0; + + /* + * Service name. Syntax is transport-specific. + */ + serv->ext_name = mystrdup(name); + + /* + * Transport type: inet (wild-card listen or virtual) or unix. + */ +#define STR_SAME !strcmp + + if (STR_SAME(transport, MASTER_XPORT_NAME_INET)) { + if (!STR_SAME(saved_interfaces, var_inet_interfaces)) { + msg_warn("service %s: ignoring %s change", + serv->ext_name, VAR_INET_INTERFACES); + msg_warn("to change %s, stop and start Postfix", + VAR_INET_INTERFACES); + } + serv->type = MASTER_SERV_TYPE_INET; + atmp = mystrdup(name); + if ((parse_err = host_port(atmp, &host, "", &port, (char *) 0)) != 0) + fatal_with_context("%s in \"%s\"", parse_err, name); + if (*host) { + serv->flags |= MASTER_FLAG_INETHOST;/* host:port */ + MASTER_INET_ADDRLIST(serv) = (INET_ADDR_LIST *) + mymalloc(sizeof(*MASTER_INET_ADDRLIST(serv))); + inet_addr_list_init(MASTER_INET_ADDRLIST(serv)); + if (inet_addr_host(MASTER_INET_ADDRLIST(serv), host) == 0) + fatal_with_context("bad hostname or network address: %s", name); + inet_addr_list_uniq(MASTER_INET_ADDRLIST(serv)); + serv->listen_fd_count = MASTER_INET_ADDRLIST(serv)->used; + } else { + MASTER_INET_ADDRLIST(serv) = + strcasecmp(saved_interfaces, INET_INTERFACES_ALL) ? + own_inet_addr_list() : /* virtual */ + wildcard_inet_addr_list(); /* wild-card */ + inet_addr_list_uniq(MASTER_INET_ADDRLIST(serv)); + serv->listen_fd_count = MASTER_INET_ADDRLIST(serv)->used; + } + MASTER_INET_PORT(serv) = mystrdup(port); + for (n = 0; /* see below */ ; n++) { + if (n >= MASTER_INET_ADDRLIST(serv)->used) { + serv->flags |= MASTER_FLAG_LOCAL_ONLY; + break; + } + if (!sock_addr_in_loopback(SOCK_ADDR_PTR(MASTER_INET_ADDRLIST(serv)->addrs + n))) + break; + } + } else if (STR_SAME(transport, MASTER_XPORT_NAME_UNIX)) { + serv->type = MASTER_SERV_TYPE_UNIX; + serv->listen_fd_count = 1; + serv->flags |= MASTER_FLAG_LOCAL_ONLY; + } else if (STR_SAME(transport, MASTER_XPORT_NAME_UXDG)) { + serv->type = MASTER_SERV_TYPE_UXDG; + serv->listen_fd_count = 1; + serv->flags |= MASTER_FLAG_LOCAL_ONLY; + } else if (STR_SAME(transport, MASTER_XPORT_NAME_FIFO)) { + serv->type = MASTER_SERV_TYPE_FIFO; + serv->listen_fd_count = 1; + serv->flags |= MASTER_FLAG_LOCAL_ONLY; +#ifdef MASTER_SERV_TYPE_PASS + } else if (STR_SAME(transport, MASTER_XPORT_NAME_PASS)) { + serv->type = MASTER_SERV_TYPE_PASS; + serv->listen_fd_count = 1; + /* If this is a connection screener, remote clients are likely. */ +#endif + } else { + fatal_with_context("bad transport type: %s", transport); + } + + /* + * Service class: public or private. + */ + private = get_bool_ent(&bufp, "private", "y"); + + /* + * Derive an internal service name. The name may depend on service + * attributes such as privacy. + */ + if (serv->type == MASTER_SERV_TYPE_INET) { + MAI_HOSTADDR_STR host_addr; + MAI_SERVPORT_STR serv_port; + struct addrinfo *res0; + + if (private) + fatal_with_context("inet service cannot be private"); + + /* + * Canonicalize endpoint names so that we correctly handle "reload" + * requests after someone changes "25" into "smtp" or vice versa. + */ + if (*host == 0) + host = 0; + /* Canonicalize numeric host and numeric or symbolic service. */ + if (hostaddr_to_sockaddr(host, port, 0, &res0) == 0) { + SOCKADDR_TO_HOSTADDR(res0->ai_addr, res0->ai_addrlen, + host ? &host_addr : (MAI_HOSTADDR_STR *) 0, + &serv_port, 0); + serv->name = (host ? concatenate("[", host_addr.buf, "]:", + serv_port.buf, (char *) 0) : + mystrdup(serv_port.buf)); + freeaddrinfo(res0); + } + /* Canonicalize numeric or symbolic service. */ + else if (hostaddr_to_sockaddr((char *) 0, port, 0, &res0) == 0) { + SOCKADDR_TO_HOSTADDR(res0->ai_addr, res0->ai_addrlen, + (MAI_HOSTADDR_STR *) 0, &serv_port, 0); + serv->name = (host ? concatenate("[", host, "]:", + serv_port.buf, (char *) 0) : + mystrdup(serv_port.buf)); + freeaddrinfo(res0); + } + /* Bad service name? */ + else + serv->name = mystrdup(name); + myfree(atmp); + } else if (serv->type == MASTER_SERV_TYPE_UNIX) { + serv->name = mail_pathname(private ? MAIL_CLASS_PRIVATE : + MAIL_CLASS_PUBLIC, name); + } else if (serv->type == MASTER_SERV_TYPE_UXDG) { + serv->name = mail_pathname(private ? MAIL_CLASS_PRIVATE : + MAIL_CLASS_PUBLIC, name); + } else if (serv->type == MASTER_SERV_TYPE_FIFO) { + serv->name = mail_pathname(private ? MAIL_CLASS_PRIVATE : + MAIL_CLASS_PUBLIC, name); +#ifdef MASTER_SERV_TYPE_PASS + } else if (serv->type == MASTER_SERV_TYPE_PASS) { + serv->name = mail_pathname(private ? MAIL_CLASS_PRIVATE : + MAIL_CLASS_PUBLIC, name); +#endif + } else { + msg_panic("bad transport type: %d", serv->type); + } + + /* + * Listen socket(s). XXX We pre-allocate storage because the number of + * sockets is frozen anyway once we build the command-line vector below. + */ + if (serv->listen_fd_count == 0) { + fatal_with_context("no valid IP address found: %s", name); + } + serv->listen_fd = (int *) mymalloc(sizeof(int) * serv->listen_fd_count); + for (n = 0; n < serv->listen_fd_count; n++) + serv->listen_fd[n] = -1; + + /* + * Privilege level. Default is to restrict process privileges to those of + * the mail owner. + */ + unprivileged = get_bool_ent(&bufp, "unprivileged", "y"); + + /* + * Chroot. Default is to restrict file system access to the mail queue. + * XXX Chroot cannot imply unprivileged service (for example, the pickup + * service runs chrooted but needs privileges to open files as the user). + */ + chroot = get_bool_ent(&bufp, "chroot", var_compat_level < 1 ? "y" : "n"); + + /* + * Wakeup timer. XXX should we require that var_proc_limit == 1? Right + * now, the only services that have a wakeup timer also happen to be the + * services that have at most one running instance: local pickup and + * local delivery. + */ + serv->wakeup_time = get_int_ent(&bufp, "wakeup_time", "0", 0); + + /* + * Find out if the wakeup time is conditional, i.e., wakeup triggers + * should not be sent until the service has actually been used. + */ + if (serv->wakeup_time > 0 && bufp[*bufp ? -2 : -1] == '?') + serv->flags |= MASTER_FLAG_CONDWAKE; + + /* + * Concurrency limit. Zero means no limit. + */ + vstring_sprintf(junk, "%d", var_proc_limit); + serv->max_proc = get_int_ent(&bufp, "max_proc", vstring_str(junk), 0); + + /* + * Path to command, + */ + command = get_str_ent(&bufp, "command", (char *) 0); + serv->path = concatenate(var_daemon_dir, "/", command, (char *) 0); + + /* + * Idle and total process count. + */ + serv->avail_proc = 0; + serv->total_proc = 0; + + /* + * Backoff time in case a service is broken. + */ + serv->throttle_delay = var_throttle_time; + + /* + * Shared channel for child status updates. + */ + serv->status_fd[0] = serv->status_fd[1] = -1; + + /* + * Child process structures. + */ + serv->children = 0; + + /* + * Command-line vector. Add "-n service_name" when the process name + * basename differs from the service name. Always add the transport. + */ + serv->args = argv_alloc(0); + argv_add(serv->args, command, (char *) 0); + if (serv->max_proc == 1) + argv_add(serv->args, "-l", (char *) 0); + if (serv->max_proc == 0) + argv_add(serv->args, "-z", (char *) 0); + if (strcmp(basename(command), name) != 0) + argv_add(serv->args, "-n", name, (char *) 0); + argv_add(serv->args, "-t", transport, (char *) 0); + if (master_detach == 0) + argv_add(serv->args, "-d", (char *) 0); + if (msg_verbose) + argv_add(serv->args, "-v", (char *) 0); + if (unprivileged) + argv_add(serv->args, "-u", (char *) 0); + if (chroot) + argv_add(serv->args, "-c", (char *) 0); + if ((serv->flags & MASTER_FLAG_LOCAL_ONLY) == 0 && serv->max_proc > 1) { + argv_add(serv->args, "-o", "stress=" CONFIG_BOOL_YES, (char *) 0); + serv->stress_param_val = + serv->args->argv[serv->args->argc - 1] + sizeof("stress=") - 1; + serv->stress_param_val[0] = 0; + } else + serv->stress_param_val = 0; + serv->stress_expire_time = 0; + if (serv->listen_fd_count > 1) + argv_add(serv->args, "-s", + vstring_str(vstring_sprintf(junk, "%d", serv->listen_fd_count)), + (char *) 0); + while ((cp = mystrtokq(&bufp, master_blanks, CHARS_BRACE)) != 0) { + if (*cp == CHARS_BRACE[0] + && (err = extpar(&cp, CHARS_BRACE, EXTPAR_FLAG_STRIP)) != 0) + fatal_with_context("%s", err); + argv_add(serv->args, cp, (char *) 0); + } + argv_terminate(serv->args); + + /* + * Cleanup. + */ + vstring_free(buf); + vstring_free(junk); + return (serv); +} + +/* print_master_ent - show service entry contents */ + +void print_master_ent(MASTER_SERV *serv) +{ + char **cpp; + + msg_info("====start service entry"); + msg_info("flags: %d", serv->flags); + msg_info("name: %s", serv->name); + msg_info("type: %s", + serv->type == MASTER_SERV_TYPE_UNIX ? MASTER_XPORT_NAME_UNIX : + serv->type == MASTER_SERV_TYPE_FIFO ? MASTER_XPORT_NAME_FIFO : + serv->type == MASTER_SERV_TYPE_INET ? MASTER_XPORT_NAME_INET : +#ifdef MASTER_SERV_TYPE_PASS + serv->type == MASTER_SERV_TYPE_PASS ? MASTER_XPORT_NAME_PASS : +#endif + serv->type == MASTER_SERV_TYPE_UXDG ? MASTER_XPORT_NAME_UXDG : + "unknown transport type"); + msg_info("listen_fd_count: %d", serv->listen_fd_count); + msg_info("wakeup: %d", serv->wakeup_time); + msg_info("max_proc: %d", serv->max_proc); + msg_info("path: %s", serv->path); + for (cpp = serv->args->argv; *cpp; cpp++) + msg_info("arg[%d]: %s", (int) (cpp - serv->args->argv), *cpp); + msg_info("avail_proc: %d", serv->avail_proc); + msg_info("total_proc: %d", serv->total_proc); + msg_info("throttle_delay: %d", serv->throttle_delay); + msg_info("status_fd %d %d", serv->status_fd[0], serv->status_fd[1]); + msg_info("children: 0x%lx", (long) serv->children); + msg_info("next: 0x%lx", (long) serv->next); + msg_info("====end service entry"); +} + +/* free_master_ent - destroy process entry */ + +void free_master_ent(MASTER_SERV *serv) +{ + + /* + * Undo what get_master_ent() created. + */ + if (serv->flags & MASTER_FLAG_INETHOST) { + inet_addr_list_free(MASTER_INET_ADDRLIST(serv)); + myfree((void *) MASTER_INET_ADDRLIST(serv)); + } + if (serv->type == MASTER_SERV_TYPE_INET) + myfree(MASTER_INET_PORT(serv)); + myfree(serv->ext_name); + myfree(serv->name); + myfree(serv->path); + argv_free(serv->args); + myfree((void *) serv->listen_fd); + myfree((void *) serv); +} diff --git a/src/master/master_flow.c b/src/master/master_flow.c new file mode 100644 index 0000000..68ae57d --- /dev/null +++ b/src/master/master_flow.c @@ -0,0 +1,33 @@ +/* System library. */ + +#include <sys_defs.h> +#include <unistd.h> +#include <stdlib.h> + +/* Utility library. */ + +#include <msg.h> +#include <iostuff.h> + +/* Application-specific. */ + +#include <master.h> +#include <master_proto.h> + +int master_flow_pipe[2]; + +/* master_flow_init - initialize the flow control channel */ + +void master_flow_init(void) +{ + const char *myname = "master_flow_init"; + + if (pipe(master_flow_pipe) < 0) + msg_fatal("%s: pipe: %m", myname); + + non_blocking(master_flow_pipe[0], NON_BLOCKING); + non_blocking(master_flow_pipe[1], NON_BLOCKING); + + close_on_exec(master_flow_pipe[0], CLOSE_ON_EXEC); + close_on_exec(master_flow_pipe[1], CLOSE_ON_EXEC); +} diff --git a/src/master/master_listen.c b/src/master/master_listen.c new file mode 100644 index 0000000..1e7f6fa --- /dev/null +++ b/src/master/master_listen.c @@ -0,0 +1,186 @@ +/*++ +/* NAME +/* master_listen 3 +/* SUMMARY +/* Postfix master - start/stop listeners +/* SYNOPSIS +/* #include "master.h" +/* +/* void master_listen_init(serv) +/* MASTER_SERV *serv; +/* +/* void master_listen_cleanup(serv) +/* MASTER_SERV *serv; +/* DESCRIPTION +/* master_listen_init() turns on the listener implemented by the +/* named process. FIFOs and UNIX-domain sockets are created with +/* mode 0622 and with ownership mail_owner. +/* +/* master_listen_cleanup() turns off the listener implemented by the +/* named process. +/* DIAGNOSTICS +/* BUGS +/* SEE ALSO +/* inet_listen(3), internet-domain listener +/* unix_listen(3), unix-domain listener +/* fifo_listen(3), named-pipe listener +/* upass_listen(3), file descriptor passing listener +/* set_eugid(3), set effective user/group attributes +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <unistd.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <listen.h> +#include <mymalloc.h> +#include <stringops.h> +#include <inet_addr_list.h> +#include <set_eugid.h> +#include <set_ugid.h> +#include <iostuff.h> +#include <myaddrinfo.h> +#include <sock_addr.h> + +/* Global library. */ + +#include <mail_params.h> + +/* Application-specific. */ + +#include "master.h" + +/* master_listen_init - enable connection requests */ + +void master_listen_init(MASTER_SERV *serv) +{ + const char *myname = "master_listen_init"; + char *end_point; + int n; + MAI_HOSTADDR_STR hostaddr; + struct sockaddr *sa; + + /* + * Find out what transport we should use, then create one or more + * listener sockets. Make the listener sockets non-blocking, so that + * child processes don't block in accept() when multiple processes are + * selecting on the same socket and only one of them gets the connection. + */ + switch (serv->type) { + + /* + * UNIX-domain or stream listener endpoints always come as singlets. + */ + case MASTER_SERV_TYPE_UNIX: + set_eugid(var_owner_uid, var_owner_gid); + serv->listen_fd[0] = + LOCAL_LISTEN(serv->name, serv->max_proc > var_proc_limit ? + serv->max_proc : var_proc_limit, NON_BLOCKING); + close_on_exec(serv->listen_fd[0], CLOSE_ON_EXEC); + set_ugid(getuid(), getgid()); + break; + + /* + * UNIX-domain datagram listener endpoints always come as singlets. + */ + case MASTER_SERV_TYPE_UXDG: + set_eugid(var_owner_uid, var_owner_gid); + serv->listen_fd[0] = + unix_dgram_listen(serv->name, NON_BLOCKING); + close_on_exec(serv->listen_fd[0], CLOSE_ON_EXEC); + set_ugid(getuid(), getgid()); + break; + + /* + * FIFO listener endpoints always come as singlets. + */ + case MASTER_SERV_TYPE_FIFO: + set_eugid(var_owner_uid, var_owner_gid); + serv->listen_fd[0] = fifo_listen(serv->name, 0622, NON_BLOCKING); + close_on_exec(serv->listen_fd[0], CLOSE_ON_EXEC); + set_ugid(getuid(), getgid()); + break; + + /* + * INET-domain listener endpoints can be wildcarded (the default) or + * bound to specific interface addresses. + * + * With dual-stack IPv4/6 systems it does not matter, we have to specify + * the addresses anyway, either explicit or wild-card. + */ + case MASTER_SERV_TYPE_INET: + for (n = 0; n < serv->listen_fd_count; n++) { + sa = SOCK_ADDR_PTR(MASTER_INET_ADDRLIST(serv)->addrs + n); + SOCKADDR_TO_HOSTADDR(sa, SOCK_ADDR_LEN(sa), &hostaddr, + (MAI_SERVPORT_STR *) 0, 0); + end_point = concatenate(hostaddr.buf, + ":", MASTER_INET_PORT(serv), (char *) 0); + serv->listen_fd[n] + = inet_listen(end_point, serv->max_proc > var_proc_limit ? + serv->max_proc : var_proc_limit, NON_BLOCKING); + close_on_exec(serv->listen_fd[n], CLOSE_ON_EXEC); + myfree(end_point); + } + break; + + /* + * Descriptor passing endpoints always come as singlets. + */ +#ifdef MASTER_SERV_TYPE_PASS + case MASTER_SERV_TYPE_PASS: + set_eugid(var_owner_uid, var_owner_gid); + serv->listen_fd[0] = + LOCAL_LISTEN(serv->name, serv->max_proc > var_proc_limit ? + serv->max_proc : var_proc_limit, NON_BLOCKING); + close_on_exec(serv->listen_fd[0], CLOSE_ON_EXEC); + set_ugid(getuid(), getgid()); + break; +#endif + default: + msg_panic("%s: unknown service type: %d", myname, serv->type); + } +} + +/* master_listen_cleanup - disable connection requests */ + +void master_listen_cleanup(MASTER_SERV *serv) +{ + const char *myname = "master_listen_cleanup"; + int n; + + /* + * XXX The listen socket is shared with child processes. Closing the + * socket in the master process does not really disable listeners in + * child processes. There seems to be no documented way to turn off a + * listener. The 4.4BSD shutdown(2) man page promises an ENOTCONN error + * when shutdown(2) is applied to a socket that is not connected. + */ + for (n = 0; n < serv->listen_fd_count; n++) { + if (close(serv->listen_fd[n]) < 0) + msg_warn("%s: close listener socket %d: %m", + myname, serv->listen_fd[n]); + serv->listen_fd[n] = -1; + } +} diff --git a/src/master/master_monitor.c b/src/master/master_monitor.c new file mode 100644 index 0000000..403d07e --- /dev/null +++ b/src/master/master_monitor.c @@ -0,0 +1,100 @@ +/*++ +/* NAME +/* master_monitor 3 +/* SUMMARY +/* Postfix master - start-up monitoring +/* SYNOPSIS +/* #include "master.h" +/* +/* int master_monitor(time_limit) +/* int time_limit; +/* DESCRIPTION +/* master_monitor() forks off a background child process, and +/* returns in the child. The result value is the file descriptor +/* on which the child process must write one byte after it +/* completes successful initialization as a daemon process. +/* +/* The foreground process waits for the child's completion for +/* a limited amount of time. It terminates with exit status 0 +/* in case of success, non-zero otherwise. +/* DIAGNOSTICS +/* Fatal errors: system call failure. +/* BUGS +/* SEE ALSO +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <signal.h> +#include <unistd.h> +#include <stdlib.h> + +/* Utility library. */ + +#include <msg.h> +#include <iostuff.h> + +/* Application-specific. */ + +#include <master.h> + +/* master_monitor - fork off a foreground monitor process */ + +int master_monitor(int time_limit) +{ + pid_t pid; + int pipes[2]; + char buf[1]; + + /* + * Sanity check. + */ + if (time_limit <= 0) + msg_panic("master_monitor: bad time limit: %d", time_limit); + + /* + * Set up the plumbing for child-to-parent communication. + */ + if (pipe(pipes) < 0) + msg_fatal("pipe: %m"); + close_on_exec(pipes[0], CLOSE_ON_EXEC); + close_on_exec(pipes[1], CLOSE_ON_EXEC); + + /* + * Fork the child, and wait for it to report successful initialization. + */ + switch (pid = fork()) { + case -1: + /* Error. */ + msg_fatal("fork: %m"); + case 0: + /* Child. Initialize as daemon in the background. */ + close(pipes[0]); + return (pipes[1]); + default: + /* Parent. Monitor the child in the foreground. */ + close(pipes[1]); + switch (timed_read(pipes[0], buf, 1, time_limit, (void *) 0)) { + default: + /* The child process still runs, but something is wrong. */ + (void) kill(pid, SIGKILL); + /* FALLTHROUGH */ + case 0: + /* The child process exited prematurely. */ + msg_fatal("daemon initialization failure"); + case 1: + /* The child process initialized successfully. */ + exit(0); + } + } +} diff --git a/src/master/master_proto.c b/src/master/master_proto.c new file mode 100644 index 0000000..e38d2e1 --- /dev/null +++ b/src/master/master_proto.c @@ -0,0 +1,89 @@ +/*++ +/* NAME +/* master_proto 3 +/* SUMMARY +/* Postfix master - status notification protocol +/* SYNOPSIS +/* #include <master_proto.h> +/* +/* int master_notify(pid, generation, status) +/* int pid; +/* unsigned generation; +/* int status; +/* DESCRIPTION +/* The master process provides a standard environment for its +/* child processes. Part of this environment is a pair of file +/* descriptors that the master process shares with all child +/* processes that provide the same service. +/* .IP MASTER_LISTEN_FD +/* The shared file descriptor for accepting client connection +/* requests. The master process listens on this socket or FIFO +/* when all child processes are busy. +/* .IP MASTER_STATUS_FD +/* The shared file descriptor for sending child status updates to +/* the master process. +/* .PP +/* A child process uses master_notify() to send a status notification +/* message to the master process. +/* .IP MASTER_STAT_AVAIL +/* The child process is ready to accept client connections. +/* .IP MASTER_STAT_TAKEN +/* Until further notice, the child process is unavailable for +/* accepting client connections. +/* .PP +/* When a child process terminates without sending a status update, +/* the master process will figure out that the child is no longer +/* available. +/* DIAGNOSTICS +/* The result is -1 in case of problems. This usually means that +/* the parent disconnected after a reload request, in order to +/* force children to commit suicide. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <unistd.h> + +/* Utility library. */ + +#include <msg.h> + +/* Global library. */ + +#include "master_proto.h" + +int master_notify(int pid, unsigned generation, int status) +{ + const char *myname = "master_notify"; + MASTER_STATUS stat; + + /* + * We use a simple binary protocol to minimize security risks. Since this + * is local IPC, there are no byte order or word length issues. The + * server treats this information as gossip, so sending a bad PID or a + * bad status code will only have amusement value. + */ + stat.pid = pid; + stat.gen = generation; + stat.avail = status; + + if (write(MASTER_STATUS_FD, (void *) &stat, sizeof(stat)) != sizeof(stat)) { + if (msg_verbose) + msg_info("%s: status %d: %m", myname, status); + return (-1); + } else { + if (msg_verbose) + msg_info("%s: status %d", myname, status); + return (0); + } +} diff --git a/src/master/master_proto.h b/src/master/master_proto.h new file mode 100644 index 0000000..6084514 --- /dev/null +++ b/src/master/master_proto.h @@ -0,0 +1,75 @@ +/*++ +/* NAME +/* master_proto 3h +/* SUMMARY +/* master process protocol +/* SYNOPSIS +/* #include <master_proto.h> +/* DESCRIPTION +/* .nf + + /* + * Transport names. The master passes the transport name on the command + * line, and thus the name is part of the master to child protocol. + */ +#define MASTER_XPORT_NAME_UNIX "unix" /* local IPC */ +#define MASTER_XPORT_NAME_FIFO "fifo" /* local IPC */ +#define MASTER_XPORT_NAME_INET "inet" /* non-local IPC */ +#define MASTER_XPORT_NAME_PASS "pass" /* local IPC */ +#define MASTER_XPORT_NAME_UXDG "unix-dgram" /* local IPC */ + + /* + * Format of a status message sent by a child process to the process + * manager. Since this is between processes on the same machine we need not + * worry about byte order and word length. + */ +typedef struct MASTER_STATUS { + int pid; /* process ID */ + unsigned gen; /* child generation number */ + int avail; /* availability */ +} MASTER_STATUS; + +#define MASTER_GEN_NAME "GENERATION" /* passed via environment */ + +#define MASTER_STAT_TAKEN 0 /* this one is occupied */ +#define MASTER_STAT_AVAIL 1 /* this process is idle */ + +extern int master_notify(int, unsigned, int); /* encapsulate status msg */ + + /* + * File descriptors inherited from the master process. The flow control pipe + * is read by receive processes and is written to by send processes. If + * receive processes get too far ahead they will pause for a brief moment. + */ +#define MASTER_FLOW_READ 3 +#define MASTER_FLOW_WRITE 4 + + /* + * File descriptors inherited from the master process. All processes that + * provide a given service share the same status file descriptor, and listen + * on the same service socket(s). The kernel decides what process gets the + * next connection. Usually the number of listening processes is small, so + * one connection will not cause a "thundering herd" effect. When no process + * listens on a given socket, the master process will. MASTER_LISTEN_FD is + * actually the lowest-numbered descriptor of a sequence of descriptors to + * listen on. + */ +#define MASTER_STATUS_FD 5 /* shared channel to parent */ +#define MASTER_LISTEN_FD 6 /* accept connections here */ + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + diff --git a/src/master/master_service.c b/src/master/master_service.c new file mode 100644 index 0000000..d5663b9 --- /dev/null +++ b/src/master/master_service.c @@ -0,0 +1,113 @@ +/*++ +/* NAME +/* master_service 3 +/* SUMMARY +/* Postfix master - start/stop services +/* SYNOPSIS +/* #include "master.h" +/* +/* void master_start_service(serv) +/* MASTER_SERV *serv; +/* +/* void master_stop_service(serv) +/* MASTER_SERV *serv; +/* +/* void master_restart_service(serv, conf_reload) +/* MASTER_SERV *serv; +/* int conf_reload; +/* DESCRIPTION +/* master_start_service() enables the named service. +/* +/* master_stop_service() disables named service. +/* +/* master_restart_service() requests all running child processes to +/* commit suicide. The conf_reload argument is either DO_CONF_RELOAD +/* (configuration files were reloaded, re-evaluate the child process +/* creation policy) or NO_CONF_RELOAD. +/* DIAGNOSTICS +/* BUGS +/* SEE ALSO +/* master_avail(3), process creation policy +/* master_wakeup(3), service automatic wakeup +/* master_status(3), child status reports +/* master_listen(3), unix/inet listeners +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include <sys_defs.h> +#include <string.h> +#include <unistd.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> + +/* Application-specific. */ + +#include "master.h" + +MASTER_SERV *master_head; + +/* master_start_service - activate service */ + +void master_start_service(MASTER_SERV *serv) +{ + + /* + * Enable connection requests, wakeup timers, and status updates from + * child processes. + */ + master_listen_init(serv); + master_avail_listen(serv); + master_status_init(serv); + master_wakeup_init(serv); +} + +/* master_stop_service - deactivate service */ + +void master_stop_service(MASTER_SERV *serv) +{ + + /* + * Undo the things that master_start_service() did. + */ + master_wakeup_cleanup(serv); + master_status_cleanup(serv); + master_avail_cleanup(serv); + master_listen_cleanup(serv); +} + +/* master_restart_service - restart service after configuration reload */ + +void master_restart_service(MASTER_SERV *serv, int conf_reload) +{ + + /* + * Undo some of the things that master_start_service() did. + */ + master_wakeup_cleanup(serv); + master_status_cleanup(serv); + + /* + * Now undo the undone. + */ + master_status_init(serv); + master_wakeup_init(serv); + + /* + * Respond to configuration change. + */ + if (conf_reload) + master_avail_listen(serv); +} diff --git a/src/master/master_sig.c b/src/master/master_sig.c new file mode 100644 index 0000000..db5e39d --- /dev/null +++ b/src/master/master_sig.c @@ -0,0 +1,275 @@ +/*++ +/* NAME +/* master_sig 3 +/* SUMMARY +/* Postfix master - signal processing +/* SYNOPSIS +/* #include "master.h" +/* +/* int master_gotsighup; +/* int master_gotsigchld; +/* +/* int master_sigsetup() +/* DESCRIPTION +/* This module implements the master process signal handling interface. +/* +/* master_gotsighup (master_gotsigchld) is set to SIGHUP (SIGCHLD) +/* when the process receives a hangup (child death) signal. +/* +/* master_sigsetup() enables processing of hangup and child death signals. +/* Receipt of SIGINT, SIGQUIT, SIGSEGV, SIGILL, or SIGTERM +/* is interpreted as a request for termination. Child processes are +/* notified of the master\'s demise by sending them a SIGTERM signal. +/* DIAGNOSTICS +/* BUGS +/* Need a way to register cleanup actions. +/* SEE ALSO +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System libraries. */ + +#include <sys_defs.h> +#include <signal.h> +#include <unistd.h> + +/* Utility library. */ + +#include <msg.h> +#include <posix_signals.h> +#include <killme_after.h> + +/* Application-specific. */ + +#include "master.h" + +#ifdef USE_SIG_RETURN +#include <sys/syscall.h> +#undef USE_SIG_PIPE +#else +#define USE_SIG_PIPE +#endif + +/* Local stuff. */ + +#ifdef USE_SIG_PIPE +#include <errno.h> +#include <fcntl.h> +#include <iostuff.h> +#include <events.h> + +int master_sig_pipe[2]; + +#define SIG_PIPE_WRITE_FD master_sig_pipe[1] +#define SIG_PIPE_READ_FD master_sig_pipe[0] +#endif + +int master_gotsigchld; +int master_gotsighup; + +#ifdef USE_SIG_RETURN + +/* master_sighup - register arrival of hangup signal */ + +static void master_sighup(int sig) +{ + + /* + * WARNING WARNING WARNING. + * + * This code runs at unpredictable moments, as a signal handler. Don't put + * any code here other than for setting a global flag. + */ + master_gotsighup = sig; +} + +/* master_sigchld - register arrival of child death signal */ + +static void master_sigchld(int sig, int code, struct sigcontext * scp) +{ + + /* + * WARNING WARNING WARNING. + * + * This code runs at unpredictable moments, as a signal handler. Don't put + * any code here other than for setting a global flag, or code that is + * intended to be run within a signal handler. + */ + master_gotsigchld = sig; + if (scp != NULL && scp->sc_syscall == SYS_select) { + scp->sc_syscall_action = SIG_RETURN; +#ifndef SA_RESTART + } else if (scp != NULL) { + scp->sc_syscall_action = SIG_RESTART; +#endif + } +} + +#else + +/* master_sighup - register arrival of hangup signal */ + +static void master_sighup(int sig) +{ + int saved_errno = errno; + + /* + * WARNING WARNING WARNING. + * + * This code runs at unpredictable moments, as a signal handler. Don't put + * any code here other than for setting a global flag, or code that is + * intended to be run within a signal handler. Restore errno in case we + * are interrupting the epilog of a failed system call. + */ + master_gotsighup = sig; + if (write(SIG_PIPE_WRITE_FD, "", 1) != 1) + msg_warn("write to SIG_PIPE_WRITE_FD failed: %m"); + errno = saved_errno; +} + +/* master_sigchld - force wakeup from select() */ + +static void master_sigchld(int unused_sig) +{ + int saved_errno = errno; + + /* + * WARNING WARNING WARNING. + * + * This code runs at unpredictable moments, as a signal handler. Don't put + * any code here other than for setting a global flag, or code that is + * intended to be run within a signal handler. Restore errno in case we + * are interrupting the epilog of a failed system call. + */ + master_gotsigchld = 1; + if (write(SIG_PIPE_WRITE_FD, "", 1) != 1) + msg_warn("write to SIG_PIPE_WRITE_FD failed: %m"); + errno = saved_errno; +} + +/* master_sig_event - called upon return from select() */ + +static void master_sig_event(int unused_event, void *unused_context) +{ + char c[1]; + + while (read(SIG_PIPE_READ_FD, c, 1) > 0) + /* void */ ; +} + +#endif + +/* master_sigdeath - die, women and children first */ + +static void master_sigdeath(int sig) +{ + const char *myname = "master_sigdeath"; + struct sigaction action; + pid_t pid = getpid(); + + /* + * Set alarm clock here for suicide after 5s. + */ + killme_after(5); + + /* + * Terminate all processes in our process group, except ourselves. + */ + sigemptyset(&action.sa_mask); + action.sa_flags = 0; + action.sa_handler = SIG_IGN; + if (sigaction(SIGTERM, &action, (struct sigaction *) 0) < 0) + msg_fatal("%s: sigaction: %m", myname); + if (kill(-pid, SIGTERM) < 0) + msg_fatal("%s: kill process group: %m", myname); + + /* + * XXX We're running from a signal handler, and should not call complex + * routines at all, but it would be even worse to silently terminate + * without informing the sysadmin. For this reason, msg(3) was made safe + * for usage by signal handlers that terminate the process. + */ + msg_info("terminating on signal %d", sig); + + /* + * Undocumented: when a process runs with PID 1, Linux won't deliver a + * signal unless the process specifies a handler (i.e. SIG_DFL is treated + * as SIG_IGN). + */ + if (init_mode) + /* Don't call exit() from a signal handler. */ + _exit(0); + + /* + * Deliver the signal to ourselves and clean up. XXX We're running as a + * signal handler and really should not be doing complicated things... + */ + sigemptyset(&action.sa_mask); + action.sa_flags = 0; + action.sa_handler = SIG_DFL; + if (sigaction(sig, &action, (struct sigaction *) 0) < 0) + msg_fatal("%s: sigaction: %m", myname); + if (kill(pid, sig) < 0) + msg_fatal("%s: kill myself: %m", myname); +} + +/* master_sigsetup - set up signal handlers */ + +void master_sigsetup(void) +{ + const char *myname = "master_sigsetup"; + struct sigaction action; + static int sigs[] = { + SIGINT, SIGQUIT, SIGILL, SIGBUS, SIGSEGV, SIGTERM, + }; + unsigned i; + + sigemptyset(&action.sa_mask); + action.sa_flags = 0; + + /* + * Prepare to kill our children when we receive any of the above signals. + */ + action.sa_handler = master_sigdeath; + for (i = 0; i < sizeof(sigs) / sizeof(sigs[0]); i++) + if (sigaction(sigs[i], &action, (struct sigaction *) 0) < 0) + msg_fatal("%s: sigaction(%d): %m", myname, sigs[i]); + +#ifdef USE_SIG_PIPE + if (pipe(master_sig_pipe)) + msg_fatal("pipe: %m"); + non_blocking(SIG_PIPE_WRITE_FD, NON_BLOCKING); + non_blocking(SIG_PIPE_READ_FD, NON_BLOCKING); + close_on_exec(SIG_PIPE_WRITE_FD, CLOSE_ON_EXEC); + close_on_exec(SIG_PIPE_READ_FD, CLOSE_ON_EXEC); + event_enable_read(SIG_PIPE_READ_FD, master_sig_event, (void *) 0); +#endif + + /* + * Intercept SIGHUP (re-read config file) and SIGCHLD (child exit). + */ +#ifdef SA_RESTART + action.sa_flags |= SA_RESTART; +#endif + action.sa_handler = master_sighup; + if (sigaction(SIGHUP, &action, (struct sigaction *) 0) < 0) + msg_fatal("%s: sigaction(%d): %m", myname, SIGHUP); + + action.sa_flags |= SA_NOCLDSTOP; + action.sa_handler = master_sigchld; + if (sigaction(SIGCHLD, &action, (struct sigaction *) 0) < 0) + msg_fatal("%s: sigaction(%d): %m", myname, SIGCHLD); +} diff --git a/src/master/master_spawn.c b/src/master/master_spawn.c new file mode 100644 index 0000000..c3b70f2 --- /dev/null +++ b/src/master/master_spawn.c @@ -0,0 +1,371 @@ +/*++ +/* NAME +/* master_spawn 3 +/* SUMMARY +/* Postfix master - child process birth and death +/* SYNOPSIS +/* #include "master.h" +/* +/* void master_spawn(serv) +/* MASTER_SERV *serv; +/* +/* void master_reap_child() +/* +/* void master_delete_children(serv) +/* MASTER_SERV *serv; +/* DESCRIPTION +/* This module creates and cleans up child processes, and applies +/* a process creation throttle in case of serious trouble. +/* This module is the working horse for the master_avail(3) process +/* creation policy module. +/* +/* master_spawn() spawns off a child process for the specified service, +/* making the child process available for servicing connection requests. +/* It is an error to call this function then the specified service is +/* throttled. +/* +/* master_reap_child() cleans up all dead child processes. One typically +/* runs this function at a convenient moment after receiving a SIGCHLD +/* signal. When a child process terminates abnormally after being used +/* for the first time, process creation for that service is throttled +/* for a configurable amount of time. +/* +/* master_delete_children() deletes all child processes that provide +/* the named service. Upon exit, the process creation throttle for that +/* service is released. +/* DIAGNOSTICS +/* Panic: interface violations, internal inconsistencies. +/* Fatal errors: out of memory. Warnings: throttle on/off. +/* BUGS +/* SEE ALSO +/* master_avail(3), process creation policy. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include <sys_defs.h> +#include <sys/wait.h> +#include <stdlib.h> +#include <unistd.h> +#include <syslog.h> /* closelog() */ +#include <signal.h> +#include <stdarg.h> +#include <syslog.h> + +/* Utility libraries. */ + +#include <msg.h> +#include <binhash.h> +#include <mymalloc.h> +#include <events.h> +#include <vstring.h> +#include <argv.h> + +/* Global library. */ + +#include <mail_conf.h> + +/* Application-specific. */ + +#include "master_proto.h" +#include "master.h" + +BINHASH *master_child_table; +static void master_unthrottle(MASTER_SERV *serv); + +/* master_unthrottle_wrapper - in case (char *) != (struct *) */ + +static void master_unthrottle_wrapper(int unused_event, void *ptr) +{ + MASTER_SERV *serv = (MASTER_SERV *) ptr; + + /* + * This routine runs after expiry of the timer set in master_throttle(), + * which gets called when it appears that the world is falling apart. + */ + master_unthrottle(serv); +} + +/* master_unthrottle - enable process creation */ + +static void master_unthrottle(MASTER_SERV *serv) +{ + + /* + * Enable process creation within this class. Disable the "unthrottle" + * timer just in case we're being called directly from the cleanup + * routine, instead of from the event manager. + */ + if ((serv->flags & MASTER_FLAG_THROTTLE) != 0) { + serv->flags &= ~MASTER_FLAG_THROTTLE; + event_cancel_timer(master_unthrottle_wrapper, (void *) serv); + if (msg_verbose) + msg_info("throttle released for command %s", serv->path); + master_avail_listen(serv); + } +} + +/* master_throttle - suspend process creation */ + +static void master_throttle(MASTER_SERV *serv) +{ + + /* + * Perhaps the command to be run is defective, perhaps some configuration + * is wrong, or perhaps the system is out of resources. Disable further + * process creation attempts for a while. + */ + if ((serv->flags & MASTER_FLAG_THROTTLE) == 0) { + serv->flags |= MASTER_FLAG_THROTTLE; + event_request_timer(master_unthrottle_wrapper, (void *) serv, + serv->throttle_delay); + if (msg_verbose) + msg_info("throttling command %s", serv->path); + master_avail_listen(serv); + } +} + +/* master_spawn - spawn off new child process if we can */ + +void master_spawn(MASTER_SERV *serv) +{ + const char *myname = "master_spawn"; + MASTER_PROC *proc; + MASTER_PID pid; + int n; + static unsigned master_generation = 0; + static VSTRING *env_gen = 0; + + if (master_child_table == 0) + master_child_table = binhash_create(0); + if (env_gen == 0) + env_gen = vstring_alloc(100); + + /* + * Sanity checks. The master_avail module is supposed to know what it is + * doing. + */ + if (!MASTER_LIMIT_OK(serv->max_proc, serv->total_proc)) + msg_panic("%s: at process limit %d", myname, serv->total_proc); + if (serv->avail_proc > 0) + msg_panic("%s: processes available: %d", myname, serv->avail_proc); + if (serv->flags & MASTER_FLAG_THROTTLE) + msg_panic("%s: throttled service: %s", myname, serv->path); + + /* + * Create a child process and connect parent and child via the status + * pipe. + */ + master_generation += 1; + switch (pid = fork()) { + + /* + * Error. We're out of some essential resource. Best recourse is to + * try again later. + */ + case -1: + msg_warn("%s: fork: %m -- throttling", myname); + master_throttle(serv); + return; + + /* + * Child process. Redirect child stdin/stdout to the parent-child + * connection and run the requested command. Leave child stderr + * alone. Disable exit handlers: they should be executed by the + * parent only. + * + * When we reach the process limit on a public internet service, we + * create stress-mode processes until the process count stays below + * the limit for some amount of time. See master_avail_listen(). + */ + case 0: + msg_cleanup((void (*) (void)) 0); /* disable exit handler */ + closelog(); /* avoid filedes leak */ + + if (master_flow_pipe[0] <= MASTER_FLOW_READ) + msg_fatal("%s: flow pipe read descriptor <= %d", + myname, MASTER_FLOW_READ); + if (DUP2(master_flow_pipe[0], MASTER_FLOW_READ) < 0) + msg_fatal("%s: dup2: %m", myname); + if (close(master_flow_pipe[0]) < 0) + msg_fatal("close %d: %m", master_flow_pipe[0]); + + if (master_flow_pipe[1] <= MASTER_FLOW_WRITE) + msg_fatal("%s: flow pipe read descriptor <= %d", + myname, MASTER_FLOW_WRITE); + if (DUP2(master_flow_pipe[1], MASTER_FLOW_WRITE) < 0) + msg_fatal("%s: dup2: %m", myname); + if (close(master_flow_pipe[1]) < 0) + msg_fatal("close %d: %m", master_flow_pipe[1]); + + close(serv->status_fd[0]); /* status channel */ + if (serv->status_fd[1] <= MASTER_STATUS_FD) + msg_fatal("%s: status file descriptor collision", myname); + if (DUP2(serv->status_fd[1], MASTER_STATUS_FD) < 0) + msg_fatal("%s: dup2 status_fd: %m", myname); + (void) close(serv->status_fd[1]); + + for (n = 0; n < serv->listen_fd_count; n++) { + if (serv->listen_fd[n] <= MASTER_LISTEN_FD + n) + msg_fatal("%s: listen file descriptor collision", myname); + if (DUP2(serv->listen_fd[n], MASTER_LISTEN_FD + n) < 0) + msg_fatal("%s: dup2 listen_fd %d: %m", + myname, serv->listen_fd[n]); + (void) close(serv->listen_fd[n]); + } + vstring_sprintf(env_gen, "%s=%o", MASTER_GEN_NAME, master_generation); + if (putenv(vstring_str(env_gen)) < 0) + msg_fatal("%s: putenv: %m", myname); + if (serv->stress_param_val && serv->stress_expire_time > event_time()) + serv->stress_param_val[0] = CONFIG_BOOL_YES[0]; + + execvp(serv->path, serv->args->argv); + msg_fatal("%s: exec %s: %m", myname, serv->path); + /* NOTREACHED */ + + /* + * Parent. Fill in a process member data structure and set up links + * between child and process. Say this process has become available. + * If this service has a wakeup timer that is turned on only when the + * service is actually used, turn on the wakeup timer. + */ + default: + if (msg_verbose) + msg_info("spawn command %s; pid %d", serv->path, pid); + proc = (MASTER_PROC *) mymalloc(sizeof(MASTER_PROC)); + proc->serv = serv; + proc->pid = pid; + proc->gen = master_generation; + proc->use_count = 0; + proc->avail = 0; + binhash_enter(master_child_table, (void *) &pid, + sizeof(pid), (void *) proc); + serv->total_proc++; + master_avail_more(serv, proc); + if (serv->flags & MASTER_FLAG_CONDWAKE) { + serv->flags &= ~MASTER_FLAG_CONDWAKE; + master_wakeup_init(serv); + if (msg_verbose) + msg_info("start conditional timer for %s", serv->name); + } + return; + } +} + +/* master_delete_child - destroy child process info */ + +static void master_delete_child(MASTER_PROC *proc) +{ + MASTER_SERV *serv; + + /* + * Undo the things that master_spawn did. Stop the process if it still + * exists, and remove it from the lookup tables. Update the number of + * available processes. + */ + serv = proc->serv; + serv->total_proc--; + if (proc->avail == MASTER_STAT_AVAIL) + master_avail_less(serv, proc); + else + master_avail_listen(serv); + binhash_delete(master_child_table, (void *) &proc->pid, + sizeof(proc->pid), (void (*) (void *)) 0); + myfree((void *) proc); +} + +/* master_reap_child - reap dead children */ + +void master_reap_child(void) +{ + MASTER_SERV *serv; + MASTER_PROC *proc; + MASTER_PID pid; + WAIT_STATUS_T status; + + /* + * Pick up termination status of all dead children. When a process failed + * on its first job, assume we see the symptom of a structural problem + * (configuration problem, system running out of resources) and back off. + */ + while ((pid = waitpid((pid_t) - 1, &status, WNOHANG)) > 0) { + if (msg_verbose) + msg_info("master_reap_child: pid %d", pid); + if ((proc = (MASTER_PROC *) binhash_find(master_child_table, + (void *) &pid, sizeof(pid))) == 0) { + if (init_mode) + continue; /* non-Postfix process */ + msg_panic("master_reap: unknown pid: %d", pid); + } + serv = proc->serv; + +#define MASTER_KILL_SIGNAL SIGTERM +#define MASTER_SENT_SIGNAL(serv, status) \ + (MASTER_MARKED_FOR_DELETION(serv) \ + && WTERMSIG(status) == MASTER_KILL_SIGNAL) + + /* + * XXX The code for WIFSTOPPED() is here in case some buggy kernel + * reports WIFSTOPPED() events to a Postfix daemon's parent process + * (the master(8) daemon) instead of the tracing process (e.g., gdb). + * + * The WIFSTOPPED() test prevents master(8) from deleting its record of + * a child process that is stopped. That would cause a master(8) + * panic (unknown child) when the child terminates. + */ + if (!NORMAL_EXIT_STATUS(status)) { + if (WIFSTOPPED(status)) { + msg_warn("process %s pid %d stopped by signal %d", + serv->path, pid, WSTOPSIG(status)); + continue; + } + if (WIFEXITED(status)) + msg_warn("process %s pid %d exit status %d", + serv->path, pid, WEXITSTATUS(status)); + if (WIFSIGNALED(status) && !MASTER_SENT_SIGNAL(serv, status)) + msg_warn("process %s pid %d killed by signal %d", + serv->path, pid, WTERMSIG(status)); + /* master_delete_children() throttles first, then kills. */ + if (proc->use_count == 0 + && (serv->flags & MASTER_FLAG_THROTTLE) == 0) { + msg_warn("%s: bad command startup -- throttling", serv->path); + master_throttle(serv); + } + } + master_delete_child(proc); + } +} + +/* master_delete_children - delete all child processes of service */ + +void master_delete_children(MASTER_SERV *serv) +{ + BINHASH_INFO **list; + BINHASH_INFO **info; + MASTER_PROC *proc; + + /* + * XXX turn on the throttle so that master_reap_child() doesn't. Someone + * has to turn off the throttle in order to stop the associated timer + * request, so we might just as well do it at the end. + */ + master_throttle(serv); + for (info = list = binhash_list(master_child_table); *info; info++) { + proc = (MASTER_PROC *) info[0]->value; + if (proc->serv == serv) + (void) kill(proc->pid, MASTER_KILL_SIGNAL); + } + while (serv->total_proc > 0) + master_reap_child(); + myfree((void *) list); + master_unthrottle(serv); +} diff --git a/src/master/master_status.c b/src/master/master_status.c new file mode 100644 index 0000000..fb3bb73 --- /dev/null +++ b/src/master/master_status.c @@ -0,0 +1,198 @@ +/*++ +/* NAME +/* master_status 3 +/* SUMMARY +/* Postfix master - process child status reports +/* SYNOPSIS +/* #include "master.h" +/* +/* void master_status_init(serv) +/* MASTER_SERV *serv; +/* +/* void master_status_cleanup(serv) +/* MASTER_SERV *serv; +/* DESCRIPTION +/* This module reads and processes status reports from child processes. +/* +/* master_status_init() enables the processing of child status updates +/* for the specified service. Child process status updates (process +/* available, process taken) are passed on to the master_avail_XXX() +/* routines. +/* +/* master_status_cleanup() disables child status update processing +/* for the specified service. +/* DIAGNOSTICS +/* Panic: internal inconsistency. Warnings: a child process sends +/* incomplete or incorrect information. +/* BUGS +/* SEE ALSO +/* master_avail(3) +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include <sys_defs.h> +#include <unistd.h> + +/* Utility library. */ + +#include <msg.h> +#include <events.h> +#include <binhash.h> +#include <iostuff.h> + +/* Application-specific. */ + +#include "master_proto.h" +#include "master.h" + +/* master_status_event - status read event handler */ + +static void master_status_event(int event, void *context) +{ + const char *myname = "master_status_event"; + MASTER_SERV *serv = (MASTER_SERV *) context; + MASTER_STATUS stat; + MASTER_PROC *proc; + MASTER_PID pid; + int n; + + if (event == 0) /* XXX Can this happen? */ + return; + + /* + * We always keep the child end of the status pipe open, so an EOF read + * condition means that we're seriously confused. We use non-blocking + * reads so that we don't get stuck when someone sends a partial message. + * Messages are short, so a partial read means someone wrote less than a + * whole status message. Hopefully the next read will be in sync again... + * We use a global child process status table because when a child dies + * only its pid is known - we do not know what service it came from. + */ + switch (n = read(serv->status_fd[0], (void *) &stat, sizeof(stat))) { + + case -1: + msg_warn("%s: read: %m", myname); + return; + + case 0: + msg_panic("%s: read EOF status", myname); + /* NOTREACHED */ + + default: + msg_warn("service %s(%s): child (pid %d) sent partial status update (%d bytes)", + serv->ext_name, serv->name, stat.pid, n); + return; + + case sizeof(stat): + pid = stat.pid; + if (msg_verbose) + msg_info("%s: pid %d gen %u avail %d", + myname, stat.pid, stat.gen, stat.avail); + } + + /* + * Sanity checks. Do not freak out when the child sends garbage because + * it is confused or for other reasons. However, be sure to freak out + * when our own data structures are inconsistent. A process not found + * condition can happen when we reap a process before receiving its + * status update, so this is not an error. + */ + if ((proc = (MASTER_PROC *) binhash_find(master_child_table, + (void *) &pid, sizeof(pid))) == 0) { + if (msg_verbose) + msg_info("%s: process id not found: %d", myname, stat.pid); + return; + } + if (proc->gen != stat.gen) { + msg_info("ignoring status update from child pid %d generation %u", + pid, stat.gen); + return; + } + if (proc->serv != serv) + msg_panic("%s: pointer corruption: %p != %p", + myname, (void *) proc->serv, (void *) serv); + + /* + * Update our idea of the child process status. Allow redundant status + * updates, because different types of events may be processed out of + * order. Otherwise, warn about weird status updates but do not take + * action. It's all gossip after all. + */ + if (proc->avail == stat.avail) + return; + switch (stat.avail) { + case MASTER_STAT_AVAIL: + proc->use_count++; + master_avail_more(serv, proc); + break; + case MASTER_STAT_TAKEN: + master_avail_less(serv, proc); + break; + default: + msg_warn("%s: ignoring unknown status: %d allegedly from pid: %d", + myname, stat.pid, stat.avail); + break; + } +} + +/* master_status_init - start status event processing for this service */ + +void master_status_init(MASTER_SERV *serv) +{ + const char *myname = "master_status_init"; + + /* + * Sanity checks. + */ + if (serv->status_fd[0] >= 0 || serv->status_fd[1] >= 0) + msg_panic("%s: status events already enabled", myname); + if (msg_verbose) + msg_info("%s: %s", myname, serv->name); + + /* + * Make the read end of this service's status pipe non-blocking so that + * we can detect partial writes on the child side. We use a duplex pipe + * so that the child side becomes readable when the master goes away. + */ + if (duplex_pipe(serv->status_fd) < 0) + msg_fatal("pipe: %m"); + non_blocking(serv->status_fd[0], BLOCKING); + close_on_exec(serv->status_fd[0], CLOSE_ON_EXEC); + close_on_exec(serv->status_fd[1], CLOSE_ON_EXEC); + event_enable_read(serv->status_fd[0], master_status_event, (void *) serv); +} + +/* master_status_cleanup - stop status event processing for this service */ + +void master_status_cleanup(MASTER_SERV *serv) +{ + const char *myname = "master_status_cleanup"; + + /* + * Sanity checks. + */ + if (serv->status_fd[0] < 0 || serv->status_fd[1] < 0) + msg_panic("%s: status events not enabled", myname); + if (msg_verbose) + msg_info("%s: %s", myname, serv->name); + + /* + * Dispose of this service's status pipe after disabling read events. + */ + event_disable_readwrite(serv->status_fd[0]); + if (close(serv->status_fd[0]) != 0) + msg_warn("%s: close status descriptor (read side): %m", myname); + if (close(serv->status_fd[1]) != 0) + msg_warn("%s: close status descriptor (write side): %m", myname); + serv->status_fd[0] = serv->status_fd[1] = -1; +} diff --git a/src/master/master_vars.c b/src/master/master_vars.c new file mode 100644 index 0000000..ac296f9 --- /dev/null +++ b/src/master/master_vars.c @@ -0,0 +1,94 @@ +/*++ +/* NAME +/* master_vars 3 +/* SUMMARY +/* Postfix master - global configuration file access +/* SYNOPSIS +/* #include "master.h" +/* +/* void master_vars_init() +/* DESCRIPTION +/* master_vars_init() reads values from the global Postfix configuration +/* file and assigns them to tunable program parameters. Where no value +/* is specified, a compiled-in default value is used. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> +#include <unistd.h> + +/* Utility library. */ + +#include <msg.h> +#include <stringops.h> +#include <mymalloc.h> + +/* Global library. */ + +#include <mail_conf.h> +#include <mail_params.h> + +/* Application-specific. */ + +#include "master.h" + + /* + * Tunable parameters. + */ +char *var_inet_protocols; +int var_throttle_time; +char *var_master_disable; + +/* master_vars_init - initialize from global Postfix configuration file */ + +void master_vars_init(void) +{ + char *path; + static const CONFIG_STR_TABLE str_table[] = { + VAR_MASTER_DISABLE, DEF_MASTER_DISABLE, &var_master_disable, 0, 0, + 0, + }; + static const CONFIG_TIME_TABLE time_table[] = { + VAR_THROTTLE_TIME, DEF_THROTTLE_TIME, &var_throttle_time, 1, 0, + 0, + }; + static char *saved_inet_protocols; + static char *saved_queue_dir; + static char *saved_config_dir; + static const MASTER_STR_WATCH str_watch_table[] = { + VAR_CONFIG_DIR, &var_config_dir, &saved_config_dir, 0, 0, + VAR_QUEUE_DIR, &var_queue_dir, &saved_queue_dir, 0, 0, + VAR_INET_PROTOCOLS, &var_inet_protocols, &saved_inet_protocols, 0, 0, + /* XXX Add inet_interfaces here after this code is burned in. */ + 0, + }; + + /* + * Flush existing main.cf settings, so that we handle deleted main.cf + * settings properly. + */ + mail_conf_flush(); + set_mail_conf_str(VAR_PROCNAME, var_procname); + mail_conf_read(); + get_mail_conf_str_table(str_table); + get_mail_conf_time_table(time_table); + path = concatenate(var_config_dir, "/", MASTER_CONF_FILE, (void *) 0); + fset_master_ent(path); + myfree(path); + + /* + * Look for parameter changes that require special attention. + */ + master_str_watch(str_watch_table); +} diff --git a/src/master/master_wakeup.c b/src/master/master_wakeup.c new file mode 100644 index 0000000..cc50924 --- /dev/null +++ b/src/master/master_wakeup.c @@ -0,0 +1,192 @@ +/*++ +/* NAME +/* master_wakeup 3 +/* SUMMARY +/* Postfix master - start/stop service wakeup timers +/* SYNOPSIS +/* #include "master.h" +/* +/* void master_wakeup_init(serv) +/* MASTER_SERV *serv; +/* +/* void master_wakeup_cleanup(serv) +/* MASTER_SERV *serv; +/* DESCRIPTION +/* This module implements automatic service wakeup. In order to +/* wakeup a service, a wakeup trigger is sent to the corresponding +/* service port or FIFO, and a timer is started to repeat this sequence +/* after a configurable amount of time. +/* +/* master_wakeup_init() wakes up the named service. No wakeup +/* is done or scheduled when a zero wakeup time is given, or when +/* the service has been throttled in the mean time. +/* It is OK to call master_wakeup_init() while a timer is already +/* running for the named service. The effect is to restart the +/* wakeup timer. +/* +/* master_wakeup_cleanup() cancels the wakeup timer for the named +/* service. It is an error to disable a service while it still has +/* an active wakeup timer (doing so would cause a dangling reference +/* to a non-existent service). +/* It is OK to call master_wakeup_cleanup() even when no timer is +/* active for the named service. +/* DIAGNOSTICS +/* BUGS +/* SEE ALSO +/* inet_trigger(3), internet-domain client +/* unix_trigger(3), unix-domain client +/* fifo_trigger(3), fifo client +/* upass_trigger(3), file descriptor passing client +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +/* Utility library. */ + +#include <msg.h> +#include <trigger.h> +#include <events.h> +#include <set_eugid.h> +#include <set_ugid.h> + +/* Global library. */ + +#include <mail_proto.h> /* triggers */ +#include <mail_params.h> + +/* Application-specific. */ + +#include "mail_server.h" +#include "master.h" + +/* master_wakeup_timer_event - wakeup event handler */ + +static void master_wakeup_timer_event(int unused_event, void *context) +{ + const char *myname = "master_wakeup_timer_event"; + MASTER_SERV *serv = (MASTER_SERV *) context; + int status; + static char wakeup = TRIGGER_REQ_WAKEUP; + + /* + * Don't wakeup services whose automatic wakeup feature was turned off in + * the mean time. + */ + if (serv->wakeup_time == 0) + return; + + /* + * Don't wake up services that are throttled. Find out what transport to + * use. We can't block here so we choose a short timeout. + */ +#define BRIEFLY 1 + + if (MASTER_THROTTLED(serv) == 0) { + if (msg_verbose) + msg_info("%s: service %s", myname, serv->name); + + switch (serv->type) { + case MASTER_SERV_TYPE_INET: + status = inet_trigger(serv->name, &wakeup, sizeof(wakeup), BRIEFLY); + break; + case MASTER_SERV_TYPE_UNIX: + status = LOCAL_TRIGGER(serv->name, &wakeup, sizeof(wakeup), BRIEFLY); + break; + case MASTER_SERV_TYPE_UXDG: + status = -1; + errno = EOPNOTSUPP; + break; +#ifdef MASTER_SERV_TYPE_PASS + case MASTER_SERV_TYPE_PASS: + status = pass_trigger(serv->name, &wakeup, sizeof(wakeup), BRIEFLY); + break; +#endif + + /* + * If someone compromises the postfix account then this must not + * overwrite files outside the chroot jail. Countermeasures: + * + * - Limit the damage by accessing the FIFO as postfix not root. + * + * - Have fifo_trigger() call safe_open() so we won't follow + * arbitrary hard/symlinks to files in/outside the chroot jail. + * + * - All non-chroot postfix-related files must be root owned (or + * postfix check complains). + * + * - The postfix user and group ID must not be shared with other + * applications (says the INSTALL documentation). + * + * Result of a discussion with Michael Tokarev, who received his + * insights from Solar Designer, who tested Postfix with a kernel + * module that is paranoid about open() calls. + */ + case MASTER_SERV_TYPE_FIFO: + set_eugid(var_owner_uid, var_owner_gid); + status = fifo_trigger(serv->name, &wakeup, sizeof(wakeup), BRIEFLY); + set_ugid(getuid(), getgid()); + break; + default: + msg_panic("%s: unknown service type: %d", myname, serv->type); + } + if (status < 0) + msg_warn("%s: service %s(%s): %m", + myname, serv->ext_name, serv->name); + } + + /* + * Schedule another wakeup event. + */ + event_request_timer(master_wakeup_timer_event, (void *) serv, + serv->wakeup_time); +} + +/* master_wakeup_init - start automatic service wakeup */ + +void master_wakeup_init(MASTER_SERV *serv) +{ + const char *myname = "master_wakeup_init"; + + if (serv->wakeup_time == 0 || (serv->flags & MASTER_FLAG_CONDWAKE)) + return; + if (msg_verbose) + msg_info("%s: service %s time %d", + myname, serv->name, serv->wakeup_time); + master_wakeup_timer_event(0, (void *) serv); +} + +/* master_wakeup_cleanup - cancel wakeup timer */ + +void master_wakeup_cleanup(MASTER_SERV *serv) +{ + const char *myname = "master_wakeup_cleanup"; + + /* + * Cleanup, even when the wakeup feature has been turned off. There might + * still be a pending timer. Don't depend on the code that reloads the + * config file to reset the wakeup timer when things change. + */ + if (msg_verbose) + msg_info("%s: service %s", myname, serv->name); + + event_cancel_timer(master_wakeup_timer_event, (void *) serv); +} diff --git a/src/master/master_watch.c b/src/master/master_watch.c new file mode 100644 index 0000000..1af26fe --- /dev/null +++ b/src/master/master_watch.c @@ -0,0 +1,151 @@ +/*++ +/* NAME +/* master_watch 3 +/* SUMMARY +/* Postfix master - monitor main.cf changes +/* SYNOPSIS +/* #include "master.h" +/* +/* void master_str_watch(str_watch_table) +/* const MASTER_STR_WATCH *str_watch_table; +/* +/* void master_int_watch(int_watch_table) +/* MASTER_INT_WATCH *int_watch_table; +/* DESCRIPTION +/* The Postfix master daemon is a long-running process. After +/* main.cf is changed, some parameter changes may require that +/* master data structures be recomputed. +/* +/* Unfortunately, some main.cf changes cannot be applied +/* on-the-fly, either because they require killing off existing +/* child processes and thus disrupt service, or because the +/* necessary support for on-the-fly data structure update has +/* not yet been implemented. Such main.cf changes trigger a +/* warning that they require that Postfix be stopped and +/* restarted. +/* +/* This module provides functions that monitor selected main.cf +/* parameters for change. The operation of these functions is +/* controlled by tables that specify the parameter name, the +/* current parameter value, a historical parameter value, +/* optional flags, and an optional notify call-back function. +/* +/* master_str_watch() monitors string-valued parameters for +/* change, and master_int_watch() does the same for integer-valued +/* parameters. Note that master_int_watch() needs read-write +/* access to its argument table, while master_str_watch() needs +/* read-only access only. +/* +/* The functions log a warning when a parameter value has +/* changed after re-reading main.cf, but the parameter is not +/* flagged in the MASTER_*_WATCH table as "updatable" with +/* MASTER_WATCH_FLAG_UPDATABLE. +/* +/* If the parameter has a notify call-back function, then the +/* function is called after main.cf is read for the first time. +/* If the parameter is flagged as "updatable", then the function +/* is also called when the parameter value changes after +/* re-reading main.cf. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> +#include <unistd.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> + +/* Application-specific. */ + +#include "master.h" + +/* master_str_watch - watch string-valued parameters for change */ + +void master_str_watch(const MASTER_STR_WATCH *str_watch_table) +{ + const MASTER_STR_WATCH *wp; + + for (wp = str_watch_table; wp->name != 0; wp++) { + + /* + * Detect changes to monitored parameter values. If a change is + * supported, we discard the backed up value and update it to the + * current value later. Otherwise we complain. + */ + if (wp->backup[0] != 0 + && strcmp(wp->backup[0], wp->value[0]) != 0) { + if ((wp->flags & MASTER_WATCH_FLAG_UPDATABLE) == 0) { + msg_warn("ignoring %s parameter value change", wp->name); + msg_warn("old value: \"%s\", new value: \"%s\"", + wp->backup[0], wp->value[0]); + msg_warn("to change %s, stop and start Postfix", wp->name); + } else { + myfree(wp->backup[0]); + wp->backup[0] = 0; + } + } + + /* + * Initialize the backed up parameter value, or update it if this + * parameter supports updates after initialization. Optionally + * notify the application that this parameter has changed. + */ + if (wp->backup[0] == 0) { + if (wp->notify != 0) + wp->notify(); + wp->backup[0] = mystrdup(wp->value[0]); + } + } +} + +/* master_int_watch - watch integer-valued parameters for change */ + +void master_int_watch(MASTER_INT_WATCH *int_watch_table) +{ + MASTER_INT_WATCH *wp; + + for (wp = int_watch_table; wp->name != 0; wp++) { + + /* + * Detect changes to monitored parameter values. If a change is + * supported, we discard the backed up value and update it to the + * current value later. Otherwise we complain. + */ + if ((wp->flags & MASTER_WATCH_FLAG_ISSET) != 0 + && wp->backup != wp->value[0]) { + if ((wp->flags & MASTER_WATCH_FLAG_UPDATABLE) == 0) { + msg_warn("ignoring %s parameter value change", wp->name); + msg_warn("old value: \"%d\", new value: \"%d\"", + wp->backup, wp->value[0]); + msg_warn("to change %s, stop and start Postfix", wp->name); + } else { + wp->flags &= ~MASTER_WATCH_FLAG_ISSET; + } + } + + /* + * Initialize the backed up parameter value, or update if it this + * parameter supports updates after initialization. Optionally + * notify the application that this parameter has changed. + */ + if ((wp->flags & MASTER_WATCH_FLAG_ISSET) == 0) { + if (wp->notify != 0) + wp->notify(); + wp->flags |= MASTER_WATCH_FLAG_ISSET; + wp->backup = wp->value[0]; + } + } +} diff --git a/src/master/multi_server.c b/src/master/multi_server.c new file mode 100644 index 0000000..465a9e2 --- /dev/null +++ b/src/master/multi_server.c @@ -0,0 +1,923 @@ +/*++ +/* NAME +/* multi_server 3 +/* SUMMARY +/* skeleton multi-threaded mail subsystem +/* SYNOPSIS +/* #include <mail_server.h> +/* +/* NORETURN multi_server_main(argc, argv, service, key, value, ...) +/* int argc; +/* char **argv; +/* void (*service)(VSTREAM *stream, char *service_name, char **argv); +/* int key; +/* +/* void multi_server_disconnect(stream) +/* VSTREAM *stream; +/* +/* void multi_server_drain() +/* DESCRIPTION +/* This module implements a skeleton for multi-threaded +/* mail subsystems: mail subsystem programs that service multiple +/* clients at the same time. The resulting program expects to be run +/* from the \fBmaster\fR process. +/* +/* multi_server_main() is the skeleton entry point. It should be +/* called from the application main program. The skeleton does all +/* the generic command-line processing, initialization of +/* configurable parameters, and connection management. +/* The skeleton never returns. +/* +/* Arguments: +/* .IP "void (*service)(VSTREAM *stream, char *service_name, char **argv)" +/* A pointer to a function that is called by the skeleton each +/* time a client sends data to the program's service port. The +/* function is run after the program has optionally dropped its +/* privileges. This function should not attempt to preserve state +/* across calls. The stream initial state is non-blocking mode. +/* Optional connection attributes are provided as a hash that +/* is attached as stream context. NOTE: the attributes are +/* destroyed after this function is called. +/* The service name argument corresponds to the service name in the +/* master.cf file. +/* The argv argument specifies command-line arguments left over +/* after options processing. +/* .PP +/* Optional arguments are specified as a null-terminated list +/* with macros that have zero or more arguments: +/* .IP "CA_MAIL_SERVER_INT_TABLE(CONFIG_INT_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_LONG_TABLE(CONFIG_LONG_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_STR_TABLE(CONFIG_STR_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_BOOL_TABLE(CONFIG_BOOL_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_TIME_TABLE(CONFIG_TIME_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_RAW_TABLE(CONFIG_RAW_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. Raw parameters are not subjected to $name +/* evaluation. +/* .IP "CA_MAIL_SERVER_NINT_TABLE(CONFIG_NINT_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_NBOOL_TABLE(CONFIG_NBOOL_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_PRE_INIT(void *(char *service_name, char **argv))" +/* A pointer to a function that is called once +/* by the skeleton after it has read the global configuration file +/* and after it has processed command-line arguments, but before +/* the skeleton has optionally relinquished the process privileges. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_POST_INIT(void *(char *service_name, char **argv))" +/* A pointer to a function that is called once +/* by the skeleton after it has optionally relinquished the process +/* privileges, but before servicing client connection requests. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_LOOP(int *(char *service_name, char **argv))" +/* A pointer to function that is executed from +/* within the event loop, whenever an I/O or timer event has happened, +/* or whenever nothing has happened for a specified amount of time. +/* The result value of the function specifies how long to wait until +/* the next event. Specify -1 to wait for "as long as it takes". +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_EXIT(void *(char *service_name, char **argv))" +/* A pointer to function that is executed immediately before normal +/* process termination. +/* .IP "CA_MAIL_SERVER_PRE_ACCEPT(void *(char *service_name, char **argv))" +/* Function to be executed prior to accepting a new connection. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_PRE_DISCONN(VSTREAM *, char *service_name, char **argv)" +/* A pointer to a function that is called +/* by the multi_server_disconnect() function (see below). +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP CA_MAIL_SERVER_IN_FLOW_DELAY +/* Pause $in_flow_delay seconds when no "mail flow control token" +/* is available. A token is consumed for each connection request. +/* .IP CA_MAIL_SERVER_SOLITARY +/* This service must be configured with process limit of 1. +/* .IP CA_MAIL_SERVER_UNLIMITED +/* This service must be configured with process limit of 0. +/* .IP CA_MAIL_SERVER_PRIVILEGED +/* This service must be configured as privileged. +/* .IP "CA_MAIL_SERVER_BOUNCE_INIT(const char *, const char **)" +/* Initialize the DSN filter for the bounce/defer service +/* clients with the specified map source and map names. +/* .PP +/* multi_server_disconnect() should be called by the application +/* to close a client connection. +/* +/* multi_server_drain() should be called when the application +/* no longer wishes to accept new client connections. Existing +/* clients are handled in a background process, and the process +/* terminates when the last client is disconnected. A non-zero +/* result means this call should be tried again later. +/* +/* The var_use_limit variable limits the number of clients that +/* a server can service before it commits suicide. +/* This value is taken from the global \fBmain.cf\fR configuration +/* file. Setting \fBvar_use_limit\fR to zero disables the client limit. +/* +/* The var_idle_limit variable limits the time that a service +/* receives no client connection requests before it commits suicide. +/* This value is taken from the global \fBmain.cf\fR configuration +/* file. Setting \fBvar_idle_limit\fR to zero disables the idle limit. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* SEE ALSO +/* master(8), master process +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <sys/time.h> /* select() */ +#include <unistd.h> +#include <signal.h> +#include <stdlib.h> +#include <limits.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <stdarg.h> +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif +#include <time.h> + +#ifdef USE_SYS_SELECT_H +#include <sys/select.h> /* select() */ +#endif + +/* Utility library. */ + +#include <msg.h> +#include <msg_vstream.h> +#include <chroot_uid.h> +#include <listen.h> +#include <events.h> +#include <vstring.h> +#include <vstream.h> +#include <msg_vstream.h> +#include <mymalloc.h> +#include <iostuff.h> +#include <stringops.h> +#include <sane_accept.h> +#include <myflock.h> +#include <safe_open.h> +#include <listen.h> +#include <watchdog.h> +#include <split_at.h> + +/* Global library. */ + +#include <mail_task.h> +#include <debug_process.h> +#include <mail_params.h> +#include <mail_conf.h> +#include <mail_dict.h> +#include <timed_ipc.h> +#include <resolve_local.h> +#include <mail_flow.h> +#include <mail_version.h> +#include <bounce.h> +#include <maillog_client.h> + +/* Process manager. */ + +#include "master_proto.h" + +/* Application-specific */ + +#include "mail_server.h" + + /* + * Global state. + */ +static int client_count; +static int use_count; +static int socket_count = 1; + +static void (*multi_server_service) (VSTREAM *, char *, char **); +static char *multi_server_name; +static char **multi_server_argv; +static void (*multi_server_accept) (int, void *); +static void (*multi_server_onexit) (char *, char **); +static void (*multi_server_pre_accept) (char *, char **); +static VSTREAM *multi_server_lock; +static int multi_server_in_flow_delay; +static unsigned multi_server_generation; +static void (*multi_server_pre_disconn) (VSTREAM *, char *, char **); +static int multi_server_saved_flags; + +/* multi_server_exit - normal termination */ + +static NORETURN multi_server_exit(void) +{ + if (multi_server_onexit) + multi_server_onexit(multi_server_name, multi_server_argv); + exit(0); +} + +/* multi_server_abort - terminate after abnormal master exit */ + +static void multi_server_abort(int unused_event, void *unused_context) +{ + if (msg_verbose) + msg_info("master disconnect -- exiting"); + multi_server_exit(); +} + +/* multi_server_timeout - idle time exceeded */ + +static void multi_server_timeout(int unused_event, void *unused_context) +{ + if (msg_verbose) + msg_info("idle timeout -- exiting"); + multi_server_exit(); +} + +/* multi_server_drain - stop accepting new clients */ + +int multi_server_drain(void) +{ + const char *myname = "multi_server_drain"; + int fd; + + switch (fork()) { + /* Try again later. */ + case -1: + return (-1); + /* Finish existing clients in the background, then terminate. */ + case 0: + (void) msg_cleanup((MSG_CLEANUP_FN) 0); + event_fork(); + for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) { + event_disable_readwrite(fd); + (void) close(fd); + /* Play safe - don't reuse this file number. */ + if (DUP2(STDIN_FILENO, fd) < 0) + msg_warn("%s: dup2(%d, %d): %m", myname, STDIN_FILENO, fd); + } + var_use_limit = 1; + return (0); + /* Let the master start a new process. */ + default: + exit(0); + } +} + +/* multi_server_disconnect - terminate client session */ + +void multi_server_disconnect(VSTREAM *stream) +{ + if (msg_verbose) + msg_info("connection closed fd %d", vstream_fileno(stream)); + if (multi_server_pre_disconn) + multi_server_pre_disconn(stream, multi_server_name, multi_server_argv); + event_disable_readwrite(vstream_fileno(stream)); + (void) vstream_fclose(stream); + client_count--; + /* Avoid integer wrap-around in a persistent process. */ + if (use_count < INT_MAX) + use_count++; + if (client_count == 0 && var_idle_limit > 0) + event_request_timer(multi_server_timeout, (void *) 0, var_idle_limit); +} + +/* multi_server_execute - in case (char *) != (struct *) */ + +static void multi_server_execute(int unused_event, void *context) +{ + VSTREAM *stream = (VSTREAM *) context; + HTABLE *attr = (vstream_flags(stream) == multi_server_saved_flags ? + (HTABLE *) vstream_context(stream) : 0); + + if (multi_server_lock != 0 + && myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + + /* + * Do not bother the application when the client disconnected. Don't drop + * the already accepted client request after "postfix reload"; that would + * be rude. + */ + if (peekfd(vstream_fileno(stream)) > 0) { + if (master_notify(var_pid, multi_server_generation, MASTER_STAT_TAKEN) < 0) + /* void */ ; + multi_server_service(stream, multi_server_name, multi_server_argv); + if (master_notify(var_pid, multi_server_generation, MASTER_STAT_AVAIL) < 0) + multi_server_abort(EVENT_NULL_TYPE, EVENT_NULL_CONTEXT); + } else { + multi_server_disconnect(stream); + } + if (attr) + htable_free(attr, myfree); +} + +/* multi_server_enable_read - enable read events */ + +static void multi_server_enable_read(int unused_event, void *context) +{ + VSTREAM *stream = (VSTREAM *) context; + + event_enable_read(vstream_fileno(stream), multi_server_execute, (void *) stream); +} + +/* multi_server_wakeup - wake up application */ + +static void multi_server_wakeup(int fd, HTABLE *attr) +{ + VSTREAM *stream; + char *tmp; + +#if defined(F_DUPFD) && (EVENTS_STYLE != EVENTS_STYLE_SELECT) +#ifndef THRESHOLD_FD_WORKAROUND +#define THRESHOLD_FD_WORKAROUND 128 +#endif + int new_fd; + + /* + * Leave some handles < FD_SETSIZE for DBMS libraries, in the unlikely + * case of a multi-server with a thousand clients. + */ + if (fd < THRESHOLD_FD_WORKAROUND) { + if ((new_fd = fcntl(fd, F_DUPFD, THRESHOLD_FD_WORKAROUND)) < 0) + msg_fatal("fcntl F_DUPFD: %m"); + (void) close(fd); + fd = new_fd; + } +#endif + if (msg_verbose) + msg_info("connection established fd %d", fd); + non_blocking(fd, BLOCKING); + close_on_exec(fd, CLOSE_ON_EXEC); + client_count++; + stream = vstream_fdopen(fd, O_RDWR); + tmp = concatenate(multi_server_name, " socket", (char *) 0); + vstream_control(stream, + CA_VSTREAM_CTL_PATH(tmp), + CA_VSTREAM_CTL_CONTEXT((void *) attr), + CA_VSTREAM_CTL_END); + myfree(tmp); + timed_ipc_setup(stream); + multi_server_saved_flags = vstream_flags(stream); + if (multi_server_in_flow_delay && mail_flow_get(1) < 0) + event_request_timer(multi_server_enable_read, (void *) stream, + var_in_flow_delay); + else + multi_server_enable_read(0, (void *) stream); +} + +/* multi_server_accept_local - accept client connection request */ + +static void multi_server_accept_local(int unused_event, void *context) +{ + int listen_fd = CAST_ANY_PTR_TO_INT(context); + int time_left = -1; + int fd; + + /* + * Be prepared for accept() to fail because some other process already + * got the connection (the number of processes competing for clients is + * kept small, so this is not a "thundering herd" problem). If the + * accept() succeeds, be sure to disable non-blocking I/O, in order to + * minimize confusion. + */ + if (client_count == 0 && var_idle_limit > 0) + time_left = event_cancel_timer(multi_server_timeout, (void *) 0); + + if (multi_server_pre_accept) + multi_server_pre_accept(multi_server_name, multi_server_argv); + fd = LOCAL_ACCEPT(listen_fd); + if (multi_server_lock != 0 + && myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + if (fd < 0) { + if (errno != EAGAIN) + msg_error("accept connection: %m"); + if (time_left >= 0) + event_request_timer(multi_server_timeout, (void *) 0, time_left); + return; + } + multi_server_wakeup(fd, (HTABLE *) 0); +} + +#ifdef MASTER_XPORT_NAME_PASS + +/* multi_server_accept_pass - accept descriptor */ + +static void multi_server_accept_pass(int unused_event, void *context) +{ + int listen_fd = CAST_ANY_PTR_TO_INT(context); + int time_left = -1; + int fd; + HTABLE *attr = 0; + + /* + * Be prepared for accept() to fail because some other process already + * got the connection (the number of processes competing for clients is + * kept small, so this is not a "thundering herd" problem). If the + * accept() succeeds, be sure to disable non-blocking I/O, in order to + * minimize confusion. + */ + if (client_count == 0 && var_idle_limit > 0) + time_left = event_cancel_timer(multi_server_timeout, (void *) 0); + + if (multi_server_pre_accept) + multi_server_pre_accept(multi_server_name, multi_server_argv); + fd = pass_accept_attr(listen_fd, &attr); + if (multi_server_lock != 0 + && myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + if (fd < 0) { + if (errno != EAGAIN) + msg_error("accept connection: %m"); + if (time_left >= 0) + event_request_timer(multi_server_timeout, (void *) 0, time_left); + return; + } + multi_server_wakeup(fd, attr); +} + +#endif + +/* multi_server_accept_inet - accept client connection request */ + +static void multi_server_accept_inet(int unused_event, void *context) +{ + int listen_fd = CAST_ANY_PTR_TO_INT(context); + int time_left = -1; + int fd; + + /* + * Be prepared for accept() to fail because some other process already + * got the connection (the number of processes competing for clients is + * kept small, so this is not a "thundering herd" problem). If the + * accept() succeeds, be sure to disable non-blocking I/O, in order to + * minimize confusion. + */ + if (client_count == 0 && var_idle_limit > 0) + time_left = event_cancel_timer(multi_server_timeout, (void *) 0); + + if (multi_server_pre_accept) + multi_server_pre_accept(multi_server_name, multi_server_argv); + fd = inet_accept(listen_fd); + if (multi_server_lock != 0 + && myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + if (fd < 0) { + if (errno != EAGAIN) + msg_error("accept connection: %m"); + if (time_left >= 0) + event_request_timer(multi_server_timeout, (void *) 0, time_left); + return; + } + multi_server_wakeup(fd, (HTABLE *) 0); +} + +/* multi_server_main - the real main program */ + +NORETURN multi_server_main(int argc, char **argv, MULTI_SERVER_FN service,...) +{ + const char *myname = "multi_server_main"; + VSTREAM *stream = 0; + char *root_dir = 0; + char *user_name = 0; + int debug_me = 0; + int daemon_mode = 1; + char *service_name = basename(argv[0]); + int delay; + int c; + int fd; + va_list ap; + MAIL_SERVER_INIT_FN pre_init = 0; + MAIL_SERVER_INIT_FN post_init = 0; + MAIL_SERVER_LOOP_FN loop = 0; + int key; + char *transport = 0; + +#if 0 + char *lock_path; + VSTRING *why; + +#endif + int alone = 0; + int zerolimit = 0; + WATCHDOG *watchdog; + char *oname_val; + char *oname; + char *oval; + const char *err; + char *generation; + int msg_vstream_needed = 0; + const char *dsn_filter_title; + const char **dsn_filter_maps; + + /* + * Process environment options as early as we can. + */ + if (getenv(CONF_ENV_VERB)) + msg_verbose = 1; + if (getenv(CONF_ENV_DEBUG)) + debug_me = 1; + + /* + * Don't die when a process goes away unexpectedly. + */ + signal(SIGPIPE, SIG_IGN); + + /* + * Don't die for frivolous reasons. + */ +#ifdef SIGXFSZ + signal(SIGXFSZ, SIG_IGN); +#endif + + /* + * May need this every now and then. + */ + var_procname = mystrdup(basename(argv[0])); + set_mail_conf_str(VAR_PROCNAME, var_procname); + + /* + * Initialize logging and exit handler. Do the syslog first, so that its + * initialization completes before we enter the optional chroot jail. + */ + maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE); + if (msg_verbose) + msg_info("daemon started"); + + /* + * Check the Postfix library version as soon as we enable logging. + */ + MAIL_VERSION_CHECK; + + /* + * Initialize from the configuration file. Allow command-line options to + * override compiled-in defaults or configured parameter values. + */ + mail_conf_suck(); + + /* + * After database open error, continue execution with reduced + * functionality. + */ + dict_allow_surrogate = 1; + + /* + * Pick up policy settings from master process. Shut up error messages to + * stderr, because no-one is going to see them. + */ + opterr = 0; + while ((c = GETOPT(argc, argv, "cdDi:lm:n:o:s:St:uvVz")) > 0) { + switch (c) { + case 'c': + root_dir = "setme"; + break; + case 'd': + daemon_mode = 0; + break; + case 'D': + debug_me = 1; + break; + case 'i': + mail_conf_update(VAR_MAX_IDLE, optarg); + break; + case 'l': + alone = 1; + break; + case 'm': + mail_conf_update(VAR_MAX_USE, optarg); + break; + case 'n': + service_name = optarg; + break; + case 'o': + oname_val = mystrdup(optarg); + if ((err = split_nameval(oname_val, &oname, &oval)) != 0) + msg_fatal("invalid \"-o %s\" option value: %s", optarg, err); + mail_conf_update(oname, oval); + myfree(oname_val); + break; + case 's': + if ((socket_count = atoi(optarg)) <= 0) + msg_fatal("invalid socket_count: %s", optarg); + break; + case 'S': + stream = VSTREAM_IN; + break; + case 'u': + user_name = "setme"; + break; + case 't': + transport = optarg; + break; + case 'v': + msg_verbose++; + break; + case 'V': + if (++msg_vstream_needed == 1) + msg_vstream_init(mail_task(var_procname), VSTREAM_ERR); + break; + case 'z': + zerolimit = 1; + break; + default: + msg_fatal("invalid option: %c", optopt); + break; + } + } + set_mail_conf_str(VAR_SERVNAME, service_name); + + /* + * Initialize generic parameters and re-initialize logging in case of a + * non-default program name or logging destination. + */ + mail_params_init(); + maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE); + + /* + * Register higher-level dictionaries and initialize the support for + * dynamically-loaded dictionarles. + */ + mail_dict_init(); + + /* + * If not connected to stdin, stdin must not be a terminal. + */ + if (daemon_mode && stream == 0 && isatty(STDIN_FILENO)) { + msg_vstream_init(var_procname, VSTREAM_ERR); + msg_fatal("do not run this command by hand"); + } + + /* + * Application-specific initialization. + */ + va_start(ap, service); + while ((key = va_arg(ap, int)) != 0) { + switch (key) { + case MAIL_SERVER_INT_TABLE: + get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *)); + break; + case MAIL_SERVER_LONG_TABLE: + get_mail_conf_long_table(va_arg(ap, CONFIG_LONG_TABLE *)); + break; + case MAIL_SERVER_STR_TABLE: + get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *)); + break; + case MAIL_SERVER_BOOL_TABLE: + get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *)); + break; + case MAIL_SERVER_TIME_TABLE: + get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *)); + break; + case MAIL_SERVER_RAW_TABLE: + get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *)); + break; + case MAIL_SERVER_NINT_TABLE: + get_mail_conf_nint_table(va_arg(ap, CONFIG_NINT_TABLE *)); + break; + case MAIL_SERVER_NBOOL_TABLE: + get_mail_conf_nbool_table(va_arg(ap, CONFIG_NBOOL_TABLE *)); + break; + case MAIL_SERVER_PRE_INIT: + pre_init = va_arg(ap, MAIL_SERVER_INIT_FN); + break; + case MAIL_SERVER_POST_INIT: + post_init = va_arg(ap, MAIL_SERVER_INIT_FN); + break; + case MAIL_SERVER_LOOP: + loop = va_arg(ap, MAIL_SERVER_LOOP_FN); + break; + case MAIL_SERVER_EXIT: + multi_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN); + break; + case MAIL_SERVER_PRE_ACCEPT: + multi_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN); + break; + case MAIL_SERVER_PRE_DISCONN: + multi_server_pre_disconn = va_arg(ap, MAIL_SERVER_DISCONN_FN); + break; + case MAIL_SERVER_IN_FLOW_DELAY: + multi_server_in_flow_delay = 1; + break; + case MAIL_SERVER_SOLITARY: + if (stream == 0 && !alone) + msg_fatal("service %s requires a process limit of 1", + service_name); + break; + case MAIL_SERVER_UNLIMITED: + if (stream == 0 && !zerolimit) + msg_fatal("service %s requires a process limit of 0", + service_name); + break; + case MAIL_SERVER_PRIVILEGED: + if (user_name) + msg_fatal("service %s requires privileged operation", + service_name); + break; + case MAIL_SERVER_BOUNCE_INIT: + dsn_filter_title = va_arg(ap, const char *); + dsn_filter_maps = va_arg(ap, const char **); + bounce_client_init(dsn_filter_title, *dsn_filter_maps); + break; + default: + msg_panic("%s: unknown argument type: %d", myname, key); + } + } + va_end(ap); + + if (root_dir) + root_dir = var_queue_dir; + if (user_name) + user_name = var_mail_owner; + + /* + * Can options be required? + */ + if (stream == 0) { + if (transport == 0) + msg_fatal("no transport type specified"); + if (strcasecmp(transport, MASTER_XPORT_NAME_INET) == 0) + multi_server_accept = multi_server_accept_inet; + else if (strcasecmp(transport, MASTER_XPORT_NAME_UNIX) == 0) + multi_server_accept = multi_server_accept_local; +#ifdef MASTER_XPORT_NAME_PASS + else if (strcasecmp(transport, MASTER_XPORT_NAME_PASS) == 0) + multi_server_accept = multi_server_accept_pass; +#endif + else + msg_fatal("unsupported transport type: %s", transport); + } + + /* + * Retrieve process generation from environment. + */ + if ((generation = getenv(MASTER_GEN_NAME)) != 0) { + if (!alldig(generation)) + msg_fatal("bad generation: %s", generation); + OCTAL_TO_UNSIGNED(multi_server_generation, generation); + if (msg_verbose) + msg_info("process generation: %s (%o)", + generation, multi_server_generation); + } + + /* + * Optionally start the debugger on ourself. + */ + if (debug_me) + debug_process(); + + /* + * Traditionally, BSD select() can't handle multiple processes selecting + * on the same socket, and wakes up every process in select(). See TCP/IP + * Illustrated volume 2 page 532. We avoid select() collisions with an + * external lock file. + */ + + /* + * XXX Can't compete for exclusive access to the listen socket because we + * also have to monitor existing client connections for service requests. + */ +#if 0 + if (stream == 0 && !alone) { + lock_path = concatenate(DEF_PID_DIR, "/", transport, + ".", service_name, (char *) 0); + why = vstring_alloc(1); + if ((multi_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600, + (struct stat *) 0, -1, -1, why)) == 0) + msg_fatal("open lock file %s: %s", lock_path, vstring_str(why)); + close_on_exec(vstream_fileno(multi_server_lock), CLOSE_ON_EXEC); + myfree(lock_path); + vstring_free(why); + } +#endif + + /* + * Set up call-back info. + */ + multi_server_service = service; + multi_server_name = service_name; + multi_server_argv = argv + optind; + + /* + * Run pre-jail initialization. + */ + if (chdir(var_queue_dir) < 0) + msg_fatal("chdir(\"%s\"): %m", var_queue_dir); + if (pre_init) + pre_init(multi_server_name, multi_server_argv); + + /* + * Optionally, restrict the damage that this process can do. + */ + resolve_local_init(); + tzset(); + chroot_uid(root_dir, user_name); + + /* + * Run post-jail initialization. + */ + if (post_init) + post_init(multi_server_name, multi_server_argv); + + /* + * Are we running as a one-shot server with the client connection on + * standard input? If so, make sure the output is written to stdout so as + * to satisfy common expectation. + */ + if (stream != 0) { + vstream_control(stream, + CA_VSTREAM_CTL_DOUBLE, + CA_VSTREAM_CTL_WRITE_FD(STDOUT_FILENO), + CA_VSTREAM_CTL_END); + service(stream, multi_server_name, multi_server_argv); + vstream_fflush(stream); + multi_server_exit(); + } + + /* + * Running as a semi-resident server. Service connection requests. + * Terminate when we have serviced a sufficient number of clients, when + * no-one has been talking to us for a configurable amount of time, or + * when the master process terminated abnormally. + */ + if (var_idle_limit > 0) + event_request_timer(multi_server_timeout, (void *) 0, var_idle_limit); + for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) { + event_enable_read(fd, multi_server_accept, CAST_INT_TO_VOID_PTR(fd)); + close_on_exec(fd, CLOSE_ON_EXEC); + } + event_enable_read(MASTER_STATUS_FD, multi_server_abort, (void *) 0); + close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC); + close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC); + close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC); + watchdog = watchdog_create(var_daemon_timeout, (WATCHDOG_FN) 0, (void *) 0); + + /* + * The event loop, at last. + */ + while (var_use_limit == 0 || use_count < var_use_limit || client_count > 0) { + if (multi_server_lock != 0) { + watchdog_stop(watchdog); + if (myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("select lock: %m"); + } + watchdog_start(watchdog); + delay = loop ? loop(multi_server_name, multi_server_argv) : -1; + event_loop(delay); + } + multi_server_exit(); +} diff --git a/src/master/single_server.c b/src/master/single_server.c new file mode 100644 index 0000000..f834d8b --- /dev/null +++ b/src/master/single_server.c @@ -0,0 +1,822 @@ +/*++ +/* NAME +/* single_server 3 +/* SUMMARY +/* skeleton single-threaded mail subsystem +/* SYNOPSIS +/* #include <mail_server.h> +/* +/* NORETURN single_server_main(argc, argv, service, key, value, ...) +/* int argc; +/* char **argv; +/* void (*service)(VSTREAM *stream, char *service_name, char **argv); +/* int key; +/* DESCRIPTION +/* This module implements a skeleton for single-threaded +/* mail subsystems: mail subsystem programs that service one +/* client at a time. The resulting program expects to be run +/* from the \fBmaster\fR process. +/* +/* single_server_main() is the skeleton entry point. It should be +/* called from the application main program. The skeleton does the +/* generic command-line options processing, initialization of +/* configurable parameters, and connection management. +/* The skeleton never returns. +/* +/* Arguments: +/* .IP "void (*service)(VSTREAM *fp, char *service_name, char **argv)" +/* A pointer to a function that is called by the skeleton each time +/* a client connects to the program's service port. The function is +/* run after the program has irrevocably dropped its privileges. +/* The stream initial state is non-blocking mode. +/* Optional connection attributes are provided as a hash that +/* is attached as stream context. +/* The service name argument corresponds to the service name in the +/* master.cf file. +/* The argv argument specifies command-line arguments left over +/* after options processing. +/* .PP +/* Optional arguments are specified as a null-terminated list +/* with macros that have zero or more arguments: +/* .IP "CA_MAIL_SERVER_INT_TABLE(CONFIG_INT_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_LONG_TABLE(CONFIG_LONG_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_STR_TABLE(CONFIG_STR_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_BOOL_TABLE(CONFIG_BOOL_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_TIME_TABLE(CONFIG_TIME_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_RAW_TABLE(CONFIG_RAW_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. Raw parameters are not subjected to $name +/* evaluation. +/* .IP "CA_MAIL_SERVER_NINT_TABLE(CONFIG_NINT_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_NBOOL_TABLE(CONFIG_NBOOL_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_PRE_INIT(void *(char *service_name, char **argv))" +/* A pointer to a function that is called once +/* by the skeleton after it has read the global configuration file +/* and after it has processed command-line arguments, but before +/* the skeleton has optionally relinquished the process privileges. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_POST_INIT(void *(char *service_name, char **argv))" +/* A pointer to a function that is called once +/* by the skeleton after it has optionally relinquished the process +/* privileges, but before servicing client connection requests. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_LOOP(int *(char *service_name, char **argv))" +/* A pointer to function that is executed from +/* within the event loop, whenever an I/O or timer event has happened, +/* or whenever nothing has happened for a specified amount of time. +/* The result value of the function specifies how long to wait until +/* the next event. Specify -1 to wait for "as long as it takes". +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_EXIT(void *(void))" +/* A pointer to function that is executed immediately before normal +/* process termination. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_PRE_ACCEPT(void *(char *service_name, char **argv))" +/* Function to be executed prior to accepting a new connection. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_IN_FLOW_DELAY(none)" +/* Pause $in_flow_delay seconds when no "mail flow control token" +/* is available. A token is consumed for each connection request. +/* .IP CA_MAIL_SERVER_SOLITARY +/* This service must be configured with process limit of 1. +/* .IP CA_MAIL_SERVER_UNLIMITED +/* This service must be configured with process limit of 0. +/* .IP CA_MAIL_SERVER_PRIVILEGED +/* This service must be configured as privileged. +/* .IP "CA_MAIL_SERVER_BOUNCE_INIT(const char *, const char **)" +/* Initialize the DSN filter for the bounce/defer service +/* clients with the specified map source and map names. +/* .IP "CA_MAIL_SERVER_RETIRE_ME" +/* Prevent a process from being reused indefinitely. After +/* (var_max_use * var_max_idle) seconds or some sane constant, +/* terminate voluntarily when the process becomes idle. +/* .PP +/* The var_use_limit variable limits the number of clients +/* that a server can service before it commits suicide. This +/* value is taken from the global \fBmain.cf\fR configuration +/* file. Setting \fBvar_use_limit\fR to zero disables the +/* client limit. +/* +/* The var_idle_limit variable limits the time that a service +/* receives no client connection requests before it commits suicide. +/* Do not change this setting before calling single_server_main(). +/* This value is taken from the global \fBmain.cf\fR configuration +/* file. Setting \fBvar_idle_limit\fR to zero disables the idle limit. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* BUGS +/* SEE ALSO +/* master(8), master process +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <unistd.h> +#include <signal.h> +#include <stdlib.h> +#include <limits.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <stdarg.h> +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif +#include <time.h> + +/* Utility library. */ + +#include <msg.h> +#include <msg_vstream.h> +#include <chroot_uid.h> +#include <vstring.h> +#include <vstream.h> +#include <msg_vstream.h> +#include <mymalloc.h> +#include <events.h> +#include <iostuff.h> +#include <stringops.h> +#include <sane_accept.h> +#include <myflock.h> +#include <safe_open.h> +#include <listen.h> +#include <watchdog.h> +#include <split_at.h> + +/* Global library. */ + +#include <mail_params.h> +#include <mail_task.h> +#include <debug_process.h> +#include <mail_conf.h> +#include <mail_dict.h> +#include <timed_ipc.h> +#include <resolve_local.h> +#include <mail_flow.h> +#include <mail_version.h> +#include <bounce.h> +#include <maillog_client.h> + +/* Process manager. */ + +#include "master_proto.h" + +/* Application-specific */ + +#include "mail_server.h" + + /* + * Global state. + */ +static int use_count; + +static void (*single_server_service) (VSTREAM *, char *, char **); +static char *single_server_name; +static char **single_server_argv; +static void (*single_server_accept) (int, void *); +static void (*single_server_onexit) (char *, char **); +static void (*single_server_pre_accept) (char *, char **); +static VSTREAM *single_server_lock; +static int single_server_in_flow_delay; +static unsigned single_server_generation; + +/* single_server_exit - normal termination */ + +static NORETURN single_server_exit(void) +{ + if (single_server_onexit) + single_server_onexit(single_server_name, single_server_argv); + exit(0); +} + +/* single_server_retire - retire when idle */ + +static NORETURN single_server_retire(int unused_event, void *unused_context) +{ + if (msg_verbose) + msg_info("time to retire -- exiting"); + single_server_exit(); +} + +/* single_server_abort - terminate after abnormal master exit */ + +static void single_server_abort(int unused_event, void *unused_context) +{ + if (msg_verbose) + msg_info("master disconnect -- exiting"); + single_server_exit(); +} + +/* single_server_timeout - idle time exceeded */ + +static void single_server_timeout(int unused_event, void *unused_context) +{ + if (msg_verbose) + msg_info("idle timeout -- exiting"); + single_server_exit(); +} + +/* single_server_wakeup - wake up application */ + +static void single_server_wakeup(int fd, HTABLE *attr) +{ + VSTREAM *stream; + char *tmp; + + /* + * If the accept() succeeds, be sure to disable non-blocking I/O, because + * the application is supposed to be single-threaded. Notice the master + * of our (un)availability to service connection requests. Commit suicide + * when the master process disconnected from us. Don't drop the already + * accepted client request after "postfix reload"; that would be rude. + */ + if (msg_verbose) + msg_info("connection established"); + non_blocking(fd, BLOCKING); + close_on_exec(fd, CLOSE_ON_EXEC); + stream = vstream_fdopen(fd, O_RDWR); + tmp = concatenate(single_server_name, " socket", (char *) 0); + vstream_control(stream, + CA_VSTREAM_CTL_PATH(tmp), + CA_VSTREAM_CTL_CONTEXT((void *) attr), + CA_VSTREAM_CTL_END); + myfree(tmp); + timed_ipc_setup(stream); + if (master_notify(var_pid, single_server_generation, MASTER_STAT_TAKEN) < 0) + /* void */ ; + if (single_server_in_flow_delay && mail_flow_get(1) < 0) + doze(var_in_flow_delay * 1000000); + single_server_service(stream, single_server_name, single_server_argv); + (void) vstream_fclose(stream); + if (master_notify(var_pid, single_server_generation, MASTER_STAT_AVAIL) < 0) + single_server_abort(EVENT_NULL_TYPE, EVENT_NULL_CONTEXT); + if (msg_verbose) + msg_info("connection closed"); + /* Avoid integer wrap-around in a persistent process. */ + if (use_count < INT_MAX) + use_count++; + if (var_idle_limit > 0) + event_request_timer(single_server_timeout, (void *) 0, var_idle_limit); + if (attr) + htable_free(attr, myfree); +} + +/* single_server_accept_local - accept client connection request */ + +static void single_server_accept_local(int unused_event, void *context) +{ + int listen_fd = CAST_ANY_PTR_TO_INT(context); + int time_left = -1; + int fd; + + /* + * Be prepared for accept() to fail because some other process already + * got the connection. We use select() + accept(), instead of simply + * blocking in accept(), because we must be able to detect that the + * master process has gone away unexpectedly. + */ + if (var_idle_limit > 0) + time_left = event_cancel_timer(single_server_timeout, (void *) 0); + + if (single_server_pre_accept) + single_server_pre_accept(single_server_name, single_server_argv); + fd = LOCAL_ACCEPT(listen_fd); + if (single_server_lock != 0 + && myflock(vstream_fileno(single_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + if (fd < 0) { + if (errno != EAGAIN) + msg_error("accept connection: %m"); + if (time_left >= 0) + event_request_timer(single_server_timeout, (void *) 0, time_left); + return; + } + single_server_wakeup(fd, (HTABLE *) 0); +} + +#ifdef MASTER_XPORT_NAME_PASS + +/* single_server_accept_pass - accept descriptor */ + +static void single_server_accept_pass(int unused_event, void *context) +{ + int listen_fd = CAST_ANY_PTR_TO_INT(context); + int time_left = -1; + int fd; + HTABLE *attr = 0; + + /* + * Be prepared for accept() to fail because some other process already + * got the connection. We use select() + accept(), instead of simply + * blocking in accept(), because we must be able to detect that the + * master process has gone away unexpectedly. + */ + if (var_idle_limit > 0) + time_left = event_cancel_timer(single_server_timeout, (void *) 0); + + if (single_server_pre_accept) + single_server_pre_accept(single_server_name, single_server_argv); + fd = pass_accept_attr(listen_fd, &attr); + if (single_server_lock != 0 + && myflock(vstream_fileno(single_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + if (fd < 0) { + if (errno != EAGAIN) + msg_error("accept connection: %m"); + if (time_left >= 0) + event_request_timer(single_server_timeout, (void *) 0, time_left); + return; + } + single_server_wakeup(fd, attr); +} + +#endif + +/* single_server_accept_inet - accept client connection request */ + +static void single_server_accept_inet(int unused_event, void *context) +{ + int listen_fd = CAST_ANY_PTR_TO_INT(context); + int time_left = -1; + int fd; + + /* + * Be prepared for accept() to fail because some other process already + * got the connection. We use select() + accept(), instead of simply + * blocking in accept(), because we must be able to detect that the + * master process has gone away unexpectedly. + */ + if (var_idle_limit > 0) + time_left = event_cancel_timer(single_server_timeout, (void *) 0); + + if (single_server_pre_accept) + single_server_pre_accept(single_server_name, single_server_argv); + fd = inet_accept(listen_fd); + if (single_server_lock != 0 + && myflock(vstream_fileno(single_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + if (fd < 0) { + if (errno != EAGAIN) + msg_error("accept connection: %m"); + if (time_left >= 0) + event_request_timer(single_server_timeout, (void *) 0, time_left); + return; + } + single_server_wakeup(fd, (HTABLE *) 0); +} + +/* single_server_main - the real main program */ + +NORETURN single_server_main(int argc, char **argv, SINGLE_SERVER_FN service,...) +{ + const char *myname = "single_server_main"; + VSTREAM *stream = 0; + char *root_dir = 0; + char *user_name = 0; + int debug_me = 0; + int daemon_mode = 1; + char *service_name = basename(argv[0]); + int delay; + int c; + int socket_count = 1; + int fd; + va_list ap; + MAIL_SERVER_INIT_FN pre_init = 0; + MAIL_SERVER_INIT_FN post_init = 0; + MAIL_SERVER_LOOP_FN loop = 0; + int key; + char *transport = 0; + char *lock_path; + VSTRING *why; + int alone = 0; + int zerolimit = 0; + WATCHDOG *watchdog; + char *oname_val; + char *oname; + char *oval; + const char *err; + char *generation; + int msg_vstream_needed = 0; + const char *dsn_filter_title; + const char **dsn_filter_maps; + int retire_me_from_flags = 0; + int retire_me = 0; + + /* + * Process environment options as early as we can. + */ + if (getenv(CONF_ENV_VERB)) + msg_verbose = 1; + if (getenv(CONF_ENV_DEBUG)) + debug_me = 1; + + /* + * Don't die when a process goes away unexpectedly. + */ + signal(SIGPIPE, SIG_IGN); + + /* + * Don't die for frivolous reasons. + */ +#ifdef SIGXFSZ + signal(SIGXFSZ, SIG_IGN); +#endif + + /* + * May need this every now and then. + */ + var_procname = mystrdup(basename(argv[0])); + set_mail_conf_str(VAR_PROCNAME, var_procname); + + /* + * Initialize logging and exit handler. Do the syslog first, so that its + * initialization completes before we enter the optional chroot jail. + */ + maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE); + if (msg_verbose) + msg_info("daemon started"); + + /* + * Check the Postfix library version as soon as we enable logging. + */ + MAIL_VERSION_CHECK; + + /* + * Initialize from the configuration file. Allow command-line options to + * override compiled-in defaults or configured parameter values. + */ + mail_conf_suck(); + + /* + * After database open error, continue execution with reduced + * functionality. + */ + dict_allow_surrogate = 1; + + /* + * Pick up policy settings from master process. Shut up error messages to + * stderr, because no-one is going to see them. + */ + opterr = 0; + while ((c = GETOPT(argc, argv, "cdDi:lm:n:o:r:s:St:uvVz")) > 0) { + switch (c) { + case 'c': + root_dir = "setme"; + break; + case 'd': + daemon_mode = 0; + break; + case 'D': + debug_me = 1; + break; + case 'i': + mail_conf_update(VAR_MAX_IDLE, optarg); + break; + case 'l': + alone = 1; + break; + case 'm': + mail_conf_update(VAR_MAX_USE, optarg); + break; + case 'n': + service_name = optarg; + break; + case 'o': + oname_val = mystrdup(optarg); + if ((err = split_nameval(oname_val, &oname, &oval)) != 0) + msg_fatal("invalid \"-o %s\" option value: %s", optarg, err); + mail_conf_update(oname, oval); + myfree(oname_val); + break; + case 'r': + if ((retire_me_from_flags = atoi(optarg)) <= 0) + msg_fatal("invalid retirement time: %s", optarg); + break; + case 's': + if ((socket_count = atoi(optarg)) <= 0) + msg_fatal("invalid socket_count: %s", optarg); + break; + case 'S': + stream = VSTREAM_IN; + break; + case 'u': + user_name = "setme"; + break; + case 't': + transport = optarg; + break; + case 'v': + msg_verbose++; + break; + case 'V': + if (++msg_vstream_needed == 1) + msg_vstream_init(mail_task(var_procname), VSTREAM_ERR); + break; + case 'z': + zerolimit = 1; + break; + default: + msg_fatal("invalid option: %c", optopt); + break; + } + } + set_mail_conf_str(VAR_SERVNAME, service_name); + + /* + * Initialize generic parameters. + */ + mail_params_init(); + maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE); + + /* + * Register higher-level dictionaries and initialize the support for + * dynamically-loaded dictionarles. + */ + mail_dict_init(); + + /* + * If not connected to stdin, stdin must not be a terminal. + */ + if (daemon_mode && stream == 0 && isatty(STDIN_FILENO)) { + msg_vstream_init(var_procname, VSTREAM_ERR); + msg_fatal("do not run this command by hand"); + } + + /* + * Application-specific initialization. + */ + va_start(ap, service); + while ((key = va_arg(ap, int)) != 0) { + switch (key) { + case MAIL_SERVER_INT_TABLE: + get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *)); + break; + case MAIL_SERVER_LONG_TABLE: + get_mail_conf_long_table(va_arg(ap, CONFIG_LONG_TABLE *)); + break; + case MAIL_SERVER_STR_TABLE: + get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *)); + break; + case MAIL_SERVER_BOOL_TABLE: + get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *)); + break; + case MAIL_SERVER_TIME_TABLE: + get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *)); + break; + case MAIL_SERVER_RAW_TABLE: + get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *)); + break; + case MAIL_SERVER_NINT_TABLE: + get_mail_conf_nint_table(va_arg(ap, CONFIG_NINT_TABLE *)); + break; + case MAIL_SERVER_NBOOL_TABLE: + get_mail_conf_nbool_table(va_arg(ap, CONFIG_NBOOL_TABLE *)); + break; + case MAIL_SERVER_PRE_INIT: + pre_init = va_arg(ap, MAIL_SERVER_INIT_FN); + break; + case MAIL_SERVER_POST_INIT: + post_init = va_arg(ap, MAIL_SERVER_INIT_FN); + break; + case MAIL_SERVER_LOOP: + loop = va_arg(ap, MAIL_SERVER_LOOP_FN); + break; + case MAIL_SERVER_EXIT: + single_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN); + break; + case MAIL_SERVER_PRE_ACCEPT: + single_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN); + break; + case MAIL_SERVER_IN_FLOW_DELAY: + single_server_in_flow_delay = 1; + break; + case MAIL_SERVER_SOLITARY: + if (stream == 0 && !alone) + msg_fatal("service %s requires a process limit of 1", + service_name); + break; + case MAIL_SERVER_UNLIMITED: + if (stream == 0 && !zerolimit) + msg_fatal("service %s requires a process limit of 0", + service_name); + break; + case MAIL_SERVER_PRIVILEGED: + if (user_name) + msg_fatal("service %s requires privileged operation", + service_name); + break; + case MAIL_SERVER_BOUNCE_INIT: + dsn_filter_title = va_arg(ap, const char *); + dsn_filter_maps = va_arg(ap, const char **); + bounce_client_init(dsn_filter_title, *dsn_filter_maps); + break; + case MAIL_SERVER_RETIRE_ME: + if (retire_me_from_flags > 0) + retire_me = retire_me_from_flags; + else if (var_idle_limit == 0 || var_use_limit == 0 + || var_idle_limit > 18000 / var_use_limit) + retire_me = 18000; + else + retire_me = var_idle_limit * var_use_limit; + break; + default: + msg_panic("%s: unknown argument type: %d", myname, key); + } + } + va_end(ap); + + if (root_dir) + root_dir = var_queue_dir; + if (user_name) + user_name = var_mail_owner; + + /* + * Can options be required? + */ + if (stream == 0) { + if (transport == 0) + msg_fatal("no transport type specified"); + if (strcasecmp(transport, MASTER_XPORT_NAME_INET) == 0) + single_server_accept = single_server_accept_inet; + else if (strcasecmp(transport, MASTER_XPORT_NAME_UNIX) == 0) + single_server_accept = single_server_accept_local; +#ifdef MASTER_XPORT_NAME_PASS + else if (strcasecmp(transport, MASTER_XPORT_NAME_PASS) == 0) + single_server_accept = single_server_accept_pass; +#endif + else + msg_fatal("unsupported transport type: %s", transport); + } + + /* + * Retrieve process generation from environment. + */ + if ((generation = getenv(MASTER_GEN_NAME)) != 0) { + if (!alldig(generation)) + msg_fatal("bad generation: %s", generation); + OCTAL_TO_UNSIGNED(single_server_generation, generation); + if (msg_verbose) + msg_info("process generation: %s (%o)", + generation, single_server_generation); + } + + /* + * Optionally start the debugger on ourself. + */ + if (debug_me) + debug_process(); + + /* + * Traditionally, BSD select() can't handle multiple processes selecting + * on the same socket, and wakes up every process in select(). See TCP/IP + * Illustrated volume 2 page 532. We avoid select() collisions with an + * external lock file. + */ + if (stream == 0 && !alone) { + lock_path = concatenate(DEF_PID_DIR, "/", transport, + ".", service_name, (void *) 0); + why = vstring_alloc(1); + if ((single_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600, + (struct stat *) 0, -1, -1, why)) == 0) + msg_fatal("open lock file %s: %s", lock_path, vstring_str(why)); + close_on_exec(vstream_fileno(single_server_lock), CLOSE_ON_EXEC); + myfree(lock_path); + vstring_free(why); + } + + /* + * Set up call-back info. + */ + single_server_service = service; + single_server_name = service_name; + single_server_argv = argv + optind; + + /* + * Run pre-jail initialization. + */ + if (chdir(var_queue_dir) < 0) + msg_fatal("chdir(\"%s\"): %m", var_queue_dir); + if (pre_init) + pre_init(single_server_name, single_server_argv); + + /* + * Optionally, restrict the damage that this process can do. + */ + resolve_local_init(); + tzset(); + chroot_uid(root_dir, user_name); + + /* + * Run post-jail initialization. + */ + if (post_init) + post_init(single_server_name, single_server_argv); + + /* + * Are we running as a one-shot server with the client connection on + * standard input? If so, make sure the output is written to stdout so as + * to satisfy common expectation. + */ + if (stream != 0) { + vstream_control(stream, + CA_VSTREAM_CTL_DOUBLE, + CA_VSTREAM_CTL_WRITE_FD(STDOUT_FILENO), + CA_VSTREAM_CTL_END); + service(stream, single_server_name, single_server_argv); + vstream_fflush(stream); + single_server_exit(); + } + + /* + * Running as a semi-resident server. Service connection requests. + * Terminate when we have serviced a sufficient number of clients, when + * no-one has been talking to us for a configurable amount of time, or + * when the master process terminated abnormally. + */ + if (var_idle_limit > 0) + event_request_timer(single_server_timeout, (void *) 0, var_idle_limit); + if (retire_me) + event_request_timer(single_server_retire, (void *) 0, retire_me); + for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) { + event_enable_read(fd, single_server_accept, CAST_INT_TO_VOID_PTR(fd)); + close_on_exec(fd, CLOSE_ON_EXEC); + } + event_enable_read(MASTER_STATUS_FD, single_server_abort, (void *) 0); + close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC); + close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC); + close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC); + watchdog = watchdog_create(var_daemon_timeout, (WATCHDOG_FN) 0, (void *) 0); + + /* + * The event loop, at last. + */ + while (var_use_limit == 0 || use_count < var_use_limit) { + if (single_server_lock != 0) { + watchdog_stop(watchdog); + if (myflock(vstream_fileno(single_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("select lock: %m"); + } + watchdog_start(watchdog); + delay = loop ? loop(single_server_name, single_server_argv) : -1; + event_loop(delay); + } + single_server_exit(); +} diff --git a/src/master/trigger_server.c b/src/master/trigger_server.c new file mode 100644 index 0000000..fa6114b --- /dev/null +++ b/src/master/trigger_server.c @@ -0,0 +1,809 @@ +/*++ +/* NAME +/* trigger_server 3 +/* SUMMARY +/* skeleton triggered mail subsystem +/* SYNOPSIS +/* #include <mail_server.h> +/* +/* NORETURN trigger_server_main(argc, argv, service, key, value, ...) +/* int argc; +/* char **argv; +/* void (*service)(char *buf, int len, char *service_name, char **argv); +/* int key; +/* DESCRIPTION +/* This module implements a skeleton for triggered +/* mail subsystems: mail subsystem programs that wake up on +/* client request and perform some activity without further +/* client interaction. This module supports local IPC via FIFOs +/* and via UNIX-domain sockets. The resulting program expects to be +/* run from the \fBmaster\fR process. +/* +/* trigger_server_main() is the skeleton entry point. It should be +/* called from the application main program. The skeleton does the +/* generic command-line options processing, initialization of +/* configurable parameters, and connection management. +/* The skeleton never returns. +/* +/* Arguments: +/* .IP "void (*service)(char *buf, int len, char *service_name, char **argv)" +/* A pointer to a function that is called by the skeleton each time +/* a client connects to the program's service port. The function is +/* run after the program has irrevocably dropped its privileges. +/* The buffer argument specifies the data read from the trigger port; +/* this data corresponds to one or more trigger requests. +/* The len argument specifies how much client data is available. +/* The maximal size of the buffer is specified via the +/* TRIGGER_BUF_SIZE manifest constant. +/* The service name argument corresponds to the service name in the +/* master.cf file. +/* The argv argument specifies command-line arguments left over +/* after options processing. +/* The \fBserver\fR argument provides the following information: +/* .PP +/* Optional arguments are specified as a null-terminated list +/* with macros that have zero or more arguments: +/* .IP "CA_MAIL_SERVER_INT_TABLE(CONFIG_INT_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_LONG_TABLE(CONFIG_LONG_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_STR_TABLE(CONFIG_STR_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_BOOL_TABLE(CONFIG_BOOL_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_TIME_TABLE(CONFIG_TIME_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_RAW_TABLE(CONFIG_RAW_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. Raw parameters are not subjected to $name +/* evaluation. +/* .IP "CA_MAIL_SERVER_NINT_TABLE(CONFIG_NINT_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_NBOOL_TABLE(CONFIG_NBOOL_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_PRE_INIT(void *(char *service_name, char **argv))" +/* A pointer to a function that is called once +/* by the skeleton after it has read the global configuration file +/* and after it has processed command-line arguments, but before +/* the skeleton has optionally relinquished the process privileges. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_POST_INIT(void *(char *service_name, char **argv))" +/* A pointer to a function that is called once +/* by the skeleton after it has optionally relinquished the process +/* privileges, but before servicing client connection requests. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_LOOP(int *(char *service_name, char **argv))" +/* A pointer to function that is executed from +/* within the event loop, whenever an I/O or timer event has happened, +/* or whenever nothing has happened for a specified amount of time. +/* The result value of the function specifies how long to wait until +/* the next event. Specify -1 to wait for "as long as it takes". +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_EXIT(void *(char *service_name, char **argv))" +/* A pointer to function that is executed immediately before normal +/* process termination. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_PRE_ACCEPT(void *(char *service_name, char **argv))" +/* Function to be executed prior to accepting a new request. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_IN_FLOW_DELAY(none)" +/* Pause $in_flow_delay seconds when no "mail flow control token" +/* is available. A token is consumed for each connection request. +/* .IP CA_MAIL_SERVER_SOLITARY +/* This service must be configured with process limit of 1. +/* .IP CA_MAIL_SERVER_UNLIMITED +/* This service must be configured with process limit of 0. +/* .IP CA_MAIL_SERVER_PRIVILEGED +/* This service must be configured as privileged. +/* .IP "CA_MAIL_SERVER_WATCHDOG(int *)" +/* Override the default 1000s watchdog timeout. The value is +/* used after command-line and main.cf file processing. +/* .IP "CA_MAIL_SERVER_BOUNCE_INIT(const char *, const char **)" +/* Initialize the DSN filter for the bounce/defer service +/* clients with the specified map source and map names. +/* .PP +/* The var_use_limit variable limits the number of clients that +/* a server can service before it commits suicide. +/* This value is taken from the global \fBmain.cf\fR configuration +/* file. Setting \fBvar_use_limit\fR to zero disables the client limit. +/* +/* The var_idle_limit variable limits the time that a service +/* receives no client connection requests before it commits suicide. +/* This value is taken from the global \fBmain.cf\fR configuration +/* file. Setting \fBvar_use_limit\fR to zero disables the idle limit. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* BUGS +/* Works with FIFO-based services only. +/* SEE ALSO +/* master(8), master process +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <unistd.h> +#include <signal.h> +#include <stdlib.h> +#include <limits.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <stdarg.h> +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif +#include <time.h> + +/* Utility library. */ + +#include <msg.h> +#include <msg_vstream.h> +#include <chroot_uid.h> +#include <vstring.h> +#include <vstream.h> +#include <msg_vstream.h> +#include <mymalloc.h> +#include <events.h> +#include <iostuff.h> +#include <stringops.h> +#include <sane_accept.h> +#include <myflock.h> +#include <safe_open.h> +#include <listen.h> +#include <watchdog.h> +#include <split_at.h> + +/* Global library. */ + +#include <mail_params.h> +#include <mail_task.h> +#include <debug_process.h> +#include <mail_conf.h> +#include <mail_dict.h> +#include <resolve_local.h> +#include <mail_flow.h> +#include <mail_version.h> +#include <bounce.h> +#include <maillog_client.h> + +/* Process manager. */ + +#include "master_proto.h" + +/* Application-specific */ + +#include "mail_server.h" + + /* + * Global state. + */ +static int use_count; + +static TRIGGER_SERVER_FN trigger_server_service; +static char *trigger_server_name; +static char **trigger_server_argv; +static void (*trigger_server_accept) (int, void *); +static void (*trigger_server_onexit) (char *, char **); +static void (*trigger_server_pre_accept) (char *, char **); +static VSTREAM *trigger_server_lock; +static int trigger_server_in_flow_delay; +static unsigned trigger_server_generation; +static int trigger_server_watchdog = 1000; + +/* trigger_server_exit - normal termination */ + +static NORETURN trigger_server_exit(void) +{ + if (trigger_server_onexit) + trigger_server_onexit(trigger_server_name, trigger_server_argv); + exit(0); +} + +/* trigger_server_abort - terminate after abnormal master exit */ + +static void trigger_server_abort(int unused_event, void *unused_context) +{ + if (msg_verbose) + msg_info("master disconnect -- exiting"); + trigger_server_exit(); +} + +/* trigger_server_timeout - idle time exceeded */ + +static void trigger_server_timeout(int unused_event, void *unused_context) +{ + if (msg_verbose) + msg_info("idle timeout -- exiting"); + trigger_server_exit(); +} + +/* trigger_server_wakeup - wake up application */ + +static void trigger_server_wakeup(int fd) +{ + char buf[TRIGGER_BUF_SIZE]; + ssize_t len; + + /* + * Commit suicide when the master process disconnected from us. Don't + * drop the already accepted client request after "postfix reload"; that + * would be rude. + */ + if (master_notify(var_pid, trigger_server_generation, MASTER_STAT_TAKEN) < 0) + /* void */ ; + if (trigger_server_in_flow_delay && mail_flow_get(1) < 0) + doze(var_in_flow_delay * 1000000); + if ((len = read(fd, buf, sizeof(buf))) >= 0) + trigger_server_service(buf, len, trigger_server_name, + trigger_server_argv); + if (master_notify(var_pid, trigger_server_generation, MASTER_STAT_AVAIL) < 0) + trigger_server_abort(EVENT_NULL_TYPE, EVENT_NULL_CONTEXT); + if (var_idle_limit > 0) + event_request_timer(trigger_server_timeout, (void *) 0, var_idle_limit); + /* Avoid integer wrap-around in a persistent process. */ + if (use_count < INT_MAX) + use_count++; +} + +/* trigger_server_accept_fifo - accept fifo client request */ + +static void trigger_server_accept_fifo(int unused_event, void *context) +{ + const char *myname = "trigger_server_accept_fifo"; + int listen_fd = CAST_ANY_PTR_TO_INT(context); + + if (trigger_server_lock != 0 + && myflock(vstream_fileno(trigger_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + + if (msg_verbose) + msg_info("%s: trigger arrived", myname); + + /* + * Read whatever the other side wrote into the FIFO. The FIFO read end is + * non-blocking so we won't get stuck when multiple processes wake up. + */ + if (trigger_server_pre_accept) + trigger_server_pre_accept(trigger_server_name, trigger_server_argv); + trigger_server_wakeup(listen_fd); +} + +/* trigger_server_accept_local - accept socket client request */ + +static void trigger_server_accept_local(int unused_event, void *context) +{ + const char *myname = "trigger_server_accept_local"; + int listen_fd = CAST_ANY_PTR_TO_INT(context); + int time_left = 0; + int fd; + + if (msg_verbose) + msg_info("%s: trigger arrived", myname); + + /* + * Read a message from a socket. Be prepared for accept() to fail because + * some other process already got the connection. The socket is + * non-blocking so we won't get stuck when multiple processes wake up. + * Don't get stuck when the client connects but sends no data. Restart + * the idle timer if this was a false alarm. + */ + if (var_idle_limit > 0) + time_left = event_cancel_timer(trigger_server_timeout, (void *) 0); + + if (trigger_server_pre_accept) + trigger_server_pre_accept(trigger_server_name, trigger_server_argv); + fd = LOCAL_ACCEPT(listen_fd); + if (trigger_server_lock != 0 + && myflock(vstream_fileno(trigger_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + if (fd < 0) { + if (errno != EAGAIN) + msg_error("accept connection: %m"); + if (time_left >= 0) + event_request_timer(trigger_server_timeout, (void *) 0, time_left); + return; + } + close_on_exec(fd, CLOSE_ON_EXEC); + if (read_wait(fd, 10) == 0) + trigger_server_wakeup(fd); + else if (time_left >= 0) + event_request_timer(trigger_server_timeout, (void *) 0, time_left); + close(fd); +} + +#ifdef MASTER_XPORT_NAME_PASS + +/* trigger_server_accept_pass - accept descriptor */ + +static void trigger_server_accept_pass(int unused_event, void *context) +{ + const char *myname = "trigger_server_accept_pass"; + int listen_fd = CAST_ANY_PTR_TO_INT(context); + int time_left = 0; + int fd; + + if (msg_verbose) + msg_info("%s: trigger arrived", myname); + + /* + * Read a message from a socket. Be prepared for accept() to fail because + * some other process already got the connection. The socket is + * non-blocking so we won't get stuck when multiple processes wake up. + * Don't get stuck when the client connects but sends no data. Restart + * the idle timer if this was a false alarm. + */ + if (var_idle_limit > 0) + time_left = event_cancel_timer(trigger_server_timeout, (void *) 0); + + if (trigger_server_pre_accept) + trigger_server_pre_accept(trigger_server_name, trigger_server_argv); + fd = pass_accept(listen_fd); + if (trigger_server_lock != 0 + && myflock(vstream_fileno(trigger_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + if (fd < 0) { + if (errno != EAGAIN) + msg_error("accept connection: %m"); + if (time_left >= 0) + event_request_timer(trigger_server_timeout, (void *) 0, time_left); + return; + } + close_on_exec(fd, CLOSE_ON_EXEC); + if (read_wait(fd, 10) == 0) + trigger_server_wakeup(fd); + else if (time_left >= 0) + event_request_timer(trigger_server_timeout, (void *) 0, time_left); + close(fd); +} + +#endif + +/* trigger_server_main - the real main program */ + +NORETURN trigger_server_main(int argc, char **argv, TRIGGER_SERVER_FN service,...) +{ + const char *myname = "trigger_server_main"; + char *root_dir = 0; + char *user_name = 0; + int debug_me = 0; + int daemon_mode = 1; + char *service_name = basename(argv[0]); + VSTREAM *stream = 0; + int delay; + int c; + int socket_count = 1; + int fd; + va_list ap; + MAIL_SERVER_INIT_FN pre_init = 0; + MAIL_SERVER_INIT_FN post_init = 0; + MAIL_SERVER_LOOP_FN loop = 0; + int key; + char buf[TRIGGER_BUF_SIZE]; + ssize_t len; + char *transport = 0; + char *lock_path; + VSTRING *why; + int alone = 0; + int zerolimit = 0; + WATCHDOG *watchdog; + char *oname_val; + char *oname; + char *oval; + const char *err; + char *generation; + int msg_vstream_needed = 0; + const char *dsn_filter_title; + const char **dsn_filter_maps; + + /* + * Process environment options as early as we can. + */ + if (getenv(CONF_ENV_VERB)) + msg_verbose = 1; + if (getenv(CONF_ENV_DEBUG)) + debug_me = 1; + + /* + * Don't die when a process goes away unexpectedly. + */ + signal(SIGPIPE, SIG_IGN); + + /* + * Don't die for frivolous reasons. + */ +#ifdef SIGXFSZ + signal(SIGXFSZ, SIG_IGN); +#endif + + /* + * May need this every now and then. + */ + var_procname = mystrdup(basename(argv[0])); + set_mail_conf_str(VAR_PROCNAME, var_procname); + + /* + * Initialize logging and exit handler. Do the syslog first, so that its + * initialization completes before we enter the optional chroot jail. + */ + maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE); + if (msg_verbose) + msg_info("daemon started"); + + /* + * Check the Postfix library version as soon as we enable logging. + */ + MAIL_VERSION_CHECK; + + /* + * Initialize from the configuration file. Allow command-line options to + * override compiled-in defaults or configured parameter values. + */ + mail_conf_suck(); + + /* + * After database open error, continue execution with reduced + * functionality. + */ + dict_allow_surrogate = 1; + + /* + * Pick up policy settings from master process. Shut up error messages to + * stderr, because no-one is going to see them. + */ + opterr = 0; + while ((c = GETOPT(argc, argv, "cdDi:lm:n:o:s:St:uvVz")) > 0) { + switch (c) { + case 'c': + root_dir = "setme"; + break; + case 'd': + daemon_mode = 0; + break; + case 'D': + debug_me = 1; + break; + case 'i': + mail_conf_update(VAR_MAX_IDLE, optarg); + break; + case 'l': + alone = 1; + break; + case 'm': + mail_conf_update(VAR_MAX_USE, optarg); + break; + case 'n': + service_name = optarg; + break; + case 'o': + oname_val = mystrdup(optarg); + if ((err = split_nameval(oname_val, &oname, &oval)) != 0) + msg_fatal("invalid \"-o %s\" option value: %s", optarg, err); + mail_conf_update(oname, oval); + myfree(oname_val); + break; + case 's': + if ((socket_count = atoi(optarg)) <= 0) + msg_fatal("invalid socket_count: %s", optarg); + break; + case 'S': + stream = VSTREAM_IN; + break; + case 't': + transport = optarg; + break; + case 'u': + user_name = "setme"; + break; + case 'v': + msg_verbose++; + break; + case 'V': + if (++msg_vstream_needed == 1) + msg_vstream_init(mail_task(var_procname), VSTREAM_ERR); + break; + case 'z': + zerolimit = 1; + break; + default: + msg_fatal("invalid option: %c", optopt); + break; + } + } + set_mail_conf_str(VAR_SERVNAME, service_name); + + /* + * Initialize generic parameters and re-initialize logging in case of a + * non-default program name or logging destination. + */ + mail_params_init(); + maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE); + + /* + * Register higher-level dictionaries and initialize the support for + * dynamically-loaded dictionarles. + */ + mail_dict_init(); + + /* + * If not connected to stdin, stdin must not be a terminal. + */ + if (daemon_mode && stream == 0 && isatty(STDIN_FILENO)) { + msg_vstream_init(var_procname, VSTREAM_ERR); + msg_fatal("do not run this command by hand"); + } + + /* + * Application-specific initialization. + */ + va_start(ap, service); + while ((key = va_arg(ap, int)) != 0) { + switch (key) { + case MAIL_SERVER_INT_TABLE: + get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *)); + break; + case MAIL_SERVER_LONG_TABLE: + get_mail_conf_long_table(va_arg(ap, CONFIG_LONG_TABLE *)); + break; + case MAIL_SERVER_STR_TABLE: + get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *)); + break; + case MAIL_SERVER_BOOL_TABLE: + get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *)); + break; + case MAIL_SERVER_TIME_TABLE: + get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *)); + break; + case MAIL_SERVER_RAW_TABLE: + get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *)); + break; + case MAIL_SERVER_NINT_TABLE: + get_mail_conf_nint_table(va_arg(ap, CONFIG_NINT_TABLE *)); + break; + case MAIL_SERVER_NBOOL_TABLE: + get_mail_conf_nbool_table(va_arg(ap, CONFIG_NBOOL_TABLE *)); + break; + case MAIL_SERVER_PRE_INIT: + pre_init = va_arg(ap, MAIL_SERVER_INIT_FN); + break; + case MAIL_SERVER_POST_INIT: + post_init = va_arg(ap, MAIL_SERVER_INIT_FN); + break; + case MAIL_SERVER_LOOP: + loop = va_arg(ap, MAIL_SERVER_LOOP_FN); + break; + case MAIL_SERVER_EXIT: + trigger_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN); + break; + case MAIL_SERVER_PRE_ACCEPT: + trigger_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN); + break; + case MAIL_SERVER_IN_FLOW_DELAY: + trigger_server_in_flow_delay = 1; + break; + case MAIL_SERVER_SOLITARY: + if (stream == 0 && !alone) + msg_fatal("service %s requires a process limit of 1", + service_name); + break; + case MAIL_SERVER_UNLIMITED: + if (stream == 0 && !zerolimit) + msg_fatal("service %s requires a process limit of 0", + service_name); + break; + case MAIL_SERVER_PRIVILEGED: + if (user_name) + msg_fatal("service %s requires privileged operation", + service_name); + break; + case MAIL_SERVER_WATCHDOG: + trigger_server_watchdog = *va_arg(ap, int *); + break; + case MAIL_SERVER_BOUNCE_INIT: + dsn_filter_title = va_arg(ap, const char *); + dsn_filter_maps = va_arg(ap, const char **); + bounce_client_init(dsn_filter_title, *dsn_filter_maps); + break; + default: + msg_panic("%s: unknown argument type: %d", myname, key); + } + } + va_end(ap); + + if (root_dir) + root_dir = var_queue_dir; + if (user_name) + user_name = var_mail_owner; + + /* + * Can options be required? + * + * XXX Initially this code was implemented with UNIX-domain sockets, but + * Solaris <= 2.5 UNIX-domain sockets misbehave hopelessly when the + * client disconnects before the server has accepted the connection. + * Symptom: the server accept() fails with EPIPE or EPROTO, but the + * socket stays readable, so that the program goes into a wasteful loop. + * + * The initial fix was to use FIFOs, but those turn out to have their own + * problems, witness the workarounds in the fifo_listen() routine. + * Therefore we support both FIFOs and UNIX-domain sockets, so that the + * user can choose whatever works best. + * + * Well, I give up. Solaris UNIX-domain sockets still don't work properly, + * so it will have to limp along with a streams-specific alternative. + */ + if (stream == 0) { + if (transport == 0) + msg_fatal("no transport type specified"); + if (strcasecmp(transport, MASTER_XPORT_NAME_UNIX) == 0) + trigger_server_accept = trigger_server_accept_local; + else if (strcasecmp(transport, MASTER_XPORT_NAME_FIFO) == 0) + trigger_server_accept = trigger_server_accept_fifo; +#ifdef MASTER_XPORT_NAME_PASS + else if (strcasecmp(transport, MASTER_XPORT_NAME_PASS) == 0) + trigger_server_accept = trigger_server_accept_pass; +#endif + else + msg_fatal("unsupported transport type: %s", transport); + } + + /* + * Retrieve process generation from environment. + */ + if ((generation = getenv(MASTER_GEN_NAME)) != 0) { + if (!alldig(generation)) + msg_fatal("bad generation: %s", generation); + OCTAL_TO_UNSIGNED(trigger_server_generation, generation); + if (msg_verbose) + msg_info("process generation: %s (%o)", + generation, trigger_server_generation); + } + + /* + * Optionally start the debugger on ourself. + */ + if (debug_me) + debug_process(); + + /* + * Traditionally, BSD select() can't handle multiple processes selecting + * on the same socket, and wakes up every process in select(). See TCP/IP + * Illustrated volume 2 page 532. We avoid select() collisions with an + * external lock file. + */ + if (stream == 0 && !alone) { + lock_path = concatenate(DEF_PID_DIR, "/", transport, + ".", service_name, (char *) 0); + why = vstring_alloc(1); + if ((trigger_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600, + (struct stat *) 0, -1, -1, why)) == 0) + msg_fatal("open lock file %s: %s", lock_path, vstring_str(why)); + close_on_exec(vstream_fileno(trigger_server_lock), CLOSE_ON_EXEC); + myfree(lock_path); + vstring_free(why); + } + + /* + * Set up call-back info. + */ + trigger_server_service = service; + trigger_server_name = service_name; + trigger_server_argv = argv + optind; + + /* + * Run pre-jail initialization. + */ + if (chdir(var_queue_dir) < 0) + msg_fatal("chdir(\"%s\"): %m", var_queue_dir); + if (pre_init) + pre_init(trigger_server_name, trigger_server_argv); + + /* + * Optionally, restrict the damage that this process can do. + */ + resolve_local_init(); + tzset(); + chroot_uid(root_dir, user_name); + + /* + * Run post-jail initialization. + */ + if (post_init) + post_init(trigger_server_name, trigger_server_argv); + + /* + * Are we running as a one-shot server with the client connection on + * standard input? + */ + if (stream != 0) { + if ((len = read(vstream_fileno(stream), buf, sizeof(buf))) <= 0) + msg_fatal("read: %m"); + service(buf, len, trigger_server_name, trigger_server_argv); + vstream_fflush(stream); + trigger_server_exit(); + } + + /* + * Running as a semi-resident server. Service connection requests. + * Terminate when we have serviced a sufficient number of clients, when + * no-one has been talking to us for a configurable amount of time, or + * when the master process terminated abnormally. + */ + if (var_idle_limit > 0) + event_request_timer(trigger_server_timeout, (void *) 0, var_idle_limit); + for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) { + event_enable_read(fd, trigger_server_accept, CAST_INT_TO_VOID_PTR(fd)); + close_on_exec(fd, CLOSE_ON_EXEC); + } + event_enable_read(MASTER_STATUS_FD, trigger_server_abort, (void *) 0); + close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC); + close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC); + close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC); + watchdog = watchdog_create(trigger_server_watchdog, + (WATCHDOG_FN) 0, (void *) 0); + + /* + * The event loop, at last. + */ + while (var_use_limit == 0 || use_count < var_use_limit) { + if (trigger_server_lock != 0) { + watchdog_stop(watchdog); + if (myflock(vstream_fileno(trigger_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("select lock: %m"); + } + watchdog_start(watchdog); + delay = loop ? loop(trigger_server_name, trigger_server_argv) : -1; + event_loop(delay); + } + trigger_server_exit(); +} |