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/smtpstone/.indent.pro | 1 + src/smtpstone/.printfck | 25 + src/smtpstone/Makefile.in | 172 +++++ src/smtpstone/hashed-deferred | 37 + src/smtpstone/hashed-incoming | 48 ++ src/smtpstone/mx-deliver | 20 + src/smtpstone/mx-explode | 33 + src/smtpstone/mx-relay | 20 + src/smtpstone/performance | 28 + src/smtpstone/qmail-deliver | 20 + src/smtpstone/qmail-explode | 20 + src/smtpstone/qmail-relay | 20 + src/smtpstone/qmqp-sink.c | 325 ++++++++ src/smtpstone/qmqp-source.c | 673 +++++++++++++++++ src/smtpstone/smtp-sink.c | 1643 +++++++++++++++++++++++++++++++++++++++++ src/smtpstone/smtp-source.c | 1188 +++++++++++++++++++++++++++++ src/smtpstone/throughput | 28 + src/smtpstone/vmail-local | 43 ++ src/smtpstone/vmail-relay | 57 ++ 19 files changed, 4401 insertions(+) create mode 120000 src/smtpstone/.indent.pro create mode 100644 src/smtpstone/.printfck create mode 100644 src/smtpstone/Makefile.in create mode 100644 src/smtpstone/hashed-deferred create mode 100644 src/smtpstone/hashed-incoming create mode 100644 src/smtpstone/mx-deliver create mode 100644 src/smtpstone/mx-explode create mode 100644 src/smtpstone/mx-relay create mode 100644 src/smtpstone/performance create mode 100644 src/smtpstone/qmail-deliver create mode 100644 src/smtpstone/qmail-explode create mode 100644 src/smtpstone/qmail-relay create mode 100644 src/smtpstone/qmqp-sink.c create mode 100644 src/smtpstone/qmqp-source.c create mode 100644 src/smtpstone/smtp-sink.c create mode 100644 src/smtpstone/smtp-source.c create mode 100644 src/smtpstone/throughput create mode 100644 src/smtpstone/vmail-local create mode 100644 src/smtpstone/vmail-relay (limited to 'src/smtpstone') diff --git a/src/smtpstone/.indent.pro b/src/smtpstone/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/smtpstone/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/smtpstone/.printfck b/src/smtpstone/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/smtpstone/.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/smtpstone/Makefile.in b/src/smtpstone/Makefile.in new file mode 100644 index 0000000..f86e019 --- /dev/null +++ b/src/smtpstone/Makefile.in @@ -0,0 +1,172 @@ +SHELL = /bin/sh +SRCS = smtp-source.c smtp-sink.c qmqp-source.c qmqp-sink.c +OBJS = smtp-source.o smtp-sink.o qmqp-source.o qmqp-sink.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +INC_DIR = ../../include +PROG = smtp-source smtp-sink qmqp-source qmqp-sink +LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +all: $(PROG) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +smtp-sink: smtp-sink.o $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ smtp-sink.o $(LIBS) $(SYSLIBS) + +smtp-source: smtp-source.o $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ smtp-source.o $(LIBS) $(SYSLIBS) + +qmqp-sink: qmqp-sink.o $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ qmqp-sink.o $(LIBS) $(SYSLIBS) + +qmqp-source: qmqp-source.o $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ qmqp-source.o $(LIBS) $(SYSLIBS) + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../bin/smtp-source ../../bin/smtp-sink ../../bin/qmqp-source \ + ../../bin/qmqp-sink + +../../bin/smtp-source: smtp-source + cp $? $@ + +../../bin/smtp-sink: smtp-sink + cp $? $@ + +../../bin/qmqp-source: qmqp-source + cp $? $@ + +../../bin/qmqp-sink: qmqp-sink + cp $? $@ + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir 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' +qmqp-sink.o: ../../include/check_arg.h +qmqp-sink.o: ../../include/events.h +qmqp-sink.o: ../../include/htable.h +qmqp-sink.o: ../../include/inet_proto.h +qmqp-sink.o: ../../include/iostuff.h +qmqp-sink.o: ../../include/listen.h +qmqp-sink.o: ../../include/mail_version.h +qmqp-sink.o: ../../include/msg.h +qmqp-sink.o: ../../include/msg_vstream.h +qmqp-sink.o: ../../include/mymalloc.h +qmqp-sink.o: ../../include/netstring.h +qmqp-sink.o: ../../include/qmqp_proto.h +qmqp-sink.o: ../../include/sys_defs.h +qmqp-sink.o: ../../include/vbuf.h +qmqp-sink.o: ../../include/vstream.h +qmqp-sink.o: ../../include/vstring.h +qmqp-sink.o: qmqp-sink.c +qmqp-source.o: ../../include/check_arg.h +qmqp-source.o: ../../include/connect.h +qmqp-source.o: ../../include/events.h +qmqp-source.o: ../../include/get_hostname.h +qmqp-source.o: ../../include/host_port.h +qmqp-source.o: ../../include/inet_proto.h +qmqp-source.o: ../../include/iostuff.h +qmqp-source.o: ../../include/mail_date.h +qmqp-source.o: ../../include/mail_version.h +qmqp-source.o: ../../include/msg.h +qmqp-source.o: ../../include/msg_vstream.h +qmqp-source.o: ../../include/myaddrinfo.h +qmqp-source.o: ../../include/mymalloc.h +qmqp-source.o: ../../include/netstring.h +qmqp-source.o: ../../include/qmqp_proto.h +qmqp-source.o: ../../include/sane_connect.h +qmqp-source.o: ../../include/split_at.h +qmqp-source.o: ../../include/sys_defs.h +qmqp-source.o: ../../include/valid_hostname.h +qmqp-source.o: ../../include/valid_mailhost_addr.h +qmqp-source.o: ../../include/vbuf.h +qmqp-source.o: ../../include/vstream.h +qmqp-source.o: ../../include/vstring.h +qmqp-source.o: qmqp-source.c +smtp-sink.o: ../../include/check_arg.h +smtp-sink.o: ../../include/chroot_uid.h +smtp-sink.o: ../../include/events.h +smtp-sink.o: ../../include/get_hostname.h +smtp-sink.o: ../../include/htable.h +smtp-sink.o: ../../include/inet_proto.h +smtp-sink.o: ../../include/iostuff.h +smtp-sink.o: ../../include/listen.h +smtp-sink.o: ../../include/mail_date.h +smtp-sink.o: ../../include/mail_version.h +smtp-sink.o: ../../include/make_dirs.h +smtp-sink.o: ../../include/msg.h +smtp-sink.o: ../../include/msg_vstream.h +smtp-sink.o: ../../include/myaddrinfo.h +smtp-sink.o: ../../include/mymalloc.h +smtp-sink.o: ../../include/myrand.h +smtp-sink.o: ../../include/sane_accept.h +smtp-sink.o: ../../include/smtp_stream.h +smtp-sink.o: ../../include/stringops.h +smtp-sink.o: ../../include/sys_defs.h +smtp-sink.o: ../../include/vbuf.h +smtp-sink.o: ../../include/vstream.h +smtp-sink.o: ../../include/vstring.h +smtp-sink.o: ../../include/vstring_vstream.h +smtp-sink.o: smtp-sink.c +smtp-source.o: ../../include/check_arg.h +smtp-source.o: ../../include/compat_va_copy.h +smtp-source.o: ../../include/connect.h +smtp-source.o: ../../include/events.h +smtp-source.o: ../../include/get_hostname.h +smtp-source.o: ../../include/host_port.h +smtp-source.o: ../../include/inet_proto.h +smtp-source.o: ../../include/iostuff.h +smtp-source.o: ../../include/mail_date.h +smtp-source.o: ../../include/mail_version.h +smtp-source.o: ../../include/msg.h +smtp-source.o: ../../include/msg_vstream.h +smtp-source.o: ../../include/myaddrinfo.h +smtp-source.o: ../../include/mymalloc.h +smtp-source.o: ../../include/sane_connect.h +smtp-source.o: ../../include/smtp_stream.h +smtp-source.o: ../../include/split_at.h +smtp-source.o: ../../include/sys_defs.h +smtp-source.o: ../../include/valid_hostname.h +smtp-source.o: ../../include/valid_mailhost_addr.h +smtp-source.o: ../../include/vbuf.h +smtp-source.o: ../../include/vstream.h +smtp-source.o: ../../include/vstring.h +smtp-source.o: ../../include/vstring_vstream.h +smtp-source.o: smtp-source.c diff --git a/src/smtpstone/hashed-deferred b/src/smtpstone/hashed-deferred new file mode 100644 index 0000000..93f0e12 --- /dev/null +++ b/src/smtpstone/hashed-deferred @@ -0,0 +1,37 @@ +Delivering 1000 deferred messages over the loopback transport, +outbound concurrency 10. smtp-sink pipelining disabled. Machine is +P230, BSD/OS 3.1, 64MB memory. + +hashing is 16 directories per level + +flat deferred queue + + start: Sun Feb 21 16:42:37 EST 1999 + done: Feb 21 16:44:35 + time: 1:58 = 118 seconds + + start: Sun Feb 21 16:48:01 EST 1999 + done: Feb 21 16:49:51 + time: 1:50 = 110 seconds + +hashed deferred queue, depth=1 (16 directories) + + start: Sun Feb 21 17:29:36 EST 1999 + done: Feb 21 17:31:32 + time: 1:56 = 116 seconds + + start: Sun Feb 21 17:33:36 EST 1999 + done: Feb 21 17:35:24 + time: 1:48 = 108 seconds + + start: Sun Feb 21 17:37:08 EST 1999 + done: Feb 21 17:39:02 + time: 1:52 = 112 seconds + +Hashing does not slow down deliveries. + +However the problem is scanning an empty deferred queue. On an idle +machine, it takes some 5 seconds to scan an empty depth=2 deferred +queue unless the blocks happen to be cached. During those 5 seconds +the queue manager will not pay attention to I/O from delivery +agents, which is bad. diff --git a/src/smtpstone/hashed-incoming b/src/smtpstone/hashed-incoming new file mode 100644 index 0000000..7bb1993 --- /dev/null +++ b/src/smtpstone/hashed-incoming @@ -0,0 +1,48 @@ +Sending 1000 messages through postfix over the loopback transport, +inbound concurrency 10. smtp-sink pipelining disabled. Machine is +P230, BSD/OS 3.1 + +accepted = time for postfix to accept all messages +moved = number of messages delivered by postfix when all mail accepted +done = time for postfix to deliver last message + +hashing is 16 directories per level + +flat incoming queue + + start: Sun Feb 21 15:12:44 EST 1999 + accepted: 66.10 real 0.50 user 1.56 sys + moved: 122 + done: Feb 21 15:15:19 + + total time: 2:35 = 155 seconds + + start: Sun Feb 21 15:30:44 EST 1999 + accepted: 67.47 real 0.48 user 1.60 sys + moved: + done: Feb 21 15:33:10 + + total time: 2:26 = 146 seconds + +hashed incoming queue, depth=1 (16 subdirs) + + start: Sun Feb 21 15:39:43 EST 1999 + accepted: 84.42 real 0.49 user 1.66 sys + moved: 144 + done: Feb 21 15:42:27 + + total time: 2:44 = 164 seconds + + start: Sun Feb 21 15:42:43 EST 1999 + accepted: 84.57 real 0.46 user 1.67 sys + moved: + done: Feb 21 15:45:34 + + total time: 2:51 = 171 seconds + + start: Sun Feb 21 15:47:11 EST 1999 + accepted: 84.11 real 0.34 user 1.72 sys + moved: + done: Feb 21 15:49:54 + + total time: 2:43 = 163 seconds diff --git a/src/smtpstone/mx-deliver b/src/smtpstone/mx-deliver new file mode 100644 index 0000000..a4f44a0 --- /dev/null +++ b/src/smtpstone/mx-deliver @@ -0,0 +1,20 @@ +MX needs 24 seconds to deliver 100 SMTP messages to one local user. +smtpd/local-delivery process limit = 100 + +/usr/bin/time ./smtp-source -s 5 -m 100 -t wietse@fist.porcupine.org fist + 10.47 real 0.12 user 0.16 sys +Jun 8 14:45:25 fist mx:smtpd[19432]: connect from spike.porcupine.org(168.100.1 +Jun 8 14:45:49 fist mx:local[19444]: 085788: to=, relay=l +Total time: 24 seconds + +/usr/bin/time ./smtp-source -s 10 -m 100 -t wietse@fist.porcupine.org fist + 9.10 real 0.06 user 0.26 sys +Jun 8 14:46:42 fist mx:smtpd[19443]: connect from spike.porcupine.org(168.100.1 +Jun 8 14:47:06 fist mx:local[19446]: 085792: to=, relay=l +Total time: 24 seconds + +/usr/bin/time ./smtp-source -s 20 -m 100 -t wietse@fist.porcupine.org fist + 9.84 real 0.09 user 0.28 sys +Jun 8 14:48:03 fist mx:smtpd[19458]: connect from spike.porcupine.org(168.100.1 +Jun 8 14:48:28 fist mx:local[19479]: 085795: to=, relay=l +Total time: 25 seconds diff --git a/src/smtpstone/mx-explode b/src/smtpstone/mx-explode new file mode 100644 index 0000000..e997147 --- /dev/null +++ b/src/smtpstone/mx-explode @@ -0,0 +1,33 @@ +MX needs 12 seconds per 1000 different remote destinations. +smtp process limit = 100, bundle_recipients = 0. + +/usr/bin/time ./smtp-source -r 1000 fist + 1.13 real 0.07 user 0.27 sys +Jun 8 13:32:18 fist mx:smtpd[18174]: connect from spike.porcupine.org(168.100.1 +Jun 8 13:32:31 fist mx:smtp[18209]: 085688: to=<544foo@spike.porcupine.org>, re +Total time: 13 seconds + +/usr/bin/time ./smtp-source -r 2000 fist + 2.55 real 0.21 user 0.48 sys +Jun 8 13:33:23 fist mx:smtpd[18174]: connect from spike.porcupine.org(168.100.1 +Jun 8 13:33:48 fist mx:smtp[18184]: 085693: to=<1041foo@spike.porcupine.org>, r +Total time: 25 seconds + +/usr/bin/time ./smtp-source -r 5000 fist +[test generating machine ran out of resources while receiving mail] + +/usr/bin/time ./smtp-source -r 1000 fist + 1.38 real 0.17 user 0.16 sys +Jun 8 15:20:33 fist mx:smtpd[27695]: connect from spike.porcupine.org(168.100.1 +Jun 8 15:20:46 fist mx:smtp[27724]: 085687: to=<493foo@spike.porcupine.org>, re +Total time: 13 seconds + +/usr/bin/time ./smtp-source -r 2000 fist + 2.64 real 0.23 user 0.46 sys +Jun 8 15:20:52 fist mx:smtpd[27695]: connect from spike.porcupine.org(168.100.1 +Jun 8 15:21:16 fist mx:smtp[27743]: 085687: to=<1086foo@spike.porcupine.org>, r +Total time: 24 seconds + +/usr/bin/time ./smtp-source -r 5000 fist +[test generating machine ran out of resources while receiving mail] + diff --git a/src/smtpstone/mx-relay b/src/smtpstone/mx-relay new file mode 100644 index 0000000..a5930cb --- /dev/null +++ b/src/smtpstone/mx-relay @@ -0,0 +1,20 @@ +MX needs 19 seconds to relay 100 messages with one recipient. +smtp/smtpd process limit = 100, bundle_recipients = 0. + +/usr/bin/time ./smtp-source -s 5 -m 100 fist + 9.56 real 0.07 user 0.20 sys +Jun 8 14:33:19 fist mx:smtpd[19366]: connect from spike.porcupine.org(168.100.1 +Jun 8 14:33:36 fist mx:smtp[19382]: 085781: to=, relay +Total time: 17 seconds + +/usr/bin/time ./smtp-source -s 10 -m 100 fist + 8.95 real 0.12 user 0.19 sys +Jun 8 14:34:22 fist mx:smtpd[19377]: connect from spike.porcupine.org(168.100.1 +Jun 8 14:34:41 fist mx:smtp[19378]: 085792: to=, relay +Total time: 19 seconds + +/usr/bin/time ./smtp-source -s 20 -m 100 fist + 9.79 real 0.11 user 0.27 sys +Jun 8 14:35:18 fist mx:smtpd[19377]: connect from spike.porcupine.org(168.100.1 +Jun 8 14:35:38 fist mx:smtp[19382]: 085794: to=, relay +Total time: 20 seconds diff --git a/src/smtpstone/performance b/src/smtpstone/performance new file mode 100644 index 0000000..805676f --- /dev/null +++ b/src/smtpstone/performance @@ -0,0 +1,28 @@ +List performance: time to forward one SMTP message with 1000, 2000 +and 5000 different remote destinations. Outbound SMTP concurrency += 100. + +dests 1000 2000 5000 time per 1000 +============================================= +qmail 15 32 80 16 +mx 13 25 (*) 13 + +(*) message sink host saturated under the load + +Local delivery performance: time to deliver 100 SMTP messages to +one recipient. Outbound SMTP concurrency = 100, inbound SMTP +concurrency = 5, 10, 20. + +concur 5 10 20 average time +============================================ +qmail 62 59 58 60 +mx 24 24 25 24 + +Relay performance: time to forward 100 SMTP messages with one +recipient. Outbound SMTP concurrency = 100, inbound SMTP concurrency += 5, 10, 20. + +concur 5 10 20 average time +============================================ +qmail 56 54 54 55 +mx 17 19 20 19 diff --git a/src/smtpstone/qmail-deliver b/src/smtpstone/qmail-deliver new file mode 100644 index 0000000..e00ab39 --- /dev/null +++ b/src/smtpstone/qmail-deliver @@ -0,0 +1,20 @@ +Qmail needs 59 seconds to deliver 100 SMTP messages to one local recipient. +Default configuration, concurrencyremote = 100. + +/usr/bin/time ./smtp-source -s 5 -m 100 -t wietse fist + 41.45 real 0.07 user 0.21 sys +Jun 8 12:17:20 fist qmail: 865786640.494072 new msg 39901 +Jun 8 12:18:22 fist qmail: 865786702.301087 end msg 39982 +Total time: 62 sec + +/usr/bin/time ./smtp-source -s 10 -m 100 -t wietse fist + 26.50 real 0.10 user 0.22 sys +Jun 8 12:14:49 fist qmail: 865786489.089492 new msg 39901 +Jun 8 12:15:48 fist qmail: 865786548.316898 end msg 39928 +Total time: 59 sec + +/usr/bin/time ./smtp-source -s 20 -m 100 -t wietse fist + 21.15 real 0.08 user 0.30 sys +Jun 8 12:19:18 fist qmail: 865786758.939755 new msg 39903 +Jun 8 12:20:16 fist qmail: 865786816.739912 end msg 40031 +Total time: 58 sec diff --git a/src/smtpstone/qmail-explode b/src/smtpstone/qmail-explode new file mode 100644 index 0000000..ed40e2c --- /dev/null +++ b/src/smtpstone/qmail-explode @@ -0,0 +1,20 @@ +Qmail needs 16 seconds per 1000 destinations. +Default configuration, concurrencyremote = 100. + +/usr/bin/time ./smtp-source -r 1000 fist + 1.16 real 0.09 user 0.24 sys +Jun 8 14:57:17 fist qmail: 865796237.334359 new msg 39906 +Jun 8 14:57:32 fist qmail: 865796252.632756 delivery 2154: success: 168.100.189 +Total time: 15 seconds + +/usr/bin/time ./smtp-source -r 2000 fist + 1.99 real 0.23 user 0.45 sys +Jun 8 14:58:11 fist qmail: 865796291.817523 new msg 39907 +Jun 8 14:58:43 fist qmail: 865796323.174117 delivery 4116: success: 168.100.189 +Total time: 32 seconds + +/usr/bin/time ./smtp-source -r 5000 fist + 4.63 real 0.58 user 1.10 sys +Jun 8 14:59:23 fist qmail: 865796363.346735 new msg 39908 +Jun 8 15:00:43 fist qmail: 865796443.209168 delivery 9153: success: 168.100.189 +Total time: 80 seconds diff --git a/src/smtpstone/qmail-relay b/src/smtpstone/qmail-relay new file mode 100644 index 0000000..8718e5e --- /dev/null +++ b/src/smtpstone/qmail-relay @@ -0,0 +1,20 @@ +Qmail needs 54 seconds to relay 100 messages with one recipient. +Default configuration, concurrencyremote = 100. + +spike_4% /usr/bin/time ./smtp-source -s 5 -m 100 fist + 43.77 real 0.05 user 0.23 sys +Jun 8 12:08:36 fist qmail: 865786116.744366 new msg 39901 +Jun 8 12:09:32 fist qmail: 865786172.791473 end msg 39921 +Total time: 56 sec + +/usr/bin/time ./smtp-source -s 10 -m 100 fist + 26.66 real 0.06 user 0.26 sys +Jun 8 12:06:20 fist qmail: 865785980.185885 new msg 39901 +Jun 8 12:07:14 fist qmail: 865786034.306429 end msg 39920 +Total time: 54 sec + +spike_8% /usr/bin/time ./smtp-source -s 20 -m 100 fist + 20.94 real 0.11 user 0.27 sys +Jun 8 12:10:52 fist qmail: 865786252.412648 new msg 39901 +Jun 8 12:11:46 fist qmail: 865786306.080605 end msg 39962 +Total time: 54 sec diff --git a/src/smtpstone/qmqp-sink.c b/src/smtpstone/qmqp-sink.c new file mode 100644 index 0000000..2fcbdd1 --- /dev/null +++ b/src/smtpstone/qmqp-sink.c @@ -0,0 +1,325 @@ +/*++ +/* NAME +/* qmqp-sink 1 +/* SUMMARY +/* parallelized QMQP test server +/* SYNOPSIS +/* .fi +/* \fBqmqp-sink\fR [\fB-46cv\fR] [\fB-x \fItime\fR] +/* [\fBinet:\fR][\fIhost\fR]:\fIport\fR \fIbacklog\fR +/* +/* \fBqmqp-sink\fR [\fB-46cv\fR] [\fB-x \fItime\fR] +/* \fBunix:\fR\fIpathname\fR \fIbacklog\fR +/* DESCRIPTION +/* \fBqmqp-sink\fR listens on the named host (or address) and port. +/* It receives messages from the network and throws them away. +/* The purpose is to measure QMQP client performance, not protocol +/* compliance. +/* Connections can be accepted on IPv4 or IPv6 endpoints, or on +/* UNIX-domain sockets. +/* IPv4 and IPv6 are the default. +/* This program is the complement of the \fBqmqp-source\fR(1) program. +/* +/* Note: this is an unsupported test program. No attempt is made +/* to maintain compatibility between successive versions. +/* +/* Arguments: +/* .IP \fB-4\fR +/* Support IPv4 only. This option has no effect when +/* Postfix is built without IPv6 support. +/* .IP \fB-6\fR +/* Support IPv6 only. This option is not available when +/* Postfix is built without IPv6 support. +/* .IP \fB-c\fR +/* Display a running counter that is updated whenever a delivery +/* is completed. +/* .IP \fB-v\fR +/* Increase verbosity. Specify \fB-v -v\fR to see some of the QMQP +/* conversation. +/* .IP "\fB-x \fItime\fR" +/* Terminate after \fItime\fR seconds. This is to facilitate memory +/* leak testing. +/* SEE ALSO +/* qmqp-source(1), QMQP message generator +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license 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 +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +typedef struct { + VSTREAM *stream; /* client connection */ + int count; /* bytes to go */ +} SINK_STATE; + +static int var_tmout; +static VSTRING *buffer; +static void disconnect(SINK_STATE *); +static int count_deliveries; +static int counter; + +/* send_reply - finish conversation */ + +static void send_reply(SINK_STATE *state) +{ + vstring_sprintf(buffer, "%cOk", QMQP_STAT_OK); + NETSTRING_PUT_BUF(state->stream, buffer); + netstring_fflush(state->stream); + if (count_deliveries) { + counter++; + vstream_printf("%d\r", counter); + vstream_fflush(VSTREAM_OUT); + } + disconnect(state); +} + +/* read_data - read over-all netstring data */ + +static void read_data(int unused_event, void *context) +{ + SINK_STATE *state = (SINK_STATE *) context; + int fd = vstream_fileno(state->stream); + int count; + + /* + * Refill the VSTREAM buffer, if necessary. + */ + if (VSTREAM_GETC(state->stream) == VSTREAM_EOF) + netstring_except(state->stream, vstream_ftimeout(state->stream) ? + NETSTRING_ERR_TIME : NETSTRING_ERR_EOF); + state->count--; + + /* + * Flush the VSTREAM buffer. As documented, vstream_fseek() discards + * unread input. + */ + if ((count = vstream_peek(state->stream)) > 0) { + state->count -= count; + if (state->count <= 0) { + send_reply(state); + return; + } + vstream_fpurge(state->stream, VSTREAM_PURGE_BOTH); + } + + /* + * Do not block while waiting for the arrival of more data. + */ + event_disable_readwrite(fd); + event_enable_read(fd, read_data, context); +} + +/* read_length - read over-all netstring length */ + +static void read_length(int event, void *context) +{ + SINK_STATE *state = (SINK_STATE *) context; + + switch (vstream_setjmp(state->stream)) { + + default: + msg_panic("unknown error reading input"); + + case NETSTRING_ERR_TIME: + msg_panic("attempt to read non-readable socket"); + /* NOTREACHED */ + + case NETSTRING_ERR_EOF: + msg_warn("lost connection"); + disconnect(state); + return; + + case NETSTRING_ERR_FORMAT: + msg_warn("netstring format error"); + disconnect(state); + return; + + case NETSTRING_ERR_SIZE: + msg_warn("netstring size error"); + disconnect(state); + return; + + /* + * Include the netstring terminator in the read byte count. This + * violates abstractions. + */ + case 0: + state->count = netstring_get_length(state->stream) + 1; + read_data(event, context); + return; + } +} + +/* disconnect - handle disconnection events */ + +static void disconnect(SINK_STATE *state) +{ + event_disable_readwrite(vstream_fileno(state->stream)); + vstream_fclose(state->stream); + myfree((void *) state); +} + +/* connect_event - handle connection events */ + +static void connect_event(int unused_event, void *context) +{ + int sock = CAST_ANY_PTR_TO_INT(context); + struct sockaddr_storage ss; + SOCKADDR_SIZE len = sizeof(ss); + struct sockaddr *sa = (struct sockaddr *) &ss; + SINK_STATE *state; + int fd; + + if ((fd = accept(sock, sa, &len)) >= 0) { + if (msg_verbose) + msg_info("connect (%s)", +#ifdef AF_LOCAL + sa->sa_family == AF_LOCAL ? "AF_LOCAL" : +#else + sa->sa_family == AF_UNIX ? "AF_UNIX" : +#endif + sa->sa_family == AF_INET ? "AF_INET" : +#ifdef AF_INET6 + sa->sa_family == AF_INET6 ? "AF_INET6" : +#endif + "unknown protocol family"); + non_blocking(fd, NON_BLOCKING); + state = (SINK_STATE *) mymalloc(sizeof(*state)); + state->stream = vstream_fdopen(fd, O_RDWR); + vstream_tweak_sock(state->stream); + netstring_setup(state->stream, var_tmout); + event_enable_read(fd, read_length, (void *) state); + } +} + +/* terminate - voluntary exit */ + +static void terminate(int unused_event, void *unused_context) +{ + exit(0); +} + +/* usage - explain */ + +static void usage(char *myname) +{ + msg_fatal("usage: %s [-cv] [-x time] [host]:port backlog", myname); +} + +MAIL_VERSION_STAMP_DECLARE; + +int main(int argc, char **argv) +{ + int sock; + int backlog; + int ch; + int ttl; + const char *protocols = INET_PROTO_NAME_ALL; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Fix 20051207. + */ + signal(SIGPIPE, SIG_IGN); + + /* + * Initialize diagnostics. + */ + msg_vstream_init(argv[0], VSTREAM_ERR); + + /* + * Parse JCL. + */ + while ((ch = GETOPT(argc, argv, "46cvx:")) > 0) { + switch (ch) { + case '4': + protocols = INET_PROTO_NAME_IPV4; + break; + case '6': + protocols = INET_PROTO_NAME_IPV6; + break; + case 'c': + count_deliveries++; + break; + case 'v': + msg_verbose++; + break; + case 'x': + if ((ttl = atoi(optarg)) <= 0) + usage(argv[0]); + event_request_timer(terminate, (void *) 0, ttl); + break; + default: + usage(argv[0]); + } + } + if (argc - optind != 2) + usage(argv[0]); + if ((backlog = atoi(argv[optind + 1])) <= 0) + usage(argv[0]); + + /* + * Initialize. + */ + (void) inet_proto_init("protocols", protocols); + buffer = vstring_alloc(1024); + if (strncmp(argv[optind], "unix:", 5) == 0) { + sock = unix_listen(argv[optind] + 5, backlog, BLOCKING); + } else { + if (strncmp(argv[optind], "inet:", 5) == 0) + argv[optind] += 5; + sock = inet_listen(argv[optind], backlog, BLOCKING); + } + + /* + * Start the event handler. + */ + event_enable_read(sock, connect_event, CAST_INT_TO_VOID_PTR(sock)); + for (;;) + event_loop(-1); +} diff --git a/src/smtpstone/qmqp-source.c b/src/smtpstone/qmqp-source.c new file mode 100644 index 0000000..9231aec --- /dev/null +++ b/src/smtpstone/qmqp-source.c @@ -0,0 +1,673 @@ +/*++ +/* NAME +/* qmqp-source 1 +/* SUMMARY +/* parallelized QMQP test generator +/* SYNOPSIS +/* .fi +/* \fBqmqp-source\fR [\fIoptions\fR] [\fBinet:\fR]\fIhost\fR[:\fIport\fR] +/* +/* \fBqmqp-source\fR [\fIoptions\fR] \fBunix:\fIpathname\fR +/* DESCRIPTION +/* \fBqmqp-source\fR connects to the named host and TCP port (default 628) +/* and sends one or more messages to it, either sequentially +/* or in parallel. The program speaks the QMQP protocol. +/* Connections can be made to UNIX-domain and IPv4 or IPv6 servers. +/* IPv4 and IPv6 are the default. +/* +/* Note: this is an unsupported test program. No attempt is made +/* to maintain compatibility between successive versions. +/* +/* Arguments: +/* .IP \fB-4\fR +/* Connect to the server with IPv4. This option has no effect when +/* Postfix is built without IPv6 support. +/* .IP \fB-6\fR +/* Connect to the server with IPv6. This option is not available when +/* Postfix is built without IPv6 support. +/* .IP \fB-c\fR +/* Display a running counter that is incremented each time +/* a delivery completes. +/* .IP "\fB-C \fIcount\fR" +/* When a host sends RESET instead of SYN|ACK, try \fIcount\fR times +/* before giving up. The default count is 1. Specify a larger count in +/* order to work around a problem with TCP/IP stacks that send RESET +/* when the listen queue is full. +/* .IP "\fB-f \fIfrom\fR" +/* Use the specified sender address (default: ). +/* .IP "\fB-l \fIlength\fR" +/* Send \fIlength\fR bytes as message payload. The length +/* includes the message headers. +/* .IP "\fB-m \fImessage_count\fR" +/* Send the specified number of messages (default: 1). +/* .IP "\fB-M \fImyhostname\fR" +/* Use the specified hostname or [address] in the default +/* sender and recipient addresses, instead of the machine +/* hostname. +/* .IP "\fB-r \fIrecipient_count\fR" +/* Send the specified number of recipients per transaction (default: 1). +/* Recipient names are generated by prepending a number to the +/* recipient address. +/* .IP "\fB-s \fIsession_count\fR" +/* Run the specified number of QMQP sessions in parallel (default: 1). +/* .IP "\fB-t \fIto\fR" +/* Use the specified recipient address (default: ). +/* .IP "\fB-R \fIinterval\fR" +/* Wait for a random period of time 0 <= n <= interval between messages. +/* Suspending one thread does not affect other delivery threads. +/* .IP \fB-v\fR +/* Make the program more verbose, for debugging purposes. +/* .IP "\fB-w \fIinterval\fR" +/* Wait a fixed time between messages. +/* Suspending one thread does not affect other delivery threads. +/* SEE ALSO +/* qmqp-sink(1), QMQP message dump +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license 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 +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include + +/* Application-specific. */ + + /* + * Per-session data structure with state. + * + * This software can maintain multiple parallel connections to the same QMQP + * server. However, it makes no more than one connection request at a time + * to avoid overwhelming the server with SYN packets and having to back off. + * Back-off would screw up the benchmark. Pending connection requests are + * kept in a linear list. + */ +typedef struct SESSION { + int xfer_count; /* # of xfers in session */ + int rcpt_done; /* # of recipients done */ + int rcpt_count; /* # of recipients to go */ + VSTREAM *stream; /* open connection */ + int connect_count; /* # of connect()s to retry */ + struct SESSION *next; /* connect() queue linkage */ +} SESSION; + +static SESSION *last_session; /* connect() queue tail */ + +static VSTRING *buffer; +static int var_line_limit = 10240; +static int var_timeout = 300; +static const char *var_myhostname; +static int session_count; +static int message_count = 1; +static struct sockaddr_storage ss; + +#undef sun +static struct sockaddr_un sun; +static struct sockaddr *sa; +static int sa_length; +static int recipients = 1; +static char *defaddr; +static char *recipient; +static char *sender; +static int message_length = 1024; +static int count = 0; +static int counter = 0; +static int connect_count = 1; +static int random_delay = 0; +static int fixed_delay = 0; +static const char *mydate; +static int mypid; + +static void enqueue_connect(SESSION *); +static void start_connect(SESSION *); +static void connect_done(int, void *); + +static void send_data(SESSION *); +static void receive_reply(int, void *); + +static VSTRING *message_buffer; +static VSTRING *sender_buffer; +static VSTRING *recipient_buffer; + +/* Silly little macros. */ + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* random_interval - generate a random value in 0 .. (small) interval */ + +static int random_interval(int interval) +{ + return (rand() % (interval + 1)); +} + +/* socket_error - look up and reset the last socket error */ + +static int socket_error(int sock) +{ + int error; + SOCKOPT_SIZE error_len; + + /* + * Some Solaris 2 versions have getsockopt() itself return the error, + * instead of returning it via the parameter list. + */ + error = 0; + error_len = sizeof(error); + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void *) &error, &error_len) < 0) + return (-1); + if (error) { + errno = error; + return (-1); + } + + /* + * No problems. + */ + return (0); +} + +/* exception_text - translate exceptions from the netstring module */ + +static char *exception_text(int except) +{ + ; + + switch (except) { + case NETSTRING_ERR_EOF: + return ("lost connection"); + case NETSTRING_ERR_TIME: + return ("timeout"); + case NETSTRING_ERR_FORMAT: + return ("netstring format error"); + case NETSTRING_ERR_SIZE: + return ("netstring size exceeds limit"); + default: + msg_panic("exception_text: unknown exception %d", except); + } + /* NOTREACHED */ +} + +/* startup - connect to server but do not wait */ + +static void startup(SESSION *session) +{ + if (message_count-- <= 0) { + myfree((void *) session); + session_count--; + return; + } + enqueue_connect(session); +} + +/* start_event - invoke startup from timer context */ + +static void start_event(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + + startup(session); +} + +/* start_another - start another session */ + +static void start_another(SESSION *session) +{ + if (random_delay > 0) { + event_request_timer(start_event, (void *) session, + random_interval(random_delay)); + } else if (fixed_delay > 0) { + event_request_timer(start_event, (void *) session, fixed_delay); + } else { + startup(session); + } +} + +/* enqueue_connect - queue a connection request */ + +static void enqueue_connect(SESSION *session) +{ + session->next = 0; + if (last_session == 0) { + last_session = session; + start_connect(session); + } else { + last_session->next = session; + last_session = session; + } +} + +/* dequeue_connect - connection request completed */ + +static void dequeue_connect(SESSION *session) +{ + if (session == last_session) { + if (session->next != 0) + msg_panic("dequeue_connect: queue ends after last"); + last_session = 0; + } else { + if (session->next == 0) + msg_panic("dequeue_connect: queue ends before last"); + start_connect(session->next); + } +} + +/* fail_connect - handle failed startup */ + +static void fail_connect(SESSION *session) +{ + if (session->connect_count-- == 1) + msg_fatal("connect: %m"); + msg_warn("connect: %m"); + event_disable_readwrite(vstream_fileno(session->stream)); + vstream_fclose(session->stream); + session->stream = 0; +#ifdef MISSING_USLEEP + doze(10); +#else + usleep(10); +#endif + start_connect(session); +} + +/* start_connect - start TCP handshake */ + +static void start_connect(SESSION *session) +{ + int fd; + struct linger linger; + + /* + * Some systems don't set the socket error when connect() fails early + * (loopback) so we must deal with the error immediately, rather than + * retrieving it later with getsockopt(). We can't use MSG_PEEK to + * distinguish between server disconnect and connection refused. + */ + if ((fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) + msg_fatal("socket: %m"); + (void) non_blocking(fd, NON_BLOCKING); + linger.l_onoff = 1; + linger.l_linger = 0; + if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (void *) &linger, + sizeof(linger)) < 0) + msg_warn("setsockopt SO_LINGER %d: %m", linger.l_linger); + session->stream = vstream_fdopen(fd, O_RDWR); + event_enable_write(fd, connect_done, (void *) session); + netstring_setup(session->stream, var_timeout); + if (sane_connect(fd, sa, sa_length) < 0 && errno != EINPROGRESS) + fail_connect(session); +} + +/* connect_done - send message sender info */ + +static void connect_done(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + int fd = vstream_fileno(session->stream); + + /* + * Try again after some delay when the connection failed, in case they + * run a Mickey Mouse protocol stack. + */ + if (socket_error(fd) < 0) { + fail_connect(session); + } else { + dequeue_connect(session); + non_blocking(fd, BLOCKING); + event_disable_readwrite(fd); + /* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */ + if (sa->sa_family == AF_INET +#ifdef AF_INET6 + || sa->sa_family == AF_INET6 +#endif + ) + vstream_tweak_tcp(session->stream); + send_data(session); + } +} + +/* send_data - send message+sender+recipients */ + +static void send_data(SESSION *session) +{ + int fd = vstream_fileno(session->stream); + int except; + + /* + * Prepare for disaster. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending message", exception_text(except)); + + /* + * Send the message content, by wrapping three netstrings into an + * over-all netstring. + * + * XXX This should be done more carefully to avoid blocking when sending + * large messages over slow networks. + */ + netstring_put_multi(session->stream, + STR(message_buffer), LEN(message_buffer), + STR(sender_buffer), LEN(sender_buffer), + STR(recipient_buffer), LEN(recipient_buffer), + (char *) 0); + netstring_fflush(session->stream); + + /* + * Wake me up when the server replies or when something bad happens. + */ + event_enable_read(fd, receive_reply, (void *) session); +} + +/* receive_reply - read server reply */ + +static void receive_reply(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + int except; + + /* + * Prepare for disaster. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while receiving server reply", exception_text(except)); + + /* + * Receive and process the server reply. + */ + netstring_get(session->stream, buffer, var_line_limit); + if (msg_verbose) + vstream_printf("<< %.*s\n", (int) LEN(buffer), STR(buffer)); + if (STR(buffer)[0] != QMQP_STAT_OK) + msg_fatal("%s error: %.*s", + STR(buffer)[0] == QMQP_STAT_RETRY ? "recoverable" : + STR(buffer)[0] == QMQP_STAT_HARD ? "unrecoverable" : + "unknown", (int) LEN(buffer) - 1, STR(buffer) + 1); + + /* + * Update the optional running counter. + */ + if (count) { + counter++; + vstream_printf("%d\r", counter); + vstream_fflush(VSTREAM_OUT); + } + + /* + * Finish this session. QMQP sends only one message per session. + */ + event_disable_readwrite(vstream_fileno(session->stream)); + vstream_fclose(session->stream); + session->stream = 0; + start_another(session); +} + +/* usage - explain */ + +static void usage(char *myname) +{ + msg_fatal("usage: %s -cv -s sess -l msglen -m msgs -C count -M myhostname -f from -t to -R delay -w delay host[:port]", myname); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - parse JCL and start the machine */ + +int main(int argc, char **argv) +{ + SESSION *session; + char *host; + char *port; + char *path; + int path_len; + int sessions = 1; + int ch; + ssize_t len; + int n; + int i; + char *buf; + const char *parse_err; + struct addrinfo *res; + int aierr; + const char *protocols = INET_PROTO_NAME_ALL; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + signal(SIGPIPE, SIG_IGN); + msg_vstream_init(argv[0], VSTREAM_ERR); + + /* + * Parse JCL. + */ + while ((ch = GETOPT(argc, argv, "46cC:f:l:m:M:r:R:s:t:vw:")) > 0) { + switch (ch) { + case '4': + protocols = INET_PROTO_NAME_IPV4; + break; + case '6': + protocols = INET_PROTO_NAME_IPV6; + break; + case 'c': + count++; + break; + case 'C': + if ((connect_count = atoi(optarg)) <= 0) + usage(argv[0]); + break; + case 'f': + sender = optarg; + break; + case 'l': + if ((message_length = atoi(optarg)) <= 0) + usage(argv[0]); + break; + case 'm': + if ((message_count = atoi(optarg)) <= 0) + usage(argv[0]); + break; + case 'M': + if (*optarg == '[') { + if (!valid_mailhost_literal(optarg, DO_GRIPE)) + msg_fatal("bad address literal: %s", optarg); + } else { + if (!valid_hostname(optarg, DO_GRIPE)) + msg_fatal("bad hostname: %s", optarg); + } + var_myhostname = optarg; + break; + case 'r': + if ((recipients = atoi(optarg)) <= 0) + usage(argv[0]); + break; + case 'R': + if (fixed_delay > 0 || (random_delay = atoi(optarg)) <= 0) + usage(argv[0]); + break; + case 's': + if ((sessions = atoi(optarg)) <= 0) + usage(argv[0]); + break; + case 't': + recipient = optarg; + break; + case 'v': + msg_verbose++; + break; + case 'w': + if (random_delay > 0 || (fixed_delay = atoi(optarg)) <= 0) + usage(argv[0]); + break; + default: + usage(argv[0]); + } + } + if (argc - optind != 1) + usage(argv[0]); + + if (random_delay > 0) + srand(getpid()); + + /* + * Translate endpoint address to internal form. + */ + (void) inet_proto_init("protocols", protocols); + if (strncmp(argv[optind], "unix:", 5) == 0) { + path = argv[optind] + 5; + path_len = strlen(path); + if (path_len >= (int) sizeof(sun.sun_path)) + msg_fatal("unix-domain name too long: %s", path); + memset((void *) &sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; +#ifdef HAS_SUN_LEN + sun.sun_len = path_len + 1; +#endif + memcpy(sun.sun_path, path, path_len); + sa = (struct sockaddr *) &sun; + sa_length = sizeof(sun); + } else { + if (strncmp(argv[optind], "inet:", 5) == 0) + argv[optind] += 5; + buf = mystrdup(argv[optind]); + if ((parse_err = host_port(buf, &host, (char *) 0, &port, "628")) != 0) + msg_fatal("%s: %s", argv[optind], parse_err); + if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0) + msg_fatal("%s: %s", argv[optind], MAI_STRERROR(aierr)); + myfree(buf); + sa = (struct sockaddr *) &ss; + if (res->ai_addrlen > sizeof(ss)) + msg_fatal("address length %d > buffer length %d", + (int) res->ai_addrlen, (int) sizeof(ss)); + memcpy((void *) sa, res->ai_addr, res->ai_addrlen); + sa_length = res->ai_addrlen; +#ifdef HAS_SA_LEN + sa->sa_len = sa_length; +#endif + freeaddrinfo(res); + } + + /* + * Allocate space for temporary buffer. + */ + buffer = vstring_alloc(100); + + /* + * Make sure we have sender and recipient addresses. + */ + if (var_myhostname == 0) + var_myhostname = get_hostname(); + if (sender == 0 || recipient == 0) { + vstring_sprintf(buffer, "foo@%s", var_myhostname); + defaddr = mystrdup(vstring_str(buffer)); + if (sender == 0) + sender = defaddr; + if (recipient == 0) + recipient = defaddr; + } + + /* + * Prepare some results that may be used multiple times: the message + * content netstring, the sender netstring, and the recipient netstrings. + */ + mydate = mail_date(time((time_t *) 0)); + mypid = getpid(); + + message_buffer = vstring_alloc(message_length + 200); + vstring_sprintf(buffer, + "From: <%s>\nTo: <%s>\nDate: %s\nMessage-Id: <%d@%s>\n\n", + sender, recipient, mydate, mypid, var_myhostname); + for (n = 1; LEN(buffer) < message_length; n++) { + for (i = 0; i < n && i < 79; i++) + VSTRING_ADDCH(buffer, 'X'); + VSTRING_ADDCH(buffer, '\n'); + } + STR(buffer)[message_length - 1] = '\n'; + netstring_memcpy(message_buffer, STR(buffer), message_length); + + len = strlen(sender); + sender_buffer = vstring_alloc(len); + netstring_memcpy(sender_buffer, sender, len); + + if (recipients == 1) { + len = strlen(recipient); + recipient_buffer = vstring_alloc(len); + netstring_memcpy(recipient_buffer, recipient, len); + } else { + recipient_buffer = vstring_alloc(100); + for (n = 0; n < recipients; n++) { + vstring_sprintf(buffer, "%d%s", n, recipient); + netstring_memcat(recipient_buffer, STR(buffer), LEN(buffer)); + } + } + + /* + * Start sessions. + */ + while (sessions-- > 0) { + session = (SESSION *) mymalloc(sizeof(*session)); + session->stream = 0; + session->xfer_count = 0; + session->connect_count = connect_count; + session->next = 0; + session_count++; + startup(session); + } + for (;;) { + event_loop(-1); + if (session_count <= 0 && message_count <= 0) { + if (count) { + VSTREAM_PUTC('\n', VSTREAM_OUT); + vstream_fflush(VSTREAM_OUT); + } + exit(0); + } + } +} diff --git a/src/smtpstone/smtp-sink.c b/src/smtpstone/smtp-sink.c new file mode 100644 index 0000000..c34f21d --- /dev/null +++ b/src/smtpstone/smtp-sink.c @@ -0,0 +1,1643 @@ +/*++ +/* NAME +/* smtp-sink 1 +/* SUMMARY +/* parallelized SMTP/LMTP test server +/* SYNOPSIS +/* .fi +/* \fBsmtp-sink\fR [\fIoptions\fR] [\fBinet:\fR][\fIhost\fR]:\fIport\fR +/* \fIbacklog\fR +/* +/* \fBsmtp-sink\fR [\fIoptions\fR] \fBunix:\fR\fIpathname\fR \fIbacklog\fR +/* DESCRIPTION +/* \fBsmtp-sink\fR listens on the named host (or address) and port. +/* It takes SMTP messages from the network and throws them away. +/* The purpose is to measure client performance, not protocol +/* compliance. +/* +/* \fBsmtp-sink\fR may also be configured to capture each mail +/* delivery transaction to file. Since disk latencies are large +/* compared to network delays, this mode of operation can +/* reduce the maximal performance by several orders of magnitude. +/* +/* Connections can be accepted on IPv4 or IPv6 endpoints, or on +/* UNIX-domain sockets. +/* IPv4 and IPv6 are the default. +/* This program is the complement of the \fBsmtp-source\fR(1) program. +/* +/* Note: this is an unsupported test program. No attempt is made +/* to maintain compatibility between successive versions. +/* +/* Arguments: +/* .IP \fB-4\fR +/* Support IPv4 only. This option has no effect when +/* Postfix is built without IPv6 support. +/* .IP \fB-6\fR +/* Support IPv6 only. This option is not available when +/* Postfix is built without IPv6 support. +/* .IP \fB-8\fR +/* Do not announce 8BITMIME support. +/* .IP \fB-a\fR +/* Do not announce SASL authentication support. +/* .IP "\fB-A \fIdelay\fR" +/* Wait \fIdelay\fR seconds after responding to DATA, then +/* abort prematurely with a 550 reply status. Do not read +/* further input from the client; this is an attempt to block +/* the client before it sends ".". Specify a zero delay value +/* to abort immediately. +/* .IP "\fB-b \fIsoft-bounce-reply\fR" +/* Use \fIsoft-bounce-reply\fR for soft reject responses. The +/* default reply is "450 4.3.0 Error: command failed". +/* .IP "\fB-B \fIhard-bounce-reply\fR" +/* Use \fIhard-bounce-reply\fR for hard reject responses. The +/* default reply is "500 5.3.0 Error: command failed". +/* .IP \fB-c\fR +/* Display running counters that are updated whenever an SMTP +/* session ends, a QUIT command is executed, or when "." is +/* received. +/* .IP \fB-C\fR +/* Disable XCLIENT support. +/* .IP "\fB-d \fIdump-template\fR" +/* Dump each mail transaction to a single-message file whose +/* name is created by expanding the \fIdump-template\fR via +/* strftime(3) and appending a pseudo-random hexadecimal number +/* (example: "%Y%m%d%H/%M." expands into "2006081203/05.809a62e3"). +/* If the template contains "/" characters, missing directories +/* are created automatically. The message dump format is +/* described below. +/* .sp +/* Note: this option keeps one capture file open for every +/* mail transaction in progress. +/* .IP "\fB-D \fIdump-template\fR" +/* Append mail transactions to a multi-message dump file whose +/* name is created by expanding the \fIdump-template\fR via +/* strftime(3). +/* If the template contains "/" characters, missing directories +/* are created automatically. The message dump format is +/* described below. +/* .sp +/* Note: this option keeps one capture file open for every +/* mail transaction in progress. +/* .IP \fB-e\fR +/* Do not announce ESMTP support. +/* .IP \fB-E\fR +/* Do not announce ENHANCEDSTATUSCODES support. +/* .IP "\fB-f \fIcommand,command,...\fR" +/* Reject the specified commands with a hard (5xx) error code. +/* This option implies \fB-p\fR. +/* .sp +/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY, +/* DATA, ., RSET, NOOP, and QUIT. Separate command names by +/* white space or commas, and use quotes to protect white space +/* from the shell. Command names are case-insensitive. +/* .IP \fB-F\fR +/* Disable XFORWARD support. +/* .IP "\fB-h\fI hostname\fR" +/* Use \fIhostname\fR in the SMTP greeting, in the HELO response, +/* and in the EHLO response. The default hostname is "smtp-sink". +/* .IP "\fB-H\fI delay\fR" +/* Delay the first read operation after receiving DATA (time +/* in seconds). Combine with a large test message and a small +/* TCP window size (see the \fB-T\fR option) to test the Postfix +/* client write_wait() implementation. +/* .IP \fB-L\fR +/* Enable LMTP instead of SMTP. +/* .IP "\fB-m \fIcount\fR (default: 256)" +/* An upper bound on the maximal number of simultaneous +/* connections that \fBsmtp-sink\fR will handle. This prevents +/* the process from running out of file descriptors. Excess +/* connections will stay queued in the TCP/IP stack. +/* .IP "\fB-M \fIcount\fR" +/* Terminate after receiving \fIcount\fR messages. +/* .IP "\fB-n \fIcount\fR" +/* Terminate after \fIcount\fR sessions. +/* .IP \fB-N\fR +/* Do not announce support for DSN. +/* .IP \fB-p\fR +/* Do not announce support for ESMTP command pipelining. +/* .IP \fB-P\fR +/* Change the server greeting so that it appears to come through +/* a CISCO PIX system. Implies \fB-e\fR. +/* .IP "\fB-q \fIcommand,command,...\fR" +/* Disconnect (without replying) after receiving one of the +/* specified commands. +/* .sp +/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY, +/* DATA, ., RSET, NOOP, and QUIT. Separate command names by +/* white space or commas, and use quotes to protect white space +/* from the shell. Command names are case-insensitive. +/* .IP "\fB-Q \fIcommand,command,...\fR" +/* Send a 421 reply and disconnect after receiving one +/* of the specified commands. +/* .sp +/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY, +/* DATA, ., RSET, NOOP, and QUIT. Separate command names by +/* white space or commas, and use quotes to protect white space +/* from the shell. Command names are case-insensitive. +/* .IP "\fB-r \fIcommand,command,...\fR" +/* Reject the specified commands with a soft (4xx) error code. +/* This option implies \fB-p\fR. +/* .sp +/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY, +/* DATA, ., RSET, NOOP, and QUIT. Separate command names by +/* white space or commas, and use quotes to protect white space +/* from the shell. Command names are case-insensitive. +/* .IP "\fB-R \fIroot-directory\fR" +/* Change the process root directory to the specified location. +/* This option requires super-user privileges. See also the +/* \fB-u\fR option. +/* .IP "\fB-s \fIcommand,command,...\fR" +/* Log the named commands to syslogd. +/* .sp +/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY, +/* DATA, ., RSET, NOOP, and QUIT. Separate command names by +/* white space or commas, and use quotes to protect white space +/* from the shell. Command names are case-insensitive. +/* .IP "\fB-S start-string\fR" +/* An optional string that is prepended to each message that is +/* written to a dump file (see the dump file format description +/* below). The following C escape sequences are supported: \ea +/* (bell), \eb (backspace), \ef (formfeed), \en (newline), \er +/* (carriage return), \et (horizontal tab), \ev (vertical tab), +/* \e\fIddd\fR (up to three octal digits) and \e\e (the backslash +/* character). +/* .IP "\fB-t \fItimeout\fR (default: 100)" +/* Limit the time for receiving a command or sending a response. +/* The time limit is specified in seconds. +/* .IP "\fB-T \fIwindowsize\fR" +/* Override the default TCP window size. To work around +/* broken TCP window scaling implementations, specify a +/* value > 0 and < 65536. +/* .IP "\fB-u \fIusername\fR" +/* Switch to the specified user privileges after opening the +/* network socket and optionally changing the process root +/* directory. This option is required when the process runs +/* with super-user privileges. See also the \fB-R\fR option. +/* .IP \fB-v\fR +/* Show the SMTP conversations. +/* .IP "\fB-w \fIdelay\fR" +/* Wait \fIdelay\fR seconds before responding to a DATA command. +/* .IP "\fB-W \fIcommand:delay[:odds]\fR" +/* Wait \fIdelay\fR seconds before responding to \fIcommand\fR. +/* If \fIodds\fR is also specified (a number between 1-99 +/* inclusive), wait for a random multiple of \fIdelay\fR. The +/* random multiplier is equal to the number of times the program +/* needs to roll a dice with a range of 0..99 inclusive, before +/* the dice produces a result greater than or equal to \fIodds\fR. +/* .IP [\fBinet:\fR][\fIhost\fR]:\fIport\fR +/* Listen on network interface \fIhost\fR (default: any interface) +/* TCP port \fIport\fR. Both \fIhost\fR and \fIport\fR may be +/* specified in numeric or symbolic form. +/* .IP \fBunix:\fR\fIpathname\fR +/* Listen on the UNIX-domain socket at \fIpathname\fR. +/* .IP \fIbacklog\fR +/* The maximum length of the queue of pending connections, +/* as defined by the \fBlisten\fR(2) system call. +/* DUMP FILE FORMAT +/* .ad +/* .fi +/* Each dumped message contains a sequence of text lines, +/* terminated with the newline character. The sequence of +/* information is as follows: +/* .IP \(bu +/* The optional string specified with the \fB-S\fR option. +/* .IP \(bu +/* The \fBsmtp-sink\fR generated headers as documented below. +/* .IP \(bu +/* The message header and body as received from the SMTP client. +/* .IP \(bu +/* An empty line. +/* .PP +/* The format of the \fBsmtp-sink\fR generated headers is as +/* follows: +/* .IP "\fBX-Client-Addr: \fItext\fR" +/* The client IP address without enclosing []. An IPv6 address +/* is prefixed with "ipv6:". This record is always present. +/* .IP "\fBX-Client-Proto: \fItext\fR" +/* The client protocol: SMTP, ESMTP or LMTP. This record is +/* always present. +/* .IP "\fBX-Helo-Args: \fItext\fR" +/* The arguments of the last HELO or EHLO command before this +/* mail delivery transaction. This record is present only if +/* the client sent a recognizable HELO or EHLO command before +/* the DATA command. +/* .IP "\fBX-Mail-Args: \fItext\fR" +/* The arguments of the MAIL command that started this mail +/* delivery transaction. This record is present exactly once. +/* .IP "\fBX-Rcpt-Args: \fItext\fR" +/* The arguments of an RCPT command within this mail delivery +/* transaction. There is one record for each RCPT command, and +/* they are in the order as sent by the client. +/* .IP "\fBReceived: \fItext\fR" +/* A message header for compatibility with mail processing +/* software. This three-line header marks the end of the headers +/* provided by \fBsmtp-sink\fR, and is formatted as follows: +/* .RS +/* .IP "\fBfrom \fIhelo\fR ([\fIaddr\fR])" +/* The HELO or EHLO command argument and client IP address. +/* If the client did not send HELO or EHLO, the client IP +/* address is used instead. +/* .IP "\fBby \fIhost\fB (smtp-sink) with \fIproto\fB id \fIrandom\fB;\fR" +/* The hostname specified with the \fB-h\fR option, the client +/* protocol (see \fBX-Client-Proto\fR above), and the pseudo-random +/* portion of the per-message capture file name. +/* .IP \fItime-stamp\fR +/* A time stamp as defined in RFC 2822. +/* .RE +/* SEE ALSO +/* smtp-source(1), SMTP/LMTP message generator +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license 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 +#include +#include +#include +#include +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include + +/* Application-specific. */ + +typedef struct SINK_STATE { + VSTREAM *stream; + VSTRING *buffer; + int data_state; + int (*read_fn) (struct SINK_STATE *); + int in_mail; + int rcpts; + char *push_back_ptr; + /* Capture file information for fake Received: header */ + MAI_HOSTADDR_STR client_addr; /* IP address */ + char *addr_prefix; /* ipv6: or empty */ + char *helo_args; /* text after HELO or EHLO */ + const char *client_proto; /* SMTP, ESMTP, LMTP */ + time_t start_time; /* MAIL command time */ + int id; /* pseudo-random */ + VSTREAM *dump_file; /* dump file or null */ + void (*delayed_response) (struct SINK_STATE *state, const char *); + char *delayed_args; +} SINK_STATE; + +#define ST_ANY 0 +#define ST_CR 1 +#define ST_CR_LF 2 +#define ST_CR_LF_DOT 3 +#define ST_CR_LF_DOT_CR 4 +#define ST_CR_LF_DOT_CR_LF 5 + +#define PUSH_BACK_PEEK(state) (*(state)->push_back_ptr != 0) +#define PUSH_BACK_GET(state) (*(state)->push_back_ptr++) +#define PUSH_BACK_SET(state, text) ((state)->push_back_ptr = (text)) + +#ifndef DEF_MAX_CLIENT_COUNT +#define DEF_MAX_CLIENT_COUNT 256 +#endif + +#define SOFT_ERROR_RESP "450 4.3.0 Error: command failed" +#define HARD_ERROR_RESP "500 5.3.0 Error: command failed" + + /* + * We can't rely on vstream auto-flushing, so we have to prepare for the + * next read request. + */ +#define SMTP_FLUSH(fp) do { \ + if (vstream_peek(fp) <= 0 && readable(vstream_fileno(fp)) <= 0) \ + smtp_flush(fp); \ + } while (0) + +static int var_tmout = 100; +static int var_max_line_length = 2048; +static char *var_myhostname; +static char *soft_error_resp = SOFT_ERROR_RESP; +static char *hard_error_resp = HARD_ERROR_RESP; +static int command_read(SINK_STATE *); +static int data_read(SINK_STATE *); +static void disconnect(SINK_STATE *); +static void read_timeout(int, void *); +static void read_event(int, void *); +static int show_count; +static int sess_count; +static int quit_count; +static int mesg_count; +static int max_quit_count; +static int max_msg_quit_count; +static int disable_pipelining; +static int disable_8bitmime; +static int disable_esmtp; +static int enable_lmtp; +static int pretend_pix; +static int disable_saslauth; +static int disable_xclient; +static int disable_xforward; +static int disable_enh_status; +static int disable_dsn; +static int max_client_count = DEF_MAX_CLIENT_COUNT; +static int client_count; +static int sock; +static int abort_delay = -1; +static int data_read_delay = 0; + +static char *single_template; /* individual template */ +static char *shared_template; /* shared template */ +static VSTRING *start_string; /* dump content prefix */ + +static const INET_PROTO_INFO *proto_info; + +#define STR(x) vstring_str(x) + +/* do_stats - show counters */ + +static void do_stats(void) +{ + vstream_printf("sess=%d quit=%d mesg=%d\r", + sess_count, quit_count, mesg_count); + vstream_fflush(VSTREAM_OUT); +} + +/* hard_err_resp - generic hard error response */ + +static void hard_err_resp(SINK_STATE *state) +{ + smtp_printf(state->stream, "%s", hard_error_resp); + SMTP_FLUSH(state->stream); +} + +/* soft_err_resp - generic soft error response */ + +static void soft_err_resp(SINK_STATE *state) +{ + smtp_printf(state->stream, "%s", soft_error_resp); + SMTP_FLUSH(state->stream); +} + +/* exp_path_template - expand template pathname, static result */ + +static VSTRING *exp_path_template(const char *template, time_t start_time) +{ + static VSTRING *path_buf = 0; + struct tm *lt; + + if (path_buf == 0) + path_buf = vstring_alloc(100); + else + VSTRING_RESET(path_buf); + lt = localtime(&start_time); + while (strftime(STR(path_buf), vstring_avail(path_buf), template, lt) == 0) + VSTRING_SPACE(path_buf, vstring_avail(path_buf) + 100); + VSTRING_SKIP(path_buf); + return (path_buf); +} + +/* make_parent_dir - create parent directory or bust */ + +static void make_parent_dir(const char *path, mode_t mode) +{ + const char *parent; + + parent = sane_dirname((VSTRING *) 0, path); + if (make_dirs(parent, mode) < 0) + msg_fatal("mkdir %s: %m", parent); +} + +/* mail_file_open - open mail capture file */ + +static void mail_file_open(SINK_STATE *state) +{ + const char *myname = "mail_file_open"; + VSTRING *path_buf; + ssize_t len; + int tries = 0; + + /* + * Save the start time for later. + */ + time(&(state->start_time)); + + /* + * Expand the per-message dumpfile pathname template. + */ + path_buf = exp_path_template(single_template, state->start_time); + + /* + * Append a random hexadecimal string to the pathname and create a new + * file. Retry with a different path if the file already exists. Create + * intermediate directories on the fly when the template specifies + * multiple pathname segments. + */ +#define ID_FORMAT "%08x" + + for (len = VSTRING_LEN(path_buf); /* void */ ; vstring_truncate(path_buf, len)) { + if (++tries > 100) + msg_fatal("%s: something is looping", myname); + state->id = myrand(); + vstring_sprintf_append(path_buf, ID_FORMAT, state->id); + if ((state->dump_file = vstream_fopen(STR(path_buf), + O_RDWR | O_CREAT | O_EXCL, + 0644)) != 0) { + break; + } else if (errno == EEXIST) { + continue; + } else if (errno == ENOENT) { + make_parent_dir(STR(path_buf), 0755); + continue; + } else { + msg_fatal("open %s: %m", STR(path_buf)); + } + } + + /* + * Don't leave temporary files behind. + */ + if (shared_template != 0 && unlink(STR(path_buf)) < 0) + msg_fatal("unlink %s: %m", STR(path_buf)); + + /* + * Do initial header records. + */ + if (start_string) + vstream_fprintf(state->dump_file, "%s", STR(start_string)); + vstream_fprintf(state->dump_file, "X-Client-Addr: %s%s\n", + state->addr_prefix, state->client_addr.buf); + vstream_fprintf(state->dump_file, "X-Client-Proto: %s\n", state->client_proto); + if (state->helo_args) + vstream_fprintf(state->dump_file, "X-Helo-Args: %s\n", state->helo_args); + /* Note: there may be more than one recipient. */ +} + +/* mail_file_finish_header - do final smtp-sink generated header records */ + +static void mail_file_finish_header(SINK_STATE *state) +{ + if (state->helo_args) + vstream_fprintf(state->dump_file, "Received: from %s ([%s%s])\n", + state->helo_args, state->addr_prefix, + state->client_addr.buf); + else + vstream_fprintf(state->dump_file, "Received: from [%s%s] ([%s%s])\n", + state->addr_prefix, state->client_addr.buf, + state->addr_prefix, state->client_addr.buf); + vstream_fprintf(state->dump_file, "\tby %s (smtp-sink)" + " with %s id " ID_FORMAT ";\n", + var_myhostname, state->client_proto, state->id); + vstream_fprintf(state->dump_file, "\t%s\n", mail_date(state->start_time)); +} + +/* mail_file_cleanup - common cleanup for capture file */ + +static void mail_file_cleanup(SINK_STATE *state) +{ + (void) vstream_fclose(state->dump_file); + state->dump_file = 0; +} + +/* mail_file_finish - handle message completion for capture file */ + +static void mail_file_finish(SINK_STATE *state) +{ + + /* + * Optionally append the captured message to a shared dumpfile. + */ + if (shared_template) { + const char *out_path; + VSTREAM *out_fp; + ssize_t count; + + /* + * Expand the shared dumpfile pathname template. + */ + out_path = STR(exp_path_template(shared_template, state->start_time)); + + /* + * Open the shared dump file. + */ +#define OUT_OPEN_FLAGS (O_WRONLY | O_CREAT | O_APPEND) +#define OUT_OPEN_MODE 0644 + + if ((out_fp = vstream_fopen(out_path, OUT_OPEN_FLAGS, OUT_OPEN_MODE)) + == 0 && errno == ENOENT) { + make_parent_dir(out_path, 0755); + out_fp = vstream_fopen(out_path, OUT_OPEN_FLAGS, OUT_OPEN_MODE); + } + if (out_fp == 0) + msg_fatal("open %s: %m", out_path); + + /* + * Append message content from single-message dump file. + */ + if (vstream_fseek(state->dump_file, 0L, SEEK_SET) < 0) + msg_fatal("seek file %s: %m", VSTREAM_PATH(state->dump_file)); + VSTRING_RESET(state->buffer); + for (;;) { + count = vstream_fread(state->dump_file, STR(state->buffer), + vstring_avail(state->buffer)); + if (count <= 0) + break; + if (vstream_fwrite(out_fp, STR(state->buffer), count) != count) + msg_fatal("append file %s: %m", out_path); + } + if (vstream_ferror(state->dump_file)) + msg_fatal("read file %s: %m", VSTREAM_PATH(state->dump_file)); + if (vstream_fclose(out_fp)) + msg_fatal("append file %s: %m", out_path); + } + mail_file_cleanup(state); +} + +/* mail_file_reset - abort mail to capture file */ + +static void mail_file_reset(SINK_STATE *state) +{ + if (shared_template == 0 + && unlink(VSTREAM_PATH(state->dump_file)) < 0 + && errno != ENOENT) + msg_fatal("unlink %s: %m", VSTREAM_PATH(state->dump_file)); + mail_file_cleanup(state); +} + +/* mail_cmd_reset - reset mail transaction information */ + +static void mail_cmd_reset(SINK_STATE *state) +{ + state->in_mail = 0; + /* Not: state->rcpts = 0. This breaks the DOT reply with LMTP. */ + if (state->dump_file) + mail_file_reset(state); +} + +/* ehlo_response - respond to EHLO command */ + +static void ehlo_response(SINK_STATE *state, const char *args) +{ +#define SKIP(cp, cond) do { \ + for (/* void */; *cp && (cond); cp++) \ + /* void */; \ + } while (0) + + /* EHLO aborts a mail transaction in progress. */ + mail_cmd_reset(state); + if (enable_lmtp == 0) + state->client_proto = "ESMTP"; + smtp_printf(state->stream, "250-%s", var_myhostname); + if (!disable_pipelining) + smtp_printf(state->stream, "250-PIPELINING"); + if (!disable_8bitmime) + smtp_printf(state->stream, "250-8BITMIME"); + if (!disable_saslauth) + smtp_printf(state->stream, "250-AUTH PLAIN LOGIN"); + if (!disable_xclient) + smtp_printf(state->stream, "250-XCLIENT NAME HELO"); + if (!disable_xforward) + smtp_printf(state->stream, "250-XFORWARD NAME ADDR PROTO HELO"); + if (!disable_enh_status) + smtp_printf(state->stream, "250-ENHANCEDSTATUSCODES"); + if (!disable_dsn) + smtp_printf(state->stream, "250-DSN"); + /* RFC 821/2821/5321: Format is replycodeoptional-text */ + smtp_printf(state->stream, "250 "); + SMTP_FLUSH(state->stream); + if (single_template) { + if (state->helo_args) + myfree(state->helo_args); + SKIP(args, ISSPACE(*args)); + state->helo_args = mystrdup(args); + } +} + +/* helo_response - respond to HELO command */ + +static void helo_response(SINK_STATE *state, const char *args) +{ + /* HELO aborts a mail transaction in progress. */ + mail_cmd_reset(state); + state->client_proto = "SMTP"; + smtp_printf(state->stream, "250 %s", var_myhostname); + SMTP_FLUSH(state->stream); + if (single_template) { + if (state->helo_args) + myfree(state->helo_args); + SKIP(args, ISSPACE(*args)); + state->helo_args = mystrdup(args); + } +} + +/* ok_response - send 250 OK */ + +static void ok_response(SINK_STATE *state, const char *unused_args) +{ + smtp_printf(state->stream, "250 2.0.0 Ok"); + SMTP_FLUSH(state->stream); +} + +/* rset_response - reset, send 250 OK */ + +static void rset_response(SINK_STATE *state, const char *unused_args) +{ + mail_cmd_reset(state); + smtp_printf(state->stream, "250 2.1.0 Ok"); + SMTP_FLUSH(state->stream); +} + +/* mail_response - reset recipient count, send 250 OK */ + +static void mail_response(SINK_STATE *state, const char *args) +{ + if (state->in_mail) { + smtp_printf(state->stream, "503 5.5.1 Error: nested MAIL command"); + SMTP_FLUSH(state->stream); + return; + } + state->in_mail++; + state->rcpts = 0; + smtp_printf(state->stream, "250 2.1.0 Ok"); + SMTP_FLUSH(state->stream); + if (single_template) { + mail_file_open(state); + SKIP(args, *args != ':'); + SKIP(args, *args == ':'); + SKIP(args, ISSPACE(*args)); + vstream_fprintf(state->dump_file, "X-Mail-Args: %s\n", args); + } +} + +/* rcpt_response - bump recipient count, send 250 OK */ + +static void rcpt_response(SINK_STATE *state, const char *args) +{ + if (state->in_mail == 0) { + smtp_printf(state->stream, "503 5.5.1 Error: need MAIL command"); + SMTP_FLUSH(state->stream); + return; + } + state->rcpts++; + smtp_printf(state->stream, "250 2.1.5 Ok"); + SMTP_FLUSH(state->stream); + /* Note: there may be more than one recipient per mail transaction. */ + if (state->dump_file) { + SKIP(args, *args != ':'); + SKIP(args, *args == ':'); + SKIP(args, ISSPACE(*args)); + vstream_fprintf(state->dump_file, "X-Rcpt-Args: %s\n", args); + } +} + +/* abort_event - delayed abort after DATA command */ + +static void abort_event(int unused_event, void *context) +{ + SINK_STATE *state = (SINK_STATE *) context; + + smtp_printf(state->stream, "550 This violates SMTP"); + SMTP_FLUSH(state->stream); + disconnect(state); +} + +/* delay_read_event - resume input event handling */ + +static void delay_read_event(int event, void *context) +{ + SINK_STATE *state = (SINK_STATE *) context; + + if (event != EVENT_TIME) + msg_panic("delay_read_event: non-timer event %d", event); + + event_enable_read(vstream_fileno(state->stream), read_event, (void *) state); + event_request_timer(read_timeout, (void *) state, var_tmout); +} + +/* delay_read - temporarily suspend input event handling */ + +static void delay_read(SINK_STATE *state, int delay) +{ + event_disable_readwrite(vstream_fileno(state->stream)); + event_cancel_timer(read_timeout, (void *) state); + event_request_timer(delay_read_event, (void *) state, delay); +} + +/* data_response - respond to DATA command */ + +static void data_response(SINK_STATE *state, const char *unused_args) +{ + if (state->in_mail == 0 || state->rcpts == 0) { + smtp_printf(state->stream, "503 5.5.1 Error: need RCPT command"); + SMTP_FLUSH(state->stream); + return; + } + /* Not: ST_ANY. */ + state->data_state = ST_CR_LF; + smtp_printf(state->stream, "354 End data with ."); + SMTP_FLUSH(state->stream); + if (abort_delay < 0) { + state->read_fn = data_read; + /* Todo: move into code that invokes the command response function. */ + if (data_read_delay > 0) + delay_read(state, data_read_delay); + } else { + /* Stop reading, send premature 550, and disconnect. */ + event_disable_readwrite(vstream_fileno(state->stream)); + event_cancel_timer(read_event, (void *) state); + event_request_timer(abort_event, (void *) state, abort_delay); + } + if (state->dump_file) + mail_file_finish_header(state); +} + +/* dot_resp_hard - hard error response to . command */ + +static void dot_resp_hard(SINK_STATE *state) +{ + if (enable_lmtp) { + while (state->rcpts-- > 0) /* XXX this could block */ + smtp_printf(state->stream, "%s", hard_error_resp); + } else { + smtp_printf(state->stream, "%s", hard_error_resp); + } + SMTP_FLUSH(state->stream); +} + +/* dot_resp_soft - soft error response to . command */ + +static void dot_resp_soft(SINK_STATE *state) +{ + if (enable_lmtp) { + while (state->rcpts-- > 0) /* XXX this could block */ + smtp_printf(state->stream, "%s", soft_error_resp); + } else { + smtp_printf(state->stream, "%s", soft_error_resp); + } + SMTP_FLUSH(state->stream); +} + +/* dot_response - response to . command */ + +static void dot_response(SINK_STATE *state, const char *unused_args) +{ + if (enable_lmtp) { + while (state->rcpts-- > 0) /* XXX this could block */ + smtp_printf(state->stream, "250 2.2.0 Ok"); + } else { + smtp_printf(state->stream, "250 2.0.0 Ok"); + } + SMTP_FLUSH(state->stream); +} + +/* quit_response - respond to QUIT command */ + +static void quit_response(SINK_STATE *state, const char *unused_args) +{ + smtp_printf(state->stream, "221 Bye"); + smtp_flush(state->stream); /* not: SMTP_FLUSH */ + if (show_count) + quit_count++; +} + +/* conn_response - respond to connect command */ + +static void conn_response(SINK_STATE *state, const char *unused_args) +{ + if (pretend_pix) + smtp_printf(state->stream, "220 ********"); + else if (disable_esmtp) + smtp_printf(state->stream, "220 %s", var_myhostname); + else + smtp_printf(state->stream, "220 %s ESMTP", var_myhostname); + SMTP_FLUSH(state->stream); +} + +/* delay_event - delayed command response */ + +static void delay_event(int unused_event, void *context) +{ + SINK_STATE *state = (SINK_STATE *) context; + + switch (vstream_setjmp(state->stream)) { + + default: + msg_panic("unknown read/write error"); + /* NOTREACHED */ + + case SMTP_ERR_TIME: + msg_warn("write timeout"); + disconnect(state); + return; + + case SMTP_ERR_EOF: + msg_warn("lost connection"); + disconnect(state); + return; + + case 0: + state->delayed_response(state, state->delayed_args); + myfree(state->delayed_args); + state->delayed_args = 0; + break; + } + + if (state->delayed_response == quit_response) { + disconnect(state); + return; + } + state->delayed_response = 0; + + /* Resume input event handling after the delayed response. */ + event_enable_read(vstream_fileno(state->stream), read_event, (void *) state); + event_request_timer(read_timeout, (void *) state, var_tmout); +} + +/* data_read - read data from socket */ + +static int data_read(SINK_STATE *state) +{ + int ch; + struct data_trans { + int state; + int want; + int next_state; + }; + static struct data_trans data_trans[] = { + ST_ANY, '\r', ST_CR, + ST_CR, '\n', ST_CR_LF, + ST_CR_LF, '.', ST_CR_LF_DOT, + ST_CR_LF_DOT, '\r', ST_CR_LF_DOT_CR, + ST_CR_LF_DOT_CR, '\n', ST_CR_LF_DOT_CR_LF, + }; + struct data_trans *dp; + + /* + * A read may result in EOF, but is never supposed to time out - a time + * out means that we were trying to read when no data was available. + */ + for (;;) { + if ((ch = VSTREAM_GETC(state->stream)) == VSTREAM_EOF) + return (-1); + for (dp = data_trans; dp->state != state->data_state; dp++) + /* void */ ; + + /* + * Try to match the current character desired by the state machine. + * If that fails, try to restart the machine with a match for its + * first state. This covers the case of a CR/LF/CR/LF sequence + * (empty line) right before the end of the message data. + */ + if (ch == dp->want) + state->data_state = dp->next_state; + else if (ch == data_trans[0].want) + state->data_state = data_trans[0].next_state; + else + state->data_state = ST_ANY; + if (state->dump_file) { + if (ch != '\r' && state->data_state != ST_CR_LF_DOT) + VSTREAM_PUTC(ch, state->dump_file); + if (vstream_ferror(state->dump_file)) + msg_fatal("append file %s: %m", VSTREAM_PATH(state->dump_file)); + } + if (state->data_state == ST_CR_LF_DOT_CR_LF) { + PUSH_BACK_SET(state, ".\r\n"); + state->read_fn = command_read; + state->data_state = ST_ANY; + if (state->dump_file) + mail_file_finish(state); + mail_cmd_reset(state); + if (show_count || max_msg_quit_count > 0) { + mesg_count++; + if (show_count) + do_stats(); + if (max_msg_quit_count > 0 && mesg_count >= max_msg_quit_count) + exit(0); + } + break; + } + + /* + * We must avoid blocking I/O, so get out of here as soon as both the + * VSTREAM and kernel read buffers dry up. + */ + if (vstream_peek(state->stream) <= 0 + && readable(vstream_fileno(state->stream)) <= 0) + return (0); + } + return (0); +} + + /* + * The table of all SMTP commands that we can handle. + */ +typedef struct SINK_COMMAND { + const char *name; + void (*response) (SINK_STATE *, const char *); + void (*hard_response) (SINK_STATE *); + void (*soft_response) (SINK_STATE *); + int flags; + int delay; + int delay_odds; +} SINK_COMMAND; + +#define FLAG_ENABLE (1<<0) /* command is enabled */ +#define FLAG_SYSLOG (1<<1) /* log the command */ +#define FLAG_HARD_ERR (1<<2) /* report hard error */ +#define FLAG_SOFT_ERR (1<<3) /* report soft error */ +#define FLAG_DISCONNECT (1<<4) /* disconnect */ +#define FLAG_CLOSE (1<<5) /* say goodbye and disconnect */ + +static SINK_COMMAND command_table[] = { + "connect", conn_response, hard_err_resp, soft_err_resp, 0, 0, 0, + "helo", helo_response, hard_err_resp, soft_err_resp, 0, 0, 0, + "ehlo", ehlo_response, hard_err_resp, soft_err_resp, 0, 0, 0, + "lhlo", ehlo_response, hard_err_resp, soft_err_resp, 0, 0, 0, + "xclient", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + "xforward", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + "auth", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + "mail", mail_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + "rcpt", rcpt_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + "data", data_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + ".", dot_response, dot_resp_hard, dot_resp_soft, FLAG_ENABLE, 0, 0, + "rset", rset_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + "noop", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + "vrfy", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + "quit", quit_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + 0, +}; + +/* reset_cmd_flags - reset per-command command flags */ + +static void reset_cmd_flags(const char *cmd, int flags) +{ + SINK_COMMAND *cmdp; + + for (cmdp = command_table; cmdp->name != 0; cmdp++) + if (strcasecmp(cmd, cmdp->name) == 0) + break; + if (cmdp->name == 0) + msg_fatal("unknown command: %s", cmd); + cmdp->flags &= ~flags; +} + +/* set_cmd_flags - set per-command command flags */ + +static void set_cmd_flags(const char *cmd, int flags) +{ + SINK_COMMAND *cmdp; + + for (cmdp = command_table; cmdp->name != 0; cmdp++) + if (strcasecmp(cmd, cmdp->name) == 0) + break; + if (cmdp->name == 0) + msg_fatal("unknown command: %s", cmd); + cmdp->flags |= flags; +} + +/* set_cmds_flags - set per-command flags for multiple commands */ + +static void set_cmds_flags(const char *cmds, int flags) +{ + char *saved_cmds; + char *cp; + char *cmd; + + saved_cmds = cp = mystrdup(cmds); + while ((cmd = mystrtok(&cp, CHARS_COMMA_SP)) != 0) + set_cmd_flags(cmd, flags); + myfree(saved_cmds); +} + +/* set_cmd_delay - set per-command delay */ + +static void set_cmd_delay(const char *cmd, int delay, int odds) +{ + SINK_COMMAND *cmdp; + + for (cmdp = command_table; cmdp->name != 0; cmdp++) + if (strcasecmp(cmd, cmdp->name) == 0) + break; + if (cmdp->name == 0) + msg_fatal("unknown command: %s", cmd); + + if (delay <= 0) + msg_fatal("non-positive '%s' delay", cmd); + if (odds < 0 || odds > 99) + msg_fatal("delay odds for '%s' out of range", cmd); + + cmdp->delay = delay; + cmdp->delay_odds = odds; +} + +/* set_cmd_delay_arg - set per-command delay from option argument */ + +static void set_cmd_delay_arg(char *arg) +{ + char *cp; + char *saved_arg; + char *cmd; + char *delay; + char *odds; + + saved_arg = cp = mystrdup(arg); + cmd = mystrtok(&cp, ":"); + delay = mystrtok(&cp, ":"); + if (cmd == 0 || delay == 0) + msg_fatal("invalid command delay argument: %s", arg); + odds = mystrtok(&cp, ""); + set_cmd_delay(cmd, atoi(delay), odds ? atoi(odds) : 0); + myfree(saved_arg); +} + +/* command_resp - respond to command */ + +static int command_resp(SINK_STATE *state, SINK_COMMAND *cmdp, + const char *command, const char *args) +{ + /* We use raw syslog. Sanitize data content and length. */ + if (cmdp->flags & FLAG_SYSLOG) + syslog(LOG_INFO, "%s %.100s", command, args); + if (cmdp->flags & FLAG_DISCONNECT) + return (-1); + if (cmdp->flags & FLAG_CLOSE) { + smtp_printf(state->stream, "421 4.0.0 Server closing connection"); + return (-1); + } + if (cmdp->flags & FLAG_HARD_ERR) { + cmdp->hard_response(state); + return (0); + } + if (cmdp->flags & FLAG_SOFT_ERR) { + cmdp->soft_response(state); + return (0); + } + if (cmdp->delay > 0) { + int delay = cmdp->delay; + + if (cmdp->delay_odds > 0) + for (delay = 0; + ((int) (100.0 * rand() / (RAND_MAX + 1.0))) < cmdp->delay_odds; + delay += cmdp->delay) + /* NOP */ ; + /* Suspend input event handling while delaying the command response. */ + event_disable_readwrite(vstream_fileno(state->stream)); + event_cancel_timer(read_timeout, (void *) state); + event_request_timer(delay_event, (void *) state, delay); + state->delayed_response = cmdp->response; + state->delayed_args = mystrdup(args); + } else { + cmdp->response(state, args); + if (cmdp->response == quit_response) + return (-1); + } + return (0); +} + +/* command_read - talk the SMTP protocol, server side */ + +static int command_read(SINK_STATE *state) +{ + char *command; + SINK_COMMAND *cmdp; + int ch; + struct cmd_trans { + int state; + int want; + int next_state; + }; + static struct cmd_trans cmd_trans[] = { + ST_ANY, '\r', ST_CR, + ST_CR, '\n', ST_CR_LF, + 0, 0, 0, + }; + struct cmd_trans *cp; + char *ptr; + + /* + * A read may result in EOF, but is never supposed to time out - a time + * out means that we were trying to read when no data was available. + */ +#define NEXT_CHAR(state) \ + (PUSH_BACK_PEEK(state) ? PUSH_BACK_GET(state) : VSTREAM_GETC(state->stream)) + + if (state->data_state == ST_CR_LF) + state->data_state = ST_ANY; /* XXX */ + for (;;) { + if ((ch = NEXT_CHAR(state)) == VSTREAM_EOF) + return (-1); + + /* + * Sanity check. We don't want to store infinitely long commands. + */ + if (VSTRING_LEN(state->buffer) >= var_max_line_length) { + msg_warn("command line too long"); + return (-1); + } + VSTRING_ADDCH(state->buffer, ch); + + /* + * Try to match the current character desired by the state machine. + * If that fails, try to restart the machine with a match for its + * first state. + */ + for (cp = cmd_trans; cp->state != state->data_state; cp++) + if (cp->want == 0) + msg_panic("command_read: unknown state: %d", state->data_state); + if (ch == cp->want) + state->data_state = cp->next_state; + else if (ch == cmd_trans[0].want) + state->data_state = cmd_trans[0].next_state; + else + state->data_state = ST_ANY; + if (state->data_state == ST_CR_LF) + break; + + /* + * We must avoid blocking I/O, so get out of here as soon as both the + * VSTREAM and kernel read buffers dry up. + * + * XXX Solaris non-blocking read() may fail on a socket when ioctl + * FIONREAD reports there is unread data. Diagnosis by Max Pashkov. + * As a workaround we use readable() (which uses poll or select()) + * instead of peek_fd() (which uses ioctl FIONREAD). Workaround added + * 20020604. + */ + if (PUSH_BACK_PEEK(state) == 0 && vstream_peek(state->stream) <= 0 + && readable(vstream_fileno(state->stream)) <= 0) + return (0); + } + + /* + * Properly terminate the result, and reset the buffer write pointer for + * reading the next command. This is ugly, but not as ugly as trying to + * deal with all the early returns below. + */ + vstring_truncate(state->buffer, VSTRING_LEN(state->buffer) - 2); + VSTRING_TERMINATE(state->buffer); + state->data_state = ST_CR_LF; + VSTRING_RESET(state->buffer); + + /* + * Got a complete command line. Parse it. + */ + ptr = vstring_str(state->buffer); + if (msg_verbose) + msg_info("%s", ptr); + if ((command = mystrtok(&ptr, " \t")) == 0) { + smtp_printf(state->stream, "500 5.5.2 Error: unknown command"); + SMTP_FLUSH(state->stream); + return (0); + } + for (cmdp = command_table; cmdp->name != 0; cmdp++) + if (strcasecmp(command, cmdp->name) == 0) + break; + if (cmdp->name == 0 || (cmdp->flags & FLAG_ENABLE) == 0) { + smtp_printf(state->stream, "500 5.5.1 Error: unknown command"); + SMTP_FLUSH(state->stream); + return (0); + } + return (command_resp(state, cmdp, command, printable(ptr, '?'))); +} + +/* read_timeout - handle timer event */ + +static void read_timeout(int unused_event, void *context) +{ + SINK_STATE *state = (SINK_STATE *) context; + + /* + * We don't send anything to the client, because we would have to set up + * an smtp_stream exception handler first. And that is just too much + * trouble. + */ + msg_warn("read timeout"); + disconnect(state); +} + +/* read_event - handle command or data read events */ + +static void read_event(int unused_event, void *context) +{ + SINK_STATE *state = (SINK_STATE *) context; + + /* + * The input reading routine not only reads input (with vstream calls) + * but also produces output (with smtp_stream calls). Because the output + * routines can raise timeout or EOF exceptions with vstream_longjmp(), + * the input reading routine needs to set up corresponding exception + * handlers with vstream_setjmp(). Guarding the input operations in the + * same manner is not useful: we must read input in non-blocking mode, so + * we never get called when the socket stays unreadable too long. And EOF + * is already trivial to detect with the vstream calls. + */ + do { + switch (vstream_setjmp(state->stream)) { + + default: + msg_panic("unknown read/write error"); + /* NOTREACHED */ + + case SMTP_ERR_TIME: + msg_warn("write timeout"); + disconnect(state); + return; + + case SMTP_ERR_EOF: + msg_warn("lost connection"); + disconnect(state); + return; + + case 0: + if (state->read_fn(state) < 0) { + if (msg_verbose) + msg_info("disconnect"); + disconnect(state); + return; + } + } + } while (PUSH_BACK_PEEK(state) != 0 || vstream_peek(state->stream) > 0); + + /* + * Reset the idle timer. Wait until the next input event, or until the + * idle timer goes off. + */ + event_request_timer(read_timeout, (void *) state, var_tmout); +} + +static void connect_event(int, void *); + +/* disconnect - handle disconnection events */ + +static void disconnect(SINK_STATE *state) +{ + event_disable_readwrite(vstream_fileno(state->stream)); + event_cancel_timer(read_timeout, (void *) state); + if (show_count) { + sess_count++; + do_stats(); + } + vstream_fclose(state->stream); + vstring_free(state->buffer); + /* Clean up file capture attributes. */ + if (state->helo_args) + myfree(state->helo_args); + /* Delete incomplete mail transaction. */ + mail_cmd_reset(state); + if (state->delayed_args) + myfree(state->delayed_args); + myfree((void *) state); + if (max_quit_count > 0 && quit_count >= max_quit_count) + exit(0); + if (client_count-- == max_client_count) + event_enable_read(sock, connect_event, (void *) 0); +} + +/* connect_event - handle connection events */ + +static void connect_event(int unused_event, void *unused_context) +{ + struct sockaddr_storage ss; + SOCKADDR_SIZE len = sizeof(ss); + struct sockaddr *sa = (struct sockaddr *) &ss; + SINK_STATE *state; + int fd; + + if ((fd = sane_accept(sock, sa, &len)) >= 0) { + /* Safety: limit the number of open sockets and capture files. */ + if (++client_count == max_client_count) + event_disable_readwrite(sock); + state = (SINK_STATE *) mymalloc(sizeof(*state)); + if (strchr((char *) proto_info->sa_family_list, sa->sa_family)) + SOCKADDR_TO_HOSTADDR(sa, len, &state->client_addr, + (MAI_SERVPORT_STR *) 0, sa->sa_family); + else + strncpy(state->client_addr.buf, "local", sizeof("local") + 0); + if (msg_verbose) + msg_info("connect (%s %s)", +#ifdef AF_LOCAL + sa->sa_family == AF_LOCAL ? "AF_LOCAL" : +#else + sa->sa_family == AF_UNIX ? "AF_UNIX" : +#endif + sa->sa_family == AF_INET ? "AF_INET" : +#ifdef AF_INET6 + sa->sa_family == AF_INET6 ? "AF_INET6" : +#endif + "unknown protocol family", + state->client_addr.buf); + non_blocking(fd, NON_BLOCKING); + state->stream = vstream_fdopen(fd, O_RDWR); + vstream_tweak_sock(state->stream); + state->buffer = vstring_alloc(1024); + state->read_fn = command_read; + state->data_state = ST_ANY; + PUSH_BACK_SET(state, ""); + smtp_timeout_setup(state->stream, var_tmout); + state->in_mail = 0; + state->rcpts = 0; + state->delayed_response = 0; + state->delayed_args = 0; + /* Initialize file capture attributes. */ +#ifdef AF_INET6 + if (sa->sa_family == AF_INET6) + state->addr_prefix = "ipv6:"; + else +#endif + state->addr_prefix = ""; + + state->helo_args = 0; + state->client_proto = enable_lmtp ? "LMTP" : "SMTP"; + state->start_time = 0; + state->id = 0; + state->dump_file = 0; + + /* + * We use the smtp_stream module to produce output. That module + * throws an exception via vstream_longjmp() in case of a timeout or + * lost connection error. Therefore we must prepare to handle these + * exceptions with vstream_setjmp(). + */ + switch (vstream_setjmp(state->stream)) { + + default: + msg_panic("unknown read/write error"); + /* NOTREACHED */ + + case SMTP_ERR_TIME: + msg_warn("write timeout"); + disconnect(state); + return; + + case SMTP_ERR_EOF: + msg_warn("lost connection"); + disconnect(state); + return; + + case 0: + if (command_resp(state, command_table, "connect", "") < 0) + disconnect(state); + else if (command_table->delay == 0) { + event_enable_read(fd, read_event, (void *) state); + event_request_timer(read_timeout, (void *) state, var_tmout); + } + } + } +} + +/* usage - explain */ + +static void usage(char *myname) +{ + msg_fatal("usage: %s [-468acCeEFLpPv] [-A abort_delay] [-b soft_bounce_reply] [-B hard_bounce_reply] [-d dump-template] [-D dump-template] [-f commands] [-h hostname] [-m max_concurrency] [-M message_quit_count] [-n quit_count] [-q commands] [-r commands] [-R root-dir] [-s commands] [-S start-string] [-u user_privs] [-w delay] [host]:port backlog", myname); +} + +MAIL_VERSION_STAMP_DECLARE; + +int main(int argc, char **argv) +{ + int backlog; + int ch; + int delay; + const char *protocols = INET_PROTO_NAME_ALL; + const char *root_dir = 0; + const char *user_privs = 0; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Fix 20051207. + */ + signal(SIGPIPE, SIG_IGN); + + /* + * Initialize diagnostics. + */ + msg_vstream_init(argv[0], VSTREAM_ERR); + + /* + * Parse JCL. + */ + while ((ch = GETOPT(argc, argv, "468aA:b:B:cCd:D:eEf:Fh:H:Ln:m:M:NpPq:Q:r:R:s:S:t:T:u:vw:W:")) > 0) { + switch (ch) { + case '4': + protocols = INET_PROTO_NAME_IPV4; + break; + case '6': + protocols = INET_PROTO_NAME_IPV6; + break; + case '8': + disable_8bitmime = 1; + break; + case 'a': + disable_saslauth = 1; + break; + case 'A': + if (!alldig(optarg) || (abort_delay = atoi(optarg)) < 0) + usage(argv[0]); + break; + case 'b': + if (optarg[0] != '4' || strspn(optarg, "0123456789") != 3) { + msg_error("bad soft error reply: %s", optarg); + usage(argv[0]); + } else + soft_error_resp = optarg; + break; + case 'B': + if (optarg[0] != '5' || strspn(optarg, "0123456789") != 3) { + msg_error("bad hard error reply: %s", optarg); + usage(argv[0]); + } else + hard_error_resp = optarg; + break; + case 'c': + show_count++; + break; + case 'C': + disable_xclient = 1; + reset_cmd_flags("xclient", FLAG_ENABLE); + break; + case 'd': + single_template = optarg; + break; + case 'D': + shared_template = optarg; + break; + case 'e': + disable_esmtp = 1; + break; + case 'E': + disable_enh_status = 1; + break; + case 'f': + set_cmds_flags(optarg, FLAG_HARD_ERR); + disable_pipelining = 1; + break; + case 'F': + disable_xforward = 1; + reset_cmd_flags("xforward", FLAG_ENABLE); + break; + case 'h': + var_myhostname = optarg; + break; + case 'H': + if ((data_read_delay = atoi(optarg)) <= 0) + msg_fatal("bad data read delay: %s", optarg); + break; + case 'L': + enable_lmtp = 1; + break; + case 'm': + if ((max_client_count = atoi(optarg)) <= 0) + msg_fatal("bad concurrency limit: %s", optarg); + break; + case 'M': + if ((max_msg_quit_count = atoi(optarg)) <= 0) + msg_fatal("bad message quit count: %s", optarg); + break; + case 'n': + if ((max_quit_count = atoi(optarg)) <= 0) + msg_fatal("bad quit count: %s", optarg); + break; + case 'N': + disable_dsn = 1; + break; + case 'p': + disable_pipelining = 1; + break; + case 'P': + pretend_pix = 1; + disable_esmtp = 1; + break; + case 'q': + set_cmds_flags(optarg, FLAG_DISCONNECT); + break; + case 'Q': + set_cmds_flags(optarg, FLAG_CLOSE); + break; + case 'r': + set_cmds_flags(optarg, FLAG_SOFT_ERR); + disable_pipelining = 1; + break; + case 'R': + root_dir = optarg; + break; + case 's': + openlog(basename(argv[0]), LOG_PID, LOG_MAIL); + set_cmds_flags(optarg, FLAG_SYSLOG); + break; + case 'S': + start_string = vstring_alloc(10); + unescape(start_string, optarg); + break; + case 't': + if ((var_tmout = atoi(optarg)) <= 0) + msg_fatal("bad timeout: %s", optarg); + break; + case 'T': + if ((inet_windowsize = atoi(optarg)) <= 0) + msg_fatal("bad TCP window size: %s", optarg); + break; + case 'u': + user_privs = optarg; + break; + case 'v': + msg_verbose++; + break; + case 'w': + if ((delay = atoi(optarg)) <= 0) + usage(argv[0]); + set_cmd_delay("data", delay, 0); + break; + case 'W': + set_cmd_delay_arg(optarg); + break; + default: + usage(argv[0]); + } + } + if (argc - optind != 2) + usage(argv[0]); + if ((backlog = atoi(argv[optind + 1])) <= 0) + usage(argv[0]); + if (single_template && shared_template) + msg_fatal("use only one of -d or -D, but not both"); + if (geteuid() == 0 && user_privs == 0) + msg_fatal("-u option is required if running as root"); + + /* + * Initialize. + */ + if (var_myhostname == 0) + var_myhostname = "smtp-sink"; + set_cmds_flags(enable_lmtp ? "lhlo" : + disable_esmtp ? "helo" : + "helo, ehlo", FLAG_ENABLE); + proto_info = inet_proto_init("protocols", protocols); + if (strncmp(argv[optind], "unix:", 5) == 0) { + sock = unix_listen(argv[optind] + 5, backlog, BLOCKING); + } else { + if (strncmp(argv[optind], "inet:", 5) == 0) + argv[optind] += 5; + sock = inet_listen(argv[optind], backlog, BLOCKING); + } + if (user_privs) + chroot_uid(root_dir, user_privs); + + if (single_template) + mysrand((int) time((time_t *) 0)); + else if (shared_template) + single_template = shared_template; + + /* + * Start the event handler. + */ + event_enable_read(sock, connect_event, (void *) 0); + for (;;) + event_loop(-1); +} diff --git a/src/smtpstone/smtp-source.c b/src/smtpstone/smtp-source.c new file mode 100644 index 0000000..be388d6 --- /dev/null +++ b/src/smtpstone/smtp-source.c @@ -0,0 +1,1188 @@ +/*++ +/* NAME +/* smtp-source 1 +/* SUMMARY +/* parallelized SMTP/LMTP test generator +/* SYNOPSIS +/* .fi +/* \fBsmtp-source\fR [\fIoptions\fR] [\fBinet:\fR]\fIhost\fR[:\fIport\fR] +/* +/* \fBsmtp-source\fR [\fIoptions\fR] \fBunix:\fIpathname\fR +/* DESCRIPTION +/* \fBsmtp-source\fR connects to the named \fIhost\fR and TCP \fIport\fR +/* (default: port 25) +/* and sends one or more messages to it, either sequentially +/* or in parallel. The program speaks either SMTP (default) or +/* LMTP. +/* Connections can be made to UNIX-domain and IPv4 or IPv6 servers. +/* IPv4 and IPv6 are the default. +/* +/* Note: this is an unsupported test program. No attempt is made +/* to maintain compatibility between successive versions. +/* +/* Arguments: +/* .IP \fB-4\fR +/* Connect to the server with IPv4. This option has no effect when +/* Postfix is built without IPv6 support. +/* .IP \fB-6\fR +/* Connect to the server with IPv6. This option is not available when +/* Postfix is built without IPv6 support. +/* .IP "\fB-A\fR" +/* Don't abort when the server sends something other than the +/* expected positive reply code. +/* .IP \fB-c\fR +/* Display a running counter that is incremented each time +/* an SMTP DATA command completes. +/* .IP "\fB-C \fIcount\fR" +/* When a host sends RESET instead of SYN|ACK, try \fIcount\fR times +/* before giving up. The default count is 1. Specify a larger count in +/* order to work around a problem with TCP/IP stacks that send RESET +/* when the listen queue is full. +/* .IP \fB-d\fR +/* Don't disconnect after sending a message; send the next +/* message over the same connection. +/* .IP "\fB-f \fIfrom\fR" +/* Use the specified sender address (default: ). +/* .IP "\fB-F \fIfile\fR" +/* Send the pre-formatted message header and body in the +/* specified \fIfile\fR, while prepending '.' before lines that +/* begin with '.', and while appending CRLF after each line. +/* .IP "\fB-l \fIlength\fR" +/* Send \fIlength\fR bytes as message payload. The length does not +/* include message headers. +/* .IP \fB-L\fR +/* Speak LMTP rather than SMTP. +/* .IP "\fB-m \fImessage_count\fR" +/* Send the specified number of messages (default: 1). +/* .IP "\fB-M \fImyhostname\fR" +/* Use the specified hostname or [address] in the HELO command +/* and in the default sender and recipient addresses, instead +/* of the machine hostname. +/* .IP "\fB-N\fR" +/* Prepend a non-repeating sequence number to each recipient +/* address. This avoids the artificial 100% hit rate in the +/* resolve and rewrite client caches and exercises the +/* trivial-rewrite daemon, better approximating Postfix +/* performance under real-life work-loads. +/* .IP \fB-o\fR +/* Old mode: don't send HELO, and don't send message headers. +/* .IP "\fB-r \fIrecipient_count\fR" +/* Send the specified number of recipients per transaction (default: 1). +/* Recipient names are generated by prepending a number to the +/* recipient address. +/* .IP "\fB-R \fIinterval\fR" +/* Wait for a random period of time 0 <= n <= interval between messages. +/* Suspending one thread does not affect other delivery threads. +/* .IP "\fB-s \fIsession_count\fR" +/* Run the specified number of SMTP sessions in parallel (default: 1). +/* .IP "\fB-S \fIsubject\fR" +/* Send mail with the named subject line (default: none). +/* .IP "\fB-t \fIto\fR" +/* Use the specified recipient address (default: ). +/* .IP "\fB-T \fIwindowsize\fR" +/* Override the default TCP window size. To work around +/* broken TCP window scaling implementations, specify a +/* value > 0 and < 65536. +/* .IP \fB-v\fR +/* Make the program more verbose, for debugging purposes. +/* .IP "\fB-w \fIinterval\fR" +/* Wait a fixed time between messages. +/* Suspending one thread does not affect other delivery threads. +/* .IP [\fBinet:\fR]\fIhost\fR[:\fIport\fR] +/* Connect via TCP to host \fIhost\fR, port \fIport\fR. The default +/* port is \fBsmtp\fR. +/* .IP \fBunix:\fIpathname\fR +/* Connect to the UNIX-domain socket at \fIpathname\fR. +/* BUGS +/* No SMTP command pipelining support. +/* SEE ALSO +/* smtp-sink(1), SMTP/LMTP message dump +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license 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 +#include +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include + +/* Application-specific. */ + + /* + * Per-session data structure with state. + * + * This software can maintain multiple parallel connections to the same SMTP + * server. However, it makes no more than one connection request at a time + * to avoid overwhelming the server with SYN packets and having to back off. + * Back-off would screw up the benchmark. Pending connection requests are + * kept in a linear list. + */ +typedef struct SESSION { + int xfer_count; /* # of xfers in session */ + int rcpt_done; /* # of recipients done */ + int rcpt_count; /* # of recipients to go */ + int rcpt_accepted; /* # of recipients accepted */ + VSTREAM *stream; /* open connection */ + int connect_count; /* # of connect()s to retry */ + struct SESSION *next; /* connect() queue linkage */ +} SESSION; + +static SESSION *last_session; /* connect() queue tail */ + + /* + * Structure with broken-up SMTP server response. + */ +typedef struct { /* server response */ + int code; /* status */ + char *str; /* text */ + VSTRING *buf; /* origin of text */ +} RESPONSE; + +static VSTRING *buffer; +static int var_line_limit = 10240; +static int var_timeout = 300; +static const char *var_myhostname; +static int session_count; +static int message_count = 1; +static struct sockaddr_storage ss; + +#undef sun +static struct sockaddr_un sun; +static struct sockaddr *sa; +static int sa_length; +static int recipients = 1; +static char *defaddr; +static char *recipient; +static char *sender; +static char *message_data; +static int message_length; +static int disconnect = 1; +static int count = 0; +static int counter = 0; +static int send_helo_first = 1; +static int send_headers = 1; +static int connect_count = 1; +static int random_delay = 0; +static int fixed_delay = 0; +static int talk_lmtp = 0; +static char *subject = 0; +static int number_rcpts = 0; +static int allow_reject = 0; + +static void enqueue_connect(SESSION *); +static void start_connect(SESSION *); +static void connect_done(int, void *); +static void read_banner(int, void *); +static void send_helo(SESSION *); +static void helo_done(int, void *); +static void send_mail(SESSION *); +static void mail_done(int, void *); +static void send_rcpt(int, void *); +static void rcpt_done(int, void *); +static void send_data(int, void *); +static void data_done(int, void *); +static void dot_done(int, void *); +static void send_rset(int, void *); +static void rset_done(int, void *); +static void send_quit(SESSION *); +static void quit_done(int, void *); +static void close_session(SESSION *); + +/* random_interval - generate a random value in 0 .. (small) interval */ + +static int random_interval(int interval) +{ + return (rand() % (interval + 1)); +} + +/* command - send an SMTP command */ + +static void command(VSTREAM *stream, char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + + /* + * Optionally, log the command before actually sending, so we can see + * what the program is trying to do. + */ + if (msg_verbose) { + va_list ap2; + + VA_COPY(ap2, ap); + vmsg_info(fmt, ap2); + va_end(ap2); + } + smtp_vprintf(stream, fmt, ap); + va_end(ap); + smtp_flush(stream); +} + +/* socket_error - look up and reset the last socket error */ + +static int socket_error(int sock) +{ + int error; + SOCKOPT_SIZE error_len; + + /* + * Some Solaris 2 versions have getsockopt() itself return the error, + * instead of returning it via the parameter list. + */ + error = 0; + error_len = sizeof(error); + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void *) &error, &error_len) < 0) + return (-1); + if (error) { + errno = error; + return (-1); + } + + /* + * No problems. + */ + return (0); +} + +/* response - read and process SMTP server response */ + +static RESPONSE *response(VSTREAM *stream, VSTRING *buf) +{ + static RESPONSE rdata; + int more; + char *cp; + + /* + * Initialize the response data buffer. smtp_get() defends against a + * denial of service attack by limiting the amount of single-line text, + * and the loop below limits the amount of multi-line text that we are + * willing to store. + */ + if (rdata.buf == 0) + rdata.buf = vstring_alloc(100); + + /* + * Censor out non-printable characters in server responses. Concatenate + * multi-line server responses. Separate the status code from the text. + * Leave further parsing up to the application. + */ +#define BUF ((char *) vstring_str(buf)) + VSTRING_RESET(rdata.buf); + for (;;) { + smtp_get(buf, stream, var_line_limit, SMTP_GET_FLAG_SKIP); + for (cp = BUF; *cp != 0; cp++) + if (!ISPRINT(*cp) && !ISSPACE(*cp)) + *cp = '?'; + cp = BUF; + if (msg_verbose) + msg_info("<<< %s", cp); + while (ISDIGIT(*cp)) + cp++; + rdata.code = (cp - BUF == 3 ? atoi(BUF) : 0); + if ((more = (*cp == '-')) != 0) + cp++; + while (ISSPACE(*cp)) + cp++; + if (VSTRING_LEN(rdata.buf) < var_line_limit) + vstring_strcat(rdata.buf, cp); + if (more == 0) + break; + if (VSTRING_LEN(rdata.buf) < var_line_limit) + VSTRING_ADDCH(rdata.buf, '\n'); + } + VSTRING_TERMINATE(rdata.buf); + rdata.str = vstring_str(rdata.buf); + return (&rdata); +} + +/* exception_text - translate exceptions from the smtp_stream module */ + +static char *exception_text(int except) +{ + switch (except) { + case SMTP_ERR_EOF: + return ("lost connection"); + case SMTP_ERR_TIME: + return ("timeout"); + default: + msg_panic("exception_text: unknown exception %d", except); + } + /* NOTREACHED */ +} + +/* startup - connect to server but do not wait */ + +static void startup(SESSION *session) +{ + if (message_count-- <= 0) { + myfree((void *) session); + session_count--; + return; + } + if (session->stream == 0) { + enqueue_connect(session); + } else { + send_mail(session); + } +} + +/* start_event - invoke startup from timer context */ + +static void start_event(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + + startup(session); +} + +/* start_another - start another session */ + +static void start_another(SESSION *session) +{ + if (random_delay > 0) { + event_request_timer(start_event, (void *) session, + random_interval(random_delay)); + } else if (fixed_delay > 0) { + event_request_timer(start_event, (void *) session, fixed_delay); + } else { + startup(session); + } +} + +/* enqueue_connect - queue a connection request */ + +static void enqueue_connect(SESSION *session) +{ + session->next = 0; + if (last_session == 0) { + last_session = session; + start_connect(session); + } else { + last_session->next = session; + last_session = session; + } +} + +/* dequeue_connect - connection request completed */ + +static void dequeue_connect(SESSION *session) +{ + if (session == last_session) { + if (session->next != 0) + msg_panic("dequeue_connect: queue ends after last"); + last_session = 0; + } else { + if (session->next == 0) + msg_panic("dequeue_connect: queue ends before last"); + start_connect(session->next); + } +} + +/* fail_connect - handle failed startup */ + +static void fail_connect(SESSION *session) +{ + if (session->connect_count-- == 1) + msg_fatal("connect: %m"); + msg_warn("connect: %m"); + event_disable_readwrite(vstream_fileno(session->stream)); + vstream_fclose(session->stream); + session->stream = 0; +#ifdef MISSING_USLEEP + doze(10); +#else + usleep(10); +#endif + start_connect(session); +} + +/* start_connect - start TCP handshake */ + +static void start_connect(SESSION *session) +{ + int fd; + struct linger linger; + + /* + * Some systems don't set the socket error when connect() fails early + * (loopback) so we must deal with the error immediately, rather than + * retrieving it later with getsockopt(). We can't use MSG_PEEK to + * distinguish between server disconnect and connection refused. + */ + if ((fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) + msg_fatal("socket: %m"); + (void) non_blocking(fd, NON_BLOCKING); + linger.l_onoff = 1; + linger.l_linger = 0; + if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (void *) &linger, + sizeof(linger)) < 0) + msg_warn("setsockopt SO_LINGER %d: %m", linger.l_linger); + session->stream = vstream_fdopen(fd, O_RDWR); + event_enable_write(fd, connect_done, (void *) session); + smtp_timeout_setup(session->stream, var_timeout); + if (inet_windowsize > 0) + set_inet_windowsize(fd, inet_windowsize); + if (sane_connect(fd, sa, sa_length) < 0 && errno != EINPROGRESS) + fail_connect(session); +} + +/* connect_done - send message sender info */ + +static void connect_done(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + int fd = vstream_fileno(session->stream); + + /* + * Try again after some delay when the connection failed, in case they + * run a Mickey Mouse protocol stack. + */ + if (socket_error(fd) < 0) { + fail_connect(session); + } else { + non_blocking(fd, BLOCKING); + /* Disable write events. */ + event_disable_readwrite(fd); + event_enable_read(fd, read_banner, (void *) session); + dequeue_connect(session); + /* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */ + if (sa->sa_family == AF_INET +#ifdef AF_INET6 + || sa->sa_family == AF_INET6 +#endif + ) + vstream_tweak_tcp(session->stream); + } +} + +/* read_banner - receive SMTP server greeting */ + +static void read_banner(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + RESPONSE *resp; + int except; + + /* + * Prepare for disaster. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while reading server greeting", exception_text(except)); + + /* + * Read and parse the server's SMTP greeting banner. + */ + if (((resp = response(session->stream, buffer))->code / 100) == 2) { + /* void */ ; + } else if (allow_reject) { + msg_warn("rejected at server banner: %d %s", resp->code, resp->str); + } else { + msg_fatal("rejected at server banner: %d %s", resp->code, resp->str); + } + + /* + * Send helo or send the envelope sender address. + */ + if (send_helo_first) + send_helo(session); + else + send_mail(session); +} + +/* send_helo - send hostname */ + +static void send_helo(SESSION *session) +{ + int except; + const char *NOCLOBBER protocol = (talk_lmtp ? "LHLO" : "HELO"); + + /* + * Send the standard greeting with our hostname + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending %s", exception_text(except), protocol); + + command(session->stream, "%s %s", protocol, var_myhostname); + + /* + * Prepare for the next event. + */ + event_enable_read(vstream_fileno(session->stream), helo_done, (void *) session); +} + +/* helo_done - handle HELO response */ + +static void helo_done(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + RESPONSE *resp; + int except; + const char *protocol = (talk_lmtp ? "LHLO" : "HELO"); + + /* + * Get response to HELO command. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending %s", exception_text(except), protocol); + + if ((resp = response(session->stream, buffer))->code / 100 == 2) { + /* void */ ; + } else if (allow_reject) { + msg_warn("%s rejected: %d %s", protocol, resp->code, resp->str); + if (resp->code == 421 || resp->code == 521) { + close_session(session); + return; + } + } else { + msg_fatal("%s rejected: %d %s", protocol, resp->code, resp->str); + } + + send_mail(session); +} + +/* send_mail - send envelope sender */ + +static void send_mail(SESSION *session) +{ + int except; + + /* + * Send the envelope sender address. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending sender", exception_text(except)); + + command(session->stream, "MAIL FROM:<%s>", sender); + + /* + * Prepare for the next event. + */ + event_enable_read(vstream_fileno(session->stream), mail_done, (void *) session); +} + +/* mail_done - handle MAIL response */ + +static void mail_done(int unused, void *context) +{ + SESSION *session = (SESSION *) context; + RESPONSE *resp; + int except; + + /* + * Get response to MAIL command. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending sender", exception_text(except)); + + if ((resp = response(session->stream, buffer))->code / 100 == 2) { + session->rcpt_count = recipients; + session->rcpt_done = 0; + session->rcpt_accepted = 0; + send_rcpt(unused, context); + } else if (allow_reject) { + msg_warn("sender rejected: %d %s", resp->code, resp->str); + if (resp->code == 421 || resp->code == 521) { + close_session(session); + return; + } + send_rset(unused, context); + } else { + msg_fatal("sender rejected: %d %s", resp->code, resp->str); + } +} + +/* send_rcpt - send recipient address */ + +static void send_rcpt(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + int except; + + /* + * Send envelope recipient address. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending recipient", exception_text(except)); + + if (session->rcpt_count > 1 || number_rcpts > 0) + command(session->stream, "RCPT TO:<%d%s>", + number_rcpts ? number_rcpts++ : session->rcpt_count, + recipient); + else + command(session->stream, "RCPT TO:<%s>", recipient); + session->rcpt_count--; + session->rcpt_done++; + + /* + * Prepare for the next event. + */ + event_enable_read(vstream_fileno(session->stream), rcpt_done, (void *) session); +} + +/* rcpt_done - handle RCPT completion */ + +static void rcpt_done(int unused, void *context) +{ + SESSION *session = (SESSION *) context; + RESPONSE *resp; + int except; + + /* + * Get response to RCPT command. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending recipient", exception_text(except)); + + if ((resp = response(session->stream, buffer))->code / 100 == 2) { + session->rcpt_accepted++; + } else if (allow_reject) { + msg_warn("recipient rejected: %d %s", resp->code, resp->str); + if (resp->code == 421 || resp->code == 521) { + close_session(session); + return; + } + } else { + msg_fatal("recipient rejected: %d %s", resp->code, resp->str); + } + + /* + * Send another RCPT command or send DATA. + */ + if (session->rcpt_count > 0) + send_rcpt(unused, context); + else if (session->rcpt_accepted > 0) + send_data(unused, context); + else + send_rset(unused, context); +} + +/* send_data - send DATA command */ + +static void send_data(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + int except; + + /* + * Request data transmission. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending DATA command", exception_text(except)); + command(session->stream, "DATA"); + + /* + * Prepare for the next event. + */ + event_enable_read(vstream_fileno(session->stream), data_done, (void *) session); +} + +/* data_done - send message content */ + +static void data_done(int unused, void *context) +{ + SESSION *session = (SESSION *) context; + RESPONSE *resp; + int except; + static const char *mydate; + static int mypid; + + /* + * Get response to DATA command. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending DATA command", exception_text(except)); + if ((resp = response(session->stream, buffer))->code == 354) { + /* see below */ ; + } else if (allow_reject) { + msg_warn("data rejected: %d %s", resp->code, resp->str); + if (resp->code == 421 || resp->code == 521) { + close_session(session); + return; + } + send_rset(unused, context); + return; + } else { + msg_fatal("data rejected: %d %s", resp->code, resp->str); + } + + /* + * Send basic header to keep mailers that bother to examine them happy. + */ + if (send_headers) { + if (mydate == 0) { + mydate = mail_date(time((time_t *) 0)); + mypid = getpid(); + } + smtp_printf(session->stream, "From: <%s>", sender); + smtp_printf(session->stream, "To: <%s>", recipient); + smtp_printf(session->stream, "Date: %s", mydate); + smtp_printf(session->stream, "Message-Id: <%04x.%04x.%04x@%s>", + mypid, vstream_fileno(session->stream), message_count, var_myhostname); + if (subject) + smtp_printf(session->stream, "Subject: %s", subject); + smtp_fputs("", 0, session->stream); + } + + /* + * Send some garbage. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending message", exception_text(except)); + if (message_length == 0) { + smtp_fputs("La de da de da 1.", 17, session->stream); + smtp_fputs("La de da de da 2.", 17, session->stream); + smtp_fputs("La de da de da 3.", 17, session->stream); + smtp_fputs("La de da de da 4.", 17, session->stream); + } else { + + /* + * XXX This may cause the process to block with message content + * larger than VSTREAM_BUFIZ bytes. + */ + smtp_fputs(message_data, message_length, session->stream); + } + + /* + * Send end of message and process the server response. + */ + command(session->stream, "."); + + /* + * Update the running counter. + */ + if (count) { + counter++; + vstream_printf("%d\r", counter); + vstream_fflush(VSTREAM_OUT); + } + + /* + * Prepare for the next event. + */ + event_enable_read(vstream_fileno(session->stream), dot_done, (void *) session); +} + +/* dot_done - send QUIT or start another transaction */ + +static void dot_done(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + RESPONSE *resp; + int except; + + /* + * Get response to "." command. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending message", exception_text(except)); + do { /* XXX this could block */ + if ((resp = response(session->stream, buffer))->code / 100 == 2) { + /* void */ ; + } else if (allow_reject) { + msg_warn("end of data rejected: %d %s", resp->code, resp->str); + if (resp->code == 421 || resp->code == 521) { + close_session(session); + return; + } + } else { + msg_fatal("end of data rejected: %d %s", resp->code, resp->str); + } + } while (talk_lmtp && --session->rcpt_done > 0); + session->xfer_count++; + + /* + * Say goodbye or send the next message. + */ + if (disconnect || message_count < 1) { + send_quit(session); + } else { + event_disable_readwrite(vstream_fileno(session->stream)); + start_another(session); + } +} + +/* send_rset - send RSET command */ + +static void send_rset(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + + command(session->stream, "RSET"); + event_enable_read(vstream_fileno(session->stream), rset_done, (void *) session); +} + +/* rset_done - handle RSET reply */ + +static void rset_done(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + RESPONSE *resp; + int except; + + /* + * Get response to RSET command. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending message", exception_text(except)); + if ((resp = response(session->stream, buffer))->code / 100 == 2) { + /* void */ + } else if (allow_reject) { + msg_warn("rset rejected: %d %s", resp->code, resp->str); + if (resp->code == 421 || resp->code == 521) { + close_session(session); + return; + } + } else { + msg_fatal("rset rejected: %d %s", resp->code, resp->str); + } + + /* + * Say goodbye or send the next message. + */ + if (disconnect || message_count < 1) { + send_quit(session); + } else { + event_disable_readwrite(vstream_fileno(session->stream)); + start_another(session); + } +} + +/* send_quit - send QUIT command */ + +static void send_quit(SESSION *session) +{ + command(session->stream, "QUIT"); + event_enable_read(vstream_fileno(session->stream), quit_done, (void *) session); +} + +/* quit_done - disconnect */ + +static void quit_done(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + + (void) response(session->stream, buffer); + event_disable_readwrite(vstream_fileno(session->stream)); + vstream_fclose(session->stream); + session->stream = 0; + start_another(session); +} + +/* close_session - disconnect, for example after 421 or 521 reply */ + +static void close_session(SESSION *session) +{ + event_disable_readwrite(vstream_fileno(session->stream)); + vstream_fclose(session->stream); + session->stream = 0; + start_another(session); +} + +/* usage - explain */ + +static void usage(char *myname) +{ + msg_fatal("usage: %s -cdLNov -s sess -l msglen -m msgs -C count -M myhostname -f from -t to -r rcptcount -R delay -w delay host[:port]", myname); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - parse JCL and start the machine */ + +int main(int argc, char **argv) +{ + SESSION *session; + char *host; + char *port; + char *path; + int path_len; + int sessions = 1; + int ch; + int i; + char *buf; + const char *parse_err; + struct addrinfo *res; + int aierr; + const char *protocols = INET_PROTO_NAME_ALL; + char *message_file = 0; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + signal(SIGPIPE, SIG_IGN); + msg_vstream_init(argv[0], VSTREAM_ERR); + + /* + * Parse JCL. + */ + while ((ch = GETOPT(argc, argv, "46AcC:df:F:l:Lm:M:Nor:R:s:S:t:T:vw:")) > 0) { + switch (ch) { + case '4': + protocols = INET_PROTO_NAME_IPV4; + break; + case '6': + protocols = INET_PROTO_NAME_IPV6; + break; + case 'A': + allow_reject = 1; + break; + case 'c': + count++; + break; + case 'C': + if ((connect_count = atoi(optarg)) <= 0) + msg_fatal("bad connection count: %s", optarg); + break; + case 'd': + disconnect = 0; + break; + case 'f': + sender = optarg; + break; + case 'F': + if (message_file == 0 && message_length > 0) + msg_fatal("-l option cannot be used with -F"); + message_file = optarg; + break; + case 'l': + if (message_file != 0) + msg_fatal("-l option cannot be used with -F"); + if ((message_length = atoi(optarg)) <= 0) + msg_fatal("bad message length: %s", optarg); + break; + case 'L': + talk_lmtp = 1; + break; + case 'm': + if ((message_count = atoi(optarg)) <= 0) + msg_fatal("bad message count: %s", optarg); + break; + case 'M': + if (*optarg == '[') { + if (!valid_mailhost_literal(optarg, DO_GRIPE)) + msg_fatal("bad address literal: %s", optarg); + } else { + if (!valid_hostname(optarg, DO_GRIPE)) + msg_fatal("bad hostname: %s", optarg); + } + var_myhostname = optarg; + break; + case 'N': + number_rcpts = 1; + break; + case 'o': + send_helo_first = 0; + send_headers = 0; + break; + case 'r': + if ((recipients = atoi(optarg)) <= 0) + msg_fatal("bad recipient count: %s", optarg); + break; + case 'R': + if (fixed_delay > 0) + msg_fatal("do not use -w and -R options at the same time"); + if ((random_delay = atoi(optarg)) <= 0) + msg_fatal("bad random delay: %s", optarg); + break; + case 's': + if ((sessions = atoi(optarg)) <= 0) + msg_fatal("bad session count: %s", optarg); + break; + case 'S': + subject = optarg; + break; + case 't': + recipient = optarg; + break; + case 'T': + if ((inet_windowsize = atoi(optarg)) <= 0) + msg_fatal("bad TCP window size: %s", optarg); + break; + case 'v': + msg_verbose++; + break; + case 'w': + if (random_delay > 0) + msg_fatal("do not use -w and -R options at the same time"); + if ((fixed_delay = atoi(optarg)) <= 0) + msg_fatal("bad fixed delay: %s", optarg); + break; + default: + usage(argv[0]); + } + } + if (argc - optind != 1) + usage(argv[0]); + + if (random_delay > 0) + srand(getpid()); + + /* + * Initialize the message content, SMTP encoded. smtp_fputs() will append + * another \r\n but we don't care. + */ + if (message_file != 0) { + VSTREAM *fp; + VSTRING *buf = vstring_alloc(100); + VSTRING *msg = vstring_alloc(100); + + if ((fp = vstream_fopen(message_file, O_RDONLY, 0)) == 0) + msg_fatal("open %s: %m", message_file); + while (vstring_get_nonl(buf, fp) != VSTREAM_EOF) { + if (*vstring_str(buf) == '.') + VSTRING_ADDCH(msg, '.'); + vstring_memcat(msg, vstring_str(buf), VSTRING_LEN(buf)); + vstring_memcat(msg, "\r\n", 2); + } + if (vstream_ferror(fp)) + msg_fatal("read %s: %m", message_file); + vstream_fclose(fp); + vstring_free(buf); + message_length = VSTRING_LEN(msg); + message_data = vstring_export(msg); + send_headers = 0; + } else if (message_length > 0) { + message_data = mymalloc(message_length); + memset(message_data, 'X', message_length); + for (i = 80; i < message_length; i += 80) { + message_data[i - 80] = "0123456789"[(i / 80) % 10]; + message_data[i - 2] = '\r'; + message_data[i - 1] = '\n'; + } + } + + /* + * Translate endpoint address to internal form. + */ + (void) inet_proto_init("protocols", protocols); + if (strncmp(argv[optind], "unix:", 5) == 0) { + path = argv[optind] + 5; + path_len = strlen(path); + if (path_len >= (int) sizeof(sun.sun_path)) + msg_fatal("unix-domain name too long: %s", path); + memset((void *) &sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; +#ifdef HAS_SUN_LEN + sun.sun_len = path_len + 1; +#endif + memcpy(sun.sun_path, path, path_len); + sa = (struct sockaddr *) &sun; + sa_length = sizeof(sun); + } else { + if (strncmp(argv[optind], "inet:", 5) == 0) + argv[optind] += 5; + buf = mystrdup(argv[optind]); + if ((parse_err = host_port(buf, &host, (char *) 0, &port, "smtp")) != 0) + msg_fatal("%s: %s", argv[optind], parse_err); + if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0) + msg_fatal("%s: %s", argv[optind], MAI_STRERROR(aierr)); + myfree(buf); + sa = (struct sockaddr *) &ss; + if (res->ai_addrlen > sizeof(ss)) + msg_fatal("address length %d > buffer length %d", + (int) res->ai_addrlen, (int) sizeof(ss)); + memcpy((void *) sa, res->ai_addr, res->ai_addrlen); + sa_length = res->ai_addrlen; +#ifdef HAS_SA_LEN + sa->sa_len = sa_length; +#endif + freeaddrinfo(res); + } + + /* + * smtp_get() makes sure the SMTP server cannot run us out of memory by + * sending never-ending lines of text. + */ + if (buffer == 0) + buffer = vstring_alloc(100); + + /* + * Make sure we have sender and recipient addresses. + */ + if (var_myhostname == 0) + var_myhostname = get_hostname(); + if (sender == 0 || recipient == 0) { + vstring_sprintf(buffer, "foo@%s", var_myhostname); + defaddr = mystrdup(vstring_str(buffer)); + if (sender == 0) + sender = defaddr; + if (recipient == 0) + recipient = defaddr; + } + + /* + * Start sessions. + */ + while (sessions-- > 0) { + session = (SESSION *) mymalloc(sizeof(*session)); + session->stream = 0; + session->xfer_count = 0; + session->connect_count = connect_count; + session->next = 0; + session_count++; + startup(session); + } + for (;;) { + event_loop(-1); + if (session_count <= 0 && message_count <= 0) { + if (count) { + VSTREAM_PUTC('\n', VSTREAM_OUT); + vstream_fflush(VSTREAM_OUT); + } + exit(0); + } + } +} diff --git a/src/smtpstone/throughput b/src/smtpstone/throughput new file mode 100644 index 0000000..4853d75 --- /dev/null +++ b/src/smtpstone/throughput @@ -0,0 +1,28 @@ +Host: P233 BSD/OS 3.1 smtp-source and smtp-sink on the same host, +100 msgs in 10 sessions. + +send = time to send 100 msgs into postfix +rest = time for Postfix to finish +total = total elapsed time + +19990627 + +send rest total +14 10 25 +10 8 18 + 9 10 19 + 9 17 26 + 8 11 19 + 8 9 17 + +19990906 + +send rest total +10 15 25 + 9 10 19 + 8 9 17 + 9 8 17 + 8 9 17 + 9 8 17 + 8 9 17 + 8 8 16 diff --git a/src/smtpstone/vmail-local b/src/smtpstone/vmail-local new file mode 100644 index 0000000..84269b2 --- /dev/null +++ b/src/smtpstone/vmail-local @@ -0,0 +1,43 @@ +fist% date; /usr/bin/time ./smtp-source -s 5 -m 100 -t wietse@nipple nipple +Sat May 9 22:08:27 EDT 1998 + 6.29 real 0.08 user 0.17 sys +May 9 22:08:27 nipple wietse[100]: postfix/smtpd[2248]: connect from fist.porcu +May 9 22:08:58 nipple postfix/local[2330]: 5082D53AD0: to=