summaryrefslogtreecommitdiffstats
path: root/src/master
diff options
context:
space:
mode:
Diffstat (limited to 'src/master')
l---------src/master/.indent.pro1
-rw-r--r--src/master/.printfck25
-rw-r--r--src/master/Makefile.in477
-rw-r--r--src/master/dgram_server.c665
-rw-r--r--src/master/event_server.c968
-rw-r--r--src/master/mail_flow.c142
-rw-r--r--src/master/mail_flow.h32
-rw-r--r--src/master/mail_server.h155
-rw-r--r--src/master/master.c598
-rw-r--r--src/master/master.h246
-rw-r--r--src/master/master_avail.c251
-rw-r--r--src/master/master_conf.c152
-rw-r--r--src/master/master_ent.c648
-rw-r--r--src/master/master_flow.c33
-rw-r--r--src/master/master_listen.c186
-rw-r--r--src/master/master_monitor.c106
-rw-r--r--src/master/master_proto.c89
-rw-r--r--src/master/master_proto.h75
-rw-r--r--src/master/master_service.c113
-rw-r--r--src/master/master_sig.c275
-rw-r--r--src/master/master_spawn.c371
-rw-r--r--src/master/master_status.c198
-rw-r--r--src/master/master_vars.c98
-rw-r--r--src/master/master_wakeup.c192
-rw-r--r--src/master/master_watch.c151
-rw-r--r--src/master/multi_server.c931
-rw-r--r--src/master/single_server.c822
-rw-r--r--src/master/trigger_server.c809
28 files changed, 8809 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..db67a68
--- /dev/null
+++ b/src/master/Makefile.in
@@ -0,0 +1,477 @@
+SHELL = /bin/sh
+SRCS = master.c master_conf.c master_ent.c master_sig.c master_avail.c \
+ master_spawn.c master_service.c master_status.c master_listen.c \
+ master_proto.c single_server.c multi_server.c master_vars.c \
+ master_wakeup.c master_flow.c master_watch.c mail_flow.c \
+ master_monitor.c dgram_server.c
+OBJS = master.o master_conf.o master_ent.o master_sig.o master_avail.o \
+ master_spawn.o master_service.o master_status.o master_listen.o \
+ master_vars.o master_wakeup.o master_watch.o master_flow.o \
+ master_monitor.o
+LIB_OBJ = single_server.o multi_server.o trigger_server.o master_proto.o \
+ mail_flow.o event_server.o dgram_server.o
+HDRS = mail_server.h master_proto.h mail_flow.h
+INT_HDR = master.h
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+LIB = lib$(LIB_PREFIX)master$(LIB_SUFFIX)
+PROG = master
+TESTPROG=
+LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
+LIB_DIR = ../../lib
+INC_DIR = ../../include
+BIN_DIR = ../../libexec
+
+.c.o:; $(CC) `for i in $(LIB_OBJ); do if [ $$i = $@ ]; then echo $(SHLIB_CFLAGS); else true; fi; done` $(CFLAGS) -c $*.c
+
+all: $(PROG) $(LIB)
+
+$(OBJS) $(LIB_OBJ): ../../conf/makedefs.out
+
+Makefile: Makefile.in
+ cat ../../conf/makedefs.out $? >$@
+
+$(PROG): $(OBJS) $(LIBS)
+ $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS)
+
+test: $(TESTPROG)
+
+tests:
+
+root_tests:
+
+$(LIB): $(LIB_OBJ)
+ $(AR) $(ARFL) $(LIB) $?
+ $(RANLIB) $(LIB)
+ $(SHLIB_LD) $(SHLIB_RPATH) -o $(LIB) $(LIB_OBJ) $(SHLIB_SYSLIBS)
+
+$(LIB_DIR)/$(LIB): $(LIB)
+ cp $(LIB) $(LIB_DIR)/$(LIB)
+ $(RANLIB) $(LIB_DIR)/$(LIB)
+
+$(BIN_DIR)/$(PROG): $(PROG)
+ cp $(PROG) $(BIN_DIR)
+
+update: $(LIB_DIR)/$(LIB) $(BIN_DIR)/$(PROG) $(HDRS)
+ -for i in $(HDRS); \
+ do \
+ cmp -s $$i $(INC_DIR)/$$i 2>/dev/null || cp $$i $(INC_DIR); \
+ done
+ cd $(INC_DIR); chmod 644 $(HDRS)
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir printfck
+ cp *.h printfck
+ sed '1,/^# do not edit/!d' Makefile >printfck/Makefile
+ set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done
+ cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o`
+
+lint:
+ lint $(DEFS) $(SRCS) $(LINTFIX)
+
+clean:
+ rm -f *.o *core $(PROG) junk $(LIB)
+ rm -rf printfck
+
+tidy: clean
+
+depend: $(MAKES)
+ (sed '1,/^# do not edit/!d' Makefile.in; \
+ set -e; for i in [a-z][a-z0-9]*.c; do \
+ $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \
+ -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \
+ -e 's/o: \.\//o: /' -e p -e '}' ; \
+ done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in
+ @$(EXPORT) make -f Makefile.in Makefile 1>&2
+
+# do not edit below this line - it is generated by 'make depend'
+dgram_server.o: ../../include/argv.h
+dgram_server.o: ../../include/attr.h
+dgram_server.o: ../../include/bounce.h
+dgram_server.o: ../../include/check_arg.h
+dgram_server.o: ../../include/chroot_uid.h
+dgram_server.o: ../../include/debug_process.h
+dgram_server.o: ../../include/deliver_request.h
+dgram_server.o: ../../include/dict.h
+dgram_server.o: ../../include/dsn.h
+dgram_server.o: ../../include/dsn_buf.h
+dgram_server.o: ../../include/events.h
+dgram_server.o: ../../include/htable.h
+dgram_server.o: ../../include/iostuff.h
+dgram_server.o: ../../include/listen.h
+dgram_server.o: ../../include/mail_conf.h
+dgram_server.o: ../../include/mail_dict.h
+dgram_server.o: ../../include/mail_params.h
+dgram_server.o: ../../include/mail_task.h
+dgram_server.o: ../../include/mail_version.h
+dgram_server.o: ../../include/maillog_client.h
+dgram_server.o: ../../include/msg.h
+dgram_server.o: ../../include/msg_stats.h
+dgram_server.o: ../../include/msg_vstream.h
+dgram_server.o: ../../include/myflock.h
+dgram_server.o: ../../include/mymalloc.h
+dgram_server.o: ../../include/nvtable.h
+dgram_server.o: ../../include/recipient_list.h
+dgram_server.o: ../../include/resolve_local.h
+dgram_server.o: ../../include/safe_open.h
+dgram_server.o: ../../include/sane_accept.h
+dgram_server.o: ../../include/split_at.h
+dgram_server.o: ../../include/stringops.h
+dgram_server.o: ../../include/sys_defs.h
+dgram_server.o: ../../include/vbuf.h
+dgram_server.o: ../../include/vstream.h
+dgram_server.o: ../../include/vstring.h
+dgram_server.o: ../../include/watchdog.h
+dgram_server.o: dgram_server.c
+dgram_server.o: mail_flow.h
+dgram_server.o: mail_server.h
+dgram_server.o: master_proto.h
+event_server.o: ../../include/argv.h
+event_server.o: ../../include/attr.h
+event_server.o: ../../include/bounce.h
+event_server.o: ../../include/check_arg.h
+event_server.o: ../../include/chroot_uid.h
+event_server.o: ../../include/debug_process.h
+event_server.o: ../../include/deliver_request.h
+event_server.o: ../../include/dict.h
+event_server.o: ../../include/dsn.h
+event_server.o: ../../include/dsn_buf.h
+event_server.o: ../../include/events.h
+event_server.o: ../../include/htable.h
+event_server.o: ../../include/iostuff.h
+event_server.o: ../../include/listen.h
+event_server.o: ../../include/mail_conf.h
+event_server.o: ../../include/mail_dict.h
+event_server.o: ../../include/mail_params.h
+event_server.o: ../../include/mail_task.h
+event_server.o: ../../include/mail_version.h
+event_server.o: ../../include/maillog_client.h
+event_server.o: ../../include/msg.h
+event_server.o: ../../include/msg_stats.h
+event_server.o: ../../include/msg_vstream.h
+event_server.o: ../../include/myflock.h
+event_server.o: ../../include/mymalloc.h
+event_server.o: ../../include/nvtable.h
+event_server.o: ../../include/recipient_list.h
+event_server.o: ../../include/resolve_local.h
+event_server.o: ../../include/safe_open.h
+event_server.o: ../../include/sane_accept.h
+event_server.o: ../../include/split_at.h
+event_server.o: ../../include/stringops.h
+event_server.o: ../../include/sys_defs.h
+event_server.o: ../../include/timed_ipc.h
+event_server.o: ../../include/vbuf.h
+event_server.o: ../../include/vstream.h
+event_server.o: ../../include/vstring.h
+event_server.o: ../../include/watchdog.h
+event_server.o: event_server.c
+event_server.o: mail_flow.h
+event_server.o: mail_server.h
+event_server.o: master_proto.h
+mail_flow.o: ../../include/iostuff.h
+mail_flow.o: ../../include/msg.h
+mail_flow.o: ../../include/sys_defs.h
+mail_flow.o: ../../include/warn_stat.h
+mail_flow.o: mail_flow.c
+mail_flow.o: mail_flow.h
+mail_flow.o: master_proto.h
+master.o: ../../include/argv.h
+master.o: ../../include/check_arg.h
+master.o: ../../include/clean_env.h
+master.o: ../../include/debug_process.h
+master.o: ../../include/events.h
+master.o: ../../include/inet_proto.h
+master.o: ../../include/iostuff.h
+master.o: ../../include/mail_conf.h
+master.o: ../../include/mail_params.h
+master.o: ../../include/mail_parm_split.h
+master.o: ../../include/mail_task.h
+master.o: ../../include/mail_version.h
+master.o: ../../include/maillog_client.h
+master.o: ../../include/msg.h
+master.o: ../../include/myflock.h
+master.o: ../../include/mymalloc.h
+master.o: ../../include/open_lock.h
+master.o: ../../include/safe.h
+master.o: ../../include/set_eugid.h
+master.o: ../../include/set_ugid.h
+master.o: ../../include/stringops.h
+master.o: ../../include/sys_defs.h
+master.o: ../../include/vbuf.h
+master.o: ../../include/vstream.h
+master.o: ../../include/vstring.h
+master.o: ../../include/watchdog.h
+master.o: master.c
+master.o: master.h
+master_avail.o: ../../include/events.h
+master_avail.o: ../../include/msg.h
+master_avail.o: ../../include/sys_defs.h
+master_avail.o: master.h
+master_avail.o: master_avail.c
+master_avail.o: master_proto.h
+master_conf.o: ../../include/argv.h
+master_conf.o: ../../include/msg.h
+master_conf.o: ../../include/sys_defs.h
+master_conf.o: master.h
+master_conf.o: master_conf.c
+master_ent.o: ../../include/argv.h
+master_ent.o: ../../include/attr.h
+master_ent.o: ../../include/check_arg.h
+master_ent.o: ../../include/compat_level.h
+master_ent.o: ../../include/host_port.h
+master_ent.o: ../../include/htable.h
+master_ent.o: ../../include/inet_addr_host.h
+master_ent.o: ../../include/inet_addr_list.h
+master_ent.o: ../../include/inet_proto.h
+master_ent.o: ../../include/iostuff.h
+master_ent.o: ../../include/mail_conf.h
+master_ent.o: ../../include/mail_params.h
+master_ent.o: ../../include/mail_proto.h
+master_ent.o: ../../include/match_service.h
+master_ent.o: ../../include/msg.h
+master_ent.o: ../../include/myaddrinfo.h
+master_ent.o: ../../include/mymalloc.h
+master_ent.o: ../../include/nvtable.h
+master_ent.o: ../../include/own_inet_addr.h
+master_ent.o: ../../include/readlline.h
+master_ent.o: ../../include/sock_addr.h
+master_ent.o: ../../include/stringops.h
+master_ent.o: ../../include/sys_defs.h
+master_ent.o: ../../include/vbuf.h
+master_ent.o: ../../include/vstream.h
+master_ent.o: ../../include/vstring.h
+master_ent.o: ../../include/wildcard_inet_addr.h
+master_ent.o: master.h
+master_ent.o: master_ent.c
+master_ent.o: master_proto.h
+master_flow.o: ../../include/iostuff.h
+master_flow.o: ../../include/msg.h
+master_flow.o: ../../include/sys_defs.h
+master_flow.o: master.h
+master_flow.o: master_flow.c
+master_flow.o: master_proto.h
+master_listen.o: ../../include/check_arg.h
+master_listen.o: ../../include/htable.h
+master_listen.o: ../../include/inet_addr_list.h
+master_listen.o: ../../include/iostuff.h
+master_listen.o: ../../include/listen.h
+master_listen.o: ../../include/mail_params.h
+master_listen.o: ../../include/msg.h
+master_listen.o: ../../include/myaddrinfo.h
+master_listen.o: ../../include/mymalloc.h
+master_listen.o: ../../include/set_eugid.h
+master_listen.o: ../../include/set_ugid.h
+master_listen.o: ../../include/sock_addr.h
+master_listen.o: ../../include/stringops.h
+master_listen.o: ../../include/sys_defs.h
+master_listen.o: ../../include/vbuf.h
+master_listen.o: ../../include/vstring.h
+master_listen.o: master.h
+master_listen.o: master_listen.c
+master_monitor.o: ../../include/iostuff.h
+master_monitor.o: ../../include/msg.h
+master_monitor.o: ../../include/sys_defs.h
+master_monitor.o: master.h
+master_monitor.o: master_monitor.c
+master_proto.o: ../../include/msg.h
+master_proto.o: ../../include/sys_defs.h
+master_proto.o: master_proto.c
+master_proto.o: master_proto.h
+master_service.o: ../../include/msg.h
+master_service.o: ../../include/mymalloc.h
+master_service.o: ../../include/sys_defs.h
+master_service.o: master.h
+master_service.o: master_service.c
+master_sig.o: ../../include/events.h
+master_sig.o: ../../include/iostuff.h
+master_sig.o: ../../include/killme_after.h
+master_sig.o: ../../include/msg.h
+master_sig.o: ../../include/posix_signals.h
+master_sig.o: ../../include/sys_defs.h
+master_sig.o: master.h
+master_sig.o: master_sig.c
+master_spawn.o: ../../include/argv.h
+master_spawn.o: ../../include/binhash.h
+master_spawn.o: ../../include/check_arg.h
+master_spawn.o: ../../include/events.h
+master_spawn.o: ../../include/mail_conf.h
+master_spawn.o: ../../include/msg.h
+master_spawn.o: ../../include/mymalloc.h
+master_spawn.o: ../../include/sys_defs.h
+master_spawn.o: ../../include/vbuf.h
+master_spawn.o: ../../include/vstring.h
+master_spawn.o: master.h
+master_spawn.o: master_proto.h
+master_spawn.o: master_spawn.c
+master_status.o: ../../include/binhash.h
+master_status.o: ../../include/events.h
+master_status.o: ../../include/iostuff.h
+master_status.o: ../../include/msg.h
+master_status.o: ../../include/sys_defs.h
+master_status.o: master.h
+master_status.o: master_proto.h
+master_status.o: master_status.c
+master_vars.o: ../../include/check_arg.h
+master_vars.o: ../../include/mail_conf.h
+master_vars.o: ../../include/mail_params.h
+master_vars.o: ../../include/msg.h
+master_vars.o: ../../include/mymalloc.h
+master_vars.o: ../../include/stringops.h
+master_vars.o: ../../include/sys_defs.h
+master_vars.o: ../../include/vbuf.h
+master_vars.o: ../../include/vstring.h
+master_vars.o: master.h
+master_vars.o: master_vars.c
+master_wakeup.o: ../../include/attr.h
+master_wakeup.o: ../../include/check_arg.h
+master_wakeup.o: ../../include/events.h
+master_wakeup.o: ../../include/htable.h
+master_wakeup.o: ../../include/iostuff.h
+master_wakeup.o: ../../include/mail_conf.h
+master_wakeup.o: ../../include/mail_params.h
+master_wakeup.o: ../../include/mail_proto.h
+master_wakeup.o: ../../include/msg.h
+master_wakeup.o: ../../include/mymalloc.h
+master_wakeup.o: ../../include/nvtable.h
+master_wakeup.o: ../../include/set_eugid.h
+master_wakeup.o: ../../include/set_ugid.h
+master_wakeup.o: ../../include/sys_defs.h
+master_wakeup.o: ../../include/trigger.h
+master_wakeup.o: ../../include/vbuf.h
+master_wakeup.o: ../../include/vstream.h
+master_wakeup.o: ../../include/vstring.h
+master_wakeup.o: mail_server.h
+master_wakeup.o: master.h
+master_wakeup.o: master_wakeup.c
+master_watch.o: ../../include/msg.h
+master_watch.o: ../../include/mymalloc.h
+master_watch.o: ../../include/sys_defs.h
+master_watch.o: master.h
+master_watch.o: master_watch.c
+multi_server.o: ../../include/argv.h
+multi_server.o: ../../include/attr.h
+multi_server.o: ../../include/bounce.h
+multi_server.o: ../../include/check_arg.h
+multi_server.o: ../../include/chroot_uid.h
+multi_server.o: ../../include/debug_process.h
+multi_server.o: ../../include/deliver_request.h
+multi_server.o: ../../include/dict.h
+multi_server.o: ../../include/dsn.h
+multi_server.o: ../../include/dsn_buf.h
+multi_server.o: ../../include/events.h
+multi_server.o: ../../include/htable.h
+multi_server.o: ../../include/iostuff.h
+multi_server.o: ../../include/listen.h
+multi_server.o: ../../include/mail_conf.h
+multi_server.o: ../../include/mail_dict.h
+multi_server.o: ../../include/mail_params.h
+multi_server.o: ../../include/mail_task.h
+multi_server.o: ../../include/mail_version.h
+multi_server.o: ../../include/maillog_client.h
+multi_server.o: ../../include/msg.h
+multi_server.o: ../../include/msg_stats.h
+multi_server.o: ../../include/msg_vstream.h
+multi_server.o: ../../include/myflock.h
+multi_server.o: ../../include/mymalloc.h
+multi_server.o: ../../include/nvtable.h
+multi_server.o: ../../include/recipient_list.h
+multi_server.o: ../../include/resolve_local.h
+multi_server.o: ../../include/safe_open.h
+multi_server.o: ../../include/sane_accept.h
+multi_server.o: ../../include/split_at.h
+multi_server.o: ../../include/stringops.h
+multi_server.o: ../../include/sys_defs.h
+multi_server.o: ../../include/timed_ipc.h
+multi_server.o: ../../include/vbuf.h
+multi_server.o: ../../include/vstream.h
+multi_server.o: ../../include/vstring.h
+multi_server.o: ../../include/watchdog.h
+multi_server.o: mail_flow.h
+multi_server.o: mail_server.h
+multi_server.o: master_proto.h
+multi_server.o: multi_server.c
+single_server.o: ../../include/argv.h
+single_server.o: ../../include/attr.h
+single_server.o: ../../include/bounce.h
+single_server.o: ../../include/check_arg.h
+single_server.o: ../../include/chroot_uid.h
+single_server.o: ../../include/debug_process.h
+single_server.o: ../../include/deliver_request.h
+single_server.o: ../../include/dict.h
+single_server.o: ../../include/dsn.h
+single_server.o: ../../include/dsn_buf.h
+single_server.o: ../../include/events.h
+single_server.o: ../../include/htable.h
+single_server.o: ../../include/iostuff.h
+single_server.o: ../../include/listen.h
+single_server.o: ../../include/mail_conf.h
+single_server.o: ../../include/mail_dict.h
+single_server.o: ../../include/mail_params.h
+single_server.o: ../../include/mail_task.h
+single_server.o: ../../include/mail_version.h
+single_server.o: ../../include/maillog_client.h
+single_server.o: ../../include/msg.h
+single_server.o: ../../include/msg_stats.h
+single_server.o: ../../include/msg_vstream.h
+single_server.o: ../../include/myflock.h
+single_server.o: ../../include/mymalloc.h
+single_server.o: ../../include/nvtable.h
+single_server.o: ../../include/recipient_list.h
+single_server.o: ../../include/resolve_local.h
+single_server.o: ../../include/safe_open.h
+single_server.o: ../../include/sane_accept.h
+single_server.o: ../../include/split_at.h
+single_server.o: ../../include/stringops.h
+single_server.o: ../../include/sys_defs.h
+single_server.o: ../../include/timed_ipc.h
+single_server.o: ../../include/vbuf.h
+single_server.o: ../../include/vstream.h
+single_server.o: ../../include/vstring.h
+single_server.o: ../../include/watchdog.h
+single_server.o: mail_flow.h
+single_server.o: mail_server.h
+single_server.o: master_proto.h
+single_server.o: single_server.c
+trigger_server.o: ../../include/argv.h
+trigger_server.o: ../../include/attr.h
+trigger_server.o: ../../include/bounce.h
+trigger_server.o: ../../include/check_arg.h
+trigger_server.o: ../../include/chroot_uid.h
+trigger_server.o: ../../include/debug_process.h
+trigger_server.o: ../../include/deliver_request.h
+trigger_server.o: ../../include/dict.h
+trigger_server.o: ../../include/dsn.h
+trigger_server.o: ../../include/dsn_buf.h
+trigger_server.o: ../../include/events.h
+trigger_server.o: ../../include/htable.h
+trigger_server.o: ../../include/iostuff.h
+trigger_server.o: ../../include/listen.h
+trigger_server.o: ../../include/mail_conf.h
+trigger_server.o: ../../include/mail_dict.h
+trigger_server.o: ../../include/mail_params.h
+trigger_server.o: ../../include/mail_task.h
+trigger_server.o: ../../include/mail_version.h
+trigger_server.o: ../../include/maillog_client.h
+trigger_server.o: ../../include/msg.h
+trigger_server.o: ../../include/msg_stats.h
+trigger_server.o: ../../include/msg_vstream.h
+trigger_server.o: ../../include/myflock.h
+trigger_server.o: ../../include/mymalloc.h
+trigger_server.o: ../../include/nvtable.h
+trigger_server.o: ../../include/recipient_list.h
+trigger_server.o: ../../include/resolve_local.h
+trigger_server.o: ../../include/safe_open.h
+trigger_server.o: ../../include/sane_accept.h
+trigger_server.o: ../../include/split_at.h
+trigger_server.o: ../../include/stringops.h
+trigger_server.o: ../../include/sys_defs.h
+trigger_server.o: ../../include/vbuf.h
+trigger_server.o: ../../include/vstream.h
+trigger_server.o: ../../include/vstring.h
+trigger_server.o: ../../include/watchdog.h
+trigger_server.o: mail_flow.h
+trigger_server.o: mail_server.h
+trigger_server.o: master_proto.h
+trigger_server.o: trigger_server.c
diff --git a/src/master/dgram_server.c b/src/master/dgram_server.c
new file mode 100644
index 0000000..e49500e
--- /dev/null
+++ b/src/master/dgram_server.c
@@ -0,0 +1,665 @@
+/*++
+/* NAME
+/* dgram_server 3
+/* SUMMARY
+/* skeleton datagram server subsystem
+/* SYNOPSIS
+/* #include <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 dictionaries.
+ */
+ mail_dict_init();
+
+ /*
+ * If not connected to stdin, stdin must not be a terminal.
+ */
+ if (daemon_mode && isatty(STDIN_FILENO)) {
+ msg_vstream_init(var_procname, VSTREAM_ERR);
+ msg_fatal("do not run this command by hand");
+ }
+
+ /*
+ * Application-specific initialization.
+ */
+ va_start(ap, service);
+ while ((key = va_arg(ap, int)) != 0) {
+ switch (key) {
+ case MAIL_SERVER_INT_TABLE:
+ get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *));
+ break;
+ case MAIL_SERVER_LONG_TABLE:
+ get_mail_conf_long_table(va_arg(ap, CONFIG_LONG_TABLE *));
+ break;
+ case MAIL_SERVER_STR_TABLE:
+ get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *));
+ break;
+ case MAIL_SERVER_BOOL_TABLE:
+ get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *));
+ break;
+ case MAIL_SERVER_TIME_TABLE:
+ get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *));
+ break;
+ case MAIL_SERVER_RAW_TABLE:
+ get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *));
+ break;
+ case MAIL_SERVER_NINT_TABLE:
+ get_mail_conf_nint_table(va_arg(ap, CONFIG_NINT_TABLE *));
+ break;
+ case MAIL_SERVER_NBOOL_TABLE:
+ get_mail_conf_nbool_table(va_arg(ap, CONFIG_NBOOL_TABLE *));
+ break;
+ case MAIL_SERVER_PRE_INIT:
+ pre_init = va_arg(ap, MAIL_SERVER_INIT_FN);
+ break;
+ case MAIL_SERVER_POST_INIT:
+ post_init = va_arg(ap, MAIL_SERVER_INIT_FN);
+ break;
+ case MAIL_SERVER_LOOP:
+ loop = va_arg(ap, MAIL_SERVER_LOOP_FN);
+ break;
+ case MAIL_SERVER_EXIT:
+ dgram_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN);
+ break;
+ case MAIL_SERVER_PRE_ACCEPT:
+ dgram_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN);
+ break;
+ case MAIL_SERVER_IN_FLOW_DELAY:
+ dgram_server_in_flow_delay = 1;
+ break;
+ case MAIL_SERVER_SOLITARY:
+ if (!alone)
+ msg_fatal("service %s requires a process limit of 1",
+ service_name);
+ break;
+ case MAIL_SERVER_UNLIMITED:
+ if (!zerolimit)
+ msg_fatal("service %s requires a process limit of 0",
+ service_name);
+ break;
+ case MAIL_SERVER_PRIVILEGED:
+ if (user_name)
+ msg_fatal("service %s requires privileged operation",
+ service_name);
+ break;
+ case MAIL_SERVER_WATCHDOG:
+ dgram_server_watchdog = *va_arg(ap, int *);
+ break;
+ case MAIL_SERVER_BOUNCE_INIT:
+ dsn_filter_title = va_arg(ap, const char *);
+ dsn_filter_maps = va_arg(ap, const char **);
+ bounce_client_init(dsn_filter_title, *dsn_filter_maps);
+ break;
+ default:
+ msg_panic("%s: unknown argument type: %d", myname, key);
+ }
+ }
+ va_end(ap);
+
+ if (root_dir)
+ root_dir = var_queue_dir;
+ if (user_name)
+ user_name = var_mail_owner;
+
+ /*
+ * Can options be required?
+ */
+ if (transport == 0)
+ msg_fatal("no transport type specified");
+ else if (strcasecmp(transport, MASTER_XPORT_NAME_UXDG) == 0)
+ dgram_server_accept = dgram_server_accept_unix;
+ else
+ msg_fatal("unsupported transport type: %s", transport);
+
+ /*
+ * Retrieve process generation from environment.
+ */
+ if ((generation = getenv(MASTER_GEN_NAME)) != 0) {
+ if (!alldig(generation))
+ msg_fatal("bad generation: %s", generation);
+ OCTAL_TO_UNSIGNED(dgram_server_generation, generation);
+ if (msg_verbose)
+ msg_info("process generation: %s (%o)",
+ generation, dgram_server_generation);
+ }
+
+ /*
+ * Optionally start the debugger on ourself.
+ */
+ if (debug_me)
+ debug_process();
+
+ /*
+ * Traditionally, BSD select() can't handle multiple processes selecting
+ * on the same socket, and wakes up every process in select(). See TCP/IP
+ * Illustrated volume 2 page 532. We avoid select() collisions with an
+ * external lock file.
+ */
+ if (!alone) {
+ lock_path = concatenate(DEF_PID_DIR, "/", transport,
+ ".", service_name, (char *) 0);
+ why = vstring_alloc(1);
+ if ((dgram_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600,
+ (struct stat *) 0, -1, -1, why)) == 0)
+ msg_fatal("open lock file %s: %s", lock_path, vstring_str(why));
+ close_on_exec(vstream_fileno(dgram_server_lock), CLOSE_ON_EXEC);
+ myfree(lock_path);
+ vstring_free(why);
+ }
+
+ /*
+ * Set up call-back info.
+ */
+ dgram_server_service = service;
+ dgram_server_name = service_name;
+ dgram_server_argv = argv + optind;
+
+ /*
+ * Run pre-jail initialization.
+ */
+ if (chdir(var_queue_dir) < 0)
+ msg_fatal("chdir(\"%s\"): %m", var_queue_dir);
+ if (pre_init)
+ pre_init(dgram_server_name, dgram_server_argv);
+
+ /*
+ * Optionally, restrict the damage that this process can do.
+ */
+ resolve_local_init();
+ tzset();
+ chroot_uid(root_dir, user_name);
+
+ /*
+ * Run post-jail initialization.
+ */
+ if (post_init)
+ post_init(dgram_server_name, dgram_server_argv);
+
+ /*
+ * Running as a semi-resident server. Service requests. Terminate when we
+ * have serviced a sufficient number of requests, when no-one has been
+ * talking to us for a configurable amount of time, or when the master
+ * process terminated abnormally.
+ */
+ if (var_idle_limit > 0)
+ event_request_timer(dgram_server_timeout, (void *) 0, var_idle_limit);
+ for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) {
+ event_enable_read(fd, dgram_server_accept, CAST_INT_TO_VOID_PTR(fd));
+ close_on_exec(fd, CLOSE_ON_EXEC);
+ }
+ event_enable_read(MASTER_STATUS_FD, dgram_server_abort, (void *) 0);
+ close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC);
+ close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC);
+ close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC);
+ watchdog = watchdog_create(dgram_server_watchdog,
+ (WATCHDOG_FN) 0, (void *) 0);
+
+ /*
+ * The event loop, at last.
+ */
+ while (var_use_limit == 0 || use_count < var_use_limit) {
+ if (dgram_server_lock != 0) {
+ watchdog_stop(watchdog);
+ if (myflock(vstream_fileno(dgram_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("select lock: %m");
+ }
+ watchdog_start(watchdog);
+ delay = loop ? loop(dgram_server_name, dgram_server_argv) : -1;
+ event_loop(delay);
+ }
+ dgram_server_exit();
+}
diff --git a/src/master/event_server.c b/src/master/event_server.c
new file mode 100644
index 0000000..9802bdf
--- /dev/null
+++ b/src/master/event_server.c
@@ -0,0 +1,968 @@
+/*++
+/* NAME
+/* event_server 3
+/* SUMMARY
+/* skeleton multi-threaded mail subsystem
+/* SYNOPSIS
+/* #include <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;
+
+/* event_server_exit - normal termination */
+
+static NORETURN event_server_exit(void)
+{
+ if (event_server_onexit)
+ event_server_onexit(event_server_name, event_server_argv);
+ exit(0);
+}
+
+/* event_server_retire - retire when idle */
+
+static void event_server_retire(int unused_event, void *unused_context)
+{
+ if (msg_verbose)
+ msg_info("time to retire -- %s", event_server_slow_exit ?
+ "draining" : "exiting");
+ event_disable_readwrite(MASTER_STATUS_FD);
+ if (event_server_slow_exit)
+ event_server_slow_exit(event_server_name, event_server_argv);
+ else
+ event_server_exit();
+}
+
+/* event_server_abort - terminate after abnormal master exit */
+
+static void event_server_abort(int unused_event, void *unused_context)
+{
+ if (msg_verbose)
+ msg_info("master disconnect -- %s", event_server_slow_exit ?
+ "draining" : "exiting");
+ event_disable_readwrite(MASTER_STATUS_FD);
+ if (event_server_slow_exit)
+ event_server_slow_exit(event_server_name, event_server_argv);
+ else
+ event_server_exit();
+}
+
+/* event_server_timeout - idle time exceeded */
+
+static void event_server_timeout(int unused_event, void *unused_context)
+{
+ if (msg_verbose)
+ msg_info("idle timeout -- exiting");
+ event_server_exit();
+}
+
+/* event_server_drain - stop accepting new clients */
+
+int event_server_drain(void)
+{
+ const char *myname = "event_server_drain";
+ int fd;
+
+ switch (fork()) {
+ /* Try again later. */
+ case -1:
+ return (-1);
+ /* Finish existing clients in the background, then terminate. */
+ case 0:
+ (void) msg_cleanup((MSG_CLEANUP_FN) 0);
+ event_fork();
+ for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) {
+ event_disable_readwrite(fd);
+ (void) close(fd);
+ /* Play safe - don't reuse this file number. */
+ if (DUP2(STDIN_FILENO, fd) < 0)
+ msg_warn("%s: dup2(%d, %d): %m", myname, STDIN_FILENO, fd);
+ }
+ var_use_limit = 1;
+ return (0);
+ /* Let the master start a new process. */
+ default:
+ exit(0);
+ }
+}
+
+/* event_server_disconnect - terminate client session */
+
+void event_server_disconnect(VSTREAM *stream)
+{
+ if (msg_verbose)
+ msg_info("connection closed fd %d", vstream_fileno(stream));
+ if (event_server_pre_disconn)
+ event_server_pre_disconn(stream, event_server_name, event_server_argv);
+ (void) vstream_fclose(stream);
+ client_count--;
+ /* Avoid integer wrap-around in a persistent process. */
+ if (use_count < INT_MAX)
+ use_count++;
+ if (client_count == 0 && var_idle_limit > 0)
+ event_request_timer(event_server_timeout, (void *) 0, var_idle_limit);
+}
+
+/* event_server_execute - in case (char *) != (struct *) */
+
+static void event_server_execute(int unused_event, void *context)
+{
+ VSTREAM *stream = (VSTREAM *) context;
+ HTABLE *attr = (HTABLE *) vstream_context(stream);
+
+ if (event_server_lock != 0
+ && myflock(vstream_fileno(event_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_NONE) < 0)
+ msg_fatal("select unlock: %m");
+
+ /*
+ * Do bother the application when the client disconnected. Don't drop the
+ * already accepted client request after "postfix reload"; that would be
+ * rude.
+ */
+ if (master_notify(var_pid, event_server_generation, MASTER_STAT_TAKEN) < 0)
+ /* void */ ;
+ event_server_service(stream, event_server_name, event_server_argv);
+ if (master_notify(var_pid, event_server_generation, MASTER_STAT_AVAIL) < 0)
+ event_server_abort(EVENT_NULL_TYPE, EVENT_NULL_CONTEXT);
+ if (attr)
+ htable_free(attr, myfree);
+}
+
+/* event_server_wakeup - wake up application */
+
+static void event_server_wakeup(int fd, HTABLE *attr)
+{
+ VSTREAM *stream;
+ char *tmp;
+
+#if defined(F_DUPFD) && (EVENTS_STYLE != EVENTS_STYLE_SELECT)
+#ifndef THRESHOLD_FD_WORKAROUND
+#define THRESHOLD_FD_WORKAROUND 128
+#endif
+ int new_fd;
+
+ /*
+ * Leave some handles < FD_SETSIZE for DBMS libraries, in the unlikely
+ * case of a multi-server with a thousand clients.
+ */
+ if (fd < THRESHOLD_FD_WORKAROUND) {
+ if ((new_fd = fcntl(fd, F_DUPFD, THRESHOLD_FD_WORKAROUND)) < 0)
+ msg_fatal("fcntl F_DUPFD: %m");
+ (void) close(fd);
+ fd = new_fd;
+ }
+#endif
+ if (msg_verbose)
+ msg_info("connection established fd %d", fd);
+ non_blocking(fd, BLOCKING);
+ close_on_exec(fd, CLOSE_ON_EXEC);
+ client_count++;
+ stream = vstream_fdopen(fd, O_RDWR);
+ tmp = concatenate(event_server_name, " socket", (char *) 0);
+ vstream_control(stream,
+ CA_VSTREAM_CTL_PATH(tmp),
+ CA_VSTREAM_CTL_CONTEXT((void *) attr),
+ CA_VSTREAM_CTL_END);
+ myfree(tmp);
+ timed_ipc_setup(stream);
+ if (event_server_in_flow_delay && mail_flow_get(1) < 0)
+ event_request_timer(event_server_execute, (void *) stream,
+ var_in_flow_delay);
+ else
+ event_server_execute(0, (void *) stream);
+}
+
+/* event_server_accept_local - accept client connection request */
+
+static void event_server_accept_local(int unused_event, void *context)
+{
+ int listen_fd = CAST_ANY_PTR_TO_INT(context);
+ int time_left = -1;
+ int fd;
+
+ /*
+ * Be prepared for accept() to fail because some other process already
+ * got the connection (the number of processes competing for clients is
+ * kept small, so this is not a "thundering herd" problem). If the
+ * accept() succeeds, be sure to disable non-blocking I/O, in order to
+ * minimize confusion.
+ */
+ if (client_count == 0 && var_idle_limit > 0)
+ time_left = event_cancel_timer(event_server_timeout, (void *) 0);
+
+ if (event_server_pre_accept)
+ event_server_pre_accept(event_server_name, event_server_argv);
+ fd = LOCAL_ACCEPT(listen_fd);
+ if (event_server_lock != 0
+ && myflock(vstream_fileno(event_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_NONE) < 0)
+ msg_fatal("select unlock: %m");
+ if (fd < 0) {
+ if (errno != EAGAIN)
+ msg_error("accept connection: %m");
+ if (time_left >= 0)
+ event_request_timer(event_server_timeout, (void *) 0, time_left);
+ return;
+ }
+ event_server_wakeup(fd, (HTABLE *) 0);
+}
+
+#ifdef MASTER_XPORT_NAME_PASS
+
+/* event_server_accept_pass - accept descriptor */
+
+static void event_server_accept_pass(int unused_event, void *context)
+{
+ int listen_fd = CAST_ANY_PTR_TO_INT(context);
+ int time_left = -1;
+ int fd;
+ HTABLE *attr = 0;
+
+ /*
+ * Be prepared for accept() to fail because some other process already
+ * got the connection (the number of processes competing for clients is
+ * kept small, so this is not a "thundering herd" problem). If the
+ * accept() succeeds, be sure to disable non-blocking I/O, in order to
+ * minimize confusion.
+ */
+ if (client_count == 0 && var_idle_limit > 0)
+ time_left = event_cancel_timer(event_server_timeout, (void *) 0);
+
+ if (event_server_pre_accept)
+ event_server_pre_accept(event_server_name, event_server_argv);
+ fd = pass_accept_attr(listen_fd, &attr);
+ if (event_server_lock != 0
+ && myflock(vstream_fileno(event_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_NONE) < 0)
+ msg_fatal("select unlock: %m");
+ if (fd < 0) {
+ if (errno != EAGAIN)
+ msg_error("accept connection: %m");
+ if (time_left >= 0)
+ event_request_timer(event_server_timeout, (void *) 0, time_left);
+ return;
+ }
+ event_server_wakeup(fd, attr);
+}
+
+#endif
+
+/* event_server_accept_inet - accept client connection request */
+
+static void event_server_accept_inet(int unused_event, void *context)
+{
+ int listen_fd = CAST_ANY_PTR_TO_INT(context);
+ int time_left = -1;
+ int fd;
+
+ /*
+ * Be prepared for accept() to fail because some other process already
+ * got the connection (the number of processes competing for clients is
+ * kept small, so this is not a "thundering herd" problem). If the
+ * accept() succeeds, be sure to disable non-blocking I/O, in order to
+ * minimize confusion.
+ */
+ if (client_count == 0 && var_idle_limit > 0)
+ time_left = event_cancel_timer(event_server_timeout, (void *) 0);
+
+ if (event_server_pre_accept)
+ event_server_pre_accept(event_server_name, event_server_argv);
+ fd = inet_accept(listen_fd);
+ if (event_server_lock != 0
+ && myflock(vstream_fileno(event_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_NONE) < 0)
+ msg_fatal("select unlock: %m");
+ if (fd < 0) {
+ if (errno != EAGAIN)
+ msg_error("accept connection: %m");
+ if (time_left >= 0)
+ event_request_timer(event_server_timeout, (void *) 0, time_left);
+ return;
+ }
+ event_server_wakeup(fd, (HTABLE *) 0);
+}
+
+/* event_server_main - the real main program */
+
+NORETURN event_server_main(int argc, char **argv, MULTI_SERVER_FN service,...)
+{
+ const char *myname = "event_server_main";
+ VSTREAM *stream = 0;
+ char *root_dir = 0;
+ char *user_name = 0;
+ int debug_me = 0;
+ int daemon_mode = 1;
+ char *service_name = basename(argv[0]);
+ int delay;
+ int c;
+ int fd;
+ va_list ap;
+ MAIL_SERVER_INIT_FN pre_init = 0;
+ MAIL_SERVER_INIT_FN post_init = 0;
+ MAIL_SERVER_LOOP_FN loop = 0;
+ int key;
+ char *transport = 0;
+
+#if 0
+ char *lock_path;
+ VSTRING *why;
+
+#endif
+ int alone = 0;
+ int zerolimit = 0;
+ WATCHDOG *watchdog;
+ char *oname_val;
+ char *oname;
+ char *oval;
+ const char *err;
+ char *generation;
+ int msg_vstream_needed = 0;
+ const char *dsn_filter_title;
+ const char **dsn_filter_maps;
+ int retire_me_from_flags = 0;
+ int retire_me = 0;
+
+ /*
+ * Process environment options as early as we can.
+ */
+ if (getenv(CONF_ENV_VERB))
+ msg_verbose = 1;
+ if (getenv(CONF_ENV_DEBUG))
+ debug_me = 1;
+
+ /*
+ * Don't die when a process goes away unexpectedly.
+ */
+ signal(SIGPIPE, SIG_IGN);
+
+ /*
+ * Don't die for frivolous reasons.
+ */
+#ifdef SIGXFSZ
+ signal(SIGXFSZ, SIG_IGN);
+#endif
+
+ /*
+ * May need this every now and then.
+ */
+ var_procname = mystrdup(basename(argv[0]));
+ set_mail_conf_str(VAR_PROCNAME, var_procname);
+
+ /*
+ * Initialize logging and exit handler. Do the syslog first, so that its
+ * initialization completes before we enter the optional chroot jail.
+ */
+ maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE);
+ if (msg_verbose)
+ msg_info("daemon started");
+
+ /*
+ * Check the Postfix library version as soon as we enable logging.
+ */
+ MAIL_VERSION_CHECK;
+
+ /*
+ * Initialize from the configuration file. Allow command-line options to
+ * override compiled-in defaults or configured parameter values.
+ */
+ mail_conf_suck();
+
+ /*
+ * After database open error, continue execution with reduced
+ * functionality.
+ */
+ dict_allow_surrogate = 1;
+
+ /*
+ * Pick up policy settings from master process. Shut up error messages to
+ * stderr, because no-one is going to see them.
+ */
+ opterr = 0;
+ while ((c = GETOPT(argc, argv, "cdDi:lm:n:o:r:s:St:uvVz")) > 0) {
+ switch (c) {
+ case 'c':
+ root_dir = "setme";
+ break;
+ case 'd':
+ daemon_mode = 0;
+ break;
+ case 'D':
+ debug_me = 1;
+ break;
+ case 'i':
+ mail_conf_update(VAR_MAX_IDLE, optarg);
+ break;
+ case 'l':
+ alone = 1;
+ break;
+ case 'm':
+ mail_conf_update(VAR_MAX_USE, optarg);
+ break;
+ case 'n':
+ service_name = optarg;
+ break;
+ case 'o':
+ oname_val = mystrdup(optarg);
+ if ((err = split_nameval(oname_val, &oname, &oval)) != 0)
+ msg_fatal("invalid \"-o %s\" option value: %s", optarg, err);
+ mail_conf_update(oname, oval);
+ myfree(oname_val);
+ break;
+ case 'r':
+ if ((retire_me_from_flags = atoi(optarg)) <= 0)
+ msg_fatal("invalid retirement time: %s", optarg);
+ break;
+ case 's':
+ if ((socket_count = atoi(optarg)) <= 0)
+ msg_fatal("invalid socket_count: %s", optarg);
+ break;
+ case 'S':
+ stream = VSTREAM_IN;
+ break;
+ case 'u':
+ user_name = "setme";
+ break;
+ case 't':
+ transport = optarg;
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ case 'V':
+ if (++msg_vstream_needed == 1)
+ msg_vstream_init(mail_task(var_procname), VSTREAM_ERR);
+ break;
+ case 'z':
+ zerolimit = 1;
+ break;
+ default:
+ msg_fatal("invalid option: %c", optopt);
+ break;
+ }
+ }
+ set_mail_conf_str(VAR_SERVNAME, service_name);
+
+ /*
+ * Initialize generic parameters and re-initialize logging in case of a
+ * non-default program name or logging destination.
+ */
+ mail_params_init();
+ maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE);
+
+ /*
+ * Register higher-level dictionaries and initialize the support for
+ * dynamically-loaded dictionaries.
+ */
+ mail_dict_init();
+
+ /*
+ * If not connected to stdin, stdin must not be a terminal.
+ */
+ if (daemon_mode && stream == 0 && isatty(STDIN_FILENO)) {
+ msg_vstream_init(var_procname, VSTREAM_ERR);
+ msg_fatal("do not run this command by hand");
+ }
+
+ /*
+ * Application-specific initialization.
+ */
+ va_start(ap, service);
+ while ((key = va_arg(ap, int)) != 0) {
+ switch (key) {
+ case MAIL_SERVER_INT_TABLE:
+ get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *));
+ break;
+ case MAIL_SERVER_LONG_TABLE:
+ get_mail_conf_long_table(va_arg(ap, CONFIG_LONG_TABLE *));
+ break;
+ case MAIL_SERVER_STR_TABLE:
+ get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *));
+ break;
+ case MAIL_SERVER_BOOL_TABLE:
+ get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *));
+ break;
+ case MAIL_SERVER_TIME_TABLE:
+ get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *));
+ break;
+ case MAIL_SERVER_RAW_TABLE:
+ get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *));
+ break;
+ case MAIL_SERVER_NINT_TABLE:
+ get_mail_conf_nint_table(va_arg(ap, CONFIG_NINT_TABLE *));
+ break;
+ case MAIL_SERVER_NBOOL_TABLE:
+ get_mail_conf_nbool_table(va_arg(ap, CONFIG_NBOOL_TABLE *));
+ break;
+ case MAIL_SERVER_PRE_INIT:
+ pre_init = va_arg(ap, MAIL_SERVER_INIT_FN);
+ break;
+ case MAIL_SERVER_POST_INIT:
+ post_init = va_arg(ap, MAIL_SERVER_INIT_FN);
+ break;
+ case MAIL_SERVER_LOOP:
+ loop = va_arg(ap, MAIL_SERVER_LOOP_FN);
+ break;
+ case MAIL_SERVER_EXIT:
+ event_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN);
+ break;
+ case MAIL_SERVER_PRE_ACCEPT:
+ event_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN);
+ break;
+ case MAIL_SERVER_PRE_DISCONN:
+ event_server_pre_disconn = va_arg(ap, MAIL_SERVER_DISCONN_FN);
+ break;
+ case MAIL_SERVER_IN_FLOW_DELAY:
+ event_server_in_flow_delay = 1;
+ break;
+ case MAIL_SERVER_SOLITARY:
+ if (stream == 0 && !alone)
+ msg_fatal("service %s requires a process limit of 1",
+ service_name);
+ break;
+ case MAIL_SERVER_UNLIMITED:
+ if (stream == 0 && !zerolimit)
+ msg_fatal("service %s requires a process limit of 0",
+ service_name);
+ break;
+ case MAIL_SERVER_PRIVILEGED:
+ if (user_name)
+ msg_fatal("service %s requires privileged operation",
+ service_name);
+ break;
+ case MAIL_SERVER_WATCHDOG:
+ event_server_watchdog = *va_arg(ap, int *);
+ break;
+ case MAIL_SERVER_SLOW_EXIT:
+ event_server_slow_exit = va_arg(ap, MAIL_SERVER_SLOW_EXIT_FN);
+ break;
+ case MAIL_SERVER_BOUNCE_INIT:
+ dsn_filter_title = va_arg(ap, const char *);
+ dsn_filter_maps = va_arg(ap, const char **);
+ bounce_client_init(dsn_filter_title, *dsn_filter_maps);
+ break;
+ case MAIL_SERVER_RETIRE_ME:
+ if (retire_me_from_flags > 0)
+ retire_me = retire_me_from_flags;
+ else if (var_idle_limit == 0 || var_use_limit == 0
+ || var_idle_limit > 18000 / var_use_limit)
+ retire_me = 18000;
+ else
+ retire_me = var_idle_limit * var_use_limit;
+ break;
+ default:
+ msg_panic("%s: unknown argument type: %d", myname, key);
+ }
+ }
+ va_end(ap);
+
+ if (root_dir)
+ root_dir = var_queue_dir;
+ if (user_name)
+ user_name = var_mail_owner;
+
+ /*
+ * Can options be required?
+ */
+ if (stream == 0) {
+ if (transport == 0)
+ msg_fatal("no transport type specified");
+ if (strcasecmp(transport, MASTER_XPORT_NAME_INET) == 0)
+ event_server_accept = event_server_accept_inet;
+ else if (strcasecmp(transport, MASTER_XPORT_NAME_UNIX) == 0)
+ event_server_accept = event_server_accept_local;
+#ifdef MASTER_XPORT_NAME_PASS
+ else if (strcasecmp(transport, MASTER_XPORT_NAME_PASS) == 0)
+ event_server_accept = event_server_accept_pass;
+#endif
+ else
+ msg_fatal("unsupported transport type: %s", transport);
+ }
+
+ /*
+ * Retrieve process generation from environment.
+ */
+ if ((generation = getenv(MASTER_GEN_NAME)) != 0) {
+ if (!alldig(generation))
+ msg_fatal("bad generation: %s", generation);
+ OCTAL_TO_UNSIGNED(event_server_generation, generation);
+ if (msg_verbose)
+ msg_info("process generation: %s (%o)",
+ generation, event_server_generation);
+ }
+
+ /*
+ * Optionally start the debugger on ourself.
+ */
+ if (debug_me)
+ debug_process();
+
+ /*
+ * Traditionally, BSD select() can't handle multiple processes selecting
+ * on the same socket, and wakes up every process in select(). See TCP/IP
+ * Illustrated volume 2 page 532. We avoid select() collisions with an
+ * external lock file.
+ */
+
+ /*
+ * XXX Can't compete for exclusive access to the listen socket because we
+ * also have to monitor existing client connections for service requests.
+ */
+#if 0
+ if (stream == 0 && !alone) {
+ lock_path = concatenate(DEF_PID_DIR, "/", transport,
+ ".", service_name, (char *) 0);
+ why = vstring_alloc(1);
+ if ((event_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600,
+ (struct stat *) 0, -1, -1, why)) == 0)
+ msg_fatal("open lock file %s: %s", lock_path, vstring_str(why));
+ close_on_exec(vstream_fileno(event_server_lock), CLOSE_ON_EXEC);
+ myfree(lock_path);
+ vstring_free(why);
+ }
+#endif
+
+ /*
+ * Set up call-back info.
+ */
+ event_server_service = service;
+ event_server_name = service_name;
+ event_server_argv = argv + optind;
+
+ /*
+ * Run pre-jail initialization.
+ */
+ if (chdir(var_queue_dir) < 0)
+ msg_fatal("chdir(\"%s\"): %m", var_queue_dir);
+ if (pre_init)
+ pre_init(event_server_name, event_server_argv);
+
+ /*
+ * Optionally, restrict the damage that this process can do.
+ */
+ resolve_local_init();
+ tzset();
+ chroot_uid(root_dir, user_name);
+
+ /*
+ * Run post-jail initialization.
+ */
+ if (post_init)
+ post_init(event_server_name, event_server_argv);
+
+ /*
+ * Are we running as a one-shot server with the client connection on
+ * standard input? If so, make sure the output is written to stdout so as
+ * to satisfy common expectation.
+ */
+ if (stream != 0) {
+ vstream_control(stream,
+ CA_VSTREAM_CTL_DOUBLE,
+ CA_VSTREAM_CTL_WRITE_FD(STDOUT_FILENO),
+ CA_VSTREAM_CTL_END);
+ service(stream, event_server_name, event_server_argv);
+ vstream_fflush(stream);
+ event_server_exit();
+ }
+
+ /*
+ * Running as a semi-resident server. Service connection requests.
+ * Terminate when we have serviced a sufficient number of clients, when
+ * no-one has been talking to us for a configurable amount of time, or
+ * when the master process terminated abnormally.
+ */
+ if (var_idle_limit > 0)
+ event_request_timer(event_server_timeout, (void *) 0, var_idle_limit);
+ if (retire_me)
+ event_request_timer(event_server_retire, (void *) 0, retire_me);
+ for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) {
+ event_enable_read(fd, event_server_accept, CAST_INT_TO_VOID_PTR(fd));
+ close_on_exec(fd, CLOSE_ON_EXEC);
+ }
+ event_enable_read(MASTER_STATUS_FD, event_server_abort, (void *) 0);
+ close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC);
+ close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC);
+ close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC);
+ watchdog = watchdog_create(event_server_watchdog,
+ (WATCHDOG_FN) 0, (void *) 0);
+
+ /*
+ * The event loop, at last.
+ */
+ while (var_use_limit == 0 || use_count < var_use_limit || client_count > 0) {
+ if (event_server_lock != 0) {
+ watchdog_stop(watchdog);
+ if (myflock(vstream_fileno(event_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("select lock: %m");
+ }
+ watchdog_start(watchdog);
+ delay = loop ? loop(event_server_name, event_server_argv) : -1;
+ event_loop(delay);
+ }
+ event_server_exit();
+}
diff --git a/src/master/mail_flow.c b/src/master/mail_flow.c
new file mode 100644
index 0000000..2958500
--- /dev/null
+++ b/src/master/mail_flow.c
@@ -0,0 +1,142 @@
+/*++
+/* NAME
+/* mail_flow 3
+/* SUMMARY
+/* global mail flow control
+/* SYNOPSIS
+/* #include <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..93703da
--- /dev/null
+++ b/src/master/mail_server.h
@@ -0,0 +1,155 @@
+/*++
+/* NAME
+/* mail_server 3h
+/* SUMMARY
+/* skeleton servers
+/* SYNOPSIS
+/* #include <mail_server.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <htable.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
+#define MAIL_SERVER_POST_ACCEPT 24
+
+typedef void (*MAIL_SERVER_INIT_FN) (char *, char **);
+typedef int (*MAIL_SERVER_LOOP_FN) (char *, char **);
+typedef void (*MAIL_SERVER_EXIT_FN) (char *, char **);
+typedef void (*MAIL_SERVER_ACCEPT_FN) (char *, char **);
+typedef void (*MAIL_SERVER_POST_ACCEPT_FN) (VSTREAM *, char *, char **, HTABLE *);
+typedef void (*MAIL_SERVER_DISCONN_FN) (VSTREAM *, char *, char **);
+typedef void (*MAIL_SERVER_SLOW_EXIT_FN) (char *, char **);
+
+/* Type-checked API for external use. */
+#define CA_MAIL_SERVER_INT_TABLE(v) MAIL_SERVER_INT_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_INT_TABLE, (v))
+#define CA_MAIL_SERVER_STR_TABLE(v) MAIL_SERVER_STR_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_STR_TABLE, (v))
+#define CA_MAIL_SERVER_BOOL_TABLE(v) MAIL_SERVER_BOOL_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_BOOL_TABLE, (v))
+#define CA_MAIL_SERVER_TIME_TABLE(v) MAIL_SERVER_TIME_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_TIME_TABLE, (v))
+#define CA_MAIL_SERVER_RAW_TABLE(v) MAIL_SERVER_RAW_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_RAW_TABLE, (v))
+#define CA_MAIL_SERVER_NINT_TABLE(v) MAIL_SERVER_NINT_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_NINT_TABLE, (v))
+#define CA_MAIL_SERVER_NBOOL_TABLE(v) MAIL_SERVER_NBOOL_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_NBOOL_TABLE, (v))
+#define CA_MAIL_SERVER_LONG_TABLE(v) MAIL_SERVER_LONG_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_LONG_TABLE, (v))
+#define CA_MAIL_SERVER_PRE_INIT(v) MAIL_SERVER_PRE_INIT, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_INIT_FN, (v))
+#define CA_MAIL_SERVER_POST_INIT(v) MAIL_SERVER_POST_INIT, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_INIT_FN, (v))
+#define CA_MAIL_SERVER_LOOP(v) MAIL_SERVER_LOOP, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_LOOP_FN, (v))
+#define CA_MAIL_SERVER_EXIT(v) MAIL_SERVER_EXIT, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_EXIT_FN, (v))
+#define CA_MAIL_SERVER_PRE_ACCEPT(v) MAIL_SERVER_PRE_ACCEPT, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_ACCEPT_FN, (v))
+#define CA_MAIL_SERVER_POST_ACCEPT(v) MAIL_SERVER_POST_ACCEPT, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_POST_ACCEPT_FN, (v))
+#define CA_MAIL_SERVER_SOLITARY MAIL_SERVER_SOLITARY
+#define CA_MAIL_SERVER_UNLIMITED MAIL_SERVER_UNLIMITED
+#define CA_MAIL_SERVER_PRE_DISCONN(v) MAIL_SERVER_PRE_DISCONN, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_DISCONN_FN, (v))
+#define CA_MAIL_SERVER_PRIVILEGED MAIL_SERVER_PRIVILEGED
+#define CA_MAIL_SERVER_WATCHDOG(v) MAIL_SERVER_WATCHDOG, CHECK_PTR(MAIL_SERVER, int, (v))
+#define CA_MAIL_SERVER_IN_FLOW_DELAY MAIL_SERVER_IN_FLOW_DELAY
+#define CA_MAIL_SERVER_SLOW_EXIT(v) MAIL_SERVER_SLOW_EXIT, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_SLOW_EXIT_FN, (v))
+#define CA_MAIL_SERVER_BOUNCE_INIT(v, w) MAIL_SERVER_BOUNCE_INIT, CHECK_PTR(MAIL_SERVER, char, (v)), CHECK_PPTR(MAIL_SERVER, char, (w))
+#define CA_MAIL_SERVER_RETIRE_ME MAIL_SERVER_RETIRE_ME
+
+CHECK_VAL_HELPER_DCL(MAIL_SERVER, MAIL_SERVER_SLOW_EXIT_FN);
+CHECK_VAL_HELPER_DCL(MAIL_SERVER, MAIL_SERVER_LOOP_FN);
+CHECK_VAL_HELPER_DCL(MAIL_SERVER, MAIL_SERVER_INIT_FN);
+CHECK_VAL_HELPER_DCL(MAIL_SERVER, MAIL_SERVER_EXIT_FN);
+CHECK_VAL_HELPER_DCL(MAIL_SERVER, MAIL_SERVER_DISCONN_FN);
+CHECK_VAL_HELPER_DCL(MAIL_SERVER, MAIL_SERVER_ACCEPT_FN);
+CHECK_VAL_HELPER_DCL(MAIL_SERVER, MAIL_SERVER_POST_ACCEPT_FN);
+CHECK_PTR_HELPER_DCL(MAIL_SERVER, int);
+CHECK_PTR_HELPER_DCL(MAIL_SERVER, char);
+CHECK_PPTR_HELPER_DCL(MAIL_SERVER, char);
+CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_TIME_TABLE);
+CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_STR_TABLE);
+CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_RAW_TABLE);
+CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_NINT_TABLE);
+CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_NBOOL_TABLE);
+CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_LONG_TABLE);
+CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_INT_TABLE);
+CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_BOOL_TABLE);
+
+ /*
+ * single_server.c
+ */
+typedef void (*SINGLE_SERVER_FN) (VSTREAM *, char *, char **);
+extern NORETURN single_server_main(int, char **, SINGLE_SERVER_FN,...);
+
+ /*
+ * multi_server.c
+ */
+typedef void (*MULTI_SERVER_FN) (VSTREAM *, char *, char **);
+extern NORETURN multi_server_main(int, char **, MULTI_SERVER_FN,...);
+extern void multi_server_disconnect(VSTREAM *);
+extern int multi_server_drain(void);
+
+ /*
+ * event_server.c
+ */
+typedef void (*EVENT_SERVER_FN) (VSTREAM *, char *, char **);
+extern NORETURN event_server_main(int, char **, EVENT_SERVER_FN,...);
+extern void event_server_disconnect(VSTREAM *);
+extern int event_server_drain(void);
+
+ /*
+ * trigger_server.c
+ */
+typedef void (*TRIGGER_SERVER_FN) (char *, ssize_t, char *, char **);
+extern NORETURN trigger_server_main(int, char **, TRIGGER_SERVER_FN,...);
+
+#define TRIGGER_BUF_SIZE 1024
+
+ /*
+ * dgram_server.c
+ */
+typedef void (*DGRAM_SERVER_FN) (char *, ssize_t, char *, char **);
+extern NORETURN dgram_server_main(int, char **, DGRAM_SERVER_FN,...);
+
+#define DGRAM_BUF_SIZE 4096
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
diff --git a/src/master/master.c b/src/master/master.c
new file mode 100644
index 0000000..1fc3fe9
--- /dev/null
+++ b/src/master/master.c
@@ -0,0 +1,598 @@
+/*++
+/* NAME
+/* master 8
+/* SUMMARY
+/* Postfix master process
+/* SYNOPSIS
+/* \fBmaster\fR [\fB-Dditvw\fR] [\fB-c \fIconfig_dir\fR] [\fB-e \fIexit_time\fR]
+/* DESCRIPTION
+/* The \fBmaster\fR(8) daemon is the resident process that runs Postfix
+/* daemons on demand: daemons to send or receive messages via the
+/* network, daemons to deliver mail locally, etc. These daemons are
+/* created on demand up to a configurable maximum number per service.
+/*
+/* Postfix daemons terminate voluntarily, either after being idle for
+/* a configurable amount of time, or after having serviced a
+/* configurable number of requests. Exceptions to this rule are the
+/* resident queue manager, address verification server, and the TLS
+/* session cache and pseudo-random number server.
+/*
+/* The behavior of the \fBmaster\fR(8) daemon is controlled by the
+/* \fBmaster.cf\fR configuration file, as described in \fBmaster\fR(5).
+/*
+/* Options:
+/* .IP "\fB-c \fIconfig_dir\fR"
+/* Read the \fBmain.cf\fR and \fBmaster.cf\fR configuration files in
+/* the named directory instead of the default configuration directory.
+/* This also overrides the configuration files for other Postfix
+/* daemon processes.
+/* .IP \fB-D\fR
+/* After initialization, run a debugger on the master process. The
+/* debugging command is specified with the \fBdebugger_command\fR in
+/* the \fBmain.cf\fR global configuration file.
+/* .IP \fB-d\fR
+/* Do not redirect stdin, stdout or stderr to /dev/null, and
+/* do not discard the controlling terminal. This must be used
+/* for debugging only.
+/* .IP "\fB-e \fIexit_time\fR"
+/* Terminate the master process after \fIexit_time\fR seconds. Child
+/* processes terminate at their convenience.
+/* .IP \fB-i\fR
+/* Enable \fBinit\fR mode: do not become a session or process
+/* group leader; and similar to \fB-s\fR, do not redirect stdout
+/* to /dev/null, so that "maillog_file = /dev/stdout" works.
+/* This mode is allowed only if the process ID equals 1.
+/* .sp
+/* This feature is available in Postfix 3.3 and later.
+/* .IP \fB-s\fR
+/* Do not redirect stdout to /dev/null, so that "maillog_file
+/* = /dev/stdout" works.
+/* .sp
+/* This feature is available in Postfix 3.4 and later.
+/* .IP \fB-t\fR
+/* Test mode. Return a zero exit status when the \fBmaster.pid\fR lock
+/* file does not exist or when that file is not locked. This is evidence
+/* that the \fBmaster\fR(8) daemon is not running.
+/* .IP \fB-v\fR
+/* Enable verbose logging for debugging purposes. This option
+/* is passed on to child processes. Multiple \fB-v\fR options
+/* make the software increasingly verbose.
+/* .IP \fB-w\fR
+/* Wait in a dummy foreground process, while the real master
+/* daemon initializes in a background process. The dummy
+/* foreground process returns a zero exit status only if the
+/* master daemon initialization is successful, and if it
+/* completes in a reasonable amount of time.
+/* .sp
+/* This feature is available in Postfix 2.10 and later.
+/* .PP
+/* Signals:
+/* .IP \fBSIGHUP\fR
+/* Upon receipt of a \fBHUP\fR signal (e.g., after "\fBpostfix reload\fR"),
+/* the master process re-reads its configuration files. If a service has
+/* been removed from the \fBmaster.cf\fR file, its running processes
+/* are terminated immediately.
+/* Otherwise, running processes are allowed to terminate as soon
+/* as is convenient, so that changes in configuration settings
+/* affect only new service requests.
+/* .IP \fBSIGTERM\fR
+/* Upon receipt of a \fBTERM\fR signal (e.g., after "\fBpostfix abort\fR"),
+/* the master process passes the signal on to its child processes and
+/* terminates.
+/* This is useful for an emergency shutdown. Normally one would
+/* terminate only the master ("\fBpostfix stop\fR") and allow running
+/* processes to finish what they are doing.
+/* DIAGNOSTICS
+/* Problems are reported to \fBsyslogd\fR(8) or \fBpostlogd\fR(8).
+/* The exit status
+/* is non-zero in case of problems, including problems while
+/* initializing as a master daemon process in the background.
+/* ENVIRONMENT
+/* .ad
+/* .fi
+/* .IP \fBMAIL_DEBUG\fR
+/* After initialization, start a debugger as specified with the
+/* \fBdebugger_command\fR configuration parameter in the \fBmain.cf\fR
+/* configuration file.
+/* .IP \fBMAIL_CONFIG\fR
+/* Directory with Postfix configuration files.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Unlike most Postfix daemon processes, the \fBmaster\fR(8) server does
+/* not automatically pick up changes to \fBmain.cf\fR. Changes
+/* to \fBmaster.cf\fR are never picked up automatically.
+/* Use the "\fBpostfix reload\fR" command after a configuration change.
+/* RESOURCE AND RATE CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBdefault_process_limit (100)\fR"
+/* The default maximal number of Postfix child processes that provide
+/* a given service.
+/* .IP "\fBmax_idle (100s)\fR"
+/* The maximum amount of time that an idle Postfix daemon process waits
+/* for an incoming connection before terminating voluntarily.
+/* .IP "\fBmax_use (100)\fR"
+/* The maximal number of incoming connections that a Postfix daemon
+/* process will service before terminating voluntarily.
+/* .IP "\fBservice_throttle_time (60s)\fR"
+/* How long the Postfix \fBmaster\fR(8) waits before forking a server that
+/* appears to be malfunctioning.
+/* .PP
+/* Available in Postfix version 2.6 and later:
+/* .IP "\fBmaster_service_disable (empty)\fR"
+/* Selectively disable \fBmaster\fR(8) listener ports by service type
+/* or by service name and type.
+/* MISCELLANEOUS CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
+/* The default location of the Postfix main.cf and master.cf
+/* configuration files.
+/* .IP "\fBdaemon_directory (see 'postconf -d' output)\fR"
+/* The directory with Postfix support programs and daemon programs.
+/* .IP "\fBdebugger_command (empty)\fR"
+/* The external command to execute when a Postfix daemon program is
+/* invoked with the -D option.
+/* .IP "\fBinet_interfaces (all)\fR"
+/* The network interface addresses that this mail system receives
+/* mail on.
+/* .IP "\fBinet_protocols (see 'postconf -d output')\fR"
+/* The Internet protocols Postfix will attempt to use when making
+/* or accepting connections.
+/* .IP "\fBimport_environment (see 'postconf -d' output)\fR"
+/* The list of environment parameters that a privileged Postfix
+/* process will import from a non-Postfix parent process, or name=value
+/* environment overrides.
+/* .IP "\fBmail_owner (postfix)\fR"
+/* The UNIX system account that owns the Postfix queue and most Postfix
+/* daemon processes.
+/* .IP "\fBprocess_id (read-only)\fR"
+/* The process ID of a Postfix command or daemon process.
+/* .IP "\fBprocess_name (read-only)\fR"
+/* The process name of a Postfix command or daemon process.
+/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
+/* The location of the Postfix top-level queue directory.
+/* .IP "\fBsyslog_facility (mail)\fR"
+/* The syslog facility of Postfix logging.
+/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR"
+/* A prefix that is prepended to the process name in syslog
+/* records, so that, for example, "smtpd" becomes "prefix/smtpd".
+/* .PP
+/* Available in Postfix 3.3 and later:
+/* .IP "\fBservice_name (read-only)\fR"
+/* The master.cf service name of a Postfix daemon process.
+/* .PP
+/* Available in Postfix 3.6 and later:
+/* .IP "\fBknown_tcp_ports (lmtp=24, smtp=25, smtps=submissions=465, submission=587)\fR"
+/* Optional setting that avoids lookups in the \fBservices\fR(5) database.
+/* FILES
+/* .ad
+/* .fi
+/* To expand the directory names below into their actual values,
+/* use the command "\fBpostconf config_directory\fR" etc.
+/* .na
+/* .nf
+/*
+/* $config_directory/main.cf, global configuration file.
+/* $config_directory/master.cf, master server configuration file.
+/* $queue_directory/pid/master.pid, master lock file.
+/* $data_directory/master.lock, master lock file.
+/* SEE ALSO
+/* qmgr(8), queue manager
+/* verify(8), address verification
+/* master(5), master.cf configuration file syntax
+/* postconf(5), main.cf configuration file syntax
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System libraries. */
+
+#include <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..5edc308
--- /dev/null
+++ b/src/master/master_ent.c
@@ -0,0 +1,648 @@
+/*++
+/* NAME
+/* master_ent 3
+/* SUMMARY
+/* Postfix master - config file access
+/* SYNOPSIS
+/* #include "master.h"
+/*
+/* void fset_master_ent(path)
+/* char *path;
+/*
+/* void set_master_ent()
+/*
+/* MASTER_SERV *get_master_ent()
+/*
+/* void end_master_ent()
+/*
+/* void print_master_ent(entry)
+/* MASTER_SERV *entry;
+/*
+/* void free_master_ent(entry)
+/* MASTER_SERV *entry;
+/* DESCRIPTION
+/* This module implements a simple programmatic interface
+/* for accessing Postfix master process configuration files.
+/*
+/* fset_master_ent() specifies the location of the master process
+/* configuration file. The pathname is copied.
+/*
+/* set_master_ent() opens the configuration file. It is an error
+/* to call this routine while the configuration file is still open.
+/* It is an error to open a configuration file without specifying
+/* its name to fset_master_ent().
+/*
+/* get_master_ent() reads the next entry from an open configuration
+/* file and returns the parsed result. A null result means the end
+/* of file was reached.
+/*
+/* print_master_ent() prints the specified service entry.
+/*
+/* end_master_ent() closes an open configuration file. It is an error
+/* to call this routine when the configuration file is not open.
+/*
+/* free_master_ent() destroys the memory used for a parsed configuration
+/* file entry.
+/* DIAGNOSTICS
+/* Panics: interface violations. Fatal errors: memory allocation
+/* failure.
+/* BUGS
+/* SEE ALSO
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System libraries. */
+
+#include <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>
+#include <compat_level.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", compat_level
+ < compat_level_from_string(COMPAT_LEVEL_1, msg_panic) ?
+ "y" : "n");
+
+ /*
+ * Wakeup timer. XXX should we require that var_proc_limit == 1? Right
+ * now, the only services that have a wakeup timer also happen to be the
+ * services that have at most one running instance: local pickup and
+ * local delivery.
+ */
+ serv->wakeup_time = get_int_ent(&bufp, "wakeup_time", "0", 0);
+
+ /*
+ * Find out if the wakeup time is conditional, i.e., wakeup triggers
+ * should not be sent until the service has actually been used.
+ */
+ if (serv->wakeup_time > 0 && bufp[*bufp ? -2 : -1] == '?')
+ serv->flags |= MASTER_FLAG_CONDWAKE;
+
+ /*
+ * Concurrency limit. Zero means no limit.
+ */
+ vstring_sprintf(junk, "%d", var_proc_limit);
+ serv->max_proc = get_int_ent(&bufp, "max_proc", vstring_str(junk), 0);
+
+ /*
+ * Path to command,
+ */
+ command = get_str_ent(&bufp, "command", (char *) 0);
+ serv->path = concatenate(var_daemon_dir, "/", command, (char *) 0);
+
+ /*
+ * Idle and total process count.
+ */
+ serv->avail_proc = 0;
+ serv->total_proc = 0;
+
+ /*
+ * Backoff time in case a service is broken.
+ */
+ serv->throttle_delay = var_throttle_time;
+
+ /*
+ * Shared channel for child status updates.
+ */
+ serv->status_fd[0] = serv->status_fd[1] = -1;
+
+ /*
+ * Child process structures.
+ */
+ serv->children = 0;
+
+ /*
+ * Command-line vector. Add "-n service_name" when the process name
+ * basename differs from the service name. Always add the transport.
+ */
+ serv->args = argv_alloc(0);
+ argv_add(serv->args, command, (char *) 0);
+ if (serv->max_proc == 1)
+ argv_add(serv->args, "-l", (char *) 0);
+ if (serv->max_proc == 0)
+ argv_add(serv->args, "-z", (char *) 0);
+ if (strcmp(basename(command), name) != 0)
+ argv_add(serv->args, "-n", name, (char *) 0);
+ argv_add(serv->args, "-t", transport, (char *) 0);
+ if (master_detach == 0)
+ argv_add(serv->args, "-d", (char *) 0);
+ if (msg_verbose)
+ argv_add(serv->args, "-v", (char *) 0);
+ if (unprivileged)
+ argv_add(serv->args, "-u", (char *) 0);
+ if (chroot)
+ argv_add(serv->args, "-c", (char *) 0);
+ if ((serv->flags & MASTER_FLAG_LOCAL_ONLY) == 0 && serv->max_proc > 1) {
+ argv_add(serv->args, "-o", "stress=" CONFIG_BOOL_YES, (char *) 0);
+ serv->stress_param_val =
+ serv->args->argv[serv->args->argc - 1] + sizeof("stress=") - 1;
+ serv->stress_param_val[0] = 0;
+ } else
+ serv->stress_param_val = 0;
+ serv->stress_expire_time = 0;
+ if (serv->listen_fd_count > 1)
+ argv_add(serv->args, "-s",
+ vstring_str(vstring_sprintf(junk, "%d", serv->listen_fd_count)),
+ (char *) 0);
+ while ((cp = mystrtokq(&bufp, master_blanks, CHARS_BRACE)) != 0) {
+ if (*cp == CHARS_BRACE[0]
+ && (err = extpar(&cp, CHARS_BRACE, EXTPAR_FLAG_STRIP)) != 0)
+ fatal_with_context("%s", err);
+ argv_add(serv->args, cp, (char *) 0);
+ }
+ argv_terminate(serv->args);
+
+ /*
+ * Cleanup.
+ */
+ vstring_free(buf);
+ vstring_free(junk);
+ return (serv);
+}
+
+/* print_master_ent - show service entry contents */
+
+void print_master_ent(MASTER_SERV *serv)
+{
+ char **cpp;
+
+ msg_info("====start service entry");
+ msg_info("flags: %d", serv->flags);
+ msg_info("name: %s", serv->name);
+ msg_info("type: %s",
+ serv->type == MASTER_SERV_TYPE_UNIX ? MASTER_XPORT_NAME_UNIX :
+ serv->type == MASTER_SERV_TYPE_FIFO ? MASTER_XPORT_NAME_FIFO :
+ serv->type == MASTER_SERV_TYPE_INET ? MASTER_XPORT_NAME_INET :
+#ifdef MASTER_SERV_TYPE_PASS
+ serv->type == MASTER_SERV_TYPE_PASS ? MASTER_XPORT_NAME_PASS :
+#endif
+ serv->type == MASTER_SERV_TYPE_UXDG ? MASTER_XPORT_NAME_UXDG :
+ "unknown transport type");
+ msg_info("listen_fd_count: %d", serv->listen_fd_count);
+ msg_info("wakeup: %d", serv->wakeup_time);
+ msg_info("max_proc: %d", serv->max_proc);
+ msg_info("path: %s", serv->path);
+ for (cpp = serv->args->argv; *cpp; cpp++)
+ msg_info("arg[%d]: %s", (int) (cpp - serv->args->argv), *cpp);
+ msg_info("avail_proc: %d", serv->avail_proc);
+ msg_info("total_proc: %d", serv->total_proc);
+ msg_info("throttle_delay: %d", serv->throttle_delay);
+ msg_info("status_fd %d %d", serv->status_fd[0], serv->status_fd[1]);
+ msg_info("children: 0x%lx", (long) serv->children);
+ msg_info("next: 0x%lx", (long) serv->next);
+ msg_info("====end service entry");
+}
+
+/* free_master_ent - destroy process entry */
+
+void free_master_ent(MASTER_SERV *serv)
+{
+
+ /*
+ * Undo what get_master_ent() created.
+ */
+ if (serv->flags & MASTER_FLAG_INETHOST) {
+ inet_addr_list_free(MASTER_INET_ADDRLIST(serv));
+ myfree((void *) MASTER_INET_ADDRLIST(serv));
+ }
+ if (serv->type == MASTER_SERV_TYPE_INET)
+ myfree(MASTER_INET_PORT(serv));
+ myfree(serv->ext_name);
+ myfree(serv->name);
+ myfree(serv->path);
+ argv_free(serv->args);
+ myfree((void *) serv->listen_fd);
+ myfree((void *) serv);
+}
diff --git a/src/master/master_flow.c b/src/master/master_flow.c
new file mode 100644
index 0000000..68ae57d
--- /dev/null
+++ b/src/master/master_flow.c
@@ -0,0 +1,33 @@
+/* System library. */
+
+#include <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..6b2ca65
--- /dev/null
+++ b/src/master/master_monitor.c
@@ -0,0 +1,106 @@
+/*++
+/* NAME
+/* master_monitor 3
+/* SUMMARY
+/* Postfix master - start-up monitoring
+/* SYNOPSIS
+/* #include "master.h"
+/*
+/* int master_monitor(time_limit)
+/* int time_limit;
+/* DESCRIPTION
+/* master_monitor() forks off a background child process, and
+/* returns in the child. The result value is the file descriptor
+/* on which the child process must write one byte after it
+/* completes successful initialization as a daemon process.
+/*
+/* The foreground process waits for the child's completion for
+/* a limited amount of time. It terminates with exit status 0
+/* in case of success, non-zero otherwise.
+/* DIAGNOSTICS
+/* Fatal errors: system call failure.
+/* BUGS
+/* SEE ALSO
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <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:
+ msg_warn("%m while waiting for daemon initialization");
+ /* The child process still runs, but something is wrong. */
+ (void) kill(pid, SIGKILL);
+ /* FALLTHROUGH */
+ case 0:
+ /* The child process exited prematurely. */
+ msg_fatal("daemon initialization failure -- see logs for details");
+ case 1:
+ /* The child process initialized successfully. */
+ exit(0);
+ }
+ }
+}
diff --git a/src/master/master_proto.c b/src/master/master_proto.c
new file mode 100644
index 0000000..e38d2e1
--- /dev/null
+++ b/src/master/master_proto.c
@@ -0,0 +1,89 @@
+/*++
+/* NAME
+/* master_proto 3
+/* SUMMARY
+/* Postfix master - status notification protocol
+/* SYNOPSIS
+/* #include <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..a2d5441
--- /dev/null
+++ b/src/master/master_vars.c
@@ -0,0 +1,98 @@
+/*++
+/* NAME
+/* master_vars 3
+/* SUMMARY
+/* Postfix master - global configuration file access
+/* SYNOPSIS
+/* #include "master.h"
+/*
+/* void master_vars_init()
+/* DESCRIPTION
+/* master_vars_init() reads values from the global Postfix configuration
+/* file and assigns them to tunable program parameters. Where no value
+/* is specified, a compiled-in default value is used.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <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.
+ */
+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..6150f22
--- /dev/null
+++ b/src/master/multi_server.c
@@ -0,0 +1,931 @@
+/*++
+/* NAME
+/* multi_server 3
+/* SUMMARY
+/* skeleton multi-threaded mail subsystem
+/* SYNOPSIS
+/* #include <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.
+/* The service name argument corresponds to the service name in the
+/* master.cf file.
+/* The argv argument specifies command-line arguments left over
+/* after options processing.
+/* .PP
+/* Optional arguments are specified as a null-terminated list
+/* with macros that have zero or more arguments:
+/* .IP "CA_MAIL_SERVER_INT_TABLE(CONFIG_INT_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_LONG_TABLE(CONFIG_LONG_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_STR_TABLE(CONFIG_STR_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_BOOL_TABLE(CONFIG_BOOL_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_TIME_TABLE(CONFIG_TIME_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_RAW_TABLE(CONFIG_RAW_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed. Raw parameters are not subjected to $name
+/* evaluation.
+/* .IP "CA_MAIL_SERVER_NINT_TABLE(CONFIG_NINT_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_NBOOL_TABLE(CONFIG_NBOOL_TABLE *)"
+/* A table with configurable parameters, to be loaded from the
+/* global Postfix configuration file. Tables are loaded in the
+/* order as specified, and multiple instances of the same type
+/* are allowed.
+/* .IP "CA_MAIL_SERVER_PRE_INIT(void *(char *service_name, char **argv))"
+/* A pointer to a function that is called once
+/* by the skeleton after it has read the global configuration file
+/* and after it has processed command-line arguments, but before
+/* the skeleton has optionally relinquished the process privileges.
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_POST_INIT(void *(char *service_name, char **argv))"
+/* A pointer to a function that is called once
+/* by the skeleton after it has optionally relinquished the process
+/* privileges, but before servicing client connection requests.
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_LOOP(int *(char *service_name, char **argv))"
+/* A pointer to function that is executed from
+/* within the event loop, whenever an I/O or timer event has happened,
+/* or whenever nothing has happened for a specified amount of time.
+/* The result value of the function specifies how long to wait until
+/* the next event. Specify -1 to wait for "as long as it takes".
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_EXIT(void *(char *service_name, char **argv))"
+/* A pointer to function that is executed immediately before normal
+/* process termination.
+/* .IP "CA_MAIL_SERVER_PRE_ACCEPT(void *(char *service_name, char **argv))"
+/* Function to be executed prior to accepting a new connection.
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_POST_ACCEPT(void *(VSTREAM *stream, char *service_name, char **argv, HTABLE *attr))"
+/* Function to be executed after accepting a new connection.
+/* The stream, service_name and argv arguments are the same
+/* as with the "service" argument. The attr argument is null
+/* or a pointer to a table with 'pass' connection attributes.
+/* The table is destroyed after the function returns.
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP "CA_MAIL_SERVER_PRE_DISCONN(VSTREAM *, char *service_name, char **argv)"
+/* A pointer to a function that is called
+/* by the multi_server_disconnect() function (see below).
+/* .sp
+/* Only the last instance of this parameter type is remembered.
+/* .IP CA_MAIL_SERVER_IN_FLOW_DELAY
+/* Pause $in_flow_delay seconds when no "mail flow control token"
+/* is available. A token is consumed for each connection request.
+/* .IP CA_MAIL_SERVER_SOLITARY
+/* This service must be configured with process limit of 1.
+/* .IP CA_MAIL_SERVER_UNLIMITED
+/* This service must be configured with process limit of 0.
+/* .IP CA_MAIL_SERVER_PRIVILEGED
+/* This service must be configured as privileged.
+/* .IP "CA_MAIL_SERVER_BOUNCE_INIT(const char *, const char **)"
+/* Initialize the DSN filter for the bounce/defer service
+/* clients with the specified map source and map names.
+/* .PP
+/* multi_server_disconnect() should be called by the application
+/* to close a client connection.
+/*
+/* multi_server_drain() should be called when the application
+/* no longer wishes to accept new client connections. Existing
+/* clients are handled in a background process, and the process
+/* terminates when the last client is disconnected. A non-zero
+/* result means this call should be tried again later.
+/*
+/* The var_use_limit variable limits the number of clients that
+/* a server can service before it commits suicide.
+/* This value is taken from the global \fBmain.cf\fR configuration
+/* file. Setting \fBvar_use_limit\fR to zero disables the client limit.
+/*
+/* The var_idle_limit variable limits the time that a service
+/* receives no client connection requests before it commits suicide.
+/* This value is taken from the global \fBmain.cf\fR configuration
+/* file. Setting \fBvar_idle_limit\fR to zero disables the idle limit.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* SEE ALSO
+/* master(8), master process
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <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 void (*multi_server_post_accept) (VSTREAM *, char *, char **, HTABLE *);
+static VSTREAM *multi_server_lock;
+static int multi_server_in_flow_delay;
+static unsigned multi_server_generation;
+static void (*multi_server_pre_disconn) (VSTREAM *, char *, char **);
+
+/* multi_server_exit - normal termination */
+
+static NORETURN multi_server_exit(void)
+{
+ if (multi_server_onexit)
+ multi_server_onexit(multi_server_name, multi_server_argv);
+ exit(0);
+}
+
+/* multi_server_abort - terminate after abnormal master exit */
+
+static void multi_server_abort(int unused_event, void *unused_context)
+{
+ if (msg_verbose)
+ msg_info("master disconnect -- exiting");
+ multi_server_exit();
+}
+
+/* multi_server_timeout - idle time exceeded */
+
+static void multi_server_timeout(int unused_event, void *unused_context)
+{
+ if (msg_verbose)
+ msg_info("idle timeout -- exiting");
+ multi_server_exit();
+}
+
+/* multi_server_drain - stop accepting new clients */
+
+int multi_server_drain(void)
+{
+ const char *myname = "multi_server_drain";
+ int fd;
+
+ switch (fork()) {
+ /* Try again later. */
+ case -1:
+ return (-1);
+ /* Finish existing clients in the background, then terminate. */
+ case 0:
+ (void) msg_cleanup((MSG_CLEANUP_FN) 0);
+ event_fork();
+ for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) {
+ event_disable_readwrite(fd);
+ (void) close(fd);
+ /* Play safe - don't reuse this file number. */
+ if (DUP2(STDIN_FILENO, fd) < 0)
+ msg_warn("%s: dup2(%d, %d): %m", myname, STDIN_FILENO, fd);
+ }
+ var_use_limit = 1;
+ return (0);
+ /* Let the master start a new process. */
+ default:
+ exit(0);
+ }
+}
+
+/* multi_server_disconnect - terminate client session */
+
+void multi_server_disconnect(VSTREAM *stream)
+{
+ if (msg_verbose)
+ msg_info("connection closed fd %d", vstream_fileno(stream));
+ if (multi_server_pre_disconn)
+ multi_server_pre_disconn(stream, multi_server_name, multi_server_argv);
+ event_disable_readwrite(vstream_fileno(stream));
+ (void) vstream_fclose(stream);
+ client_count--;
+ /* Avoid integer wrap-around in a persistent process. */
+ if (use_count < INT_MAX)
+ use_count++;
+ if (client_count == 0 && var_idle_limit > 0)
+ event_request_timer(multi_server_timeout, (void *) 0, var_idle_limit);
+}
+
+/* multi_server_execute - in case (char *) != (struct *) */
+
+static void multi_server_execute(int unused_event, void *context)
+{
+ VSTREAM *stream = (VSTREAM *) context;
+
+ if (multi_server_lock != 0
+ && myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_NONE) < 0)
+ msg_fatal("select unlock: %m");
+
+ /*
+ * Do not bother the application when the client disconnected. Don't drop
+ * the already accepted client request after "postfix reload"; that would
+ * be rude.
+ */
+ if (peekfd(vstream_fileno(stream)) > 0) {
+ if (master_notify(var_pid, multi_server_generation, MASTER_STAT_TAKEN) < 0)
+ /* void */ ;
+ multi_server_service(stream, multi_server_name, multi_server_argv);
+ if (master_notify(var_pid, multi_server_generation, MASTER_STAT_AVAIL) < 0)
+ multi_server_abort(EVENT_NULL_TYPE, EVENT_NULL_CONTEXT);
+ } else {
+ multi_server_disconnect(stream);
+ }
+}
+
+/* multi_server_enable_read - enable read events */
+
+static void multi_server_enable_read(int unused_event, void *context)
+{
+ VSTREAM *stream = (VSTREAM *) context;
+
+ event_enable_read(vstream_fileno(stream), multi_server_execute, (void *) stream);
+}
+
+/* multi_server_wakeup - wake up application */
+
+static void multi_server_wakeup(int fd, HTABLE *attr)
+{
+ VSTREAM *stream;
+ char *tmp;
+
+#if defined(F_DUPFD) && (EVENTS_STYLE != EVENTS_STYLE_SELECT)
+#ifndef THRESHOLD_FD_WORKAROUND
+#define THRESHOLD_FD_WORKAROUND 128
+#endif
+ int new_fd;
+
+ /*
+ * Leave some handles < FD_SETSIZE for DBMS libraries, in the unlikely
+ * case of a multi-server with a thousand clients.
+ */
+ if (fd < THRESHOLD_FD_WORKAROUND) {
+ if ((new_fd = fcntl(fd, F_DUPFD, THRESHOLD_FD_WORKAROUND)) < 0)
+ msg_fatal("fcntl F_DUPFD: %m");
+ (void) close(fd);
+ fd = new_fd;
+ }
+#endif
+ if (msg_verbose)
+ msg_info("connection established fd %d", fd);
+ non_blocking(fd, BLOCKING);
+ close_on_exec(fd, CLOSE_ON_EXEC);
+ client_count++;
+ stream = vstream_fdopen(fd, O_RDWR);
+ tmp = concatenate(multi_server_name, " socket", (char *) 0);
+ vstream_control(stream,
+ CA_VSTREAM_CTL_PATH(tmp),
+ CA_VSTREAM_CTL_END);
+ myfree(tmp);
+ timed_ipc_setup(stream);
+ if (multi_server_in_flow_delay && mail_flow_get(1) < 0)
+ event_request_timer(multi_server_enable_read, (void *) stream,
+ var_in_flow_delay);
+ else
+ multi_server_enable_read(0, (void *) stream);
+ if (multi_server_post_accept)
+ multi_server_post_accept(stream, multi_server_name, multi_server_argv, attr);
+ else if (attr)
+ msg_warn("service ignores 'pass' connection attributes");
+ if (attr)
+ htable_free(attr, myfree);
+}
+
+/* multi_server_accept_local - accept client connection request */
+
+static void multi_server_accept_local(int unused_event, void *context)
+{
+ int listen_fd = CAST_ANY_PTR_TO_INT(context);
+ int time_left = -1;
+ int fd;
+
+ /*
+ * Be prepared for accept() to fail because some other process already
+ * got the connection (the number of processes competing for clients is
+ * kept small, so this is not a "thundering herd" problem). If the
+ * accept() succeeds, be sure to disable non-blocking I/O, in order to
+ * minimize confusion.
+ */
+ if (client_count == 0 && var_idle_limit > 0)
+ time_left = event_cancel_timer(multi_server_timeout, (void *) 0);
+
+ if (multi_server_pre_accept)
+ multi_server_pre_accept(multi_server_name, multi_server_argv);
+ fd = LOCAL_ACCEPT(listen_fd);
+ if (multi_server_lock != 0
+ && myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_NONE) < 0)
+ msg_fatal("select unlock: %m");
+ if (fd < 0) {
+ if (errno != EAGAIN)
+ msg_error("accept connection: %m");
+ if (time_left >= 0)
+ event_request_timer(multi_server_timeout, (void *) 0, time_left);
+ return;
+ }
+ multi_server_wakeup(fd, (HTABLE *) 0);
+}
+
+#ifdef MASTER_XPORT_NAME_PASS
+
+/* multi_server_accept_pass - accept descriptor */
+
+static void multi_server_accept_pass(int unused_event, void *context)
+{
+ int listen_fd = CAST_ANY_PTR_TO_INT(context);
+ int time_left = -1;
+ int fd;
+ HTABLE *attr = 0;
+
+ /*
+ * Be prepared for accept() to fail because some other process already
+ * got the connection (the number of processes competing for clients is
+ * kept small, so this is not a "thundering herd" problem). If the
+ * accept() succeeds, be sure to disable non-blocking I/O, in order to
+ * minimize confusion.
+ */
+ if (client_count == 0 && var_idle_limit > 0)
+ time_left = event_cancel_timer(multi_server_timeout, (void *) 0);
+
+ if (multi_server_pre_accept)
+ multi_server_pre_accept(multi_server_name, multi_server_argv);
+ fd = pass_accept_attr(listen_fd, &attr);
+ if (multi_server_lock != 0
+ && myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_NONE) < 0)
+ msg_fatal("select unlock: %m");
+ if (fd < 0) {
+ if (errno != EAGAIN)
+ msg_error("accept connection: %m");
+ if (time_left >= 0)
+ event_request_timer(multi_server_timeout, (void *) 0, time_left);
+ return;
+ }
+ multi_server_wakeup(fd, attr);
+}
+
+#endif
+
+/* multi_server_accept_inet - accept client connection request */
+
+static void multi_server_accept_inet(int unused_event, void *context)
+{
+ int listen_fd = CAST_ANY_PTR_TO_INT(context);
+ int time_left = -1;
+ int fd;
+
+ /*
+ * Be prepared for accept() to fail because some other process already
+ * got the connection (the number of processes competing for clients is
+ * kept small, so this is not a "thundering herd" problem). If the
+ * accept() succeeds, be sure to disable non-blocking I/O, in order to
+ * minimize confusion.
+ */
+ if (client_count == 0 && var_idle_limit > 0)
+ time_left = event_cancel_timer(multi_server_timeout, (void *) 0);
+
+ if (multi_server_pre_accept)
+ multi_server_pre_accept(multi_server_name, multi_server_argv);
+ fd = inet_accept(listen_fd);
+ if (multi_server_lock != 0
+ && myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_NONE) < 0)
+ msg_fatal("select unlock: %m");
+ if (fd < 0) {
+ if (errno != EAGAIN)
+ msg_error("accept connection: %m");
+ if (time_left >= 0)
+ event_request_timer(multi_server_timeout, (void *) 0, time_left);
+ return;
+ }
+ multi_server_wakeup(fd, (HTABLE *) 0);
+}
+
+/* multi_server_main - the real main program */
+
+NORETURN multi_server_main(int argc, char **argv, MULTI_SERVER_FN service,...)
+{
+ const char *myname = "multi_server_main";
+ VSTREAM *stream = 0;
+ char *root_dir = 0;
+ char *user_name = 0;
+ int debug_me = 0;
+ int daemon_mode = 1;
+ char *service_name = basename(argv[0]);
+ int delay;
+ int c;
+ int fd;
+ va_list ap;
+ MAIL_SERVER_INIT_FN pre_init = 0;
+ MAIL_SERVER_INIT_FN post_init = 0;
+ MAIL_SERVER_LOOP_FN loop = 0;
+ int key;
+ char *transport = 0;
+
+#if 0
+ char *lock_path;
+ VSTRING *why;
+
+#endif
+ int alone = 0;
+ int zerolimit = 0;
+ WATCHDOG *watchdog;
+ char *oname_val;
+ char *oname;
+ char *oval;
+ const char *err;
+ char *generation;
+ int msg_vstream_needed = 0;
+ const char *dsn_filter_title;
+ const char **dsn_filter_maps;
+
+ /*
+ * Process environment options as early as we can.
+ */
+ if (getenv(CONF_ENV_VERB))
+ msg_verbose = 1;
+ if (getenv(CONF_ENV_DEBUG))
+ debug_me = 1;
+
+ /*
+ * Don't die when a process goes away unexpectedly.
+ */
+ signal(SIGPIPE, SIG_IGN);
+
+ /*
+ * Don't die for frivolous reasons.
+ */
+#ifdef SIGXFSZ
+ signal(SIGXFSZ, SIG_IGN);
+#endif
+
+ /*
+ * May need this every now and then.
+ */
+ var_procname = mystrdup(basename(argv[0]));
+ set_mail_conf_str(VAR_PROCNAME, var_procname);
+
+ /*
+ * Initialize logging and exit handler. Do the syslog first, so that its
+ * initialization completes before we enter the optional chroot jail.
+ */
+ maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE);
+ if (msg_verbose)
+ msg_info("daemon started");
+
+ /*
+ * Check the Postfix library version as soon as we enable logging.
+ */
+ MAIL_VERSION_CHECK;
+
+ /*
+ * Initialize from the configuration file. Allow command-line options to
+ * override compiled-in defaults or configured parameter values.
+ */
+ mail_conf_suck();
+
+ /*
+ * After database open error, continue execution with reduced
+ * functionality.
+ */
+ dict_allow_surrogate = 1;
+
+ /*
+ * Pick up policy settings from master process. Shut up error messages to
+ * stderr, because no-one is going to see them.
+ */
+ opterr = 0;
+ while ((c = GETOPT(argc, argv, "cdDi:lm:n:o:s:St:uvVz")) > 0) {
+ switch (c) {
+ case 'c':
+ root_dir = "setme";
+ break;
+ case 'd':
+ daemon_mode = 0;
+ break;
+ case 'D':
+ debug_me = 1;
+ break;
+ case 'i':
+ mail_conf_update(VAR_MAX_IDLE, optarg);
+ break;
+ case 'l':
+ alone = 1;
+ break;
+ case 'm':
+ mail_conf_update(VAR_MAX_USE, optarg);
+ break;
+ case 'n':
+ service_name = optarg;
+ break;
+ case 'o':
+ oname_val = mystrdup(optarg);
+ if ((err = split_nameval(oname_val, &oname, &oval)) != 0)
+ msg_fatal("invalid \"-o %s\" option value: %s", optarg, err);
+ mail_conf_update(oname, oval);
+ myfree(oname_val);
+ break;
+ case 's':
+ if ((socket_count = atoi(optarg)) <= 0)
+ msg_fatal("invalid socket_count: %s", optarg);
+ break;
+ case 'S':
+ stream = VSTREAM_IN;
+ break;
+ case 'u':
+ user_name = "setme";
+ break;
+ case 't':
+ transport = optarg;
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ case 'V':
+ if (++msg_vstream_needed == 1)
+ msg_vstream_init(mail_task(var_procname), VSTREAM_ERR);
+ break;
+ case 'z':
+ zerolimit = 1;
+ break;
+ default:
+ msg_fatal("invalid option: %c", optopt);
+ break;
+ }
+ }
+ set_mail_conf_str(VAR_SERVNAME, service_name);
+
+ /*
+ * Initialize generic parameters and re-initialize logging in case of a
+ * non-default program name or logging destination.
+ */
+ mail_params_init();
+ maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE);
+
+ /*
+ * Register higher-level dictionaries and initialize the support for
+ * dynamically-loaded dictionaries.
+ */
+ mail_dict_init();
+
+ /*
+ * If not connected to stdin, stdin must not be a terminal.
+ */
+ if (daemon_mode && stream == 0 && isatty(STDIN_FILENO)) {
+ msg_vstream_init(var_procname, VSTREAM_ERR);
+ msg_fatal("do not run this command by hand");
+ }
+
+ /*
+ * Application-specific initialization.
+ */
+ va_start(ap, service);
+ while ((key = va_arg(ap, int)) != 0) {
+ switch (key) {
+ case MAIL_SERVER_INT_TABLE:
+ get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *));
+ break;
+ case MAIL_SERVER_LONG_TABLE:
+ get_mail_conf_long_table(va_arg(ap, CONFIG_LONG_TABLE *));
+ break;
+ case MAIL_SERVER_STR_TABLE:
+ get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *));
+ break;
+ case MAIL_SERVER_BOOL_TABLE:
+ get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *));
+ break;
+ case MAIL_SERVER_TIME_TABLE:
+ get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *));
+ break;
+ case MAIL_SERVER_RAW_TABLE:
+ get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *));
+ break;
+ case MAIL_SERVER_NINT_TABLE:
+ get_mail_conf_nint_table(va_arg(ap, CONFIG_NINT_TABLE *));
+ break;
+ case MAIL_SERVER_NBOOL_TABLE:
+ get_mail_conf_nbool_table(va_arg(ap, CONFIG_NBOOL_TABLE *));
+ break;
+ case MAIL_SERVER_PRE_INIT:
+ pre_init = va_arg(ap, MAIL_SERVER_INIT_FN);
+ break;
+ case MAIL_SERVER_POST_INIT:
+ post_init = va_arg(ap, MAIL_SERVER_INIT_FN);
+ break;
+ case MAIL_SERVER_LOOP:
+ loop = va_arg(ap, MAIL_SERVER_LOOP_FN);
+ break;
+ case MAIL_SERVER_EXIT:
+ multi_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN);
+ break;
+ case MAIL_SERVER_PRE_ACCEPT:
+ multi_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN);
+ break;
+ case MAIL_SERVER_POST_ACCEPT:
+ multi_server_post_accept = va_arg(ap, MAIL_SERVER_POST_ACCEPT_FN);
+ break;
+ case MAIL_SERVER_PRE_DISCONN:
+ multi_server_pre_disconn = va_arg(ap, MAIL_SERVER_DISCONN_FN);
+ break;
+ case MAIL_SERVER_IN_FLOW_DELAY:
+ multi_server_in_flow_delay = 1;
+ break;
+ case MAIL_SERVER_SOLITARY:
+ if (stream == 0 && !alone)
+ msg_fatal("service %s requires a process limit of 1",
+ service_name);
+ break;
+ case MAIL_SERVER_UNLIMITED:
+ if (stream == 0 && !zerolimit)
+ msg_fatal("service %s requires a process limit of 0",
+ service_name);
+ break;
+ case MAIL_SERVER_PRIVILEGED:
+ if (user_name)
+ msg_fatal("service %s requires privileged operation",
+ service_name);
+ break;
+ case MAIL_SERVER_BOUNCE_INIT:
+ dsn_filter_title = va_arg(ap, const char *);
+ dsn_filter_maps = va_arg(ap, const char **);
+ bounce_client_init(dsn_filter_title, *dsn_filter_maps);
+ break;
+ default:
+ msg_panic("%s: unknown argument type: %d", myname, key);
+ }
+ }
+ va_end(ap);
+
+ if (root_dir)
+ root_dir = var_queue_dir;
+ if (user_name)
+ user_name = var_mail_owner;
+
+ /*
+ * Can options be required?
+ */
+ if (stream == 0) {
+ if (transport == 0)
+ msg_fatal("no transport type specified");
+ if (strcasecmp(transport, MASTER_XPORT_NAME_INET) == 0)
+ multi_server_accept = multi_server_accept_inet;
+ else if (strcasecmp(transport, MASTER_XPORT_NAME_UNIX) == 0)
+ multi_server_accept = multi_server_accept_local;
+#ifdef MASTER_XPORT_NAME_PASS
+ else if (strcasecmp(transport, MASTER_XPORT_NAME_PASS) == 0)
+ multi_server_accept = multi_server_accept_pass;
+#endif
+ else
+ msg_fatal("unsupported transport type: %s", transport);
+ }
+
+ /*
+ * Retrieve process generation from environment.
+ */
+ if ((generation = getenv(MASTER_GEN_NAME)) != 0) {
+ if (!alldig(generation))
+ msg_fatal("bad generation: %s", generation);
+ OCTAL_TO_UNSIGNED(multi_server_generation, generation);
+ if (msg_verbose)
+ msg_info("process generation: %s (%o)",
+ generation, multi_server_generation);
+ }
+
+ /*
+ * Optionally start the debugger on ourself.
+ */
+ if (debug_me)
+ debug_process();
+
+ /*
+ * Traditionally, BSD select() can't handle multiple processes selecting
+ * on the same socket, and wakes up every process in select(). See TCP/IP
+ * Illustrated volume 2 page 532. We avoid select() collisions with an
+ * external lock file.
+ */
+
+ /*
+ * XXX Can't compete for exclusive access to the listen socket because we
+ * also have to monitor existing client connections for service requests.
+ */
+#if 0
+ if (stream == 0 && !alone) {
+ lock_path = concatenate(DEF_PID_DIR, "/", transport,
+ ".", service_name, (char *) 0);
+ why = vstring_alloc(1);
+ if ((multi_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600,
+ (struct stat *) 0, -1, -1, why)) == 0)
+ msg_fatal("open lock file %s: %s", lock_path, vstring_str(why));
+ close_on_exec(vstream_fileno(multi_server_lock), CLOSE_ON_EXEC);
+ myfree(lock_path);
+ vstring_free(why);
+ }
+#endif
+
+ /*
+ * Set up call-back info.
+ */
+ multi_server_service = service;
+ multi_server_name = service_name;
+ multi_server_argv = argv + optind;
+
+ /*
+ * Run pre-jail initialization.
+ */
+ if (chdir(var_queue_dir) < 0)
+ msg_fatal("chdir(\"%s\"): %m", var_queue_dir);
+ if (pre_init)
+ pre_init(multi_server_name, multi_server_argv);
+
+ /*
+ * Optionally, restrict the damage that this process can do.
+ */
+ resolve_local_init();
+ tzset();
+ chroot_uid(root_dir, user_name);
+
+ /*
+ * Run post-jail initialization.
+ */
+ if (post_init)
+ post_init(multi_server_name, multi_server_argv);
+
+ /*
+ * Are we running as a one-shot server with the client connection on
+ * standard input? If so, make sure the output is written to stdout so as
+ * to satisfy common expectation.
+ */
+ if (stream != 0) {
+ vstream_control(stream,
+ CA_VSTREAM_CTL_DOUBLE,
+ CA_VSTREAM_CTL_WRITE_FD(STDOUT_FILENO),
+ CA_VSTREAM_CTL_END);
+ service(stream, multi_server_name, multi_server_argv);
+ vstream_fflush(stream);
+ multi_server_exit();
+ }
+
+ /*
+ * Running as a semi-resident server. Service connection requests.
+ * Terminate when we have serviced a sufficient number of clients, when
+ * no-one has been talking to us for a configurable amount of time, or
+ * when the master process terminated abnormally.
+ */
+ if (var_idle_limit > 0)
+ event_request_timer(multi_server_timeout, (void *) 0, var_idle_limit);
+ for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) {
+ event_enable_read(fd, multi_server_accept, CAST_INT_TO_VOID_PTR(fd));
+ close_on_exec(fd, CLOSE_ON_EXEC);
+ }
+ event_enable_read(MASTER_STATUS_FD, multi_server_abort, (void *) 0);
+ close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC);
+ close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC);
+ close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC);
+ watchdog = watchdog_create(var_daemon_timeout, (WATCHDOG_FN) 0, (void *) 0);
+
+ /*
+ * The event loop, at last.
+ */
+ while (var_use_limit == 0 || use_count < var_use_limit || client_count > 0) {
+ if (multi_server_lock != 0) {
+ watchdog_stop(watchdog);
+ if (myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("select lock: %m");
+ }
+ watchdog_start(watchdog);
+ delay = loop ? loop(multi_server_name, multi_server_argv) : -1;
+ event_loop(delay);
+ }
+ multi_server_exit();
+}
diff --git a/src/master/single_server.c b/src/master/single_server.c
new file mode 100644
index 0000000..38f22b7
--- /dev/null
+++ b/src/master/single_server.c
@@ -0,0 +1,822 @@
+/*++
+/* NAME
+/* single_server 3
+/* SUMMARY
+/* skeleton single-threaded mail subsystem
+/* SYNOPSIS
+/* #include <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 dictionaries.
+ */
+ mail_dict_init();
+
+ /*
+ * If not connected to stdin, stdin must not be a terminal.
+ */
+ if (daemon_mode && stream == 0 && isatty(STDIN_FILENO)) {
+ msg_vstream_init(var_procname, VSTREAM_ERR);
+ msg_fatal("do not run this command by hand");
+ }
+
+ /*
+ * Application-specific initialization.
+ */
+ va_start(ap, service);
+ while ((key = va_arg(ap, int)) != 0) {
+ switch (key) {
+ case MAIL_SERVER_INT_TABLE:
+ get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *));
+ break;
+ case MAIL_SERVER_LONG_TABLE:
+ get_mail_conf_long_table(va_arg(ap, CONFIG_LONG_TABLE *));
+ break;
+ case MAIL_SERVER_STR_TABLE:
+ get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *));
+ break;
+ case MAIL_SERVER_BOOL_TABLE:
+ get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *));
+ break;
+ case MAIL_SERVER_TIME_TABLE:
+ get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *));
+ break;
+ case MAIL_SERVER_RAW_TABLE:
+ get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *));
+ break;
+ case MAIL_SERVER_NINT_TABLE:
+ get_mail_conf_nint_table(va_arg(ap, CONFIG_NINT_TABLE *));
+ break;
+ case MAIL_SERVER_NBOOL_TABLE:
+ get_mail_conf_nbool_table(va_arg(ap, CONFIG_NBOOL_TABLE *));
+ break;
+ case MAIL_SERVER_PRE_INIT:
+ pre_init = va_arg(ap, MAIL_SERVER_INIT_FN);
+ break;
+ case MAIL_SERVER_POST_INIT:
+ post_init = va_arg(ap, MAIL_SERVER_INIT_FN);
+ break;
+ case MAIL_SERVER_LOOP:
+ loop = va_arg(ap, MAIL_SERVER_LOOP_FN);
+ break;
+ case MAIL_SERVER_EXIT:
+ single_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN);
+ break;
+ case MAIL_SERVER_PRE_ACCEPT:
+ single_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN);
+ break;
+ case MAIL_SERVER_IN_FLOW_DELAY:
+ single_server_in_flow_delay = 1;
+ break;
+ case MAIL_SERVER_SOLITARY:
+ if (stream == 0 && !alone)
+ msg_fatal("service %s requires a process limit of 1",
+ service_name);
+ break;
+ case MAIL_SERVER_UNLIMITED:
+ if (stream == 0 && !zerolimit)
+ msg_fatal("service %s requires a process limit of 0",
+ service_name);
+ break;
+ case MAIL_SERVER_PRIVILEGED:
+ if (user_name)
+ msg_fatal("service %s requires privileged operation",
+ service_name);
+ break;
+ case MAIL_SERVER_BOUNCE_INIT:
+ dsn_filter_title = va_arg(ap, const char *);
+ dsn_filter_maps = va_arg(ap, const char **);
+ bounce_client_init(dsn_filter_title, *dsn_filter_maps);
+ break;
+ case MAIL_SERVER_RETIRE_ME:
+ if (retire_me_from_flags > 0)
+ retire_me = retire_me_from_flags;
+ else if (var_idle_limit == 0 || var_use_limit == 0
+ || var_idle_limit > 18000 / var_use_limit)
+ retire_me = 18000;
+ else
+ retire_me = var_idle_limit * var_use_limit;
+ break;
+ default:
+ msg_panic("%s: unknown argument type: %d", myname, key);
+ }
+ }
+ va_end(ap);
+
+ if (root_dir)
+ root_dir = var_queue_dir;
+ if (user_name)
+ user_name = var_mail_owner;
+
+ /*
+ * Can options be required?
+ */
+ if (stream == 0) {
+ if (transport == 0)
+ msg_fatal("no transport type specified");
+ if (strcasecmp(transport, MASTER_XPORT_NAME_INET) == 0)
+ single_server_accept = single_server_accept_inet;
+ else if (strcasecmp(transport, MASTER_XPORT_NAME_UNIX) == 0)
+ single_server_accept = single_server_accept_local;
+#ifdef MASTER_XPORT_NAME_PASS
+ else if (strcasecmp(transport, MASTER_XPORT_NAME_PASS) == 0)
+ single_server_accept = single_server_accept_pass;
+#endif
+ else
+ msg_fatal("unsupported transport type: %s", transport);
+ }
+
+ /*
+ * Retrieve process generation from environment.
+ */
+ if ((generation = getenv(MASTER_GEN_NAME)) != 0) {
+ if (!alldig(generation))
+ msg_fatal("bad generation: %s", generation);
+ OCTAL_TO_UNSIGNED(single_server_generation, generation);
+ if (msg_verbose)
+ msg_info("process generation: %s (%o)",
+ generation, single_server_generation);
+ }
+
+ /*
+ * Optionally start the debugger on ourself.
+ */
+ if (debug_me)
+ debug_process();
+
+ /*
+ * Traditionally, BSD select() can't handle multiple processes selecting
+ * on the same socket, and wakes up every process in select(). See TCP/IP
+ * Illustrated volume 2 page 532. We avoid select() collisions with an
+ * external lock file.
+ */
+ if (stream == 0 && !alone) {
+ lock_path = concatenate(DEF_PID_DIR, "/", transport,
+ ".", service_name, (void *) 0);
+ why = vstring_alloc(1);
+ if ((single_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600,
+ (struct stat *) 0, -1, -1, why)) == 0)
+ msg_fatal("open lock file %s: %s", lock_path, vstring_str(why));
+ close_on_exec(vstream_fileno(single_server_lock), CLOSE_ON_EXEC);
+ myfree(lock_path);
+ vstring_free(why);
+ }
+
+ /*
+ * Set up call-back info.
+ */
+ single_server_service = service;
+ single_server_name = service_name;
+ single_server_argv = argv + optind;
+
+ /*
+ * Run pre-jail initialization.
+ */
+ if (chdir(var_queue_dir) < 0)
+ msg_fatal("chdir(\"%s\"): %m", var_queue_dir);
+ if (pre_init)
+ pre_init(single_server_name, single_server_argv);
+
+ /*
+ * Optionally, restrict the damage that this process can do.
+ */
+ resolve_local_init();
+ tzset();
+ chroot_uid(root_dir, user_name);
+
+ /*
+ * Run post-jail initialization.
+ */
+ if (post_init)
+ post_init(single_server_name, single_server_argv);
+
+ /*
+ * Are we running as a one-shot server with the client connection on
+ * standard input? If so, make sure the output is written to stdout so as
+ * to satisfy common expectation.
+ */
+ if (stream != 0) {
+ vstream_control(stream,
+ CA_VSTREAM_CTL_DOUBLE,
+ CA_VSTREAM_CTL_WRITE_FD(STDOUT_FILENO),
+ CA_VSTREAM_CTL_END);
+ service(stream, single_server_name, single_server_argv);
+ vstream_fflush(stream);
+ single_server_exit();
+ }
+
+ /*
+ * Running as a semi-resident server. Service connection requests.
+ * Terminate when we have serviced a sufficient number of clients, when
+ * no-one has been talking to us for a configurable amount of time, or
+ * when the master process terminated abnormally.
+ */
+ if (var_idle_limit > 0)
+ event_request_timer(single_server_timeout, (void *) 0, var_idle_limit);
+ if (retire_me)
+ event_request_timer(single_server_retire, (void *) 0, retire_me);
+ for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) {
+ event_enable_read(fd, single_server_accept, CAST_INT_TO_VOID_PTR(fd));
+ close_on_exec(fd, CLOSE_ON_EXEC);
+ }
+ event_enable_read(MASTER_STATUS_FD, single_server_abort, (void *) 0);
+ close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC);
+ close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC);
+ close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC);
+ watchdog = watchdog_create(var_daemon_timeout, (WATCHDOG_FN) 0, (void *) 0);
+
+ /*
+ * The event loop, at last.
+ */
+ while (var_use_limit == 0 || use_count < var_use_limit) {
+ if (single_server_lock != 0) {
+ watchdog_stop(watchdog);
+ if (myflock(vstream_fileno(single_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("select lock: %m");
+ }
+ watchdog_start(watchdog);
+ delay = loop ? loop(single_server_name, single_server_argv) : -1;
+ event_loop(delay);
+ }
+ single_server_exit();
+}
diff --git a/src/master/trigger_server.c b/src/master/trigger_server.c
new file mode 100644
index 0000000..c483a9e
--- /dev/null
+++ b/src/master/trigger_server.c
@@ -0,0 +1,809 @@
+/*++
+/* NAME
+/* trigger_server 3
+/* SUMMARY
+/* skeleton triggered mail subsystem
+/* SYNOPSIS
+/* #include <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 dictionaries.
+ */
+ mail_dict_init();
+
+ /*
+ * If not connected to stdin, stdin must not be a terminal.
+ */
+ if (daemon_mode && stream == 0 && isatty(STDIN_FILENO)) {
+ msg_vstream_init(var_procname, VSTREAM_ERR);
+ msg_fatal("do not run this command by hand");
+ }
+
+ /*
+ * Application-specific initialization.
+ */
+ va_start(ap, service);
+ while ((key = va_arg(ap, int)) != 0) {
+ switch (key) {
+ case MAIL_SERVER_INT_TABLE:
+ get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *));
+ break;
+ case MAIL_SERVER_LONG_TABLE:
+ get_mail_conf_long_table(va_arg(ap, CONFIG_LONG_TABLE *));
+ break;
+ case MAIL_SERVER_STR_TABLE:
+ get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *));
+ break;
+ case MAIL_SERVER_BOOL_TABLE:
+ get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *));
+ break;
+ case MAIL_SERVER_TIME_TABLE:
+ get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *));
+ break;
+ case MAIL_SERVER_RAW_TABLE:
+ get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *));
+ break;
+ case MAIL_SERVER_NINT_TABLE:
+ get_mail_conf_nint_table(va_arg(ap, CONFIG_NINT_TABLE *));
+ break;
+ case MAIL_SERVER_NBOOL_TABLE:
+ get_mail_conf_nbool_table(va_arg(ap, CONFIG_NBOOL_TABLE *));
+ break;
+ case MAIL_SERVER_PRE_INIT:
+ pre_init = va_arg(ap, MAIL_SERVER_INIT_FN);
+ break;
+ case MAIL_SERVER_POST_INIT:
+ post_init = va_arg(ap, MAIL_SERVER_INIT_FN);
+ break;
+ case MAIL_SERVER_LOOP:
+ loop = va_arg(ap, MAIL_SERVER_LOOP_FN);
+ break;
+ case MAIL_SERVER_EXIT:
+ trigger_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN);
+ break;
+ case MAIL_SERVER_PRE_ACCEPT:
+ trigger_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN);
+ break;
+ case MAIL_SERVER_IN_FLOW_DELAY:
+ trigger_server_in_flow_delay = 1;
+ break;
+ case MAIL_SERVER_SOLITARY:
+ if (stream == 0 && !alone)
+ msg_fatal("service %s requires a process limit of 1",
+ service_name);
+ break;
+ case MAIL_SERVER_UNLIMITED:
+ if (stream == 0 && !zerolimit)
+ msg_fatal("service %s requires a process limit of 0",
+ service_name);
+ break;
+ case MAIL_SERVER_PRIVILEGED:
+ if (user_name)
+ msg_fatal("service %s requires privileged operation",
+ service_name);
+ break;
+ case MAIL_SERVER_WATCHDOG:
+ trigger_server_watchdog = *va_arg(ap, int *);
+ break;
+ case MAIL_SERVER_BOUNCE_INIT:
+ dsn_filter_title = va_arg(ap, const char *);
+ dsn_filter_maps = va_arg(ap, const char **);
+ bounce_client_init(dsn_filter_title, *dsn_filter_maps);
+ break;
+ default:
+ msg_panic("%s: unknown argument type: %d", myname, key);
+ }
+ }
+ va_end(ap);
+
+ if (root_dir)
+ root_dir = var_queue_dir;
+ if (user_name)
+ user_name = var_mail_owner;
+
+ /*
+ * Can options be required?
+ *
+ * XXX Initially this code was implemented with UNIX-domain sockets, but
+ * Solaris <= 2.5 UNIX-domain sockets misbehave hopelessly when the
+ * client disconnects before the server has accepted the connection.
+ * Symptom: the server accept() fails with EPIPE or EPROTO, but the
+ * socket stays readable, so that the program goes into a wasteful loop.
+ *
+ * The initial fix was to use FIFOs, but those turn out to have their own
+ * problems, witness the workarounds in the fifo_listen() routine.
+ * Therefore we support both FIFOs and UNIX-domain sockets, so that the
+ * user can choose whatever works best.
+ *
+ * Well, I give up. Solaris UNIX-domain sockets still don't work properly,
+ * so it will have to limp along with a streams-specific alternative.
+ */
+ if (stream == 0) {
+ if (transport == 0)
+ msg_fatal("no transport type specified");
+ if (strcasecmp(transport, MASTER_XPORT_NAME_UNIX) == 0)
+ trigger_server_accept = trigger_server_accept_local;
+ else if (strcasecmp(transport, MASTER_XPORT_NAME_FIFO) == 0)
+ trigger_server_accept = trigger_server_accept_fifo;
+#ifdef MASTER_XPORT_NAME_PASS
+ else if (strcasecmp(transport, MASTER_XPORT_NAME_PASS) == 0)
+ trigger_server_accept = trigger_server_accept_pass;
+#endif
+ else
+ msg_fatal("unsupported transport type: %s", transport);
+ }
+
+ /*
+ * Retrieve process generation from environment.
+ */
+ if ((generation = getenv(MASTER_GEN_NAME)) != 0) {
+ if (!alldig(generation))
+ msg_fatal("bad generation: %s", generation);
+ OCTAL_TO_UNSIGNED(trigger_server_generation, generation);
+ if (msg_verbose)
+ msg_info("process generation: %s (%o)",
+ generation, trigger_server_generation);
+ }
+
+ /*
+ * Optionally start the debugger on ourself.
+ */
+ if (debug_me)
+ debug_process();
+
+ /*
+ * Traditionally, BSD select() can't handle multiple processes selecting
+ * on the same socket, and wakes up every process in select(). See TCP/IP
+ * Illustrated volume 2 page 532. We avoid select() collisions with an
+ * external lock file.
+ */
+ if (stream == 0 && !alone) {
+ lock_path = concatenate(DEF_PID_DIR, "/", transport,
+ ".", service_name, (char *) 0);
+ why = vstring_alloc(1);
+ if ((trigger_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600,
+ (struct stat *) 0, -1, -1, why)) == 0)
+ msg_fatal("open lock file %s: %s", lock_path, vstring_str(why));
+ close_on_exec(vstream_fileno(trigger_server_lock), CLOSE_ON_EXEC);
+ myfree(lock_path);
+ vstring_free(why);
+ }
+
+ /*
+ * Set up call-back info.
+ */
+ trigger_server_service = service;
+ trigger_server_name = service_name;
+ trigger_server_argv = argv + optind;
+
+ /*
+ * Run pre-jail initialization.
+ */
+ if (chdir(var_queue_dir) < 0)
+ msg_fatal("chdir(\"%s\"): %m", var_queue_dir);
+ if (pre_init)
+ pre_init(trigger_server_name, trigger_server_argv);
+
+ /*
+ * Optionally, restrict the damage that this process can do.
+ */
+ resolve_local_init();
+ tzset();
+ chroot_uid(root_dir, user_name);
+
+ /*
+ * Run post-jail initialization.
+ */
+ if (post_init)
+ post_init(trigger_server_name, trigger_server_argv);
+
+ /*
+ * Are we running as a one-shot server with the client connection on
+ * standard input?
+ */
+ if (stream != 0) {
+ if ((len = read(vstream_fileno(stream), buf, sizeof(buf))) <= 0)
+ msg_fatal("read: %m");
+ service(buf, len, trigger_server_name, trigger_server_argv);
+ vstream_fflush(stream);
+ trigger_server_exit();
+ }
+
+ /*
+ * Running as a semi-resident server. Service connection requests.
+ * Terminate when we have serviced a sufficient number of clients, when
+ * no-one has been talking to us for a configurable amount of time, or
+ * when the master process terminated abnormally.
+ */
+ if (var_idle_limit > 0)
+ event_request_timer(trigger_server_timeout, (void *) 0, var_idle_limit);
+ for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) {
+ event_enable_read(fd, trigger_server_accept, CAST_INT_TO_VOID_PTR(fd));
+ close_on_exec(fd, CLOSE_ON_EXEC);
+ }
+ event_enable_read(MASTER_STATUS_FD, trigger_server_abort, (void *) 0);
+ close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC);
+ close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC);
+ close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC);
+ watchdog = watchdog_create(trigger_server_watchdog,
+ (WATCHDOG_FN) 0, (void *) 0);
+
+ /*
+ * The event loop, at last.
+ */
+ while (var_use_limit == 0 || use_count < var_use_limit) {
+ if (trigger_server_lock != 0) {
+ watchdog_stop(watchdog);
+ if (myflock(vstream_fileno(trigger_server_lock), INTERNAL_LOCK,
+ MYFLOCK_OP_EXCLUSIVE) < 0)
+ msg_fatal("select lock: %m");
+ }
+ watchdog_start(watchdog);
+ delay = loop ? loop(trigger_server_name, trigger_server_argv) : -1;
+ event_loop(delay);
+ }
+ trigger_server_exit();
+}