From b7c15c31519dc44c1f691e0466badd556ffe9423 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 18:18:56 +0200 Subject: Adding upstream version 3.7.10. Signed-off-by: Daniel Baumann --- src/local/.indent.pro | 1 + src/local/.printfck | 25 ++ src/local/Makefile.in | 686 +++++++++++++++++++++++++++++ src/local/Musings | 39 ++ src/local/alias.c | 386 +++++++++++++++++ src/local/biff_notify.c | 98 +++++ src/local/biff_notify.h | 30 ++ src/local/bounce_workaround.c | 159 +++++++ src/local/command.c | 251 +++++++++++ src/local/deliver_attr.c | 105 +++++ src/local/dotforward.c | 304 +++++++++++++ src/local/file.c | 195 +++++++++ src/local/forward.c | 393 +++++++++++++++++ src/local/include.c | 223 ++++++++++ src/local/indirect.c | 94 ++++ src/local/local.c | 988 ++++++++++++++++++++++++++++++++++++++++++ src/local/local.h | 251 +++++++++++ src/local/local_expand.c | 180 ++++++++ src/local/mailbox.c | 373 ++++++++++++++++ src/local/maildir.c | 257 +++++++++++ src/local/recipient.c | 307 +++++++++++++ src/local/resolve.c | 170 ++++++++ src/local/token.c | 222 ++++++++++ src/local/unknown.c | 187 ++++++++ 24 files changed, 5924 insertions(+) create mode 120000 src/local/.indent.pro create mode 100644 src/local/.printfck create mode 100644 src/local/Makefile.in create mode 100644 src/local/Musings create mode 100644 src/local/alias.c create mode 100644 src/local/biff_notify.c create mode 100644 src/local/biff_notify.h create mode 100644 src/local/bounce_workaround.c create mode 100644 src/local/command.c create mode 100644 src/local/deliver_attr.c create mode 100644 src/local/dotforward.c create mode 100644 src/local/file.c create mode 100644 src/local/forward.c create mode 100644 src/local/include.c create mode 100644 src/local/indirect.c create mode 100644 src/local/local.c create mode 100644 src/local/local.h create mode 100644 src/local/local_expand.c create mode 100644 src/local/mailbox.c create mode 100644 src/local/maildir.c create mode 100644 src/local/recipient.c create mode 100644 src/local/resolve.c create mode 100644 src/local/token.c create mode 100644 src/local/unknown.c (limited to 'src/local') diff --git a/src/local/.indent.pro b/src/local/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/local/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/local/.printfck b/src/local/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/local/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/local/Makefile.in b/src/local/Makefile.in new file mode 100644 index 0000000..648ad51 --- /dev/null +++ b/src/local/Makefile.in @@ -0,0 +1,686 @@ +SHELL = /bin/sh +SRCS = alias.c command.c dotforward.c file.c forward.c \ + include.c indirect.c local.c mailbox.c recipient.c resolve.c token.c \ + deliver_attr.c maildir.c biff_notify.c unknown.c \ + local_expand.c bounce_workaround.c +OBJS = alias.o command.o dotforward.o file.o forward.o \ + include.o indirect.o local.o mailbox.o recipient.o resolve.o token.o \ + deliver_attr.o maildir.o biff_notify.o unknown.o \ + local_expand.o bounce_workaround.c +HDRS = local.h +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +PROG = local +TESTPROG= +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +alias.o: ../../include/argv.h +alias.o: ../../include/attr.h +alias.o: ../../include/been_here.h +alias.o: ../../include/bounce.h +alias.o: ../../include/canon_addr.h +alias.o: ../../include/check_arg.h +alias.o: ../../include/defer.h +alias.o: ../../include/deliver_request.h +alias.o: ../../include/delivered_hdr.h +alias.o: ../../include/dict.h +alias.o: ../../include/dsn.h +alias.o: ../../include/dsn_buf.h +alias.o: ../../include/dsn_mask.h +alias.o: ../../include/fold_addr.h +alias.o: ../../include/htable.h +alias.o: ../../include/mail_params.h +alias.o: ../../include/maps.h +alias.o: ../../include/mbox_conf.h +alias.o: ../../include/msg.h +alias.o: ../../include/msg_stats.h +alias.o: ../../include/myflock.h +alias.o: ../../include/mymalloc.h +alias.o: ../../include/mypwd.h +alias.o: ../../include/nvtable.h +alias.o: ../../include/recipient_list.h +alias.o: ../../include/resolve_clnt.h +alias.o: ../../include/sent.h +alias.o: ../../include/stringops.h +alias.o: ../../include/sys_defs.h +alias.o: ../../include/tok822.h +alias.o: ../../include/trace.h +alias.o: ../../include/vbuf.h +alias.o: ../../include/vstream.h +alias.o: ../../include/vstring.h +alias.o: alias.c +alias.o: local.h +biff_notify.o: ../../include/iostuff.h +biff_notify.o: ../../include/msg.h +biff_notify.o: ../../include/sys_defs.h +biff_notify.o: biff_notify.c +biff_notify.o: biff_notify.h +bounce_workaround.o: ../../include/argv.h +bounce_workaround.o: ../../include/attr.h +bounce_workaround.o: ../../include/been_here.h +bounce_workaround.o: ../../include/bounce.h +bounce_workaround.o: ../../include/canon_addr.h +bounce_workaround.o: ../../include/check_arg.h +bounce_workaround.o: ../../include/defer.h +bounce_workaround.o: ../../include/deliver_request.h +bounce_workaround.o: ../../include/delivered_hdr.h +bounce_workaround.o: ../../include/dict.h +bounce_workaround.o: ../../include/dsn.h +bounce_workaround.o: ../../include/dsn_buf.h +bounce_workaround.o: ../../include/fold_addr.h +bounce_workaround.o: ../../include/htable.h +bounce_workaround.o: ../../include/mail_params.h +bounce_workaround.o: ../../include/maps.h +bounce_workaround.o: ../../include/mbox_conf.h +bounce_workaround.o: ../../include/msg.h +bounce_workaround.o: ../../include/msg_stats.h +bounce_workaround.o: ../../include/myflock.h +bounce_workaround.o: ../../include/mymalloc.h +bounce_workaround.o: ../../include/nvtable.h +bounce_workaround.o: ../../include/recipient_list.h +bounce_workaround.o: ../../include/resolve_clnt.h +bounce_workaround.o: ../../include/split_addr.h +bounce_workaround.o: ../../include/split_at.h +bounce_workaround.o: ../../include/stringops.h +bounce_workaround.o: ../../include/strip_addr.h +bounce_workaround.o: ../../include/sys_defs.h +bounce_workaround.o: ../../include/tok822.h +bounce_workaround.o: ../../include/vbuf.h +bounce_workaround.o: ../../include/vstream.h +bounce_workaround.o: ../../include/vstring.h +bounce_workaround.o: bounce_workaround.c +bounce_workaround.o: local.h +command.o: ../../include/argv.h +command.o: ../../include/attr.h +command.o: ../../include/been_here.h +command.o: ../../include/bounce.h +command.o: ../../include/check_arg.h +command.o: ../../include/defer.h +command.o: ../../include/deliver_request.h +command.o: ../../include/delivered_hdr.h +command.o: ../../include/dict.h +command.o: ../../include/dsn.h +command.o: ../../include/dsn_buf.h +command.o: ../../include/dsn_util.h +command.o: ../../include/fold_addr.h +command.o: ../../include/htable.h +command.o: ../../include/mac_parse.h +command.o: ../../include/mail_copy.h +command.o: ../../include/mail_params.h +command.o: ../../include/mail_parm_split.h +command.o: ../../include/maps.h +command.o: ../../include/mbox_conf.h +command.o: ../../include/msg.h +command.o: ../../include/msg_stats.h +command.o: ../../include/myflock.h +command.o: ../../include/mymalloc.h +command.o: ../../include/nvtable.h +command.o: ../../include/pipe_command.h +command.o: ../../include/recipient_list.h +command.o: ../../include/resolve_clnt.h +command.o: ../../include/sent.h +command.o: ../../include/sys_defs.h +command.o: ../../include/tok822.h +command.o: ../../include/vbuf.h +command.o: ../../include/vstream.h +command.o: ../../include/vstring.h +command.o: command.c +command.o: local.h +deliver_attr.o: ../../include/argv.h +deliver_attr.o: ../../include/attr.h +deliver_attr.o: ../../include/been_here.h +deliver_attr.o: ../../include/check_arg.h +deliver_attr.o: ../../include/deliver_request.h +deliver_attr.o: ../../include/delivered_hdr.h +deliver_attr.o: ../../include/dict.h +deliver_attr.o: ../../include/dsn.h +deliver_attr.o: ../../include/dsn_buf.h +deliver_attr.o: ../../include/fold_addr.h +deliver_attr.o: ../../include/htable.h +deliver_attr.o: ../../include/maps.h +deliver_attr.o: ../../include/mbox_conf.h +deliver_attr.o: ../../include/msg.h +deliver_attr.o: ../../include/msg_stats.h +deliver_attr.o: ../../include/myflock.h +deliver_attr.o: ../../include/mymalloc.h +deliver_attr.o: ../../include/nvtable.h +deliver_attr.o: ../../include/recipient_list.h +deliver_attr.o: ../../include/resolve_clnt.h +deliver_attr.o: ../../include/sys_defs.h +deliver_attr.o: ../../include/tok822.h +deliver_attr.o: ../../include/vbuf.h +deliver_attr.o: ../../include/vstream.h +deliver_attr.o: ../../include/vstring.h +deliver_attr.o: deliver_attr.c +deliver_attr.o: local.h +dotforward.o: ../../include/argv.h +dotforward.o: ../../include/attr.h +dotforward.o: ../../include/been_here.h +dotforward.o: ../../include/bounce.h +dotforward.o: ../../include/check_arg.h +dotforward.o: ../../include/defer.h +dotforward.o: ../../include/deliver_request.h +dotforward.o: ../../include/delivered_hdr.h +dotforward.o: ../../include/dict.h +dotforward.o: ../../include/dsn.h +dotforward.o: ../../include/dsn_buf.h +dotforward.o: ../../include/dsn_mask.h +dotforward.o: ../../include/ext_prop.h +dotforward.o: ../../include/fold_addr.h +dotforward.o: ../../include/htable.h +dotforward.o: ../../include/iostuff.h +dotforward.o: ../../include/lstat_as.h +dotforward.o: ../../include/mac_expand.h +dotforward.o: ../../include/mac_parse.h +dotforward.o: ../../include/mail_conf.h +dotforward.o: ../../include/mail_params.h +dotforward.o: ../../include/maps.h +dotforward.o: ../../include/mbox_conf.h +dotforward.o: ../../include/msg.h +dotforward.o: ../../include/msg_stats.h +dotforward.o: ../../include/myflock.h +dotforward.o: ../../include/mymalloc.h +dotforward.o: ../../include/mypwd.h +dotforward.o: ../../include/nvtable.h +dotforward.o: ../../include/open_as.h +dotforward.o: ../../include/recipient_list.h +dotforward.o: ../../include/resolve_clnt.h +dotforward.o: ../../include/sent.h +dotforward.o: ../../include/stringops.h +dotforward.o: ../../include/sys_defs.h +dotforward.o: ../../include/tok822.h +dotforward.o: ../../include/trace.h +dotforward.o: ../../include/vbuf.h +dotforward.o: ../../include/vstream.h +dotforward.o: ../../include/vstring.h +dotforward.o: dotforward.c +dotforward.o: local.h +file.o: ../../include/argv.h +file.o: ../../include/attr.h +file.o: ../../include/been_here.h +file.o: ../../include/bounce.h +file.o: ../../include/check_arg.h +file.o: ../../include/defer.h +file.o: ../../include/deliver_flock.h +file.o: ../../include/deliver_request.h +file.o: ../../include/delivered_hdr.h +file.o: ../../include/dict.h +file.o: ../../include/dsn.h +file.o: ../../include/dsn_buf.h +file.o: ../../include/dsn_util.h +file.o: ../../include/fold_addr.h +file.o: ../../include/htable.h +file.o: ../../include/mail_copy.h +file.o: ../../include/mail_params.h +file.o: ../../include/maps.h +file.o: ../../include/mbox_conf.h +file.o: ../../include/mbox_open.h +file.o: ../../include/msg.h +file.o: ../../include/msg_stats.h +file.o: ../../include/myflock.h +file.o: ../../include/mymalloc.h +file.o: ../../include/nvtable.h +file.o: ../../include/recipient_list.h +file.o: ../../include/resolve_clnt.h +file.o: ../../include/safe_open.h +file.o: ../../include/sent.h +file.o: ../../include/set_eugid.h +file.o: ../../include/sys_defs.h +file.o: ../../include/tok822.h +file.o: ../../include/vbuf.h +file.o: ../../include/vstream.h +file.o: ../../include/vstring.h +file.o: file.c +file.o: local.h +forward.o: ../../include/argv.h +forward.o: ../../include/attr.h +forward.o: ../../include/been_here.h +forward.o: ../../include/bounce.h +forward.o: ../../include/check_arg.h +forward.o: ../../include/cleanup_user.h +forward.o: ../../include/deliver_request.h +forward.o: ../../include/delivered_hdr.h +forward.o: ../../include/dict.h +forward.o: ../../include/dsn.h +forward.o: ../../include/dsn_buf.h +forward.o: ../../include/dsn_mask.h +forward.o: ../../include/fold_addr.h +forward.o: ../../include/htable.h +forward.o: ../../include/iostuff.h +forward.o: ../../include/mail_date.h +forward.o: ../../include/mail_params.h +forward.o: ../../include/mail_proto.h +forward.o: ../../include/maps.h +forward.o: ../../include/mark_corrupt.h +forward.o: ../../include/mbox_conf.h +forward.o: ../../include/msg.h +forward.o: ../../include/msg_stats.h +forward.o: ../../include/myflock.h +forward.o: ../../include/mymalloc.h +forward.o: ../../include/nvtable.h +forward.o: ../../include/rec_type.h +forward.o: ../../include/recipient_list.h +forward.o: ../../include/record.h +forward.o: ../../include/resolve_clnt.h +forward.o: ../../include/sent.h +forward.o: ../../include/smtputf8.h +forward.o: ../../include/stringops.h +forward.o: ../../include/sys_defs.h +forward.o: ../../include/tok822.h +forward.o: ../../include/vbuf.h +forward.o: ../../include/vstream.h +forward.o: ../../include/vstring.h +forward.o: ../../include/vstring_vstream.h +forward.o: forward.c +forward.o: local.h +include.o: ../../include/argv.h +include.o: ../../include/attr.h +include.o: ../../include/been_here.h +include.o: ../../include/bounce.h +include.o: ../../include/check_arg.h +include.o: ../../include/defer.h +include.o: ../../include/deliver_request.h +include.o: ../../include/delivered_hdr.h +include.o: ../../include/dict.h +include.o: ../../include/dsn.h +include.o: ../../include/dsn_buf.h +include.o: ../../include/ext_prop.h +include.o: ../../include/fold_addr.h +include.o: ../../include/htable.h +include.o: ../../include/iostuff.h +include.o: ../../include/mail_params.h +include.o: ../../include/maps.h +include.o: ../../include/mbox_conf.h +include.o: ../../include/msg.h +include.o: ../../include/msg_stats.h +include.o: ../../include/myflock.h +include.o: ../../include/mymalloc.h +include.o: ../../include/mypwd.h +include.o: ../../include/nvtable.h +include.o: ../../include/open_as.h +include.o: ../../include/recipient_list.h +include.o: ../../include/resolve_clnt.h +include.o: ../../include/sent.h +include.o: ../../include/stat_as.h +include.o: ../../include/sys_defs.h +include.o: ../../include/tok822.h +include.o: ../../include/vbuf.h +include.o: ../../include/vstream.h +include.o: ../../include/vstring.h +include.o: include.c +include.o: local.h +indirect.o: ../../include/argv.h +indirect.o: ../../include/attr.h +indirect.o: ../../include/been_here.h +indirect.o: ../../include/bounce.h +indirect.o: ../../include/check_arg.h +indirect.o: ../../include/defer.h +indirect.o: ../../include/deliver_request.h +indirect.o: ../../include/delivered_hdr.h +indirect.o: ../../include/dict.h +indirect.o: ../../include/dsn.h +indirect.o: ../../include/dsn_buf.h +indirect.o: ../../include/fold_addr.h +indirect.o: ../../include/htable.h +indirect.o: ../../include/mail_params.h +indirect.o: ../../include/maps.h +indirect.o: ../../include/mbox_conf.h +indirect.o: ../../include/msg.h +indirect.o: ../../include/msg_stats.h +indirect.o: ../../include/myflock.h +indirect.o: ../../include/mymalloc.h +indirect.o: ../../include/nvtable.h +indirect.o: ../../include/recipient_list.h +indirect.o: ../../include/resolve_clnt.h +indirect.o: ../../include/sent.h +indirect.o: ../../include/sys_defs.h +indirect.o: ../../include/tok822.h +indirect.o: ../../include/vbuf.h +indirect.o: ../../include/vstream.h +indirect.o: ../../include/vstring.h +indirect.o: indirect.c +indirect.o: local.h +local.o: ../../include/argv.h +local.o: ../../include/attr.h +local.o: ../../include/been_here.h +local.o: ../../include/check_arg.h +local.o: ../../include/deliver_completed.h +local.o: ../../include/deliver_request.h +local.o: ../../include/delivered_hdr.h +local.o: ../../include/dict.h +local.o: ../../include/dsn.h +local.o: ../../include/dsn_buf.h +local.o: ../../include/ext_prop.h +local.o: ../../include/flush_clnt.h +local.o: ../../include/fold_addr.h +local.o: ../../include/htable.h +local.o: ../../include/iostuff.h +local.o: ../../include/mail_addr.h +local.o: ../../include/mail_conf.h +local.o: ../../include/mail_params.h +local.o: ../../include/mail_server.h +local.o: ../../include/mail_version.h +local.o: ../../include/maps.h +local.o: ../../include/mbox_conf.h +local.o: ../../include/msg.h +local.o: ../../include/msg_stats.h +local.o: ../../include/myflock.h +local.o: ../../include/mymalloc.h +local.o: ../../include/name_mask.h +local.o: ../../include/nvtable.h +local.o: ../../include/recipient_list.h +local.o: ../../include/resolve_clnt.h +local.o: ../../include/set_eugid.h +local.o: ../../include/sys_defs.h +local.o: ../../include/tok822.h +local.o: ../../include/vbuf.h +local.o: ../../include/vstream.h +local.o: ../../include/vstring.h +local.o: local.c +local.o: local.h +local_expand.o: ../../include/argv.h +local_expand.o: ../../include/attr.h +local_expand.o: ../../include/been_here.h +local_expand.o: ../../include/check_arg.h +local_expand.o: ../../include/deliver_request.h +local_expand.o: ../../include/delivered_hdr.h +local_expand.o: ../../include/dict.h +local_expand.o: ../../include/dsn.h +local_expand.o: ../../include/dsn_buf.h +local_expand.o: ../../include/fold_addr.h +local_expand.o: ../../include/htable.h +local_expand.o: ../../include/mac_expand.h +local_expand.o: ../../include/mac_parse.h +local_expand.o: ../../include/mail_params.h +local_expand.o: ../../include/maps.h +local_expand.o: ../../include/mbox_conf.h +local_expand.o: ../../include/msg_stats.h +local_expand.o: ../../include/myflock.h +local_expand.o: ../../include/mymalloc.h +local_expand.o: ../../include/nvtable.h +local_expand.o: ../../include/recipient_list.h +local_expand.o: ../../include/resolve_clnt.h +local_expand.o: ../../include/sys_defs.h +local_expand.o: ../../include/tok822.h +local_expand.o: ../../include/vbuf.h +local_expand.o: ../../include/vstream.h +local_expand.o: ../../include/vstring.h +local_expand.o: local.h +local_expand.o: local_expand.c +mailbox.o: ../../include/argv.h +mailbox.o: ../../include/attr.h +mailbox.o: ../../include/been_here.h +mailbox.o: ../../include/bounce.h +mailbox.o: ../../include/check_arg.h +mailbox.o: ../../include/defer.h +mailbox.o: ../../include/deliver_pass.h +mailbox.o: ../../include/deliver_request.h +mailbox.o: ../../include/delivered_hdr.h +mailbox.o: ../../include/dict.h +mailbox.o: ../../include/dsn.h +mailbox.o: ../../include/dsn_buf.h +mailbox.o: ../../include/dsn_util.h +mailbox.o: ../../include/fold_addr.h +mailbox.o: ../../include/htable.h +mailbox.o: ../../include/iostuff.h +mailbox.o: ../../include/mail_copy.h +mailbox.o: ../../include/mail_params.h +mailbox.o: ../../include/mail_proto.h +mailbox.o: ../../include/maps.h +mailbox.o: ../../include/mbox_conf.h +mailbox.o: ../../include/mbox_open.h +mailbox.o: ../../include/msg.h +mailbox.o: ../../include/msg_stats.h +mailbox.o: ../../include/myflock.h +mailbox.o: ../../include/mymalloc.h +mailbox.o: ../../include/mypwd.h +mailbox.o: ../../include/nvtable.h +mailbox.o: ../../include/recipient_list.h +mailbox.o: ../../include/resolve_clnt.h +mailbox.o: ../../include/safe_open.h +mailbox.o: ../../include/sent.h +mailbox.o: ../../include/set_eugid.h +mailbox.o: ../../include/stringops.h +mailbox.o: ../../include/sys_defs.h +mailbox.o: ../../include/tok822.h +mailbox.o: ../../include/vbuf.h +mailbox.o: ../../include/vstream.h +mailbox.o: ../../include/vstring.h +mailbox.o: ../../include/warn_stat.h +mailbox.o: biff_notify.h +mailbox.o: local.h +mailbox.o: mailbox.c +maildir.o: ../../include/argv.h +maildir.o: ../../include/attr.h +maildir.o: ../../include/been_here.h +maildir.o: ../../include/bounce.h +maildir.o: ../../include/check_arg.h +maildir.o: ../../include/defer.h +maildir.o: ../../include/deliver_request.h +maildir.o: ../../include/delivered_hdr.h +maildir.o: ../../include/dict.h +maildir.o: ../../include/dsn.h +maildir.o: ../../include/dsn_buf.h +maildir.o: ../../include/dsn_util.h +maildir.o: ../../include/fold_addr.h +maildir.o: ../../include/get_hostname.h +maildir.o: ../../include/htable.h +maildir.o: ../../include/mail_copy.h +maildir.o: ../../include/mail_params.h +maildir.o: ../../include/make_dirs.h +maildir.o: ../../include/maps.h +maildir.o: ../../include/mbox_conf.h +maildir.o: ../../include/mbox_open.h +maildir.o: ../../include/msg.h +maildir.o: ../../include/msg_stats.h +maildir.o: ../../include/myflock.h +maildir.o: ../../include/mymalloc.h +maildir.o: ../../include/nvtable.h +maildir.o: ../../include/recipient_list.h +maildir.o: ../../include/resolve_clnt.h +maildir.o: ../../include/safe_open.h +maildir.o: ../../include/sane_fsops.h +maildir.o: ../../include/sent.h +maildir.o: ../../include/set_eugid.h +maildir.o: ../../include/stringops.h +maildir.o: ../../include/sys_defs.h +maildir.o: ../../include/tok822.h +maildir.o: ../../include/vbuf.h +maildir.o: ../../include/vstream.h +maildir.o: ../../include/vstring.h +maildir.o: ../../include/warn_stat.h +maildir.o: local.h +maildir.o: maildir.c +recipient.o: ../../include/argv.h +recipient.o: ../../include/attr.h +recipient.o: ../../include/been_here.h +recipient.o: ../../include/bounce.h +recipient.o: ../../include/canon_addr.h +recipient.o: ../../include/check_arg.h +recipient.o: ../../include/defer.h +recipient.o: ../../include/deliver_request.h +recipient.o: ../../include/delivered_hdr.h +recipient.o: ../../include/dict.h +recipient.o: ../../include/dsn.h +recipient.o: ../../include/dsn_buf.h +recipient.o: ../../include/ext_prop.h +recipient.o: ../../include/fold_addr.h +recipient.o: ../../include/htable.h +recipient.o: ../../include/mail_params.h +recipient.o: ../../include/maps.h +recipient.o: ../../include/mbox_conf.h +recipient.o: ../../include/msg.h +recipient.o: ../../include/msg_stats.h +recipient.o: ../../include/myflock.h +recipient.o: ../../include/mymalloc.h +recipient.o: ../../include/mypwd.h +recipient.o: ../../include/nvtable.h +recipient.o: ../../include/recipient_list.h +recipient.o: ../../include/resolve_clnt.h +recipient.o: ../../include/split_addr.h +recipient.o: ../../include/split_at.h +recipient.o: ../../include/stat_as.h +recipient.o: ../../include/stringops.h +recipient.o: ../../include/strip_addr.h +recipient.o: ../../include/sys_defs.h +recipient.o: ../../include/tok822.h +recipient.o: ../../include/vbuf.h +recipient.o: ../../include/vstream.h +recipient.o: ../../include/vstring.h +recipient.o: local.h +recipient.o: recipient.c +resolve.o: ../../include/argv.h +resolve.o: ../../include/attr.h +resolve.o: ../../include/been_here.h +resolve.o: ../../include/bounce.h +resolve.o: ../../include/check_arg.h +resolve.o: ../../include/defer.h +resolve.o: ../../include/deliver_request.h +resolve.o: ../../include/delivered_hdr.h +resolve.o: ../../include/dict.h +resolve.o: ../../include/dsn.h +resolve.o: ../../include/dsn_buf.h +resolve.o: ../../include/fold_addr.h +resolve.o: ../../include/htable.h +resolve.o: ../../include/iostuff.h +resolve.o: ../../include/mail_params.h +resolve.o: ../../include/mail_proto.h +resolve.o: ../../include/maps.h +resolve.o: ../../include/mbox_conf.h +resolve.o: ../../include/msg.h +resolve.o: ../../include/msg_stats.h +resolve.o: ../../include/myflock.h +resolve.o: ../../include/mymalloc.h +resolve.o: ../../include/nvtable.h +resolve.o: ../../include/recipient_list.h +resolve.o: ../../include/resolve_clnt.h +resolve.o: ../../include/rewrite_clnt.h +resolve.o: ../../include/sys_defs.h +resolve.o: ../../include/tok822.h +resolve.o: ../../include/vbuf.h +resolve.o: ../../include/vstream.h +resolve.o: ../../include/vstring.h +resolve.o: local.h +resolve.o: resolve.c +token.o: ../../include/argv.h +token.o: ../../include/attr.h +token.o: ../../include/been_here.h +token.o: ../../include/bounce.h +token.o: ../../include/check_arg.h +token.o: ../../include/defer.h +token.o: ../../include/deliver_request.h +token.o: ../../include/delivered_hdr.h +token.o: ../../include/dict.h +token.o: ../../include/dsn.h +token.o: ../../include/dsn_buf.h +token.o: ../../include/fold_addr.h +token.o: ../../include/htable.h +token.o: ../../include/mail_params.h +token.o: ../../include/maps.h +token.o: ../../include/mbox_conf.h +token.o: ../../include/msg.h +token.o: ../../include/msg_stats.h +token.o: ../../include/myflock.h +token.o: ../../include/mymalloc.h +token.o: ../../include/nvtable.h +token.o: ../../include/readlline.h +token.o: ../../include/recipient_list.h +token.o: ../../include/resolve_clnt.h +token.o: ../../include/stringops.h +token.o: ../../include/sys_defs.h +token.o: ../../include/tok822.h +token.o: ../../include/vbuf.h +token.o: ../../include/vstream.h +token.o: ../../include/vstring.h +token.o: ../../include/vstring_vstream.h +token.o: local.h +token.o: token.c +unknown.o: ../../include/argv.h +unknown.o: ../../include/attr.h +unknown.o: ../../include/been_here.h +unknown.o: ../../include/bounce.h +unknown.o: ../../include/canon_addr.h +unknown.o: ../../include/check_arg.h +unknown.o: ../../include/defer.h +unknown.o: ../../include/deliver_pass.h +unknown.o: ../../include/deliver_request.h +unknown.o: ../../include/delivered_hdr.h +unknown.o: ../../include/dict.h +unknown.o: ../../include/dsn.h +unknown.o: ../../include/dsn_buf.h +unknown.o: ../../include/fold_addr.h +unknown.o: ../../include/htable.h +unknown.o: ../../include/iostuff.h +unknown.o: ../../include/mail_addr.h +unknown.o: ../../include/mail_params.h +unknown.o: ../../include/mail_proto.h +unknown.o: ../../include/maps.h +unknown.o: ../../include/mbox_conf.h +unknown.o: ../../include/msg.h +unknown.o: ../../include/msg_stats.h +unknown.o: ../../include/myflock.h +unknown.o: ../../include/mymalloc.h +unknown.o: ../../include/nvtable.h +unknown.o: ../../include/recipient_list.h +unknown.o: ../../include/resolve_clnt.h +unknown.o: ../../include/sent.h +unknown.o: ../../include/stringops.h +unknown.o: ../../include/sys_defs.h +unknown.o: ../../include/tok822.h +unknown.o: ../../include/vbuf.h +unknown.o: ../../include/vstream.h +unknown.o: ../../include/vstring.h +unknown.o: local.h +unknown.o: unknown.c diff --git a/src/local/Musings b/src/local/Musings new file mode 100644 index 0000000..6149a2e --- /dev/null +++ b/src/local/Musings @@ -0,0 +1,39 @@ +Local delivery models + +The "monolithic" model: recursively expand the complete initial +recipient list (via aliases, mailing lists, .forward files) to one +expanded recipient list (mail addresses, shell commands, files, +mailboxes). Sort/uniq the expanded recipient list, and deliver. + +The "forward as if sent by recipient" model: each level of recursion +(aliases, mailing lists, forward files) takes one entire iteration +through the mail system. Non-recursively expand one local recipient +(via alias, mailing list, the recipient's .forward file) to a list +of expanded recipients. Sort/uniq the list and deliver by re-injecting +messages into the mail system. Since recipient expansion uses a +non-recursive algorithm, the mailer might loop indefinitely, +re-injecting messages into itself. These local forwarding loops +must be broken by stamping a message when it reaches the local +delivery stage (e.g., by adding a Delivered-To: message header). + +The Postfix system uses a hybrid approach. It does recursive alias +expansion, but only one initial recipient at a time. It delivers +to expanded recipients by re-submitting the message into the mail +system, so it can keep track of the delivery status for each expanded +recipient. Because alias expansion does not look in .forward files, +it cannot prevent local forwarding loops. The Postfix system adds +Delivered: message headers to break local and external forwarding +loops. + +Delivery status management + +The "exact" model: maintain on file the delivery status of each +expanded recipient: remote recipients, shell commands and files, +including the privileges for delivery to shell commands and files. + +The "safe" model: maintain on file only the delivery status of +non-sensitive destinations (local or remote addresses). Deliver to +sensitive destinations first (commands, files), but do not keep a +record of their status on file (including privileges). This means +that the mail system will occasionally deliver the same message +more than once to a file or command. diff --git a/src/local/alias.c b/src/local/alias.c new file mode 100644 index 0000000..99e3dd6 --- /dev/null +++ b/src/local/alias.c @@ -0,0 +1,386 @@ +/*++ +/* NAME +/* alias 3 +/* SUMMARY +/* alias data base lookups +/* SYNOPSIS +/* #include "local.h" +/* +/* int deliver_alias(state, usr_attr, name, statusp) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* char *name; +/* int *statusp; +/* DESCRIPTION +/* deliver_alias() looks up the expansion of the recipient in +/* the global alias database and delivers the message to the +/* listed destinations. The result is zero when no alias was found +/* or when the message should be delivered to the user instead. +/* +/* deliver_alias() has wired-in knowledge about a few reserved +/* recipient names. +/* .IP \(bu +/* When no alias is found for the local \fIpostmaster\fR or +/* \fImailer-daemon\fR a warning is issued and the message +/* is discarded. +/* .IP \(bu +/* When an alias exists for recipient \fIname\fR, and an alias +/* exists for \fIowner-name\fR, the sender address is changed +/* to \fIowner-name\fR, and the owner delivery attribute is +/* set accordingly. This feature is disabled with +/* "owner_request_special = no". +/* .PP +/* Arguments: +/* .IP state +/* Attributes that specify the message, recipient and more. +/* Expansion type (alias, include, .forward). +/* A table with the results from expanding aliases or lists. +/* A table with delivered-to: addresses taken from the message. +/* .IP usr_attr +/* User attributes (rights, environment). +/* .IP name +/* The alias to be looked up. +/* .IP statusp +/* Delivery status. See below. +/* DIAGNOSTICS +/* Fatal errors: out of memory. The delivery status is non-zero +/* when delivery should be tried again. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" + +/* Application-specific. */ + +#define NO 0 +#define YES 1 + +/* deliver_alias - expand alias file entry */ + +int deliver_alias(LOCAL_STATE state, USER_ATTR usr_attr, + char *name, int *statusp) +{ + const char *myname = "deliver_alias"; + const char *alias_result; + char *saved_alias_result; + char *owner; + char **cpp; + struct mypasswd *alias_pwd; + VSTRING *canon_owner; + DICT *dict; + const char *owner_rhs; /* owner alias, RHS */ + int alias_count; + int dsn_notify; + char *dsn_envid; + int dsn_ret; + const char *dsn_orcpt; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * DUPLICATE/LOOP ELIMINATION + * + * We cannot do duplicate elimination here. Sendmail compatibility requires + * that we allow multiple deliveries to the same alias, even recursively! + * For example, we must deliver to mailbox any messages that are addressed + * to the alias of a user that lists that same alias in her own .forward + * file. Yuck! This is just an example of some really perverse semantics + * that people will expect Postfix to implement just like sendmail. + * + * We can recognize one special case: when an alias includes its own name, + * deliver to the user instead, just like sendmail. Otherwise, we just + * bail out when nesting reaches some unreasonable depth, and blame it on + * a possible alias loop. + */ + if (state.msg_attr.exp_from != 0 + && strcasecmp_utf8(state.msg_attr.exp_from, name) == 0) + return (NO); + if (state.level > 100) { + msg_warn("alias database loop for %s", name); + dsb_simple(state.msg_attr.why, "5.4.6", + "alias database loop for %s", name); + *statusp = bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + return (YES); + } + state.msg_attr.exp_from = name; + + /* + * There are a bunch of roles that we're trying to keep track of. + * + * First, there's the issue of whose rights should be used when delivering + * to "|command" or to /file/name. With alias databases, the rights are + * those of who owns the alias, i.e. the database owner. With aliases + * owned by root, a default user is used instead. When an alias with + * default rights references an include file owned by an ordinary user, + * we must use the rights of the include file owner, otherwise the + * include file owner could take control of the default account. + * + * Secondly, there's the question of who to notify of delivery problems. + * With aliases that have an owner- alias, the latter is used to set the + * sender and owner attributes. Otherwise, the owner attribute is reset + * (the alias is globally visible and could be sent to by anyone). + */ + for (cpp = alias_maps->argv->argv; *cpp; cpp++) { + if ((dict = dict_handle(*cpp)) == 0) + msg_panic("%s: dictionary not found: %s", myname, *cpp); + if ((alias_result = dict_get(dict, name)) != 0) { + if (msg_verbose) + msg_info("%s: %s: %s = %s", myname, *cpp, name, alias_result); + + /* + * Don't expand a verify-only request. + */ + if (state.request->flags & DEL_REQ_FLAG_MTA_VRFY) { + dsb_simple(state.msg_attr.why, "2.0.0", + "aliased to %s", alias_result); + *statusp = sent(BOUNCE_FLAGS(state.request), + SENT_ATTR(state.msg_attr)); + return (YES); + } + + /* + * DELIVERY POLICY + * + * Update the expansion type attribute, so we can decide if + * deliveries to |command and /file/name are allowed at all. + */ + state.msg_attr.exp_type = EXPAND_TYPE_ALIAS; + + /* + * DELIVERY RIGHTS + * + * What rights to use for |command and /file/name deliveries? The + * command and file code will use default rights when the alias + * database is owned by root, otherwise it will use the rights of + * the alias database owner. + */ + if (dict->owner.status == DICT_OWNER_TRUSTED) { + alias_pwd = 0; + RESET_USER_ATTR(usr_attr, state.level); + } else { + if (dict->owner.status == DICT_OWNER_UNKNOWN) { + msg_warn("%s: no owner UID for alias database %s", + myname, *cpp); + dsb_simple(state.msg_attr.why, "4.3.0", + "mail system configuration error"); + *statusp = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + return (YES); + } + if ((errno = mypwuid_err(dict->owner.uid, &alias_pwd)) != 0 + || alias_pwd == 0) { + msg_warn(errno ? + "cannot find alias database owner for %s: %m" : + "cannot find alias database owner for %s", *cpp); + dsb_simple(state.msg_attr.why, "4.3.0", + "cannot find alias database owner"); + *statusp = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + return (YES); + } + SET_USER_ATTR(usr_attr, alias_pwd, state.level); + } + + /* + * WHERE TO REPORT DELIVERY PROBLEMS. + * + * Use the owner- alias if one is specified, otherwise reset the + * owner attribute and use the include file ownership if we can. + * Save the dict_lookup() result before something clobbers it. + * + * Don't match aliases that are based on regexps. + */ +#define OWNER_ASSIGN(own) \ + (own = (var_ownreq_special == 0 ? 0 : \ + concatenate("owner-", name, (char *) 0))) + + saved_alias_result = mystrdup(alias_result); + if (OWNER_ASSIGN(owner) != 0 + && (owner_rhs = maps_find(alias_maps, owner, DICT_FLAG_NONE)) != 0) { + canon_owner = canon_addr_internal(vstring_alloc(10), + var_exp_own_alias ? owner_rhs : owner); + /* Set envelope sender and owner attribute. */ + SET_OWNER_ATTR(state.msg_attr, STR(canon_owner), state.level); + } else { + canon_owner = 0; + /* Note: this does not reset the envelope sender. */ + if (var_reset_owner_attr) + RESET_OWNER_ATTR(state.msg_attr, state.level); + } + + /* + * EXTERNAL LOOP CONTROL + * + * Set the delivered message attribute to the recipient, so that + * this message will list the correct forwarding address. + */ + if (var_frozen_delivered == 0) + state.msg_attr.delivered = state.msg_attr.rcpt.address; + + /* + * Deliver. + */ + alias_count = 0; + if (owner != 0 && alias_maps->error != 0) { + dsb_simple(state.msg_attr.why, "4.3.0", + "alias database unavailable"); + *statusp = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + } else { + + /* + * XXX DSN + * + * When delivering to a mailing list (i.e. the envelope sender + * is replaced) the ENVID, NOTIFY, RET, and ORCPT parameters + * which accompany the redistributed message MUST NOT be + * derived from those of the original message. + * + * When delivering to an alias (i.e. the envelope sender is not + * replaced) any ENVID, RET, or ORCPT parameters are + * propagated to all forwarding addresses associated with + * that alias. The NOTIFY parameter is propagated to the + * forwarding addresses, except that any SUCCESS keyword is + * removed. + */ +#define DSN_SAVE_UPDATE(saved, old, new) do { \ + saved = old; \ + old = new; \ + } while (0) + + DSN_SAVE_UPDATE(dsn_notify, state.msg_attr.rcpt.dsn_notify, + dsn_notify == DSN_NOTIFY_SUCCESS ? + DSN_NOTIFY_NEVER : + dsn_notify & ~DSN_NOTIFY_SUCCESS); + if (canon_owner != 0) { + DSN_SAVE_UPDATE(dsn_envid, state.msg_attr.dsn_envid, ""); + DSN_SAVE_UPDATE(dsn_ret, state.msg_attr.dsn_ret, 0); + DSN_SAVE_UPDATE(dsn_orcpt, state.msg_attr.rcpt.dsn_orcpt, ""); + state.msg_attr.rcpt.orig_addr = ""; + } + *statusp = + deliver_token_string(state, usr_attr, saved_alias_result, + &alias_count); +#if 0 + if (var_ownreq_special + && strncmp("owner-", state.msg_attr.sender, 6) != 0 + && alias_count > 10) + msg_warn("mailing list \"%s\" needs an \"owner-%s\" alias", + name, name); +#endif + if (alias_count < 1) { + msg_warn("no recipient in alias lookup result for %s", name); + dsb_simple(state.msg_attr.why, "4.3.0", + "alias database unavailable"); + *statusp = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + } else { + + /* + * XXX DSN + * + * When delivering to a mailing list (i.e. the envelope + * sender address is replaced) and NOTIFY=SUCCESS was + * specified, report a DSN of "delivered". + * + * When delivering to an alias (i.e. the envelope sender + * address is not replaced) and NOTIFY=SUCCESS was + * specified, report a DSN of "expanded". + */ + if (dsn_notify & DSN_NOTIFY_SUCCESS) { + state.msg_attr.rcpt.dsn_notify = dsn_notify; + if (canon_owner != 0) { + state.msg_attr.dsn_envid = dsn_envid; + state.msg_attr.dsn_ret = dsn_ret; + state.msg_attr.rcpt.dsn_orcpt = dsn_orcpt; + } + dsb_update(state.msg_attr.why, "2.0.0", canon_owner ? + "delivered" : "expanded", + DSB_SKIP_RMTA, DSB_SKIP_REPLY, + "alias expanded"); + (void) trace_append(BOUNCE_FLAG_NONE, + SENT_ATTR(state.msg_attr)); + } + } + } + myfree(saved_alias_result); + if (owner) + myfree(owner); + if (canon_owner) + vstring_free(canon_owner); + if (alias_pwd) + mypwfree(alias_pwd); + return (YES); + } + + /* + * If the alias database was inaccessible for some reason, defer + * further delivery for the current top-level recipient. + */ + if (alias_result == 0 && dict->error != 0) { + msg_warn("%s:%s: lookup of '%s' failed", + dict->type, dict->name, name); + dsb_simple(state.msg_attr.why, "4.3.0", + "alias database unavailable"); + *statusp = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + return (YES); + } else { + if (msg_verbose) + msg_info("%s: %s: %s not found", myname, *cpp, name); + } + } + + /* + * Try delivery to a local user instead. + */ + return (NO); +} diff --git a/src/local/biff_notify.c b/src/local/biff_notify.c new file mode 100644 index 0000000..a6a4925 --- /dev/null +++ b/src/local/biff_notify.c @@ -0,0 +1,98 @@ +/*++ +/* NAME +/* biff_notify 3 +/* SUMMARY +/* send biff notification +/* SYNOPSIS +/* #include +/* +/* void biff_notify(text, len) +/* const char *text; +/* ssize_t len; +/* DESCRIPTION +/* biff_notify() sends a \fBBIFF\fR notification request to the +/* \fBcomsat\fR daemon. +/* +/* Arguments: +/* .IP text +/* Null-terminated text (username@mailbox-offset). +/* .IP len +/* Length of text, including null terminator. +/* BUGS +/* The \fBBIFF\fR "service" can be a noticeable load for +/* systems that have many logged-in users. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include + +/* Application-specific. */ + +#include + +/* biff_notify - notify recipient via the biff "protocol" */ + +void biff_notify(const char *text, ssize_t len) +{ + static struct sockaddr_in sin; + static int sock = -1; + struct hostent *hp; + struct servent *sp; + + /* + * Initialize a socket address structure, or re-use an existing one. + */ + if (sin.sin_family == 0) { + if ((sp = getservbyname("biff", "udp")) == 0) { + msg_warn("service not found: biff/udp"); + return; + } + if ((hp = gethostbyname("localhost")) == 0) { + msg_warn("host not found: localhost"); + return; + } + if ((int) hp->h_length > (int) sizeof(sin.sin_addr)) { + msg_warn("bad address size %d for localhost", hp->h_length); + return; + } + sin.sin_family = hp->h_addrtype; + sin.sin_port = sp->s_port; + memcpy((void *) &sin.sin_addr, hp->h_addr_list[0], hp->h_length); + } + + /* + * Open a socket, or re-use an existing one. + */ + if (sock < 0) { + if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + msg_warn("socket: %m"); + return; + } + close_on_exec(sock, CLOSE_ON_EXEC); + } + + /* + * Biff! + */ + if (sendto(sock, text, len, 0, (struct sockaddr *) &sin, sizeof(sin)) != len) + msg_warn("biff_notify: %m"); +} diff --git a/src/local/biff_notify.h b/src/local/biff_notify.h new file mode 100644 index 0000000..8b76f9d --- /dev/null +++ b/src/local/biff_notify.h @@ -0,0 +1,30 @@ +#ifndef _BIFF_H_INCLUDED_ +#define _BIFF_H_INCLUDED_ + +/*++ +/* NAME +/* biff_notify 3h +/* SUMMARY +/* read logical line +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern void biff_notify(const char *, ssize_t); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/local/bounce_workaround.c b/src/local/bounce_workaround.c new file mode 100644 index 0000000..7fe4aaa --- /dev/null +++ b/src/local/bounce_workaround.c @@ -0,0 +1,159 @@ +/*++ +/* NAME +/* bounce_workaround 3 +/* SUMMARY +/* Send non-delivery notification with sender override +/* SYNOPSIS +/* #include "local.h" +/* +/* int bounce_workaround(state) +/* LOCAL_STATE state; +/* DESCRIPTION +/* This module works around a limitation in the bounce daemon +/* protocol, namely, the assumption that the envelope sender +/* address in a queue file is the delivery status notification +/* address for all recipients in that queue file. The assumption +/* is not valid when the local(8) delivery agent overrides the +/* envelope sender address by an owner- alias, for one or more +/* recipients in the queue file. +/* +/* Sender address override is a problem only when delivering +/* to command or file, or when breaking a Delivered-To loop. +/* The local(8) delivery agent saves normal recipients to a +/* new queue file, together with the replacement envelope +/* sender address; delivery then proceeds from that new queue +/* file, and no workaround is needed. +/* +/* The workaround sends one non-delivery notification for each +/* failed delivery that has a replacement sender address. The +/* notifications are not aggregated, unlike notifications to +/* non-replaced sender addresses. In practice, a local alias +/* rarely has more than one file or command destination (if +/* only because soft error handling is problematic). +/* +/* Arguments: +/* .IP state +/* The attributes that specify the message, recipient and more. +/* Attributes describing alias, include or forward expansion. +/* A table with the results from expanding aliases or lists. +/* A table with delivered-to: addresses taken from the message. +/* The non-delivery status must be either 4.X.X or 5.X.X. +/* DIAGNOSTICS +/* Fatal errors: out of memory. The result is non-zero when +/* the operation should be tried again. Warnings: malformed +/* address. +/* BUGS +/* The proper fix is to record in the bounce logfile an error +/* return address for each individual recipient. This would +/* eliminate the need for VERP-specific bounce protocol code, +/* and would move complexity from the bounce client side to +/* the bounce server side where it more likely belongs. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this +/* software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" + +int bounce_workaround(LOCAL_STATE state) +{ + const char *myname = "bounce_workaround"; + VSTRING *canon_owner = 0; + int rcpt_stat; + + /* + * Look up the substitute sender address. + */ + if (var_ownreq_special) { + char *stripped_recipient; + char *owner_alias; + const char *owner_expansion; + +#define FIND_OWNER(lhs, rhs, addr) { \ + lhs = concatenate("owner-", addr, (char *) 0); \ + (void) split_at_right(lhs, '@'); \ + rhs = maps_find(alias_maps, lhs, DICT_FLAG_NONE); \ + } + + FIND_OWNER(owner_alias, owner_expansion, state.msg_attr.rcpt.address); + if (alias_maps->error == 0 && owner_expansion == 0 + && (stripped_recipient = strip_addr(state.msg_attr.rcpt.address, + (char **) 0, + var_rcpt_delim)) != 0) { + myfree(owner_alias); + FIND_OWNER(owner_alias, owner_expansion, stripped_recipient); + myfree(stripped_recipient); + } + if (alias_maps->error == 0 && owner_expansion != 0) { + canon_owner = canon_addr_internal(vstring_alloc(10), + var_exp_own_alias ? + owner_expansion : owner_alias); + SET_OWNER_ATTR(state.msg_attr, STR(canon_owner), state.level); + } + myfree(owner_alias); + if (alias_maps->error != 0) { + /* At this point, canon_owner == 0. */ + dsb_simple(state.msg_attr.why, "4.3.0", + "alias database unavailable"); + return (defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr))); + } + } + + /* + * Send a delivery status notification with a single recipient to the + * substitute sender address, before completion of the delivery request. + */ + if (canon_owner) { + rcpt_stat = + (STR(state.msg_attr.why->status)[0] == '4' ? + defer_one : bounce_one) + (BOUNCE_FLAGS(state.request), + BOUNCE_ONE_ATTR(state.msg_attr)); + vstring_free(canon_owner); + } + + /* + * Send a regular delivery status notification, after completion of the + * delivery request. + */ + else { + rcpt_stat = + (STR(state.msg_attr.why->status)[0] == '4' ? + defer_append : bounce_append) + (BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + } + return (rcpt_stat); +} diff --git a/src/local/command.c b/src/local/command.c new file mode 100644 index 0000000..4781daf --- /dev/null +++ b/src/local/command.c @@ -0,0 +1,251 @@ +/*++ +/* NAME +/* command 3 +/* SUMMARY +/* message delivery to shell command +/* SYNOPSIS +/* #include "local.h" +/* +/* int deliver_command(state, usr_attr, command) +/* LOCAL_STATE state; +/* USER_ATTR exp_attr; +/* const char *command; +/* DESCRIPTION +/* deliver_command() runs a command with a message as standard +/* input. A limited amount of standard output and standard error +/* output is captured for diagnostics purposes. +/* Duplicate commands for the same recipient are suppressed. +/* A limited amount of information is exported via the environment: +/* HOME, SHELL, LOGNAME, USER, EXTENSION, DOMAIN, RECIPIENT (entire +/* address) LOCAL (just the local part) and SENDER. The exported +/* information is censored with var_cmd_filter. +/* +/* Arguments: +/* .IP state +/* The attributes that specify the message, recipient and more. +/* Attributes describing the alias, include or forward expansion. +/* A table with the results from expanding aliases or lists. +/* .IP usr_attr +/* Attributes describing user rights and environment. +/* .IP command +/* The shell command to be executed. If possible, the command is +/* executed without actually invoking a shell. if the command is +/* the mailbox_command, it is subjected to $name expansion. +/* DIAGNOSTICS +/* deliver_command() returns non-zero when delivery should be +/* tried again, +/* SEE ALSO +/* mailbox(3) deliver to mailbox +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" + +/* deliver_command - deliver to shell command */ + +int deliver_command(LOCAL_STATE state, USER_ATTR usr_attr, const char *command) +{ + const char *myname = "deliver_command"; + DSN_BUF *why = state.msg_attr.why; + int cmd_status; + int deliver_status; + ARGV *env; + int copy_flags; + char **cpp; + char *cp; + ARGV *export_env; + VSTRING *exec_dir; + int expand_status; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * DUPLICATE ELIMINATION + * + * Skip this command if it was already delivered to as this user. + */ + if (been_here(state.dup_filter, "command %s:%ld %s", + state.msg_attr.user, (long) usr_attr.uid, command)) + return (0); + + /* + * Don't deliver a trace-only request. + */ + if (DEL_REQ_TRACE_ONLY(state.request->flags)) { + dsb_simple(why, "2.0.0", "delivers to command: %s", command); + return (sent(BOUNCE_FLAGS(state.request), + SENT_ATTR(state.msg_attr))); + } + + /* + * DELIVERY RIGHTS + * + * Choose a default uid and gid when none have been selected (i.e. values + * are still zero). + */ + if (usr_attr.uid == 0 && (usr_attr.uid = var_default_uid) == 0) + msg_panic("privileged default user id"); + if (usr_attr.gid == 0 && (usr_attr.gid = var_default_gid) == 0) + msg_panic("privileged default group id"); + + /* + * Deliver. + */ + copy_flags = MAIL_COPY_FROM | MAIL_COPY_RETURN_PATH + | MAIL_COPY_ORIG_RCPT; + if (local_deliver_hdr_mask & DELIVER_HDR_CMD) + copy_flags |= MAIL_COPY_DELIVERED; + + if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) + msg_fatal("%s: seek queue file %s: %m", + myname, VSTREAM_PATH(state.msg_attr.fp)); + + /* + * Pass additional environment information. XXX This should be + * configurable. However, passing untrusted information via environment + * parameters opens up a whole can of worms. Lesson from web servers: + * don't let any network data even near a shell. It causes trouble. + */ + env = argv_alloc(1); + if (usr_attr.home) + argv_add(env, "HOME", usr_attr.home, ARGV_END); + argv_add(env, + "LOGNAME", state.msg_attr.user, + "USER", state.msg_attr.user, + "SENDER", state.msg_attr.sender, + "RECIPIENT", state.msg_attr.rcpt.address, + "LOCAL", state.msg_attr.local, + ARGV_END); + if (usr_attr.shell) + argv_add(env, "SHELL", usr_attr.shell, ARGV_END); + if (state.msg_attr.domain) + argv_add(env, "DOMAIN", state.msg_attr.domain, ARGV_END); + if (state.msg_attr.extension) + argv_add(env, "EXTENSION", state.msg_attr.extension, ARGV_END); + if (state.msg_attr.rcpt.orig_addr && state.msg_attr.rcpt.orig_addr[0]) + argv_add(env, "ORIGINAL_RECIPIENT", state.msg_attr.rcpt.orig_addr, + ARGV_END); + +#define EXPORT_REQUEST(name, value) \ + if ((value)[0]) argv_add(env, (name), (value), ARGV_END); + + EXPORT_REQUEST("CLIENT_HOSTNAME", state.msg_attr.request->client_name); + EXPORT_REQUEST("CLIENT_ADDRESS", state.msg_attr.request->client_addr); + EXPORT_REQUEST("CLIENT_HELO", state.msg_attr.request->client_helo); + EXPORT_REQUEST("CLIENT_PROTOCOL", state.msg_attr.request->client_proto); + EXPORT_REQUEST("SASL_METHOD", state.msg_attr.request->sasl_method); + EXPORT_REQUEST("SASL_SENDER", state.msg_attr.request->sasl_sender); + EXPORT_REQUEST("SASL_USERNAME", state.msg_attr.request->sasl_username); + + argv_terminate(env); + + /* + * Censor out undesirable characters from exported data. + */ + for (cpp = env->argv; *cpp; cpp += 2) + for (cp = cpp[1]; *(cp += strspn(cp, var_cmd_exp_filter)) != 0;) + *cp++ = '_'; + + /* + * Evaluate the command execution directory. Defer delivery if expansion + * fails. + */ + export_env = mail_parm_split(VAR_EXPORT_ENVIRON, var_export_environ); + exec_dir = vstring_alloc(10); + expand_status = local_expand(exec_dir, var_exec_directory, + &state, &usr_attr, var_exec_exp_filter); + + if (expand_status & MAC_PARSE_ERROR) { + cmd_status = PIPE_STAT_DEFER; + dsb_simple(why, "4.3.5", "mail system configuration error"); + msg_warn("bad parameter value syntax for %s: %s", + VAR_EXEC_DIRECTORY, var_exec_directory); + } else { + cmd_status = pipe_command(state.msg_attr.fp, why, + CA_PIPE_CMD_UID(usr_attr.uid), + CA_PIPE_CMD_GID(usr_attr.gid), + CA_PIPE_CMD_COMMAND(command), + CA_PIPE_CMD_COPY_FLAGS(copy_flags), + CA_PIPE_CMD_SENDER(state.msg_attr.sender), + CA_PIPE_CMD_ORIG_RCPT(state.msg_attr.rcpt.orig_addr), + CA_PIPE_CMD_DELIVERED(state.msg_attr.delivered), + CA_PIPE_CMD_TIME_LIMIT(var_command_maxtime), + CA_PIPE_CMD_ENV(env->argv), + CA_PIPE_CMD_EXPORT(export_env->argv), + CA_PIPE_CMD_SHELL(var_local_cmd_shell), + CA_PIPE_CMD_CWD(*STR(exec_dir) ? + STR(exec_dir) : (char *) 0), + CA_PIPE_CMD_END); + } + vstring_free(exec_dir); + argv_free(export_env); + argv_free(env); + + /* + * Depending on the result, bounce or defer the message. + */ + switch (cmd_status) { + case PIPE_STAT_OK: + dsb_simple(why, "2.0.0", "delivered to command: %s", command); + deliver_status = sent(BOUNCE_FLAGS(state.request), + SENT_ATTR(state.msg_attr)); + break; + case PIPE_STAT_BOUNCE: + case PIPE_STAT_DEFER: + /* Account for possible owner- sender address override. */ + deliver_status = bounce_workaround(state); + break; + case PIPE_STAT_CORRUPT: + deliver_status = DEL_STAT_DEFER; + break; + default: + msg_panic("%s: bad status %d", myname, cmd_status); + /* NOTREACHED */ + } + + return (deliver_status); +} diff --git a/src/local/deliver_attr.c b/src/local/deliver_attr.c new file mode 100644 index 0000000..9ed18e2 --- /dev/null +++ b/src/local/deliver_attr.c @@ -0,0 +1,105 @@ +/*++ +/* NAME +/* deliver_attr 3 +/* SUMMARY +/* initialize message delivery attributes +/* SYNOPSIS +/* #include "local.h" +/* +/* void deliver_attr_init(attrp) +/* DELIVER_ATTR *attrp; +/* +/* void deliver_attr_dump(attrp) +/* DELIVER_ATTR *attrp; +/* +/* void deliver_attr_free(attrp) +/* DELIVER_ATTR *attrp; +/* DESCRIPTION +/* deliver_attr_init() initializes a structure with message delivery +/* attributes to a known initial state (all zeros). +/* +/* deliver_attr_dump() logs the contents of the given attribute list. +/* +/* deliver_attr_free() releases memory that was allocated by +/* deliver_attr_init(). +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" + +/* deliver_attr_init - set message delivery attributes to all-zero state */ + +void deliver_attr_init(DELIVER_ATTR *attrp) +{ + attrp->level = 0; + attrp->fp = 0; + attrp->queue_name = 0; + attrp->queue_id = 0; + attrp->offset = 0; + attrp->sender = 0; + RECIPIENT_ASSIGN(&(attrp->rcpt), 0, 0, 0, 0, 0); + attrp->domain = 0; + attrp->local = 0; + attrp->user = 0; + attrp->extension = 0; + attrp->unmatched = 0; + attrp->owner = 0; + attrp->delivered = 0; + attrp->relay = 0; + attrp->exp_type = 0; + attrp->exp_from = 0; + attrp->why = dsb_create(); +} + +/* deliver_attr_dump - log message delivery attributes */ + +void deliver_attr_dump(DELIVER_ATTR *attrp) +{ + msg_info("level: %d", attrp->level); + msg_info("path: %s", VSTREAM_PATH(attrp->fp)); + msg_info("fp: 0x%lx", (long) attrp->fp); + msg_info("queue_name: %s", attrp->queue_name ? attrp->queue_name : "null"); + msg_info("queue_id: %s", attrp->queue_id ? attrp->queue_id : "null"); + msg_info("offset: %ld", attrp->rcpt.offset); + msg_info("sender: %s", attrp->sender ? attrp->sender : "null"); + msg_info("recipient: %s", attrp->rcpt.address ? attrp->rcpt.address : "null"); + msg_info("domain: %s", attrp->domain ? attrp->domain : "null"); + msg_info("local: %s", attrp->local ? attrp->local : "null"); + msg_info("user: %s", attrp->user ? attrp->user : "null"); + msg_info("extension: %s", attrp->extension ? attrp->extension : "null"); + msg_info("unmatched: %s", attrp->unmatched ? attrp->unmatched : "null"); + msg_info("owner: %s", attrp->owner ? attrp->owner : "null"); + msg_info("delivered: %s", attrp->delivered ? attrp->delivered : "null"); + msg_info("relay: %s", attrp->relay ? attrp->relay : "null"); + msg_info("exp_type: %d", attrp->exp_type); + msg_info("exp_from: %s", attrp->exp_from ? attrp->exp_from : "null"); + msg_info("why: %s", attrp->why ? "buffer" : "null"); +} + +/* deliver_attr_free - release storage */ + +void deliver_attr_free(DELIVER_ATTR *attrp) +{ + dsb_free(attrp->why); +} diff --git a/src/local/dotforward.c b/src/local/dotforward.c new file mode 100644 index 0000000..3ce2cfc --- /dev/null +++ b/src/local/dotforward.c @@ -0,0 +1,304 @@ +/*++ +/* NAME +/* dotforward 3 +/* SUMMARY +/* $HOME/.forward file expansion +/* SYNOPSIS +/* #include "local.h" +/* +/* int deliver_dotforward(state, usr_attr, statusp) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* int *statusp; +/* DESCRIPTION +/* deliver_dotforward() delivers a message to the destinations +/* listed in a recipient's .forward file(s) as specified through +/* the forward_path configuration parameter. The result is +/* zero when no acceptable .forward file was found, or when +/* a recipient is listed in her own .forward file. Expansions +/* are scrutinized with the forward_expansion_filter parameter. +/* +/* Arguments: +/* .IP state +/* Message delivery attributes (sender, recipient etc.). +/* Attributes describing alias, include or forward expansion. +/* A table with the results from expanding aliases or lists. +/* A table with delivered-to: addresses taken from the message. +/* .IP usr_attr +/* Attributes describing user rights and environment. +/* .IP statusp +/* Message delivery status. See below. +/* DIAGNOSTICS +/* Fatal errors: out of memory. Warnings: bad $HOME/.forward +/* file type, permissions or ownership. The message delivery +/* status is non-zero when delivery should be tried again. +/* SEE ALSO +/* include(3) include file processor. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#ifdef USE_PATHS_H +#include +#endif +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" + +#define NO 0 +#define YES 1 + +/* deliver_dotforward - expand contents of .forward file */ + +int deliver_dotforward(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp) +{ + const char *myname = "deliver_dotforward"; + struct stat st; + VSTRING *path; + struct mypasswd *mypwd; + int fd; + VSTREAM *fp; + int status; + int forward_found = NO; + int lookup_status; + int addr_count; + char *saved_forward_path; + char *lhs; + char *next; + int expand_status; + int saved_notify; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * Skip this module if per-user forwarding is disabled. + */ + if (*var_forward_path == 0) + return (NO); + + /* + * Skip non-existing users. The mailbox delivery routine will catch the + * error. + */ + if ((errno = mypwnam_err(state.msg_attr.user, &mypwd)) != 0) { + msg_warn("error looking up passwd info for %s: %m", + state.msg_attr.user); + dsb_simple(state.msg_attr.why, "4.0.0", "user lookup error"); + *statusp = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + return (YES); + } + if (mypwd == 0) + return (NO); + + /* + * From here on no early returns or we have a memory leak. + */ + + /* + * EXTERNAL LOOP CONTROL + * + * Set the delivered message attribute to the recipient, so that this + * message will list the correct forwarding address. + */ + if (var_frozen_delivered == 0) + state.msg_attr.delivered = state.msg_attr.rcpt.address; + + /* + * DELIVERY RIGHTS + * + * Do not inherit rights from the .forward file owner. Instead, use the + * recipient's rights, and insist that the .forward file is owned by the + * recipient. This is a small but significant difference. Use the + * recipient's rights for all /file and |command deliveries, and pass on + * these rights to command/file destinations in included files. When + * these are the rights of root, the /file and |command delivery routines + * will use unprivileged default rights instead. Better safe than sorry. + */ + SET_USER_ATTR(usr_attr, mypwd, state.level); + + /* + * DELIVERY POLICY + * + * Update the expansion type attribute so that we can decide if deliveries + * to |command and /file/name are allowed at all. + */ + state.msg_attr.exp_type = EXPAND_TYPE_FWD; + + /* + * WHERE TO REPORT DELIVERY PROBLEMS + * + * Set the owner attribute so that 1) include files won't set the sender to + * be this user and 2) mail forwarded to other local users will be + * resubmitted as a new queue file. + */ + state.msg_attr.owner = state.msg_attr.user; + + /* + * Search the forward_path for an existing forward file. + * + * If unmatched extensions should never be propagated, or if a forward file + * name includes the address extension, don't propagate the extension to + * the recipient addresses. + */ + status = 0; + path = vstring_alloc(100); + saved_forward_path = mystrdup(var_forward_path); + next = saved_forward_path; + lookup_status = -1; + + while ((lhs = mystrtok(&next, CHARS_COMMA_SP)) != 0) { + expand_status = local_expand(path, lhs, &state, &usr_attr, + var_fwd_exp_filter); + if ((expand_status & (MAC_PARSE_ERROR | MAC_PARSE_UNDEF)) == 0) { + lookup_status = + lstat_as(STR(path), &st, usr_attr.uid, usr_attr.gid); + if (msg_verbose) + msg_info("%s: path %s expand_status %d look_status %d", myname, + STR(path), expand_status, lookup_status); + if (lookup_status >= 0) { + if ((expand_status & LOCAL_EXP_EXTENSION_MATCHED) != 0 + || (local_ext_prop_mask & EXT_PROP_FORWARD) == 0) + state.msg_attr.unmatched = 0; + break; + } + } + } + + /* + * Process the forward file. + * + * Assume that usernames do not have file system meta characters. Open the + * .forward file as the user. Ignore files that aren't regular files, + * files that are owned by the wrong user, or files that have world write + * permission enabled. + * + * DUPLICATE/LOOP ELIMINATION + * + * If this user includes (an alias of) herself in her own .forward file, + * deliver to the user instead. + */ + if (lookup_status >= 0) { + + /* + * Don't expand a verify-only request. + */ + if (state.request->flags & DEL_REQ_FLAG_MTA_VRFY) { + dsb_simple(state.msg_attr.why, "2.0.0", + "forward via file: %s", STR(path)); + *statusp = sent(BOUNCE_FLAGS(state.request), + SENT_ATTR(state.msg_attr)); + forward_found = YES; + } else if (been_here(state.dup_filter, "forward %s", STR(path)) == 0) { + state.msg_attr.exp_from = state.msg_attr.local; + if (S_ISREG(st.st_mode) == 0) { + msg_warn("file %s is not a regular file", STR(path)); + } else if (st.st_uid != 0 && st.st_uid != usr_attr.uid) { + msg_warn("file %s has bad owner uid %ld", + STR(path), (long) st.st_uid); + } else if (st.st_mode & 002) { + msg_warn("file %s is world writable", STR(path)); + } else if ((fd = open_as(STR(path), O_RDONLY, 0, usr_attr.uid, usr_attr.gid)) < 0) { + msg_warn("cannot open file %s: %m", STR(path)); + } else { + + /* + * XXX DSN. When delivering to an alias (i.e. the envelope + * sender address is not replaced) any ENVID, RET, or ORCPT + * parameters are propagated to all forwarding addresses + * associated with that alias. The NOTIFY parameter is + * propagated to the forwarding addresses, except that any + * SUCCESS keyword is removed. + */ + close_on_exec(fd, CLOSE_ON_EXEC); + addr_count = 0; + fp = vstream_fdopen(fd, O_RDONLY); + saved_notify = state.msg_attr.rcpt.dsn_notify; + state.msg_attr.rcpt.dsn_notify = + (saved_notify == DSN_NOTIFY_SUCCESS ? + DSN_NOTIFY_NEVER : saved_notify & ~DSN_NOTIFY_SUCCESS); + status = deliver_token_stream(state, usr_attr, fp, &addr_count); + if (vstream_fclose(fp)) + msg_warn("close file %s: %m", STR(path)); + if (addr_count > 0) { + forward_found = YES; + been_here(state.dup_filter, "forward-done %s", STR(path)); + + /* + * XXX DSN. When delivering to an alias (i.e. the + * envelope sender address is not replaced) and the + * original NOTIFY parameter for the alias contained the + * SUCCESS keyword, an "expanded" DSN is issued for the + * alias. + */ + if (status == 0 && (saved_notify & DSN_NOTIFY_SUCCESS)) { + state.msg_attr.rcpt.dsn_notify = saved_notify; + dsb_update(state.msg_attr.why, "2.0.0", "expanded", + DSB_SKIP_RMTA, DSB_SKIP_REPLY, + "alias expanded"); + (void) trace_append(BOUNCE_FLAG_NONE, + SENT_ATTR(state.msg_attr)); + } + } + } + } else if (been_here_check(state.dup_filter, "forward-done %s", STR(path)) != 0) + forward_found = YES; /* else we're recursive */ + } + + /* + * Clean up. + */ + vstring_free(path); + myfree(saved_forward_path); + mypwfree(mypwd); + + *statusp = status; + return (forward_found); +} diff --git a/src/local/file.c b/src/local/file.c new file mode 100644 index 0000000..0cc4c18 --- /dev/null +++ b/src/local/file.c @@ -0,0 +1,195 @@ +/*++ +/* NAME +/* file 3 +/* SUMMARY +/* mail delivery to arbitrary file +/* SYNOPSIS +/* #include "local.h" +/* +/* int deliver_file(state, usr_attr, path) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* char *path; +/* DESCRIPTION +/* deliver_file() appends a message to a file, UNIX mailbox format, +/* or qmail maildir format, +/* with duplicate suppression. It will deliver only to non-executable +/* regular files. +/* +/* Arguments: +/* .IP state +/* The attributes that specify the message, recipient and more. +/* Attributes describing alias, include or forward expansion. +/* A table with the results from expanding aliases or lists. +/* .IP usr_attr +/* Attributes describing user rights and environment information. +/* .IP path +/* The file to deliver to. If the name ends in '/', delivery is done +/* in qmail maildir format, otherwise delivery is done in UNIX mailbox +/* format. +/* DIAGNOSTICS +/* deliver_file() returns non-zero when delivery should be tried again. +/* SEE ALSO +/* defer(3) +/* bounce(3) +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" + +/* deliver_file - deliver to file */ + +int deliver_file(LOCAL_STATE state, USER_ATTR usr_attr, char *path) +{ + const char *myname = "deliver_file"; + struct stat st; + MBOX *mp; + DSN_BUF *why = state.msg_attr.why; + int mail_copy_status = MAIL_COPY_STAT_WRITE; + int deliver_status; + int copy_flags; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * DUPLICATE ELIMINATION + * + * Skip this file if it was already delivered to as this user. + */ + if (been_here(state.dup_filter, "file %ld %s", (long) usr_attr.uid, path)) + return (0); + + /* + * DELIVERY POLICY + * + * Do we allow delivery to files? + */ + if ((local_file_deliver_mask & state.msg_attr.exp_type) == 0) { + dsb_simple(why, "5.7.1", "mail to file is restricted"); + /* Account for possible owner- sender address override. */ + return (bounce_workaround(state)); + } + + /* + * Don't deliver trace-only requests. + */ + if (DEL_REQ_TRACE_ONLY(state.request->flags)) { + dsb_simple(why, "2.0.0", "delivers to file: %s", path); + return (sent(BOUNCE_FLAGS(state.request), + SENT_ATTR(state.msg_attr))); + } + + /* + * DELIVERY RIGHTS + * + * Use a default uid/gid when none are given. + */ + if (usr_attr.uid == 0 && (usr_attr.uid = var_default_uid) == 0) + msg_panic("privileged default user id"); + if (usr_attr.gid == 0 && (usr_attr.gid = var_default_gid) == 0) + msg_panic("privileged default group id"); + + /* + * If the name ends in /, use maildir-style delivery instead. + */ + if (path[strlen(path) - 1] == '/') + return (deliver_maildir(state, usr_attr, path)); + + /* + * Deliver. From here on, no early returns or we have a memory leak. + */ + if (msg_verbose) + msg_info("deliver_file (%ld,%ld): %s", + (long) usr_attr.uid, (long) usr_attr.gid, path); + if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) + msg_fatal("seek queue file %s: %m", state.msg_attr.queue_id); + + /* + * As the specified user, open or create the file, lock it, and append + * the message. + */ + copy_flags = MAIL_COPY_MBOX; + if ((local_deliver_hdr_mask & DELIVER_HDR_FILE) == 0) + copy_flags &= ~MAIL_COPY_DELIVERED; + + set_eugid(usr_attr.uid, usr_attr.gid); + mp = mbox_open(path, O_APPEND | O_CREAT | O_WRONLY, + S_IRUSR | S_IWUSR, &st, -1, -1, + local_mbox_lock_mask | MBOX_DOT_LOCK_MAY_FAIL, + "5.2.0", why); + if (mp != 0) { + if (S_ISREG(st.st_mode) && st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) { + vstream_fclose(mp->fp); + dsb_simple(why, "5.7.1", "file is executable"); + } else { + mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), mp->fp, + S_ISREG(st.st_mode) ? copy_flags : + (copy_flags & ~MAIL_COPY_TOFILE), + "\n", why); + } + mbox_release(mp); + } + set_eugid(var_owner_uid, var_owner_gid); + + /* + * As the mail system, bounce, defer delivery, or report success. + */ + if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) { + deliver_status = DEL_STAT_DEFER; + } else if (mail_copy_status != 0) { + vstring_sprintf_prepend(why->reason, + "cannot append message to file %s: ", path); + /* Account for possible owner- sender address override. */ + deliver_status = bounce_workaround(state); + } else { + dsb_simple(why, "2.0.0", "delivered to file: %s", path); + deliver_status = sent(BOUNCE_FLAGS(state.request), + SENT_ATTR(state.msg_attr)); + } + return (deliver_status); +} diff --git a/src/local/forward.c b/src/local/forward.c new file mode 100644 index 0000000..722dcf7 --- /dev/null +++ b/src/local/forward.c @@ -0,0 +1,393 @@ +/*++ +/* NAME +/* forward 3 +/* SUMMARY +/* message forwarding +/* SYNOPSIS +/* #include "local.h" +/* +/* int forward_init() +/* +/* int forward_append(attr) +/* DELIVER_ATTR attr; +/* +/* int forward_finish(request, attr, cancel) +/* DELIVER_REQUEST *request; +/* DELIVER_ATTR attr; +/* int cancel; +/* DESCRIPTION +/* This module implements the client interface for message +/* forwarding. +/* +/* forward_init() initializes internal data structures. +/* +/* forward_append() appends a recipient to the list of recipients +/* that will receive a message with the specified message sender +/* and delivered-to addresses. +/* +/* forward_finish() forwards the actual message contents and +/* releases the memory allocated by forward_init() and by +/* forward_append(). When the \fIcancel\fR argument is true, no +/* messages will be forwarded. The \fIattr\fR argument specifies +/* the original message delivery attributes as they were before +/* alias or forward expansions. +/* DIAGNOSTICS +/* A non-zero result means that the requested operation should +/* be tried again. +/* Warnings: problems connecting to the forwarding service, +/* corrupt message file. A corrupt message is saved to the +/* "corrupt" queue for further inspection. +/* Fatal: out of memory. +/* Panic: missing forward_init() or forward_finish() call. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" + + /* + * Use one cleanup service connection for each (delivered to, sender) pair. + */ +static HTABLE *forward_dt; + +typedef struct FORWARD_INFO { + VSTREAM *cleanup; /* clean up service handle */ + char *queue_id; /* forwarded message queue id */ + struct timeval posting_time; /* posting time */ +} FORWARD_INFO; + +/* forward_init - prepare for forwarding */ + +int forward_init(void) +{ + + /* + * Sanity checks. + */ + if (forward_dt != 0) + msg_panic("forward_init: missing forward_finish call"); + + forward_dt = htable_create(0); + return (0); +} + +/* forward_open - open connection to cleanup service */ + +static FORWARD_INFO *forward_open(DELIVER_REQUEST *request, const char *sender) +{ + VSTRING *buffer = vstring_alloc(100); + FORWARD_INFO *info; + VSTREAM *cleanup; + +#define FORWARD_OPEN_RETURN(res) do { \ + vstring_free(buffer); \ + return (res); \ + } while (0) + + /* + * Contact the cleanup service and save the new mail queue id. Request + * that the cleanup service bounces bad messages to the sender so that we + * can avoid the trouble of bounce management. + * + * In case you wonder what kind of bounces, examples are "too many hops", + * "message too large", perhaps some others. The reason not to bounce + * ourselves is that we don't really know who the recipients are. + */ + cleanup = mail_connect(MAIL_CLASS_PUBLIC, var_cleanup_service, BLOCKING); + if (cleanup == 0) { + msg_warn("connect to %s/%s: %m", + MAIL_CLASS_PUBLIC, var_cleanup_service); + FORWARD_OPEN_RETURN(0); + } + close_on_exec(vstream_fileno(cleanup), CLOSE_ON_EXEC); + if (attr_scan(cleanup, ATTR_FLAG_STRICT, + RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_CLEANUP), + RECV_ATTR_STR(MAIL_ATTR_QUEUEID, buffer), + ATTR_TYPE_END) != 1) { + vstream_fclose(cleanup); + FORWARD_OPEN_RETURN(0); + } + info = (FORWARD_INFO *) mymalloc(sizeof(FORWARD_INFO)); + info->cleanup = cleanup; + info->queue_id = mystrdup(STR(buffer)); + GETTIMEOFDAY(&info->posting_time); + +#define FORWARD_CLEANUP_FLAGS \ + (CLEANUP_FLAG_BOUNCE | CLEANUP_FLAG_MASK_INTERNAL \ + | smtputf8_autodetect(MAIL_SRC_MASK_FORWARD) \ + | ((request->smtputf8 & SMTPUTF8_FLAG_REQUESTED) ? \ + CLEANUP_FLAG_SMTPUTF8 : 0)) + + attr_print(cleanup, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_FLAGS, FORWARD_CLEANUP_FLAGS), + ATTR_TYPE_END); + + /* + * Send initial message envelope information. For bounces, set the + * designated sender: mailing list owner, posting user, whatever. + */ + rec_fprintf(cleanup, REC_TYPE_TIME, REC_TYPE_TIME_FORMAT, + REC_TYPE_TIME_ARG(info->posting_time)); + rec_fputs(cleanup, REC_TYPE_FROM, sender); + + /* + * Don't send the original envelope ID or full/headers return mask if it + * was reset due to mailing list expansion. + */ + if (request->dsn_ret) + rec_fprintf(cleanup, REC_TYPE_ATTR, "%s=%d", + MAIL_ATTR_DSN_RET, request->dsn_ret); + if (request->dsn_envid && *(request->dsn_envid)) + rec_fprintf(cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_DSN_ENVID, request->dsn_envid); + + /* + * Zero-length attribute values are place holders for unavailable + * attribute values. See qmgr_message.c. They are not meant to be + * propagated to queue files. + */ +#define PASS_ATTR(fp, name, value) do { \ + if ((value) && *(value)) \ + rec_fprintf((fp), REC_TYPE_ATTR, "%s=%s", (name), (value)); \ + } while (0) + + /* + * XXX encapsulate these as one object. + */ + PASS_ATTR(cleanup, MAIL_ATTR_LOG_CLIENT_NAME, request->client_name); + PASS_ATTR(cleanup, MAIL_ATTR_LOG_CLIENT_ADDR, request->client_addr); + PASS_ATTR(cleanup, MAIL_ATTR_LOG_PROTO_NAME, request->client_proto); + PASS_ATTR(cleanup, MAIL_ATTR_LOG_HELO_NAME, request->client_helo); + PASS_ATTR(cleanup, MAIL_ATTR_SASL_METHOD, request->sasl_method); + PASS_ATTR(cleanup, MAIL_ATTR_SASL_USERNAME, request->sasl_username); + PASS_ATTR(cleanup, MAIL_ATTR_SASL_SENDER, request->sasl_sender); + PASS_ATTR(cleanup, MAIL_ATTR_LOG_IDENT, request->log_ident); + PASS_ATTR(cleanup, MAIL_ATTR_RWR_CONTEXT, request->rewrite_context); + + FORWARD_OPEN_RETURN(info); +} + +/* forward_append - append recipient to message envelope */ + +int forward_append(DELIVER_ATTR attr) +{ + FORWARD_INFO *info; + HTABLE *table_snd; + + /* + * Sanity checks. + */ + if (msg_verbose) + msg_info("forward delivered=%s sender=%s recip=%s", + attr.delivered, attr.sender, attr.rcpt.address); + if (forward_dt == 0) + msg_panic("forward_append: missing forward_init call"); + + /* + * In order to find the recipient list, first index a table by + * delivered-to header address, then by envelope sender address. + */ + if ((table_snd = (HTABLE *) htable_find(forward_dt, attr.delivered)) == 0) { + table_snd = htable_create(0); + htable_enter(forward_dt, attr.delivered, (void *) table_snd); + } + if ((info = (FORWARD_INFO *) htable_find(table_snd, attr.sender)) == 0) { + if ((info = forward_open(attr.request, attr.sender)) == 0) + return (-1); + htable_enter(table_snd, attr.sender, (void *) info); + } + + /* + * Append the recipient to the message envelope. Don't send the original + * recipient or notification mask if it was reset due to mailing list + * expansion. + */ + if (*attr.rcpt.dsn_orcpt) + rec_fprintf(info->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_DSN_ORCPT, attr.rcpt.dsn_orcpt); + if (attr.rcpt.dsn_notify) + rec_fprintf(info->cleanup, REC_TYPE_ATTR, "%s=%d", + MAIL_ATTR_DSN_NOTIFY, attr.rcpt.dsn_notify); + if (*attr.rcpt.orig_addr) + rec_fputs(info->cleanup, REC_TYPE_ORCP, attr.rcpt.orig_addr); + rec_fputs(info->cleanup, REC_TYPE_RCPT, attr.rcpt.address); + + return (vstream_ferror(info->cleanup)); +} + +/* forward_send - send forwarded message */ + +static int forward_send(FORWARD_INFO *info, DELIVER_REQUEST *request, + DELIVER_ATTR attr, char *delivered) +{ + const char *myname = "forward_send"; + VSTRING *buffer = vstring_alloc(100); + VSTRING *folded; + int status; + int rec_type = 0; + + /* + * Start the message content segment. Prepend our Delivered-To: header to + * the message data. Stop at the first error. XXX Rely on the front-end + * services to enforce record size limits. + */ + rec_fputs(info->cleanup, REC_TYPE_MESG, ""); + vstring_strcpy(buffer, delivered); + rec_fprintf(info->cleanup, REC_TYPE_NORM, "Received: by %s (%s)", + var_myhostname, var_mail_name); + rec_fprintf(info->cleanup, REC_TYPE_NORM, "\tid %s; %s", + info->queue_id, mail_date(info->posting_time.tv_sec)); + if (local_deliver_hdr_mask & DELIVER_HDR_FWD) { + folded = vstring_alloc(100); + rec_fprintf(info->cleanup, REC_TYPE_NORM, "Delivered-To: %s", + casefold(folded, (STR(buffer)))); + vstring_free(folded); + } + if ((status = vstream_ferror(info->cleanup)) == 0) + if (vstream_fseek(attr.fp, attr.offset, SEEK_SET) < 0) + msg_fatal("%s: seek queue file %s: %m:", + myname, VSTREAM_PATH(attr.fp)); + while (status == 0 && (rec_type = rec_get(attr.fp, buffer, 0)) > 0) { + if (rec_type != REC_TYPE_CONT && rec_type != REC_TYPE_NORM) + break; + status = (REC_PUT_BUF(info->cleanup, rec_type, buffer) != rec_type); + } + if (status == 0 && rec_type != REC_TYPE_XTRA) { + msg_warn("%s: bad record type: %d in message content", + info->queue_id, rec_type); + status |= mark_corrupt(attr.fp); + } + + /* + * Send the end-of-data marker only when there were no errors. + */ + if (status == 0) { + rec_fputs(info->cleanup, REC_TYPE_XTRA, ""); + rec_fputs(info->cleanup, REC_TYPE_END, ""); + } + + /* + * Retrieve the cleanup service completion status only if there are no + * problems. + */ + if (status == 0) + if (vstream_fflush(info->cleanup) + || attr_scan(info->cleanup, ATTR_FLAG_MISSING, + RECV_ATTR_INT(MAIL_ATTR_STATUS, &status), + ATTR_TYPE_END) != 1) + status = 1; + + /* + * Log successful forwarding. + * + * XXX DSN alias and .forward expansion already report SUCCESS, so don't do + * it again here. + */ + if (status == 0) { + attr.rcpt.dsn_notify = + (attr.rcpt.dsn_notify == DSN_NOTIFY_SUCCESS ? + DSN_NOTIFY_NEVER : attr.rcpt.dsn_notify & ~DSN_NOTIFY_SUCCESS); + dsb_update(attr.why, "2.0.0", "relayed", DSB_SKIP_RMTA, DSB_SKIP_REPLY, + "forwarded as %s", info->queue_id); + status = sent(BOUNCE_FLAGS(request), SENT_ATTR(attr)); + } + + /* + * Cleanup. + */ + vstring_free(buffer); + return (status); +} + +/* forward_finish - complete message forwarding requests and clean up */ + +int forward_finish(DELIVER_REQUEST *request, DELIVER_ATTR attr, int cancel) +{ + HTABLE_INFO **dt_list; + HTABLE_INFO **dt; + HTABLE_INFO **sn_list; + HTABLE_INFO **sn; + HTABLE *table_snd; + char *delivered; + char *sender; + FORWARD_INFO *info; + int status = cancel; + + /* + * Sanity checks. + */ + if (forward_dt == 0) + msg_panic("forward_finish: missing forward_init call"); + + /* + * Walk over all delivered-to header addresses and over each envelope + * sender address. + */ + for (dt = dt_list = htable_list(forward_dt); *dt; dt++) { + delivered = dt[0]->key; + table_snd = (HTABLE *) dt[0]->value; + for (sn = sn_list = htable_list(table_snd); *sn; sn++) { + sender = sn[0]->key; + info = (FORWARD_INFO *) sn[0]->value; + if (status == 0) + status |= forward_send(info, request, attr, delivered); + if (msg_verbose) + msg_info("forward_finish: delivered %s sender %s status %d", + delivered, sender, status); + (void) vstream_fclose(info->cleanup); + myfree(info->queue_id); + myfree((void *) info); + } + myfree((void *) sn_list); + htable_free(table_snd, (void (*) (void *)) 0); + } + myfree((void *) dt_list); + htable_free(forward_dt, (void (*) (void *)) 0); + forward_dt = 0; + return (status); +} diff --git a/src/local/include.c b/src/local/include.c new file mode 100644 index 0000000..a213d3c --- /dev/null +++ b/src/local/include.c @@ -0,0 +1,223 @@ +/*++ +/* NAME +/* deliver_include 3 +/* SUMMARY +/* deliver to addresses listed in include file +/* SYNOPSIS +/* #include "local.h" +/* +/* int deliver_include(state, usr_attr, path) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* char *path; +/* DESCRIPTION +/* deliver_include() processes the contents of the named include +/* file and delivers to each address listed. Some sanity checks +/* are done on the include file permissions and type. +/* +/* Arguments: +/* .IP state +/* The attributes that specify the message, recipient and more. +/* Attributes describing alias, include, or forward expansion. +/* A table with the results from expanding aliases or lists. +/* A table with delivered-to: addresses taken from the message. +/* .IP usr_attr +/* Attributes describing user rights and environment. +/* .IP path +/* Pathname of the include file. +/* DIAGNOSTICS +/* Fatal errors: out of memory. Warnings: bad include file type +/* or permissions. The result is non-zero when delivery should be +/* tried again. +/* SEE ALSO +/* token(3) tokenize list +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" + +/* deliver_include - open include file and deliver */ + +int deliver_include(LOCAL_STATE state, USER_ATTR usr_attr, char *path) +{ + const char *myname = "deliver_include"; + struct stat st; + struct mypasswd *file_pwd = 0; + int status; + VSTREAM *fp; + int fd; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * DUPLICATE ELIMINATION + * + * Don't process this include file more than once as this particular user. + */ + if (been_here(state.dup_filter, "include %ld %s", (long) usr_attr.uid, path)) + return (0); + state.msg_attr.exp_from = state.msg_attr.local; + + /* + * Can of worms. Allow this include file to be symlinked, but disallow + * inclusion of special files or of files with world write permission + * enabled. + */ + if (*path != '/') { + msg_warn(":include:%s uses a relative path", path); + dsb_simple(state.msg_attr.why, "5.3.5", + "mail system configuration error"); + return (bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr))); + } + if (stat_as(path, &st, usr_attr.uid, usr_attr.gid) < 0) { + msg_warn("unable to lookup :include: file %s: %m", path); + dsb_simple(state.msg_attr.why, "5.3.5", + "mail system configuration error"); + return (bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr))); + } + if (S_ISREG(st.st_mode) == 0) { + msg_warn(":include: file %s is not a regular file", path); + dsb_simple(state.msg_attr.why, "5.3.5", + "mail system configuration error"); + return (bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr))); + } + if (st.st_mode & S_IWOTH) { + msg_warn(":include: file %s is world writable", path); + dsb_simple(state.msg_attr.why, "5.3.5", + "mail system configuration error"); + return (bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr))); + } + + /* + * DELIVERY POLICY + * + * Set the expansion type attribute so that we can decide if destinations + * such as /file/name and |command are allowed at all. + */ + state.msg_attr.exp_type = EXPAND_TYPE_INCL; + + /* + * DELIVERY RIGHTS + * + * When a non-root include file is listed in a root-owned alias, use the + * rights of the include file owner. We do not want to give the include + * file owner control of the default account. + * + * When an include file is listed in a user-owned alias or .forward file, + * leave the delivery rights alone. Users should not be able to make + * things happen with someone else's rights just by including some file + * that is owned by their victim. + */ + if (usr_attr.uid == 0) { + if ((errno = mypwuid_err(st.st_uid, &file_pwd)) != 0 || file_pwd == 0) { + msg_warn(errno ? "cannot find username for uid %ld: %m" : + "cannot find username for uid %ld", (long) st.st_uid); + msg_warn("%s: cannot find :include: file owner username", path); + dsb_simple(state.msg_attr.why, "4.3.5", + "mail system configuration error"); + return (defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr))); + } + if (file_pwd->pw_uid != 0) + SET_USER_ATTR(usr_attr, file_pwd, state.level); + } + + /* + * MESSAGE FORWARDING + * + * When no owner attribute is set (either via an owner- alias, or as part of + * .forward file processing), set the owner attribute, to disable direct + * delivery of local recipients. By now it is clear that the owner + * attribute should have been called forwarder instead. + */ + if (state.msg_attr.owner == 0) + state.msg_attr.owner = state.msg_attr.rcpt.address; + + /* + * From here on no early returns or we have a memory leak. + * + * FILE OPEN RIGHTS + * + * Use the delivery rights to open the include file. When no delivery rights + * were established sofar, the file containing the :include: is owned by + * root, so it should be OK to open any file that is accessible to root. + * The command and file delivery routines are responsible for setting the + * proper delivery rights. These are the rights of the default user, in + * case the :include: is in a root-owned alias. + * + * Don't propagate unmatched extensions unless permitted to do so. + */ +#define FOPEN_AS(p,u,g) ((fd = open_as(p,O_RDONLY,0,u,g)) >= 0 ? \ + vstream_fdopen(fd,O_RDONLY) : 0) + + if ((fp = FOPEN_AS(path, usr_attr.uid, usr_attr.gid)) == 0) { + msg_warn("cannot open include file %s: %m", path); + dsb_simple(state.msg_attr.why, "5.3.5", + "mail system configuration error"); + status = bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + } else { + if ((local_ext_prop_mask & EXT_PROP_INCLUDE) == 0) + state.msg_attr.unmatched = 0; + close_on_exec(vstream_fileno(fp), CLOSE_ON_EXEC); + status = deliver_token_stream(state, usr_attr, fp, (int *) 0); + if (vstream_fclose(fp)) + msg_warn("close %s: %m", path); + } + + /* + * Cleanup. + */ + if (file_pwd) + mypwfree(file_pwd); + + return (status); +} diff --git a/src/local/indirect.c b/src/local/indirect.c new file mode 100644 index 0000000..a9699a5 --- /dev/null +++ b/src/local/indirect.c @@ -0,0 +1,94 @@ +/*++ +/* NAME +/* indirect 3 +/* SUMMARY +/* indirect delivery +/* SYNOPSIS +/* #include "local.h" +/* +/* void deliver_indirect(state) +/* LOCAL_STATE state; +/* char *recipient; +/* DESCRIPTION +/* deliver_indirect() delivers a message via the message +/* forwarding service, with duplicate filtering up to a +/* configurable number of recipients. +/* +/* Arguments: +/* .IP state +/* The attributes that specify the message, sender and more. +/* A table with the results from expanding aliases or lists. +/* CONFIGURATION VARIABLES +/* duplicate_filter_limit, duplicate filter size limit +/* DIAGNOSTICS +/* The result is non-zero when the operation should be tried again. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" + +/* deliver_indirect - deliver mail via forwarding service */ + +int deliver_indirect(LOCAL_STATE state) +{ + + /* + * Suppress duplicate expansion results. Add some sugar to the name to + * avoid collisions with other duplicate filters. Allow the user to + * specify an upper bound on the size of the duplicate filter, so that we + * can handle huge mailing lists with millions of recipients. + */ + if (msg_verbose) + msg_info("deliver_indirect: %s", state.msg_attr.rcpt.address); + if (been_here(state.dup_filter, "indirect %s", + state.msg_attr.rcpt.address)) + return (0); + + /* + * Don't forward a trace-only request. + */ + if (DEL_REQ_TRACE_ONLY(state.request->flags)) { + dsb_simple(state.msg_attr.why, "2.0.0", "forwards to %s", + state.msg_attr.rcpt.address); + return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr))); + } + + /* + * Send the address to the forwarding service. Inherit the delivered + * attribute from the alias or from the .forward file owner. + */ + if (forward_append(state.msg_attr)) { + dsb_simple(state.msg_attr.why, "4.3.0", "unable to forward message"); + return (defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr))); + } + return (0); +} diff --git a/src/local/local.c b/src/local/local.c new file mode 100644 index 0000000..32bdea7 --- /dev/null +++ b/src/local/local.c @@ -0,0 +1,988 @@ +/*++ +/* NAME +/* local 8 +/* SUMMARY +/* Postfix local mail delivery +/* SYNOPSIS +/* \fBlocal\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The \fBlocal\fR(8) daemon processes delivery requests from the +/* Postfix queue manager to deliver mail to local recipients. +/* Each delivery request specifies a queue file, a sender address, +/* a domain or host to deliver to, and one or more recipients. +/* This program expects to be run from the \fBmaster\fR(8) process +/* manager. +/* +/* The \fBlocal\fR(8) daemon updates queue files and marks recipients +/* as finished, or it informs the queue manager that delivery should +/* be tried again at a later time. Delivery status reports are sent +/* to the \fBbounce\fR(8), \fBdefer\fR(8) or \fBtrace\fR(8) daemon as +/* appropriate. +/* CASE FOLDING +/* .ad +/* .fi +/* All delivery decisions are made using the bare recipient +/* name (i.e. the address localpart), folded to lower case. +/* See also under ADDRESS EXTENSION below for a few exceptions. +/* SYSTEM-WIDE AND USER-LEVEL ALIASING +/* .ad +/* .fi +/* The system administrator can set up one or more system-wide +/* \fBsendmail\fR-style alias databases. +/* Users can have \fBsendmail\fR-style ~/.\fBforward\fR files. +/* Mail for \fIname\fR is delivered to the alias \fIname\fR, to +/* destinations in ~\fIname\fR/.\fBforward\fR, to the mailbox owned +/* by the user \fIname\fR, or it is sent back as undeliverable. +/* +/* The system administrator can specify a comma/space separated list +/* of ~\fR/.\fBforward\fR like files through the \fBforward_path\fR +/* configuration parameter. Upon delivery, the local delivery agent +/* tries each pathname in the list until a file is found. +/* +/* Delivery via ~/.\fBforward\fR files is done with the privileges +/* of the recipient. +/* Thus, ~/.\fBforward\fR like files must be readable by the +/* recipient, and their parent directory needs to have "execute" +/* permission for the recipient. +/* +/* The \fBforward_path\fR parameter is subject to interpolation of +/* \fB$user\fR (recipient username), \fB$home\fR (recipient home +/* directory), \fB$shell\fR (recipient shell), \fB$recipient\fR +/* (complete recipient address), \fB$extension\fR (recipient address +/* extension), \fB$domain\fR (recipient domain), \fB$local\fR +/* (entire recipient address localpart) and +/* \fB$recipient_delimiter.\fR The forms \fI${name?value}\fR +/* and \fI${name?{value}}\fR (Postfix 3.0 and later) expand +/* conditionally to \fIvalue\fR when \fI$name\fR is defined, +/* and the forms \fI${name:value}\fR \fI${name:{value}}\fR +/* (Postfix 3.0 and later) expand conditionally to \fIvalue\fR +/* when \fI$name\fR is not defined. The form +/* \fI${name?{value1}:{value2}}\fR (Postfix 3.0 and later) +/* expands conditionally to \fIvalue1\fR when \fI$name\fR is +/* defined, or \fIvalue2\fR otherwise. Characters that may +/* have special meaning to the shell or file system are replaced +/* with underscores. The list of acceptable characters is +/* specified with the \fBforward_expansion_filter\fR configuration +/* parameter. +/* +/* An alias or ~/.\fBforward\fR file may list any combination of external +/* commands, destination file names, \fB:include:\fR directives, or +/* mail addresses. +/* See \fBaliases\fR(5) for a precise description. Each line in a +/* user's .\fBforward\fR file has the same syntax as the right-hand part +/* of an alias. +/* +/* When an address is found in its own alias expansion, delivery is +/* made to the user instead. When a user is listed in the user's own +/* ~/.\fBforward\fR file, delivery is made to the user's mailbox instead. +/* An empty ~/.\fBforward\fR file means do not forward mail. +/* +/* In order to prevent the mail system from using up unreasonable +/* amounts of memory, input records read from \fB:include:\fR or from +/* ~/.\fBforward\fR files are broken up into chunks of length +/* \fBline_length_limit\fR. +/* +/* While expanding aliases, ~/.\fBforward\fR files, and so on, the +/* program attempts to avoid duplicate deliveries. The +/* \fBduplicate_filter_limit\fR configuration parameter limits the +/* number of remembered recipients. +/* MAIL FORWARDING +/* .ad +/* .fi +/* For the sake of reliability, forwarded mail is re-submitted as +/* a new message, so that each recipient has a separate on-file +/* delivery status record. +/* +/* In order to stop mail forwarding loops early, the software adds an +/* optional +/* \fBDelivered-To:\fR header with the final envelope recipient address. If +/* mail arrives for a recipient that is already listed in a +/* \fBDelivered-To:\fR header, the message is bounced. +/* MAILBOX DELIVERY +/* .ad +/* .fi +/* The default per-user mailbox is a file in the UNIX mail spool +/* directory (\fB/var/mail/\fIuser\fR or \fB/var/spool/mail/\fIuser\fR); +/* the location can be specified with the \fBmail_spool_directory\fR +/* configuration parameter. Specify a name ending in \fB/\fR for +/* \fBqmail\fR-compatible \fBmaildir\fR delivery. +/* +/* Alternatively, the per-user mailbox can be a file in the user's home +/* directory with a name specified via the \fBhome_mailbox\fR +/* configuration parameter. Specify a relative path name. Specify a name +/* ending in \fB/\fR for \fBqmail\fR-compatible \fBmaildir\fR delivery. +/* +/* Mailbox delivery can be delegated to an external command specified +/* with the \fBmailbox_command_maps\fR and \fBmailbox_command\fR +/* configuration parameters. The command +/* executes with the privileges of the recipient user (exceptions: +/* secondary groups are not enabled; in case of delivery as root, +/* the command executes with the privileges of \fBdefault_privs\fR). +/* +/* Mailbox delivery can be delegated to alternative message transports +/* specified in the \fBmaster.cf\fR file. +/* The \fBmailbox_transport_maps\fR and \fBmailbox_transport\fR +/* configuration parameters specify an optional +/* message transport that is to be used for all local recipients, +/* regardless of whether they are found in the UNIX passwd database. +/* The \fBfallback_transport_maps\fR and +/* \fBfallback_transport\fR parameters specify an optional +/* message transport +/* for recipients that are not found in the aliases(5) or UNIX +/* passwd database. +/* +/* In the case of UNIX-style mailbox delivery, +/* the \fBlocal\fR(8) daemon prepends a "\fBFrom \fIsender time_stamp\fR" +/* envelope header to each message, prepends an +/* \fBX-Original-To:\fR header with the recipient address as given to +/* Postfix, prepends an +/* optional \fBDelivered-To:\fR header +/* with the final envelope recipient address, prepends a \fBReturn-Path:\fR +/* header with the envelope sender address, prepends a \fB>\fR character +/* to lines beginning with "\fBFrom \fR", and appends an empty line. +/* The mailbox is locked for exclusive access while delivery is in +/* progress. In case of problems, an attempt is made to truncate the +/* mailbox to its original length. +/* +/* In the case of \fBmaildir\fR delivery, the local daemon prepends +/* an optional +/* \fBDelivered-To:\fR header with the final envelope recipient address, +/* prepends an +/* \fBX-Original-To:\fR header with the recipient address as given to +/* Postfix, +/* and prepends a \fBReturn-Path:\fR header with the envelope sender +/* address. +/* EXTERNAL COMMAND DELIVERY +/* .ad +/* .fi +/* The \fBallow_mail_to_commands\fR configuration parameter restricts +/* delivery to external commands. The default setting (\fBalias, +/* forward\fR) forbids command destinations in \fB:include:\fR files. +/* +/* Optionally, the process working directory is changed to the path +/* specified with \fBcommand_execution_directory\fR (Postfix 2.2 and +/* later). Failure to change directory causes mail to be deferred. +/* +/* The \fBcommand_execution_directory\fR parameter value is subject +/* to interpolation of \fB$user\fR (recipient username), +/* \fB$home\fR (recipient home directory), \fB$shell\fR +/* (recipient shell), \fB$recipient\fR (complete recipient +/* address), \fB$extension\fR (recipient address extension), +/* \fB$domain\fR (recipient domain), \fB$local\fR (entire +/* recipient address localpart) and \fB$recipient_delimiter.\fR +/* The forms \fI${name?value}\fR and \fI${name?{value}}\fR +/* (Postfix 3.0 and later) expand conditionally to \fIvalue\fR +/* when \fI$name\fR is defined, and the forms \fI${name:value}\fR +/* and \fI${name:{value}}\fR (Postfix 3.0 and later) expand +/* conditionally to \fIvalue\fR when \fI$name\fR is not defined. +/* The form \fI${name?{value1}:{value2}}\fR (Postfix 3.0 and +/* later) expands conditionally to \fIvalue1\fR when \fI$name\fR +/* is defined, or \fIvalue2\fR otherwise. Characters that may +/* have special meaning to the shell or file system are replaced +/* with underscores. The list of acceptable characters +/* is specified with the \fBexecution_directory_expansion_filter\fR +/* configuration parameter. +/* +/* The command is executed directly where possible. Assistance by the +/* shell (\fB/bin/sh\fR on UNIX systems) is used only when the command +/* contains shell magic characters, or when the command invokes a shell +/* built-in command. +/* +/* A limited amount of command output (standard output and standard +/* error) is captured for inclusion with non-delivery status reports. +/* A command is forcibly terminated if it does not complete within +/* \fBcommand_time_limit\fR seconds. Command exit status codes are +/* expected to follow the conventions defined in <\fBsysexits.h\fR>. +/* Exit status 0 means normal successful completion. +/* +/* Postfix version 2.3 and later support RFC 3463-style enhanced +/* status codes. If a command terminates with a non-zero exit +/* status, and the command output begins with an enhanced +/* status code, this status code takes precedence over the +/* non-zero exit status. +/* +/* A limited amount of message context is exported via environment +/* variables. Characters that may have special meaning to the shell +/* are replaced with underscores. The list of acceptable characters +/* is specified with the \fBcommand_expansion_filter\fR configuration +/* parameter. +/* .IP \fBSHELL\fR +/* The recipient user's login shell. +/* .IP \fBHOME\fR +/* The recipient user's home directory. +/* .IP \fBUSER\fR +/* The bare recipient name. +/* .IP \fBEXTENSION\fR +/* The optional recipient address extension. +/* .IP \fBDOMAIN\fR +/* The recipient address domain part. +/* .IP \fBLOGNAME\fR +/* The bare recipient name. +/* .IP \fBLOCAL\fR +/* The entire recipient address localpart (text to the left of the +/* rightmost @ character). +/* .IP \fBORIGINAL_RECIPIENT\fR +/* The entire recipient address, before any address rewriting +/* or aliasing (Postfix 2.5 and later). +/* .IP \fBRECIPIENT\fR +/* The entire recipient address. +/* .IP \fBSENDER\fR +/* The entire sender address. +/* .PP +/* Additional remote client information is made available via +/* the following environment variables: +/* .IP \fBCLIENT_ADDRESS\fR +/* Remote client network address. Available as of Postfix 2.2. +/* .IP \fBCLIENT_HELO\fR +/* Remote client EHLO command parameter. Available as of Postfix 2.2. +/* .IP \fBCLIENT_HOSTNAME\fR +/* Remote client hostname. Available as of Postfix 2.2. +/* .IP \fBCLIENT_PROTOCOL\fR +/* Remote client protocol. Available as of Postfix 2.2. +/* .IP \fBSASL_METHOD\fR +/* SASL authentication method specified in the +/* remote client AUTH command. Available as of Postfix 2.2. +/* .IP \fBSASL_SENDER\fR +/* SASL sender address specified in the remote client MAIL +/* FROM command. Available as of Postfix 2.2. +/* .IP \fBSASL_USERNAME\fR +/* SASL username specified in the remote client AUTH command. +/* Available as of Postfix 2.2. +/* .PP +/* The \fBPATH\fR environment variable is always reset to a +/* system-dependent default path, and environment variables +/* whose names are blessed by the \fBexport_environment\fR +/* configuration parameter are exported unchanged. +/* +/* The current working directory is the mail queue directory. +/* +/* The \fBlocal\fR(8) daemon prepends a "\fBFrom \fIsender time_stamp\fR" +/* envelope header to each message, prepends an +/* \fBX-Original-To:\fR header with the recipient address as given to +/* Postfix, prepends an +/* optional \fBDelivered-To:\fR +/* header with the final recipient envelope address, prepends a +/* \fBReturn-Path:\fR header with the sender envelope address, +/* and appends no empty line. +/* EXTERNAL FILE DELIVERY +/* .ad +/* .fi +/* The delivery format depends on the destination filename syntax. +/* The default is to use UNIX-style mailbox format. Specify a name +/* ending in \fB/\fR for \fBqmail\fR-compatible \fBmaildir\fR delivery. +/* +/* The \fBallow_mail_to_files\fR configuration parameter restricts +/* delivery to external files. The default setting (\fBalias, +/* forward\fR) forbids file destinations in \fB:include:\fR files. +/* +/* In the case of UNIX-style mailbox delivery, +/* the \fBlocal\fR(8) daemon prepends a "\fBFrom \fIsender time_stamp\fR" +/* envelope header to each message, prepends an +/* \fBX-Original-To:\fR header with the recipient address as given to +/* Postfix, prepends an +/* optional \fBDelivered-To:\fR +/* header with the final recipient envelope address, prepends a \fB>\fR +/* character to lines beginning with "\fBFrom \fR", and appends an +/* empty line. +/* The envelope sender address is available in the \fBReturn-Path:\fR +/* header. +/* When the destination is a regular file, it is locked for exclusive +/* access while delivery is in progress. In case of problems, an attempt +/* is made to truncate a regular file to its original length. +/* +/* In the case of \fBmaildir\fR delivery, the local daemon prepends +/* an optional +/* \fBDelivered-To:\fR header with the final envelope recipient address, +/* and prepends an +/* \fBX-Original-To:\fR header with the recipient address as given to +/* Postfix. +/* The envelope sender address is available in the \fBReturn-Path:\fR +/* header. +/* ADDRESS EXTENSION +/* .ad +/* .fi +/* The optional \fBrecipient_delimiter\fR configuration parameter +/* specifies how to separate address extensions from local recipient +/* names. +/* +/* For example, with "\fBrecipient_delimiter = +\fR", mail for +/* \fIname\fR+\fIfoo\fR is delivered to the alias \fIname\fR+\fIfoo\fR +/* or to the alias \fIname\fR, to the destinations listed in +/* ~\fIname\fR/.\fBforward\fR+\fIfoo\fR or in ~\fIname\fR/.\fBforward\fR, +/* to the mailbox owned by the user \fIname\fR, or it is sent back as +/* undeliverable. +/* DELIVERY RIGHTS +/* .ad +/* .fi +/* Deliveries to external files and external commands are made with +/* the rights of the receiving user on whose behalf the delivery is made. +/* In the absence of a user context, the \fBlocal\fR(8) daemon uses the +/* owner rights of the \fB:include:\fR file or alias database. +/* When those files are owned by the superuser, delivery is made with +/* the rights specified with the \fBdefault_privs\fR configuration +/* parameter. +/* STANDARDS +/* RFC 822 (ARPA Internet Text Messages) +/* RFC 3463 (Enhanced status codes) +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* Corrupted message files are marked so that the queue +/* manager can move them to the \fBcorrupt\fR queue afterwards. +/* +/* Depending on the setting of the \fBnotify_classes\fR parameter, +/* the postmaster is notified of bounces and of other trouble. +/* SECURITY +/* .ad +/* .fi +/* The \fBlocal\fR(8) delivery agent needs a dual personality +/* 1) to access the private Postfix queue and IPC mechanisms, +/* 2) to impersonate the recipient and deliver to recipient-specified +/* files or commands. It is therefore security sensitive. +/* +/* The \fBlocal\fR(8) delivery agent disallows regular expression +/* substitution of $1 etc. in \fBalias_maps\fR, because that +/* would open a security hole. +/* +/* The \fBlocal\fR(8) delivery agent will silently ignore +/* requests to use the \fBproxymap\fR(8) server within +/* \fBalias_maps\fR. Instead it will open the table directly. +/* Before Postfix version 2.2, the \fBlocal\fR(8) delivery +/* agent will terminate with a fatal error. +/* BUGS +/* For security reasons, the message delivery status of external commands +/* or of external files is never checkpointed to file. As a result, +/* the program may occasionally deliver more than once to a command or +/* external file. Better safe than sorry. +/* +/* Mutually-recursive aliases or ~/.\fBforward\fR files are not detected +/* early. The resulting mail forwarding loop is broken by the use of the +/* \fBDelivered-To:\fR message header. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Changes to \fBmain.cf\fR are picked up automatically, as \fBlocal\fR(8) +/* processes run for only a limited amount of time. Use the command +/* "\fBpostfix reload\fR" to speed up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* COMPATIBILITY CONTROLS +/* .ad +/* .fi +/* .IP "\fBbiff (yes)\fR" +/* Whether or not to use the local biff service. +/* .IP "\fBexpand_owner_alias (no)\fR" +/* When delivering to an alias "\fIaliasname\fR" that has an +/* "owner-\fIaliasname\fR" companion alias, set the envelope sender +/* address to the expansion of the "owner-\fIaliasname\fR" alias. +/* .IP "\fBowner_request_special (yes)\fR" +/* Enable special treatment for owner-\fIlistname\fR entries in the +/* \fBaliases\fR(5) file, and don't split owner-\fIlistname\fR and +/* \fIlistname\fR-request address localparts when the recipient_delimiter +/* is set to "-". +/* .IP "\fBsun_mailtool_compatibility (no)\fR" +/* Obsolete SUN mailtool compatibility feature. +/* .PP +/* Available in Postfix version 2.3 and later: +/* .IP "\fBfrozen_delivered_to (yes)\fR" +/* Update the \fBlocal\fR(8) delivery agent's idea of the Delivered-To: +/* address (see prepend_delivered_header) only once, at the start of +/* a delivery attempt; do not update the Delivered-To: address while +/* expanding aliases or .forward files. +/* .PP +/* Available in Postfix version 2.5.3 and later: +/* .IP "\fBstrict_mailbox_ownership (yes)\fR" +/* Defer delivery when a mailbox file is not owned by its recipient. +/* .IP "\fBreset_owner_alias (no)\fR" +/* Reset the \fBlocal\fR(8) delivery agent's idea of the owner-alias +/* attribute, when delivering mail to a child alias that does not have +/* its own owner alias. +/* .PP +/* Available in Postfix version 3.0 and later: +/* .IP "\fBlocal_delivery_status_filter ($default_delivery_status_filter)\fR" +/* Optional filter for the \fBlocal\fR(8) delivery agent to change the +/* status code or explanatory text of successful or unsuccessful +/* deliveries. +/* DELIVERY METHOD CONTROLS +/* .ad +/* .fi +/* The precedence of \fBlocal\fR(8) delivery methods from high to low is: +/* aliases, .forward files, mailbox_transport_maps, +/* mailbox_transport, mailbox_command_maps, mailbox_command, +/* home_mailbox, mail_spool_directory, fallback_transport_maps, +/* fallback_transport, and luser_relay. +/* .IP "\fBalias_maps (see 'postconf -d' output)\fR" +/* The alias databases that are used for \fBlocal\fR(8) delivery. +/* .IP "\fBforward_path (see 'postconf -d' output)\fR" +/* The \fBlocal\fR(8) delivery agent search list for finding a .forward +/* file with user-specified delivery methods. +/* .IP "\fBmailbox_transport_maps (empty)\fR" +/* Optional lookup tables with per-recipient message delivery +/* transports to use for \fBlocal\fR(8) mailbox delivery, whether or not the +/* recipients are found in the UNIX passwd database. +/* .IP "\fBmailbox_transport (empty)\fR" +/* Optional message delivery transport that the \fBlocal\fR(8) delivery +/* agent should use for mailbox delivery to all local recipients, +/* whether or not they are found in the UNIX passwd database. +/* .IP "\fBmailbox_command_maps (empty)\fR" +/* Optional lookup tables with per-recipient external commands to use +/* for \fBlocal\fR(8) mailbox delivery. +/* .IP "\fBmailbox_command (empty)\fR" +/* Optional external command that the \fBlocal\fR(8) delivery agent should +/* use for mailbox delivery. +/* .IP "\fBhome_mailbox (empty)\fR" +/* Optional pathname of a mailbox file relative to a \fBlocal\fR(8) user's +/* home directory. +/* .IP "\fBmail_spool_directory (see 'postconf -d' output)\fR" +/* The directory where \fBlocal\fR(8) UNIX-style mailboxes are kept. +/* .IP "\fBfallback_transport_maps (empty)\fR" +/* Optional lookup tables with per-recipient message delivery +/* transports for recipients that the \fBlocal\fR(8) delivery agent could +/* not find in the \fBaliases\fR(5) or UNIX password database. +/* .IP "\fBfallback_transport (empty)\fR" +/* Optional message delivery transport that the \fBlocal\fR(8) delivery +/* agent should use for names that are not found in the \fBaliases\fR(5) +/* or UNIX password database. +/* .IP "\fBluser_relay (empty)\fR" +/* Optional catch-all destination for unknown \fBlocal\fR(8) recipients. +/* .PP +/* Available in Postfix version 2.2 and later: +/* .IP "\fBcommand_execution_directory (empty)\fR" +/* The \fBlocal\fR(8) delivery agent working directory for delivery to +/* external commands. +/* MAILBOX LOCKING CONTROLS +/* .ad +/* .fi +/* .IP "\fBdeliver_lock_attempts (20)\fR" +/* The maximal number of attempts to acquire an exclusive lock on a +/* mailbox file or \fBbounce\fR(8) logfile. +/* .IP "\fBdeliver_lock_delay (1s)\fR" +/* The time between attempts to acquire an exclusive lock on a mailbox +/* file or \fBbounce\fR(8) logfile. +/* .IP "\fBstale_lock_time (500s)\fR" +/* The time after which a stale exclusive mailbox lockfile is removed. +/* .IP "\fBmailbox_delivery_lock (see 'postconf -d' output)\fR" +/* How to lock a UNIX-style \fBlocal\fR(8) mailbox before attempting delivery. +/* RESOURCE AND RATE CONTROLS +/* .ad +/* .fi +/* .IP "\fBcommand_time_limit (1000s)\fR" +/* Time limit for delivery to external commands. +/* .IP "\fBduplicate_filter_limit (1000)\fR" +/* The maximal number of addresses remembered by the address +/* duplicate filter for \fBaliases\fR(5) or \fBvirtual\fR(5) alias expansion, or +/* for \fBshowq\fR(8) queue displays. +/* .IP "\fBmailbox_size_limit (51200000)\fR" +/* The maximal size of any \fBlocal\fR(8) individual mailbox or maildir +/* file, or zero (no limit). +/* .PP +/* Implemented in the qmgr(8) daemon: +/* .IP "\fBlocal_destination_concurrency_limit (2)\fR" +/* The maximal number of parallel deliveries via the local mail +/* delivery transport to the same recipient (when +/* "local_destination_recipient_limit = 1") or the maximal number of +/* parallel deliveries to the same local domain (when +/* "local_destination_recipient_limit > 1"). +/* .IP "\fBlocal_destination_recipient_limit (1)\fR" +/* The maximal number of recipients per message delivery via the +/* local mail delivery transport. +/* SECURITY CONTROLS +/* .ad +/* .fi +/* .IP "\fBallow_mail_to_commands (alias, forward)\fR" +/* Restrict \fBlocal\fR(8) mail delivery to external commands. +/* .IP "\fBallow_mail_to_files (alias, forward)\fR" +/* Restrict \fBlocal\fR(8) mail delivery to external files. +/* .IP "\fBcommand_expansion_filter (see 'postconf -d' output)\fR" +/* Restrict the characters that the \fBlocal\fR(8) delivery agent allows in +/* $name expansions of $mailbox_command and $command_execution_directory. +/* .IP "\fBdefault_privs (nobody)\fR" +/* The default rights used by the \fBlocal\fR(8) delivery agent for delivery +/* to an external file or command. +/* .IP "\fBforward_expansion_filter (see 'postconf -d' output)\fR" +/* Restrict the characters that the \fBlocal\fR(8) delivery agent allows in +/* $name expansions of $forward_path. +/* .PP +/* Available in Postfix version 2.2 and later: +/* .IP "\fBexecution_directory_expansion_filter (see 'postconf -d' output)\fR" +/* Restrict the characters that the \fBlocal\fR(8) delivery agent allows +/* in $name expansions of $command_execution_directory. +/* .PP +/* Available in Postfix version 2.5.3 and later: +/* .IP "\fBstrict_mailbox_ownership (yes)\fR" +/* Defer delivery when a mailbox file is not owned by its recipient. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBdelay_logging_resolution_limit (2)\fR" +/* The maximal number of digits after the decimal point when logging +/* sub-second delay values. +/* .IP "\fBexport_environment (see 'postconf -d' output)\fR" +/* The list of environment variables that a Postfix process will export +/* to non-Postfix processes. +/* .IP "\fBipc_timeout (3600s)\fR" +/* The time limit for sending or receiving information over an internal +/* communication channel. +/* .IP "\fBlocal_command_shell (empty)\fR" +/* Optional shell program for \fBlocal\fR(8) delivery to non-Postfix commands. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBmax_use (100)\fR" +/* The maximal number of incoming connections that a Postfix daemon +/* process will service before terminating voluntarily. +/* .IP "\fBprepend_delivered_header (command, file, forward)\fR" +/* The message delivery contexts where the Postfix \fBlocal\fR(8) delivery +/* agent prepends a Delivered-To: message header with the address +/* that the mail was delivered to. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBpropagate_unmatched_extensions (canonical, virtual)\fR" +/* What address lookup tables copy an address extension from the lookup +/* key to the lookup result. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBrecipient_delimiter (empty)\fR" +/* The set of characters that can separate an email address +/* localpart, user name, or a .forward file name from its extension. +/* .IP "\fBrequire_home_directory (no)\fR" +/* Require that a \fBlocal\fR(8) recipient's home directory exists +/* before mail delivery is attempted. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix version 3.3 and later: +/* .IP "\fBenable_original_recipient (yes)\fR" +/* Enable support for the original recipient address after an +/* address is rewritten to a different address (for example with +/* aliasing or with canonical mapping). +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* .PP +/* Available in Postfix 3.5 and later: +/* .IP "\fBinfo_log_address_format (external)\fR" +/* The email address form that will be used in non-debug logging +/* (info, warning, etc.). +/* FILES +/* The following are examples; details differ between systems. +/* $HOME/.forward, per-user aliasing +/* /etc/aliases, system-wide alias database +/* /var/spool/mail, system mailboxes +/* SEE ALSO +/* qmgr(8), queue manager +/* bounce(8), delivery status reports +/* newaliases(1), create/update alias database +/* postalias(1), create/update alias database +/* aliases(5), format of alias database +/* postconf(5), configuration parameters +/* master(5), generic daemon options +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* HISTORY +/* .ad +/* .fi +/* The \fBDelivered-To:\fR message header appears in the \fBqmail\fR +/* system by Daniel Bernstein. +/* +/* The \fImaildir\fR structure appears in the \fBqmail\fR system +/* by Daniel Bernstein. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#ifdef USE_PATHS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Single server skeleton. */ + +#include + +/* Application-specific. */ + +#include "local.h" + + /* + * Tunable parameters. + */ +char *var_allow_commands; +char *var_allow_files; +char *var_alias_maps; +int var_dup_filter_limit; +int var_command_maxtime; /* You can now leave this here. */ +char *var_home_mailbox; +char *var_mailbox_command; +char *var_mailbox_cmd_maps; +char *var_rcpt_fdelim; +char *var_local_cmd_shell; +char *var_luser_relay; +int var_biff; +char *var_mail_spool_dir; +char *var_mailbox_transport; +char *var_mbox_transp_maps; +char *var_fallback_transport; +char *var_fbck_transp_maps; +char *var_exec_directory; +char *var_exec_exp_filter; +char *var_forward_path; +char *var_cmd_exp_filter; +char *var_fwd_exp_filter; +char *var_prop_extension; +int var_exp_own_alias; +char *var_deliver_hdr; +int var_stat_home_dir; +int var_mailtool_compat; +char *var_mailbox_lock; +long var_mailbox_limit; +bool var_frozen_delivered; +bool var_reset_owner_attr; +bool var_strict_mbox_owner; + +int local_cmd_deliver_mask; +int local_file_deliver_mask; +int local_ext_prop_mask; +int local_deliver_hdr_mask; +int local_mbox_lock_mask; +MAPS *alias_maps; +char *var_local_dsn_filter; + +/* local_deliver - deliver message with extreme prejudice */ + +static int local_deliver(DELIVER_REQUEST *rqst, char *service) +{ + const char *myname = "local_deliver"; + RECIPIENT *rcpt_end = rqst->rcpt_list.info + rqst->rcpt_list.len; + RECIPIENT *rcpt; + int rcpt_stat; + int msg_stat; + LOCAL_STATE state; + USER_ATTR usr_attr; + + if (msg_verbose) + msg_info("local_deliver: %s from %s", rqst->queue_id, rqst->sender); + + /* + * Initialize the delivery attributes that are not recipient specific. + * While messages are being delivered and while aliases or forward files + * are being expanded, this attribute list is being changed constantly. + * For this reason, the list is passed on by value (except when it is + * being initialized :-), so that there is no need to undo attribute + * changes made by lower-level routines. The alias/include/forward + * expansion attribute list is part of a tree with self and parent + * references (see the EXPAND_ATTR definitions). The user-specific + * attributes are security sensitive, and are therefore kept separate. + * All this results in a noticeable level of clumsiness, but passing + * things around by value gives good protection against accidental change + * by subroutines. + */ + state.level = 0; + deliver_attr_init(&state.msg_attr); + state.msg_attr.queue_name = rqst->queue_name; + state.msg_attr.queue_id = rqst->queue_id; + state.msg_attr.fp = rqst->fp; + state.msg_attr.offset = rqst->data_offset; + state.msg_attr.encoding = rqst->encoding; + state.msg_attr.smtputf8 = rqst->smtputf8; + state.msg_attr.sender = rqst->sender; + state.msg_attr.dsn_envid = rqst->dsn_envid; + state.msg_attr.dsn_ret = rqst->dsn_ret; + state.msg_attr.relay = service; + state.msg_attr.msg_stats = rqst->msg_stats; + state.msg_attr.request = rqst; + RESET_OWNER_ATTR(state.msg_attr, state.level); + RESET_USER_ATTR(usr_attr, state.level); + state.loop_info = delivered_hdr_init(rqst->fp, rqst->data_offset, + FOLD_ADDR_ALL); + state.request = rqst; + + /* + * Iterate over each recipient named in the delivery request. When the + * mail delivery status for a given recipient is definite (i.e. bounced + * or delivered), update the message queue file and cross off the + * recipient. Update the per-message delivery status. + */ + for (msg_stat = 0, rcpt = rqst->rcpt_list.info; rcpt < rcpt_end; rcpt++) { + state.dup_filter = been_here_init(var_dup_filter_limit, BH_FLAG_FOLD); + forward_init(); + state.msg_attr.rcpt = *rcpt; + rcpt_stat = deliver_recipient(state, usr_attr); + rcpt_stat |= forward_finish(rqst, state.msg_attr, rcpt_stat); + if (rcpt_stat == 0 && (rqst->flags & DEL_REQ_FLAG_SUCCESS)) + deliver_completed(state.msg_attr.fp, rcpt->offset); + been_here_free(state.dup_filter); + msg_stat |= rcpt_stat; + } + + /* + * Clean up. + */ + delivered_hdr_free(state.loop_info); + deliver_attr_free(&state.msg_attr); + + return (msg_stat); +} + +/* local_service - perform service for client */ + +static void local_service(VSTREAM *stream, char *service, char **argv) +{ + DELIVER_REQUEST *request; + int status; + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * This routine runs whenever a client connects to the UNIX-domain socket + * that is dedicated to local mail delivery service. What we see below is + * a little protocol to (1) tell the client that we are ready, (2) read a + * delivery request from the client, and (3) report the completion status + * of that request. + */ + if ((request = deliver_request_read(stream)) != 0) { + status = local_deliver(request, service); + deliver_request_done(stream, request, status); + } +} + +/* local_mask_init - initialize delivery restrictions */ + +static void local_mask_init(void) +{ + static const NAME_MASK file_mask[] = { + "alias", EXPAND_TYPE_ALIAS, + "forward", EXPAND_TYPE_FWD, + "include", EXPAND_TYPE_INCL, + 0, + }; + static const NAME_MASK command_mask[] = { + "alias", EXPAND_TYPE_ALIAS, + "forward", EXPAND_TYPE_FWD, + "include", EXPAND_TYPE_INCL, + 0, + }; + static const NAME_MASK deliver_mask[] = { + "command", DELIVER_HDR_CMD, + "file", DELIVER_HDR_FILE, + "forward", DELIVER_HDR_FWD, + 0, + }; + + local_file_deliver_mask = name_mask(VAR_ALLOW_FILES, file_mask, + var_allow_files); + local_cmd_deliver_mask = name_mask(VAR_ALLOW_COMMANDS, command_mask, + var_allow_commands); + local_ext_prop_mask = + ext_prop_mask(VAR_PROP_EXTENSION, var_prop_extension); + local_deliver_hdr_mask = name_mask(VAR_DELIVER_HDR, deliver_mask, + var_deliver_hdr); + local_mbox_lock_mask = mbox_lock_mask(var_mailbox_lock); + if (var_mailtool_compat) { + msg_warn("%s: deprecated parameter, use \"%s = dotlock\" instead", + VAR_MAILTOOL_COMPAT, VAR_MAILBOX_LOCK); + local_mbox_lock_mask &= MBOX_DOT_LOCK; + } + if (local_mbox_lock_mask == 0) + msg_fatal("parameter %s specifies no applicable mailbox locking method", + VAR_MAILBOX_LOCK); +} + +/* pre_accept - see if tables have changed */ + +static void pre_accept(char *unused_name, char **unused_argv) +{ + const char *table; + + if ((table = dict_changed_name()) != 0) { + msg_info("table %s has changed -- restarting", table); + exit(0); + } +} + +/* post_init - post-jail initialization */ + +static void post_init(char *unused_name, char **unused_argv) +{ + + /* + * Drop privileges most of the time, and set up delivery restrictions. + */ + set_eugid(var_owner_uid, var_owner_gid); + local_mask_init(); +} + +/* pre_init - pre-jail initialization */ + +static void pre_init(char *unused_name, char **unused_argv) +{ + + /* + * Reset the file size limit from the message size limit to the mailbox + * size limit. XXX This still isn't accurate because the file size limit + * also affects delivery to command. + * + * A file size limit protects the machine against runaway software errors. + * It is not suitable to enforce mail quota, because users can get around + * mail quota by delivering to /file/name or to |command. + * + * We can't have mailbox size limit smaller than the message size limit, + * because that prohibits the delivery agent from updating the queue + * file. + */ + if (ENFORCING_SIZE_LIMIT(var_mailbox_limit)) { + if (!ENFORCING_SIZE_LIMIT(var_message_limit)) + msg_fatal("configuration error: %s is limited but %s is " + "unlimited", VAR_MAILBOX_LIMIT, VAR_MESSAGE_LIMIT); + if (var_mailbox_limit < var_message_limit) + msg_fatal("configuration error: %s is smaller than %s", + VAR_MAILBOX_LIMIT, VAR_MESSAGE_LIMIT); + set_file_limit(var_mailbox_limit); + } + alias_maps = maps_create("aliases", var_alias_maps, + DICT_FLAG_LOCK | DICT_FLAG_PARANOID + | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + + flush_init(); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - pass control to the single-threaded skeleton */ + +int main(int argc, char **argv) +{ + static const CONFIG_TIME_TABLE time_table[] = { + VAR_COMMAND_MAXTIME, DEF_COMMAND_MAXTIME, &var_command_maxtime, 1, 0, + 0, + }; + static const CONFIG_INT_TABLE int_table[] = { + VAR_DUP_FILTER_LIMIT, DEF_DUP_FILTER_LIMIT, &var_dup_filter_limit, 0, 0, + 0, + }; + static const CONFIG_LONG_TABLE long_table[] = { + VAR_MAILBOX_LIMIT, DEF_MAILBOX_LIMIT, &var_mailbox_limit, 0, 0, + 0, + }; + static const CONFIG_STR_TABLE str_table[] = { + VAR_ALIAS_MAPS, DEF_ALIAS_MAPS, &var_alias_maps, 0, 0, + VAR_HOME_MAILBOX, DEF_HOME_MAILBOX, &var_home_mailbox, 0, 0, + VAR_ALLOW_COMMANDS, DEF_ALLOW_COMMANDS, &var_allow_commands, 0, 0, + VAR_ALLOW_FILES, DEF_ALLOW_FILES, &var_allow_files, 0, 0, + VAR_LOCAL_CMD_SHELL, DEF_LOCAL_CMD_SHELL, &var_local_cmd_shell, 0, 0, + VAR_MAIL_SPOOL_DIR, DEF_MAIL_SPOOL_DIR, &var_mail_spool_dir, 0, 0, + VAR_MAILBOX_TRANSP, DEF_MAILBOX_TRANSP, &var_mailbox_transport, 0, 0, + VAR_MBOX_TRANSP_MAPS, DEF_MBOX_TRANSP_MAPS, &var_mbox_transp_maps, 0, 0, + VAR_FALLBACK_TRANSP, DEF_FALLBACK_TRANSP, &var_fallback_transport, 0, 0, + VAR_FBCK_TRANSP_MAPS, DEF_FBCK_TRANSP_MAPS, &var_fbck_transp_maps, 0, 0, + VAR_CMD_EXP_FILTER, DEF_CMD_EXP_FILTER, &var_cmd_exp_filter, 1, 0, + VAR_FWD_EXP_FILTER, DEF_FWD_EXP_FILTER, &var_fwd_exp_filter, 1, 0, + VAR_EXEC_EXP_FILTER, DEF_EXEC_EXP_FILTER, &var_exec_exp_filter, 1, 0, + VAR_PROP_EXTENSION, DEF_PROP_EXTENSION, &var_prop_extension, 0, 0, + VAR_DELIVER_HDR, DEF_DELIVER_HDR, &var_deliver_hdr, 0, 0, + VAR_MAILBOX_LOCK, DEF_MAILBOX_LOCK, &var_mailbox_lock, 1, 0, + VAR_MAILBOX_CMD_MAPS, DEF_MAILBOX_CMD_MAPS, &var_mailbox_cmd_maps, 0, 0, + VAR_LOCAL_DSN_FILTER, DEF_LOCAL_DSN_FILTER, &var_local_dsn_filter, 0, 0, + 0, + }; + static const CONFIG_BOOL_TABLE bool_table[] = { + VAR_BIFF, DEF_BIFF, &var_biff, + VAR_EXP_OWN_ALIAS, DEF_EXP_OWN_ALIAS, &var_exp_own_alias, + VAR_STAT_HOME_DIR, DEF_STAT_HOME_DIR, &var_stat_home_dir, + VAR_MAILTOOL_COMPAT, DEF_MAILTOOL_COMPAT, &var_mailtool_compat, + VAR_FROZEN_DELIVERED, DEF_FROZEN_DELIVERED, &var_frozen_delivered, + VAR_RESET_OWNER_ATTR, DEF_RESET_OWNER_ATTR, &var_reset_owner_attr, + VAR_STRICT_MBOX_OWNER, DEF_STRICT_MBOX_OWNER, &var_strict_mbox_owner, + 0, + }; + + /* Suppress $name expansion upon loading. */ + static const CONFIG_RAW_TABLE raw_table[] = { + VAR_EXEC_DIRECTORY, DEF_EXEC_DIRECTORY, &var_exec_directory, 0, 0, + VAR_FORWARD_PATH, DEF_FORWARD_PATH, &var_forward_path, 0, 0, + VAR_MAILBOX_COMMAND, DEF_MAILBOX_COMMAND, &var_mailbox_command, 0, 0, + VAR_LUSER_RELAY, DEF_LUSER_RELAY, &var_luser_relay, 0, 0, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + single_server_main(argc, argv, local_service, + CA_MAIL_SERVER_INT_TABLE(int_table), + CA_MAIL_SERVER_LONG_TABLE(long_table), + CA_MAIL_SERVER_STR_TABLE(str_table), + CA_MAIL_SERVER_RAW_TABLE(raw_table), + CA_MAIL_SERVER_BOOL_TABLE(bool_table), + CA_MAIL_SERVER_TIME_TABLE(time_table), + CA_MAIL_SERVER_PRE_INIT(pre_init), + CA_MAIL_SERVER_POST_INIT(post_init), + CA_MAIL_SERVER_PRE_ACCEPT(pre_accept), + CA_MAIL_SERVER_PRIVILEGED, + CA_MAIL_SERVER_BOUNCE_INIT(VAR_LOCAL_DSN_FILTER, + &var_local_dsn_filter), + 0); +} diff --git a/src/local/local.h b/src/local/local.h new file mode 100644 index 0000000..4052000 --- /dev/null +++ b/src/local/local.h @@ -0,0 +1,251 @@ +/*++ +/* NAME +/* local 3h +/* SUMMARY +/* local mail delivery +/* SYNOPSIS +/* #include "local.h" +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Global library. + */ +#include +#include +#include +#include +#include +#include +#include +#include + + /* + * User attributes: these control the privileges for delivery to external + * commands, external files, or mailboxes, and the initial environment of + * external commands. + */ +typedef struct USER_ATTR { + uid_t uid; /* file/command access */ + gid_t gid; /* file/command access */ + char *home; /* null or home directory */ + char *logname; /* null or login name */ + char *shell; /* null or login shell */ +} USER_ATTR; + + /* + * Critical macros. Not for obscurity, but to ensure consistency. + */ +#define RESET_USER_ATTR(usr_attr, level) { \ + usr_attr.uid = 0; usr_attr.gid = 0; usr_attr.home = 0; \ + usr_attr.logname = 0; usr_attr.shell = 0; \ + if (msg_verbose) \ + msg_info("%s[%d]: reset user_attr", myname, level); \ + } + +#define SET_USER_ATTR(usr_attr, pwd, level) { \ + usr_attr.uid = pwd->pw_uid; usr_attr.gid = pwd->pw_gid; \ + usr_attr.home = pwd->pw_dir; usr_attr.logname = pwd->pw_name; \ + usr_attr.shell = pwd->pw_shell; \ + if (msg_verbose) \ + msg_info("%s[%d]: set user_attr: %s", \ + myname, level, pwd->pw_name); \ + } + + /* + * The delivery attributes are inherited from files, from aliases, and from + * whatnot. Some of the information is changed on the fly. DELIVER_ATTR + * structures are therefore passed by value, so there is no need to undo + * changes. + */ +typedef struct DELIVER_ATTR { + int level; /* recursion level */ + VSTREAM *fp; /* open queue file */ + char *queue_name; /* mail queue id */ + char *queue_id; /* mail queue id */ + long offset; /* data offset */ + char *encoding; /* MIME encoding */ + int smtputf8; /* from delivery request */ + const char *sender; /* taken from envelope */ + char *dsn_envid; /* DSN envelope ID */ + int dsn_ret; /* DSN headers/full */ + RECIPIENT rcpt; /* from delivery request */ + char *domain; /* recipient domain */ + char *local; /* recipient full localpart */ + char *user; /* recipient localpart, base name */ + char *extension; /* recipient localpart, extension */ + char *unmatched; /* unmatched extension */ + const char *owner; /* null or list owner */ + const char *delivered; /* for loop detection */ + char *relay; /* relay host */ + MSG_STATS msg_stats; /* time profile */ + int exp_type; /* expansion type. see below */ + char *exp_from; /* expanded_from */ + DELIVER_REQUEST *request; /* the kitchen sink */ + DSN_BUF *why; /* delivery status */ +} DELIVER_ATTR; + +extern void deliver_attr_init(DELIVER_ATTR *); +extern void deliver_attr_dump(DELIVER_ATTR *); +extern void deliver_attr_free(DELIVER_ATTR *); + +#define EXPAND_TYPE_ALIAS (1<<0) +#define EXPAND_TYPE_FWD (1<<1) +#define EXPAND_TYPE_INCL (1<<2) + + /* + * Rather than schlepping around dozens of arguments, here is one that has + * all. Well, almost. The user attributes are just a bit too sensitive, so + * they are passed around separately. + */ +typedef struct LOCAL_STATE { + int level; /* nesting level, for logging */ + DELIVER_ATTR msg_attr; /* message attributes */ + BH_TABLE *dup_filter; /* internal duplicate filter */ + DELIVERED_HDR_INFO *loop_info; /* external loop filter */ + DELIVER_REQUEST *request; /* as from queue manager */ +} LOCAL_STATE; + +#define RESET_OWNER_ATTR(msg_attr, level) { \ + msg_attr.owner = 0; \ + if (msg_verbose) \ + msg_info("%s[%d]: reset owner attr", myname, level); \ + } + +#define SET_OWNER_ATTR(msg_attr, who, level) { \ + msg_attr.sender = msg_attr.owner = who; \ + if (msg_verbose) \ + msg_info("%s[%d]: set owner attr: %s", \ + myname, level, who); \ + } + + /* + * Bundle up some often-user attributes. + */ +#define BOUNCE_FLAGS(request) DEL_REQ_TRACE_FLAGS((request)->flags) + +#define BOUNCE_ATTR(attr) \ + attr.queue_id, &attr.msg_stats, &attr.rcpt, attr.relay, \ + DSN_FROM_DSN_BUF(attr.why) +#define BOUNCE_ONE_ATTR(attr) \ + attr.queue_name, attr.queue_id, attr.encoding, attr.smtputf8, \ + attr.sender, attr.dsn_envid, attr.dsn_ret, \ + &attr.msg_stats, &attr.rcpt, attr.relay, \ + DSN_FROM_DSN_BUF(attr.why) +#define SENT_ATTR(attr) \ + attr.queue_id, &attr.msg_stats, &attr.rcpt, attr.relay, \ + DSN_FROM_DSN_BUF(attr.why) +#define OPENED_ATTR(attr) \ + attr.queue_id, attr.sender +#define COPY_ATTR(attr) \ + attr.sender, attr.rcpt.orig_addr, attr.delivered, attr.fp + +#define MSG_LOG_STATE(m, p) \ + msg_info("%s[%d]: local %s recip %s exten %s deliver %s exp_from %s", \ + m, \ + p.level, \ + p.msg_attr.local ? p.msg_attr.local : "" , \ + p.msg_attr.rcpt.address ? p.msg_attr.rcpt.address : "", \ + p.msg_attr.extension ? p.msg_attr.extension : "", \ + p.msg_attr.delivered ? p.msg_attr.delivered : "", \ + p.msg_attr.exp_from ? p.msg_attr.exp_from : "") + + /* + * "inner" nodes of the delivery graph. + */ +extern int deliver_recipient(LOCAL_STATE, USER_ATTR); +extern int deliver_alias(LOCAL_STATE, USER_ATTR, char *, int *); +extern int deliver_dotforward(LOCAL_STATE, USER_ATTR, int *); +extern int deliver_include(LOCAL_STATE, USER_ATTR, char *); +extern int deliver_token(LOCAL_STATE, USER_ATTR, TOK822 *); +extern int deliver_token_string(LOCAL_STATE, USER_ATTR, char *, int *); +extern int deliver_token_stream(LOCAL_STATE, USER_ATTR, VSTREAM *, int *); +extern int deliver_resolve_tree(LOCAL_STATE, USER_ATTR, TOK822 *); +extern int deliver_resolve_addr(LOCAL_STATE, USER_ATTR, char *); + + /* + * "leaf" nodes of the delivery graph. + */ +extern int deliver_mailbox(LOCAL_STATE, USER_ATTR, int *); +extern int deliver_command(LOCAL_STATE, USER_ATTR, const char *); +extern int deliver_file(LOCAL_STATE, USER_ATTR, char *); +extern int deliver_indirect(LOCAL_STATE); +extern int deliver_maildir(LOCAL_STATE, USER_ATTR, char *); +extern int deliver_unknown(LOCAL_STATE, USER_ATTR); + + /* + * Restrictions on delivery to sensitive destinations. + */ +extern int local_file_deliver_mask; +extern int local_cmd_deliver_mask; + + /* + * Restrictions on extension propagation. + */ +extern int local_ext_prop_mask; + + /* + * Mailbox lock protocol. + */ +extern int local_mbox_lock_mask; + + /* + * When to prepend a Delivered-To: header upon external delivery. + */ +#define DELIVER_HDR_CMD (1<<0) +#define DELIVER_HDR_FILE (1<<1) +#define DELIVER_HDR_FWD (1<<2) + +extern int local_deliver_hdr_mask; + + /* + * forward.c + */ +extern int forward_init(void); +extern int forward_append(DELIVER_ATTR); +extern int forward_finish(DELIVER_REQUEST *, DELIVER_ATTR, int); + + /* + * feature.c + */ +extern int feature_control(const char *); + + /* + * local_expand.c + */ +int local_expand(VSTRING *, const char *, LOCAL_STATE *, USER_ATTR *, const char *); + +#define LOCAL_EXP_EXTENSION_MATCHED (1< +#include + +/* Utility library. */ + +#include +#include + +/* Global library */ + +#include + +/* Application-specific. */ + +#include "local.h" + +typedef struct { + LOCAL_STATE *state; + USER_ATTR *usr_attr; + int status; +} LOCAL_EXP; + +/* local_expand_lookup - mac_expand() lookup routine */ + +static const char *local_expand_lookup(const char *name, int mode, void *ptr) +{ + LOCAL_EXP *local = (LOCAL_EXP *) ptr; + static char rcpt_delim[2]; + +#define STREQ(x,y) (*(x) == *(y) && strcmp((x), (y)) == 0) + + if (STREQ(name, "user")) { + return (local->state->msg_attr.user); + } else if (STREQ(name, "home")) { + return (local->usr_attr->home); + } else if (STREQ(name, "shell")) { + return (local->usr_attr->shell); + } else if (STREQ(name, "domain")) { + return (local->state->msg_attr.domain); + } else if (STREQ(name, "local")) { + return (local->state->msg_attr.local); + } else if (STREQ(name, "mailbox")) { + return (local->state->msg_attr.local); + } else if (STREQ(name, "recipient")) { + return (local->state->msg_attr.rcpt.address); + } else if (STREQ(name, "extension")) { + if (mode == MAC_EXP_MODE_USE) + local->status |= LOCAL_EXP_EXTENSION_MATCHED; + return (local->state->msg_attr.extension); + } else if (STREQ(name, "recipient_delimiter")) { + rcpt_delim[0] = + local->state->msg_attr.local[strlen(local->state->msg_attr.user)]; + if (rcpt_delim[0] == 0) + rcpt_delim[0] = var_rcpt_delim[0]; + rcpt_delim[1] = 0; + return (rcpt_delim[0] ? rcpt_delim : 0); +#if 0 + } else if (STREQ(name, "client_hostname")) { + return (local->state->msg_attr.request->client_name); + } else if (STREQ(name, "client_address")) { + return (local->state->msg_attr.request->client_addr); + } else if (STREQ(name, "client_protocol")) { + return (local->state->msg_attr.request->client_proto); + } else if (STREQ(name, "client_helo")) { + return (local->state->msg_attr.request->client_helo); + } else if (STREQ(name, "sasl_method")) { + return (local->state->msg_attr.request->sasl_method); + } else if (STREQ(name, "sasl_sender")) { + return (local->state->msg_attr.request->sasl_sender); + } else if (STREQ(name, "sasl_username")) { + return (local->state->msg_attr.request->sasl_username); +#endif + } else { + return (0); + } +} + +/* local_expand - expand message delivery attributes */ + +int local_expand(VSTRING *result, const char *pattern, + LOCAL_STATE *state, USER_ATTR *usr_attr, const char *filter) +{ + LOCAL_EXP local; + int expand_status; + + local.state = state; + local.usr_attr = usr_attr; + local.status = 0; + expand_status = mac_expand(result, pattern, MAC_EXP_FLAG_NONE, + filter, local_expand_lookup, (void *) &local); + return (local.status | expand_status); +} diff --git a/src/local/mailbox.c b/src/local/mailbox.c new file mode 100644 index 0000000..ed55291 --- /dev/null +++ b/src/local/mailbox.c @@ -0,0 +1,373 @@ +/*++ +/* NAME +/* mailbox 3 +/* SUMMARY +/* mailbox delivery +/* SYNOPSIS +/* #include "local.h" +/* +/* int deliver_mailbox(state, usr_attr, statusp) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* int *statusp; +/* DESCRIPTION +/* deliver_mailbox() delivers to mailbox, with duplicate +/* suppression. The default is direct mailbox delivery to +/* /var/[spool/]mail/\fIuser\fR; when a \fIhome_mailbox\fR +/* has been configured, mail is delivered to ~/$\fIhome_mailbox\fR; +/* and when a \fImailbox_command\fR has been configured, the message +/* is piped into the command instead. +/* +/* A zero result means that the named user was not found. +/* +/* Arguments: +/* .IP state +/* The attributes that specify the message, recipient and more. +/* Attributes describing alias, include or forward expansion. +/* A table with the results from expanding aliases or lists. +/* .IP usr_attr +/* Attributes describing user rights and environment. +/* .IP statusp +/* Delivery status: see below. +/* DIAGNOSTICS +/* The message delivery status is non-zero when delivery should be tried +/* again. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" +#include "biff_notify.h" + +#define YES 1 +#define NO 0 + +/* deliver_mailbox_file - deliver to recipient mailbox */ + +static int deliver_mailbox_file(LOCAL_STATE state, USER_ATTR usr_attr) +{ + const char *myname = "deliver_mailbox_file"; + char *spool_dir; + char *mailbox; + DSN_BUF *why = state.msg_attr.why; + MBOX *mp; + int mail_copy_status; + int deliver_status; + int copy_flags; + VSTRING *biff; + off_t end; + struct stat st; + uid_t spool_uid; + gid_t spool_gid; + uid_t chown_uid; + gid_t chown_gid; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * Don't deliver trace-only requests. + */ + if (DEL_REQ_TRACE_ONLY(state.request->flags)) { + dsb_simple(why, "2.0.0", "delivers to mailbox"); + return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr))); + } + + /* + * Initialize. Assume the operation will fail. Set the delivered + * attribute to reflect the final recipient. + */ + if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) + msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp)); + if (var_frozen_delivered == 0) + state.msg_attr.delivered = state.msg_attr.rcpt.address; + mail_copy_status = MAIL_COPY_STAT_WRITE; + if (*var_home_mailbox) { + spool_dir = 0; + mailbox = concatenate(usr_attr.home, "/", var_home_mailbox, (char *) 0); + } else { + spool_dir = var_mail_spool_dir; + mailbox = concatenate(spool_dir, "/", state.msg_attr.user, (char *) 0); + } + + /* + * Mailbox delivery with least privilege. As long as we do not use root + * privileges this code may also work over NFS. + * + * If delivering to the recipient's home directory, perform all operations + * (including file locking) as that user (Mike Muuss, Army Research + * Laboratory, USA). + * + * If delivering to the mail spool directory, and the spool directory is + * world-writable, deliver as the recipient; if the spool directory is + * group-writable, use the recipient user id and the mail spool group id. + * + * Otherwise, use root privileges and chown the mailbox if we create it. + */ + if (spool_dir == 0 + || stat(spool_dir, &st) < 0 + || (st.st_mode & S_IWOTH) != 0) { + spool_uid = usr_attr.uid; + spool_gid = usr_attr.gid; + } else if ((st.st_mode & S_IWGRP) != 0) { + spool_uid = usr_attr.uid; + spool_gid = st.st_gid; + } else { + spool_uid = 0; + spool_gid = 0; + } + if (spool_uid == usr_attr.uid) { + chown_uid = -1; + chown_gid = -1; + } else { + chown_uid = usr_attr.uid; + chown_gid = usr_attr.gid; + } + if (msg_verbose) + msg_info("spool_uid/gid %ld/%ld chown_uid/gid %ld/%ld", + (long) spool_uid, (long) spool_gid, + (long) chown_uid, (long) chown_gid); + + /* + * Lock the mailbox and open/create the mailbox file. Depending on the + * type of locking used, we lock first or we open first. + * + * Write the file as the recipient, so that file quota work. + */ + copy_flags = MAIL_COPY_MBOX; + if ((local_deliver_hdr_mask & DELIVER_HDR_FILE) == 0) + copy_flags &= ~MAIL_COPY_DELIVERED; + + set_eugid(spool_uid, spool_gid); + mp = mbox_open(mailbox, O_APPEND | O_WRONLY | O_CREAT, + S_IRUSR | S_IWUSR, &st, chown_uid, chown_gid, + local_mbox_lock_mask, "5.2.0", why); + if (mp != 0) { + if (spool_uid != usr_attr.uid || spool_gid != usr_attr.gid) + set_eugid(usr_attr.uid, usr_attr.gid); + if (S_ISREG(st.st_mode) == 0) { + vstream_fclose(mp->fp); + dsb_simple(why, "5.2.0", + "destination %s is not a regular file", mailbox); + } else if (var_strict_mbox_owner && st.st_uid != usr_attr.uid) { + vstream_fclose(mp->fp); + dsb_simple(why, "4.2.0", + "destination %s is not owned by recipient", mailbox); + msg_warn("specify \"%s = no\" to ignore mailbox ownership mismatch", + VAR_STRICT_MBOX_OWNER); + } else { + if ((end = vstream_fseek(mp->fp, (off_t) 0, SEEK_END)) < 0) + msg_fatal("seek mailbox file %s: %m", mailbox); + mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), mp->fp, + copy_flags, "\n", why); + } + if (spool_uid != usr_attr.uid || spool_gid != usr_attr.gid) + set_eugid(spool_uid, spool_gid); + mbox_release(mp); + } + set_eugid(var_owner_uid, var_owner_gid); + + /* + * As the mail system, bounce, defer delivery, or report success. + */ + if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) { + deliver_status = DEL_STAT_DEFER; + } else if (mail_copy_status != 0) { + vstring_sprintf_prepend(why->reason, + "cannot update mailbox %s for user %s. ", + mailbox, state.msg_attr.user); + deliver_status = + (STR(why->status)[0] == '4' ? + defer_append : bounce_append) + (BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); + } else { + dsb_simple(why, "2.0.0", "delivered to mailbox"); + deliver_status = sent(BOUNCE_FLAGS(state.request), + SENT_ATTR(state.msg_attr)); + if (var_biff) { + biff = vstring_alloc(100); + vstring_sprintf(biff, "%s@%ld", usr_attr.logname, (long) end); + biff_notify(STR(biff), VSTRING_LEN(biff) + 1); + vstring_free(biff); + } + } + + /* + * Clean up. + */ + myfree(mailbox); + return (deliver_status); +} + +/* deliver_mailbox - deliver to recipient mailbox */ + +int deliver_mailbox(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp) +{ + const char *myname = "deliver_mailbox"; + int status; + struct mypasswd *mbox_pwd; + char *path; + static MAPS *transp_maps; + const char *map_transport; + static MAPS *cmd_maps; + const char *map_command; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * DUPLICATE ELIMINATION + * + * Don't come here more than once, whether or not the recipient exists. + */ + if (been_here(state.dup_filter, "mailbox %s", state.msg_attr.local)) + return (YES); + + /* + * Delegate mailbox delivery to another message transport. + */ + if (*var_mbox_transp_maps && transp_maps == 0) + transp_maps = maps_create(VAR_MBOX_TRANSP_MAPS, var_mbox_transp_maps, + DICT_FLAG_LOCK | DICT_FLAG_NO_REGSUB + | DICT_FLAG_UTF8_REQUEST); + /* The -1 is a hint for the down-stream deliver_completed() function. */ + if (transp_maps + && (map_transport = maps_find(transp_maps, state.msg_attr.user, + DICT_FLAG_NONE)) != 0) { + state.msg_attr.rcpt.offset = -1L; + *statusp = deliver_pass(MAIL_CLASS_PRIVATE, map_transport, + state.request, &state.msg_attr.rcpt); + return (YES); + } else if (transp_maps && transp_maps->error != 0) { + /* Details in the logfile. */ + dsb_simple(state.msg_attr.why, "4.3.0", "table lookup failure"); + *statusp = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + return (YES); + } + if (*var_mailbox_transport) { + state.msg_attr.rcpt.offset = -1L; + *statusp = deliver_pass(MAIL_CLASS_PRIVATE, var_mailbox_transport, + state.request, &state.msg_attr.rcpt); + return (YES); + } + + /* + * Skip delivery when this recipient does not exist. + */ + if ((errno = mypwnam_err(state.msg_attr.user, &mbox_pwd)) != 0) { + msg_warn("error looking up passwd info for %s: %m", + state.msg_attr.user); + dsb_simple(state.msg_attr.why, "4.0.0", "user lookup error"); + *statusp = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + return (YES); + } + if (mbox_pwd == 0) + return (NO); + + /* + * No early returns or we have a memory leak. + */ + + /* + * DELIVERY RIGHTS + * + * Use the rights of the recipient user. + */ + SET_USER_ATTR(usr_attr, mbox_pwd, state.level); + + /* + * Deliver to mailbox, maildir or to external command. + */ +#define LAST_CHAR(s) (s[strlen(s) - 1]) + + if (*var_mailbox_cmd_maps && cmd_maps == 0) + cmd_maps = maps_create(VAR_MAILBOX_CMD_MAPS, var_mailbox_cmd_maps, + DICT_FLAG_LOCK | DICT_FLAG_PARANOID + | DICT_FLAG_UTF8_REQUEST); + + if (cmd_maps && (map_command = maps_find(cmd_maps, state.msg_attr.user, + DICT_FLAG_NONE)) != 0) { + status = deliver_command(state, usr_attr, map_command); + } else if (cmd_maps && cmd_maps->error != 0) { + /* Details in the logfile. */ + dsb_simple(state.msg_attr.why, "4.3.0", "table lookup failure"); + status = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + } else if (*var_mailbox_command) { + status = deliver_command(state, usr_attr, var_mailbox_command); + } else if (*var_home_mailbox && LAST_CHAR(var_home_mailbox) == '/') { + path = concatenate(usr_attr.home, "/", var_home_mailbox, (char *) 0); + status = deliver_maildir(state, usr_attr, path); + myfree(path); + } else if (*var_mail_spool_dir && LAST_CHAR(var_mail_spool_dir) == '/') { + path = concatenate(var_mail_spool_dir, state.msg_attr.user, + "/", (char *) 0); + status = deliver_maildir(state, usr_attr, path); + myfree(path); + } else + status = deliver_mailbox_file(state, usr_attr); + + /* + * Cleanup. + */ + mypwfree(mbox_pwd); + *statusp = status; + return (YES); +} diff --git a/src/local/maildir.c b/src/local/maildir.c new file mode 100644 index 0000000..46b8641 --- /dev/null +++ b/src/local/maildir.c @@ -0,0 +1,257 @@ +/*++ +/* NAME +/* maildir 3 +/* SUMMARY +/* delivery to maildir +/* SYNOPSIS +/* #include "local.h" +/* +/* int deliver_maildir(state, usr_attr, path) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* char *path; +/* DESCRIPTION +/* deliver_maildir() delivers a message to a qmail maildir. +/* +/* Arguments: +/* .IP state +/* The attributes that specify the message, recipient and more. +/* Attributes describing alias, include or forward expansion. +/* A table with the results from expanding aliases or lists. +/* .IP usr_attr +/* Attributes describing user rights and environment information. +/* .IP path +/* The maildir to deliver to, including trailing slash. +/* DIAGNOSTICS +/* deliver_maildir() always succeeds or it bounces the message. +/* SEE ALSO +/* bounce(3) +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" + +/* deliver_maildir - delivery to maildir-style mailbox */ + +int deliver_maildir(LOCAL_STATE state, USER_ATTR usr_attr, char *path) +{ + const char *myname = "deliver_maildir"; + char *newdir; + char *tmpdir; + char *curdir; + char *tmpfile; + char *newfile; + DSN_BUF *why = state.msg_attr.why; + VSTRING *buf; + VSTREAM *dst; + int mail_copy_status; + int deliver_status; + int copy_flags; + struct stat st; + struct timeval starttime; + + GETTIMEOFDAY(&starttime); + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * Don't deliver trace-only requests. + */ + if (DEL_REQ_TRACE_ONLY(state.request->flags)) { + dsb_simple(why, "2.0.0", "delivers to maildir"); + return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr))); + } + + /* + * Initialize. Assume the operation will fail. Set the delivered + * attribute to reflect the final recipient. + */ + if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) + msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp)); + if (var_frozen_delivered == 0) + state.msg_attr.delivered = state.msg_attr.rcpt.address; + mail_copy_status = MAIL_COPY_STAT_WRITE; + buf = vstring_alloc(100); + + copy_flags = MAIL_COPY_TOFILE | MAIL_COPY_RETURN_PATH | MAIL_COPY_ORIG_RCPT; + if (local_deliver_hdr_mask & DELIVER_HDR_FILE) + copy_flags |= MAIL_COPY_DELIVERED; + + newdir = concatenate(path, "new/", (char *) 0); + tmpdir = concatenate(path, "tmp/", (char *) 0); + curdir = concatenate(path, "cur/", (char *) 0); + + /* + * Create and write the file as the recipient, so that file quota work. + * Create any missing directories on the fly. The file name is chosen + * according to ftp://koobera.math.uic.edu/www/proto/maildir.html: + * + * "A unique name has three pieces, separated by dots. On the left is the + * result of time(). On the right is the result of gethostname(). In the + * middle is something that doesn't repeat within one second on a single + * host. I fork a new process for each delivery, so I just use the + * process ID. If you're delivering several messages from one process, + * use starttime.pid_count.host, where starttime is the time that your + * process started, and count is the number of messages you've + * delivered." + * + * Well, that stopped working on fast machines, and on operating systems + * that randomize process ID values. When creating a file in tmp/ we use + * the process ID because it still is an exclusive resource. When moving + * the file to new/ we use the device number and inode number. I do not + * care if this breaks on a remote AFS file system, because people should + * know better. + * + * On January 26, 2003, http://cr.yp.to/proto/maildir.html said: + * + * A unique name has three pieces, separated by dots. On the left is the + * result of time() or the second counter from gettimeofday(). On the + * right is the result of gethostname(). (To deal with invalid host + * names, replace / with \057 and : with \072.) In the middle is a + * delivery identifier, discussed below. + * + * [...] + * + * Modern delivery identifiers are created by concatenating enough of the + * following strings to guarantee uniqueness: + * + * [...] + * + * In, where n is (in hexadecimal) the UNIX inode number of this file. + * Unfortunately, inode numbers aren't always available through NFS. + * + * Vn, where n is (in hexadecimal) the UNIX device number of this file. + * Unfortunately, device numbers aren't always available through NFS. + * (Device numbers are also not helpful with the standard UNIX + * filesystem: a maildir has to be within a single UNIX device for link() + * and rename() to work.) + * + * Mn, where n is (in decimal) the microsecond counter from the same + * gettimeofday() used for the left part of the unique name. + * + * Pn, where n is (in decimal) the process ID. + * + * [...] + */ + set_eugid(usr_attr.uid, usr_attr.gid); + vstring_sprintf(buf, "%lu.P%d.%s", + (unsigned long) starttime.tv_sec, var_pid, get_hostname()); + tmpfile = concatenate(tmpdir, STR(buf), (char *) 0); + newfile = 0; + if ((dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0 + && (errno != ENOENT + || make_dirs(tmpdir, 0700) < 0 + || (dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0)) { + dsb_simple(why, mbox_dsn(errno, "5.2.0"), + "create maildir file %s: %m", tmpfile); + } else if (fstat(vstream_fileno(dst), &st) < 0) { + + /* + * Coverity 200604: file descriptor leak in code that never executes. + * Code replaced by msg_fatal(), as it is not worthwhile to continue + * after an impossible error condition. + */ + msg_fatal("fstat %s: %m", tmpfile); + } else { + vstring_sprintf(buf, "%lu.V%lxI%lxM%lu.%s", + (unsigned long) starttime.tv_sec, + (unsigned long) st.st_dev, + (unsigned long) st.st_ino, + (unsigned long) starttime.tv_usec, + get_hostname()); + newfile = concatenate(newdir, STR(buf), (char *) 0); + if ((mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), + dst, copy_flags, "\n", + why)) == 0) { + if (sane_link(tmpfile, newfile) < 0 + && (errno != ENOENT + || (make_dirs(curdir, 0700), make_dirs(newdir, 0700)) < 0 + || sane_link(tmpfile, newfile) < 0)) { + dsb_simple(why, mbox_dsn(errno, "5.2.0"), + "create maildir file %s: %m", newfile); + mail_copy_status = MAIL_COPY_STAT_WRITE; + } + } + if (unlink(tmpfile) < 0) + msg_warn("remove %s: %m", tmpfile); + } + set_eugid(var_owner_uid, var_owner_gid); + + /* + * As the mail system, bounce or defer delivery. + */ + if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) { + deliver_status = DEL_STAT_DEFER; + } else if (mail_copy_status != 0) { + if (errno == EACCES) { + msg_warn("maildir access problem for UID/GID=%lu/%lu: %s", + (long) usr_attr.uid, (long) usr_attr.gid, + STR(why->reason)); + msg_warn("perhaps you need to create the maildirs in advance"); + } + vstring_sprintf_prepend(why->reason, "maildir delivery failed: "); + deliver_status = + (STR(why->status)[0] == '4' ? + defer_append : bounce_append) + (BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); + } else { + dsb_simple(why, "2.0.0", "delivered to maildir"); + deliver_status = sent(BOUNCE_FLAGS(state.request), + SENT_ATTR(state.msg_attr)); + } + vstring_free(buf); + myfree(newdir); + myfree(tmpdir); + myfree(curdir); + myfree(tmpfile); + if (newfile) + myfree(newfile); + return (deliver_status); +} diff --git a/src/local/recipient.c b/src/local/recipient.c new file mode 100644 index 0000000..e3f4d1c --- /dev/null +++ b/src/local/recipient.c @@ -0,0 +1,307 @@ +/*++ +/* NAME +/* recipient 3 +/* SUMMARY +/* deliver to one local recipient +/* SYNOPSIS +/* #include "local.h" +/* +/* int deliver_recipient(state, usr_attr) +/* LOCAL_STATE state; +/* USER_ATTR *usr_attr; +/* DESCRIPTION +/* deliver_recipient() delivers a message to a local recipient. +/* It is called initially when the queue manager requests +/* delivery to a local recipient, and is called recursively +/* when an alias or forward file expands to a local recipient. +/* +/* When called recursively with, for example, a result from alias +/* or forward file expansion, aliases are expanded immediately, +/* but mail for non-alias destinations is submitted as a new +/* message, so that each recipient has a dedicated queue file +/* message delivery status record (in a shared queue file). +/* +/* When the \fIrecipient_delimiter\fR configuration parameter +/* is set, it is used to separate cookies off recipient names. +/* A common setting is to have "recipient_delimiter = +" +/* so that mail for \fIuser+foo\fR is delivered to \fIuser\fR, +/* with a "Delivered-To: user+foo@domain" header line. +/* +/* Arguments: +/* .IP state +/* The attributes that specify the message, sender, and more. +/* Attributes describing alias, include or forward expansion. +/* A table with the results from expanding aliases or lists. +/* A table with delivered-to: addresses taken from the message. +/* .IP usr_attr +/* Attributes describing user rights and environment. +/* DIAGNOSTICS +/* deliver_recipient() returns non-zero when delivery should be +/* tried again. +/* BUGS +/* Mutually-recursive aliases or $HOME/.forward files aren't +/* detected when they could be. The resulting mail forwarding loop +/* is broken by the use of the Delivered-To: message header. +/* SEE ALSO +/* alias(3) delivery to aliases +/* mailbox(3) delivery to mailbox +/* dotforward(3) delivery to destinations in .forward file +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" + +/* deliver_switch - branch on recipient type */ + +static int deliver_switch(LOCAL_STATE state, USER_ATTR usr_attr) +{ + const char *myname = "deliver_switch"; + int status = 0; + struct stat st; + struct mypasswd *mypwd; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + + /* + * \user is special: it means don't do any alias or forward expansion. + * + * XXX This code currently does not work due to revision of the RFC822 + * address parser. \user should be permitted only in locally specified + * aliases, includes or forward files. + * + * XXX Should test for presence of user home directory. + */ + if (state.msg_attr.rcpt.address[0] == '\\') { + state.msg_attr.rcpt.address++, state.msg_attr.local++, state.msg_attr.user++; + if (deliver_mailbox(state, usr_attr, &status) == 0) + status = deliver_unknown(state, usr_attr); + return (status); + } + + /* + * Otherwise, alias expansion has highest precedence. First look up the + * full localpart, then the bare user. Obey the address extension + * propagation policy. + */ + state.msg_attr.unmatched = 0; + if (deliver_alias(state, usr_attr, state.msg_attr.local, &status)) + return (status); + if (state.msg_attr.extension != 0) { + if (local_ext_prop_mask & EXT_PROP_ALIAS) + state.msg_attr.unmatched = state.msg_attr.extension; + if (deliver_alias(state, usr_attr, state.msg_attr.user, &status)) + return (status); + state.msg_attr.unmatched = state.msg_attr.extension; + } + + /* + * Special case for mail locally forwarded or aliased to a different + * local address. Resubmit the message via the cleanup service, so that + * each recipient gets a separate delivery queue file status record in + * the new queue file. The downside of this approach is that mutually + * recursive .forward files cause a mail forwarding loop. Fortunately, + * the loop can be broken by the use of the Delivered-To: message header. + * + * The code below must not trigger on mail sent to an alias that has no + * owner- companion, so that mail for an alias first.last->username is + * delivered directly, instead of going through username->first.last + * canonical mappings in the cleanup service. The downside of this + * approach is that recipients in the expansion of an alias without + * owner- won't have separate delivery queue file status records, because + * for them, the message won't be resubmitted as a new queue file. + * + * Do something sensible on systems that receive mail for multiple domains, + * such as primary.name and secondary.name. Don't resubmit the message + * when mail for `user@secondary.name' is delivered to a .forward file + * that lists `user' or `user@primary.name'. We already know that the + * recipient domain is local, so we only have to compare local parts. + */ + if (state.msg_attr.owner != 0 + && strcasecmp_utf8(state.msg_attr.owner, state.msg_attr.user) != 0) + return (deliver_indirect(state)); + + /* + * Always forward recipients in :include: files. + */ + if (state.msg_attr.exp_type == EXPAND_TYPE_INCL) + return (deliver_indirect(state)); + + /* + * Delivery to local user. First try expansion of the recipient's + * $HOME/.forward file, then mailbox delivery. Back off when the user's + * home directory does not exist. + */ + mypwd = 0; + if (var_stat_home_dir + && (errno = mypwnam_err(state.msg_attr.user, &mypwd)) != 0) { + msg_warn("error looking up passwd info for %s: %m", + state.msg_attr.user); + dsb_simple(state.msg_attr.why, "4.0.0", "user lookup error"); + return (defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr))); + } + if (mypwd != 0) { + if (stat_as(mypwd->pw_dir, &st, mypwd->pw_uid, mypwd->pw_gid) < 0) { + dsb_simple(state.msg_attr.why, "4.3.0", + "cannot access home directory %s: %m", mypwd->pw_dir); + mypwfree(mypwd); + return (defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr))); + } + mypwfree(mypwd); + } + if (deliver_dotforward(state, usr_attr, &status) == 0 + && deliver_mailbox(state, usr_attr, &status) == 0) + status = deliver_unknown(state, usr_attr); + return (status); +} + +/* deliver_recipient - deliver one local recipient */ + +int deliver_recipient(LOCAL_STATE state, USER_ATTR usr_attr) +{ + const char *myname = "deliver_recipient"; + VSTRING *folded; + int rcpt_stat; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * Duplicate filter. + */ + if (been_here(state.dup_filter, "recipient %d %s", + state.level, state.msg_attr.rcpt.address)) + return (0); + + /* + * With each level of recursion, detect and break external message + * forwarding loops. + * + * If the looping recipient address has an owner- alias, send the error + * report there instead. + * + * XXX A delivery agent cannot change the envelope sender address for + * bouncing. As a workaround we use a one-recipient bounce procedure. + * + * The proper fix would be to record in the bounce logfile an error return + * address for each individual recipient. This would also eliminate the + * need for VERP specific bouncing code, at the cost of complicating the + * normal bounce sending procedure, but would simplify the code below. + */ + if (delivered_hdr_find(state.loop_info, state.msg_attr.rcpt.address)) { + dsb_simple(state.msg_attr.why, "5.4.6", "mail forwarding loop for %s", + state.msg_attr.rcpt.address); + /* Account for possible owner- sender address override. */ + return (bounce_workaround(state)); + } + + /* + * Set up the recipient-specific attributes. If this is forwarded mail, + * leave the delivered attribute alone, so that the forwarded message + * will show the correct forwarding recipient. + */ + if (state.msg_attr.delivered == 0) + state.msg_attr.delivered = state.msg_attr.rcpt.address; + folded = vstring_alloc(100); + state.msg_attr.local = casefold(folded, state.msg_attr.rcpt.address); + if ((state.msg_attr.domain = split_at_right(state.msg_attr.local, '@')) == 0) + msg_warn("no @ in recipient address: %s", state.msg_attr.local); + + /* + * Address extension management. + * + * XXX Fix 20100422, finalized 20100529: it is too error-prone to + * distinguish between "no extension" and "no valid extension", so we + * drop an invalid extension from the recipient address local-part. + */ + state.msg_attr.user = mystrdup(state.msg_attr.local); + if (*var_rcpt_delim) { + state.msg_attr.extension = + split_addr(state.msg_attr.user, var_rcpt_delim); + if (state.msg_attr.extension && strchr(state.msg_attr.extension, '/')) { + msg_warn("%s: address with illegal extension: %s", + state.msg_attr.queue_id, state.msg_attr.local); + state.msg_attr.extension = 0; + /* XXX Can't myfree + mystrdup, must truncate instead. */ + state.msg_attr.local[strlen(state.msg_attr.user)] = 0; + /* Truncating is safe. The code below rejects null usernames. */ + } + } else + state.msg_attr.extension = 0; + state.msg_attr.unmatched = state.msg_attr.extension; + + /* + * Do not allow null usernames. + */ + if (state.msg_attr.user[0] == 0) { + dsb_simple(state.msg_attr.why, "5.1.3", + "null username in \"%s\"", state.msg_attr.rcpt.address); + return (bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr))); + } + + /* + * Run the recipient through the delivery switch. + */ + if (msg_verbose) + deliver_attr_dump(&state.msg_attr); + rcpt_stat = deliver_switch(state, usr_attr); + + /* + * Clean up. + */ + vstring_free(folded); + myfree(state.msg_attr.user); + + return (rcpt_stat); +} diff --git a/src/local/resolve.c b/src/local/resolve.c new file mode 100644 index 0000000..a6aa9d0 --- /dev/null +++ b/src/local/resolve.c @@ -0,0 +1,170 @@ +/*++ +/* NAME +/* resolve 3 +/* SUMMARY +/* resolve recipient and deliver locally or remotely +/* SYNOPSIS +/* #include "local.h" +/* +/* int deliver_resolve_tree(state, usr_attr, addr) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* TOK822 *addr; +/* +/* int deliver_resolve_addr(state, usr_attr, addr) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* char *addr; +/* DESCRIPTION +/* deliver_resolve_XXX() resolves a recipient that is the result from +/* e.g., alias expansion, and delivers locally or via forwarding. +/* +/* Arguments: +/* .IP state +/* The attributes that specify the message, sender and more. +/* A table with the results from expanding aliases or lists. +/* A table with delivered-to: addresses taken from the message. +/* .IP addr +/* An address from, e.g., alias expansion. +/* DIAGNOSTICS +/* Fatal errors: out of memory. The result is non-zero when the +/* operation should be tried again. Warnings: malformed address. +/* SEE ALSO +/* recipient(3) local delivery +/* indirect(3) deliver via forwarding +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" + +/* deliver_resolve_addr - resolve and deliver */ + +int deliver_resolve_addr(LOCAL_STATE state, USER_ATTR usr_attr, char *addr) +{ + TOK822 *tree; + int result; + + tree = tok822_scan_addr(addr); + result = deliver_resolve_tree(state, usr_attr, tree); + tok822_free_tree(tree); + return (result); +} + +/* deliver_resolve_tree - resolve and deliver */ + +int deliver_resolve_tree(LOCAL_STATE state, USER_ATTR usr_attr, TOK822 *addr) +{ + const char *myname = "deliver_resolve_tree"; + RESOLVE_REPLY reply; + int status; + ssize_t ext_len; + char *ratsign; + int rcpt_delim; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * Initialize. + */ + resolve_clnt_init(&reply); + + /* + * Rewrite the address to canonical form, just like the cleanup service + * does. Then, resolve the address to (transport, nexhop, recipient), + * just like the queue manager does. The only part missing here is the + * virtual address substitution. Message forwarding fixes most of that. + */ + tok822_rewrite(addr, REWRITE_CANON); + tok822_resolve(addr, &reply); + + /* + * First, a healthy portion of error handling. + */ + if (reply.flags & RESOLVE_FLAG_FAIL) { + dsb_simple(state.msg_attr.why, "4.3.0", "address resolver failure"); + status = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + } else if (reply.flags & RESOLVE_FLAG_ERROR) { + dsb_simple(state.msg_attr.why, "5.1.3", + "bad recipient address syntax: %s", STR(reply.recipient)); + status = bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + } else { + + /* + * Splice in the optional unmatched address extension. + */ + if (state.msg_attr.unmatched) { + rcpt_delim = state.msg_attr.local[strlen(state.msg_attr.user)]; + if ((ratsign = strrchr(STR(reply.recipient), '@')) == 0) { + VSTRING_ADDCH(reply.recipient, rcpt_delim); + vstring_strcat(reply.recipient, state.msg_attr.unmatched); + } else { + ext_len = strlen(state.msg_attr.unmatched); + VSTRING_SPACE(reply.recipient, ext_len + 2); + if ((ratsign = strrchr(STR(reply.recipient), '@')) == 0) + msg_panic("%s: recipient @ botch", myname); + memmove(ratsign + ext_len + 1, ratsign, strlen(ratsign) + 1); + *ratsign = rcpt_delim; + memcpy(ratsign + 1, state.msg_attr.unmatched, ext_len); + VSTRING_SKIP(reply.recipient); + } + } + state.msg_attr.rcpt.address = STR(reply.recipient); + + /* + * Delivery to a local or non-local address. For a while there was + * some ugly code to force local recursive alias expansions on a host + * with no authority over the local domain, but that code was just + * too unclean. + */ + if (strcmp(state.msg_attr.relay, STR(reply.transport)) == 0) { + status = deliver_recipient(state, usr_attr); + } else { + status = deliver_indirect(state); + } + } + + /* + * Cleanup. + */ + resolve_clnt_free(&reply); + + return (status); +} diff --git a/src/local/token.c b/src/local/token.c new file mode 100644 index 0000000..2eb0c28 --- /dev/null +++ b/src/local/token.c @@ -0,0 +1,222 @@ +/*++ +/* NAME +/* token 3 +/* SUMMARY +/* tokenize alias/include/.forward entries and deliver +/* SYNOPSIS +/* #include "local.h" +/* +/* int deliver_token(state, usr_attr, addr) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* TOK822 *addr; +/* +/* int deliver_token_string(state, usr_attr, string, addr_count) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* char *string; +/* int *addr_count; +/* +/* int deliver_token_stream(state, usr_attr, fp, addr_count) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* VSTREAM *fp; +/* int *addr_count; +/* DESCRIPTION +/* This module delivers to addresses listed in an alias database +/* entry, in an include file, or in a .forward file. +/* +/* deliver_token() delivers to the address in the given token: +/* an absolute /path/name, a ~/path/name relative to the recipient's +/* home directory, an :include:/path/name request, an external +/* "|command", or a mail address. +/* +/* deliver_token_string() delivers to all addresses listed in +/* the specified string. +/* +/* deliver_token_stream() delivers to all addresses listed in +/* the specified stream. Input records > \fIline_length_limit\fR +/* are broken up into multiple records, to prevent the mail +/* system from using unreasonable amounts of memory. +/* +/* Arguments: +/* .IP state +/* The attributes that specify the message, recipient and more. +/* Attributes describing alias, include or forward expansion. +/* A table with the results from expanding aliases or lists. +/* A table with delivered-to: addresses taken from the message. +/* .IP usr_attr +/* Attributes describing user rights and environment. +/* .IP addr +/* A parsed address from an include file, alias file or .forward file. +/* .IP string +/* A null-terminated string. +/* .IP fp +/* A readable stream. +/* .IP addr_count +/* Null pointer, or the address of a counter that is incremented +/* by the number of destinations found by the tokenizer. +/* DIAGNOSTICS +/* Fatal errors: out of memory. The result is non-zero when the +/* operation should be tried again. Warnings: malformed address. +/* SEE ALSO +/* list_token(3) tokenize list +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" + +/* deliver_token_home - expand ~token */ + +static int deliver_token_home(LOCAL_STATE state, USER_ATTR usr_attr, char *addr) +{ + char *full_path; + int status; + + if (addr[1] != '/') { /* disallow ~user */ + msg_warn("bad home directory syntax for: %s", addr); + dsb_simple(state.msg_attr.why, "5.3.5", + "mail system configuration error"); + status = bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + } else if (usr_attr.home == 0) { /* require user context */ + msg_warn("unknown home directory for: %s", addr); + dsb_simple(state.msg_attr.why, "5.3.5", + "mail system configuration error"); + status = bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + } else if (usr_attr.home[0] == '/' && usr_attr.home[1] == 0) { + status = deliver_file(state, usr_attr, addr + 1); + } else { /* expand ~ to home */ + full_path = concatenate(usr_attr.home, addr + 1, (char *) 0); + status = deliver_file(state, usr_attr, full_path); + myfree(full_path); + } + return (status); +} + +/* deliver_token - deliver to expansion of include file or alias */ + +int deliver_token(LOCAL_STATE state, USER_ATTR usr_attr, TOK822 *addr) +{ + VSTRING *addr_buf = vstring_alloc(100); + static char include[] = ":include:"; + int status; + char *path; + + tok822_internalize(addr_buf, addr->head, TOK822_STR_DEFL); + if (msg_verbose) + msg_info("deliver_token: %s", STR(addr_buf)); + + if (*STR(addr_buf) == '/') { + status = deliver_file(state, usr_attr, STR(addr_buf)); + } else if (*STR(addr_buf) == '~') { + status = deliver_token_home(state, usr_attr, STR(addr_buf)); + } else if (*STR(addr_buf) == '|') { + if ((local_cmd_deliver_mask & state.msg_attr.exp_type) == 0) { + dsb_simple(state.msg_attr.why, "5.7.1", + "mail to command is restricted"); + status = bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + } else + status = deliver_command(state, usr_attr, STR(addr_buf) + 1); + } else if (strncasecmp(STR(addr_buf), include, sizeof(include) - 1) == 0) { + path = STR(addr_buf) + sizeof(include) - 1; + status = deliver_include(state, usr_attr, path); + } else { + status = deliver_resolve_tree(state, usr_attr, addr); + } + vstring_free(addr_buf); + + return (status); +} + +/* deliver_token_string - tokenize string and deliver */ + +int deliver_token_string(LOCAL_STATE state, USER_ATTR usr_attr, + char *string, int *addr_count) +{ + TOK822 *tree; + TOK822 *addr; + int status = 0; + + if (msg_verbose) + msg_info("deliver_token_string: %s", string); + + tree = tok822_parse(string); + for (addr = tree; addr != 0; addr = addr->next) { + if (addr->type == TOK822_ADDR) { + if (addr_count) + (*addr_count)++; + status |= deliver_token(state, usr_attr, addr); + } + } + tok822_free_tree(tree); + return (status); +} + +/* deliver_token_stream - tokenize stream and deliver */ + +int deliver_token_stream(LOCAL_STATE state, USER_ATTR usr_attr, + VSTREAM *fp, int *addr_count) +{ + VSTRING *buf = vstring_alloc(100); + int status = 0; + + if (msg_verbose) + msg_info("deliver_token_stream: %s", VSTREAM_PATH(fp)); + + while (vstring_fgets_bound(buf, fp, var_line_limit)) { + if (*STR(buf) != '#') { + status = deliver_token_string(state, usr_attr, STR(buf), addr_count); + if (status != 0) + break; + } + } + if (vstream_ferror(fp)) { + dsb_simple(state.msg_attr.why, "4.3.0", + "error reading forwarding file: %m"); + status = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + } + vstring_free(buf); + return (status); +} diff --git a/src/local/unknown.c b/src/local/unknown.c new file mode 100644 index 0000000..96443e1 --- /dev/null +++ b/src/local/unknown.c @@ -0,0 +1,187 @@ +/*++ +/* NAME +/* unknown 3 +/* SUMMARY +/* delivery of unknown recipients +/* SYNOPSIS +/* #include "local.h" +/* +/* int deliver_unknown(state, usr_attr) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* DESCRIPTION +/* deliver_unknown() delivers a message for unknown recipients. +/* .IP \(bu +/* If an alternative message transport is specified via the +/* fallback_transport parameter, delivery is delegated to the +/* named transport. +/* .IP \(bu +/* If an alternative address is specified via the luser_relay +/* configuration parameter, mail is forwarded to that address. +/* .IP \(bu +/* Otherwise the recipient is bounced. +/* .PP +/* The luser_relay parameter is subjected to $name expansion of +/* the standard message attributes: $user, $home, $shell, $domain, +/* $recipient, $mailbox, $extension, $recipient_delimiter, not +/* all of which actually make sense. +/* +/* Arguments: +/* .IP state +/* Message delivery attributes (sender, recipient etc.). +/* Attributes describing alias, include or forward expansion. +/* A table with the results from expanding aliases or lists. +/* A table with delivered-to: addresses taken from the message. +/* .IP usr_attr +/* Attributes describing user rights and environment. +/* DIAGNOSTICS +/* The result status is non-zero when delivery should be tried again. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" + +#define STREQ(x,y) (strcasecmp((x),(y)) == 0) + +/* deliver_unknown - delivery for unknown recipients */ + +int deliver_unknown(LOCAL_STATE state, USER_ATTR usr_attr) +{ + const char *myname = "deliver_unknown"; + int status; + VSTRING *expand_luser; + VSTRING *canon_luser; + static MAPS *transp_maps; + const char *map_transport; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * DUPLICATE/LOOP ELIMINATION + * + * Don't deliver the same user twice. + */ + if (been_here(state.dup_filter, "%s %s", myname, state.msg_attr.local)) + return (0); + + /* + * The fall-back transport specifies a delivery mechanism that handles + * users not found in the aliases or UNIX passwd databases. + */ + if (*var_fbck_transp_maps && transp_maps == 0) + transp_maps = maps_create(VAR_FBCK_TRANSP_MAPS, var_fbck_transp_maps, + DICT_FLAG_LOCK | DICT_FLAG_NO_REGSUB + | DICT_FLAG_UTF8_REQUEST); + /* The -1 is a hint for the down-stream deliver_completed() function. */ + if (transp_maps + && (map_transport = maps_find(transp_maps, state.msg_attr.user, + DICT_FLAG_NONE)) != 0) { + state.msg_attr.rcpt.offset = -1L; + return (deliver_pass(MAIL_CLASS_PRIVATE, map_transport, + state.request, &state.msg_attr.rcpt)); + } else if (transp_maps && transp_maps->error != 0) { + /* Details in the logfile. */ + dsb_simple(state.msg_attr.why, "4.3.0", "table lookup failure"); + return (defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr))); + } + if (*var_fallback_transport) { + state.msg_attr.rcpt.offset = -1L; + return (deliver_pass(MAIL_CLASS_PRIVATE, var_fallback_transport, + state.request, &state.msg_attr.rcpt)); + } + + /* + * Subject the luser_relay address to $name expansion, disable + * propagation of unmatched address extension, and re-inject the address + * into the delivery machinery. Do not give special treatment to "|stuff" + * or /stuff. + */ + if (*var_luser_relay) { + state.msg_attr.unmatched = 0; + expand_luser = vstring_alloc(100); + canon_luser = vstring_alloc(100); + local_expand(expand_luser, var_luser_relay, &state, &usr_attr, (void *) 0); + /* In case luser_relay specifies a domain-less address. */ + canon_addr_external(canon_luser, vstring_str(expand_luser)); + /* Assumes that the address resolver won't change the address. */ + if (STREQ(vstring_str(canon_luser), state.msg_attr.rcpt.address)) { + dsb_simple(state.msg_attr.why, "5.1.1", + "unknown user: \"%s\"", state.msg_attr.user); + status = bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + } else { + status = deliver_resolve_addr(state, usr_attr, STR(expand_luser)); + } + vstring_free(canon_luser); + vstring_free(expand_luser); + return (status); + } + + /* + * If no alias was found for a required reserved name, toss the message + * into the bit bucket, and issue a warning instead. + */ + if (STREQ(state.msg_attr.user, MAIL_ADDR_MAIL_DAEMON) + || STREQ(state.msg_attr.user, MAIL_ADDR_POSTMASTER)) { + msg_warn("required alias not found: %s", state.msg_attr.user); + dsb_simple(state.msg_attr.why, "2.0.0", "discarded"); + return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr))); + } + + /* + * Bounce the message when no luser relay is specified. + */ + dsb_simple(state.msg_attr.why, "5.1.1", + "unknown user: \"%s\"", state.msg_attr.user); + return (bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr))); +} -- cgit v1.2.3