summaryrefslogtreecommitdiffstats
path: root/src/smtpstone
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 01:46:30 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 01:46:30 +0000
commitb5896ba9f6047e7031e2bdee0622d543e11a6734 (patch)
treefd7b460593a2fee1be579bec5697e6d887ea3421 /src/smtpstone
parentInitial commit. (diff)
downloadpostfix-2f07919848e7bd4a084699d26e8821e3a00696d9.tar.xz
postfix-2f07919848e7bd4a084699d26e8821e3a00696d9.zip
Adding upstream version 3.4.23.upstream/3.4.23upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
l---------src/smtpstone/.indent.pro1
-rw-r--r--src/smtpstone/.printfck25
-rw-r--r--src/smtpstone/Makefile.in172
-rw-r--r--src/smtpstone/hashed-deferred37
-rw-r--r--src/smtpstone/hashed-incoming48
-rw-r--r--src/smtpstone/mx-deliver20
-rw-r--r--src/smtpstone/mx-explode33
-rw-r--r--src/smtpstone/mx-relay20
-rw-r--r--src/smtpstone/performance28
-rw-r--r--src/smtpstone/qmail-deliver20
-rw-r--r--src/smtpstone/qmail-explode20
-rw-r--r--src/smtpstone/qmail-relay20
-rw-r--r--src/smtpstone/qmqp-sink.c325
-rw-r--r--src/smtpstone/qmqp-source.c673
-rw-r--r--src/smtpstone/smtp-sink.c1643
-rw-r--r--src/smtpstone/smtp-source.c1188
-rw-r--r--src/smtpstone/throughput28
-rw-r--r--src/smtpstone/vmail-local43
-rw-r--r--src/smtpstone/vmail-relay57
19 files changed, 4401 insertions, 0 deletions
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=<wietse@porcupine.org>, 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=<wietse@porcupine.org>, 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=<wietse@porcupine.org>, 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=<foo@spike.porcupine.org>, 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=<foo@spike.porcupine.org>, 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=<foo@spike.porcupine.org>, 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 <sys_defs.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <signal.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <listen.h>
+#include <events.h>
+#include <mymalloc.h>
+#include <iostuff.h>
+#include <msg_vstream.h>
+#include <netstring.h>
+#include <inet_proto.h>
+
+/* Global library. */
+
+#include <qmqp_proto.h>
+#include <mail_version.h>
+
+/* 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: <foo@myhostname>).
+/* .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: <foo@myhostname>).
+/* .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 <sys_defs.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <netinet/in.h>
+#include <sys/un.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <msg_vstream.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <get_hostname.h>
+#include <split_at.h>
+#include <connect.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <iostuff.h>
+#include <netstring.h>
+#include <sane_connect.h>
+#include <host_port.h>
+#include <myaddrinfo.h>
+#include <inet_proto.h>
+#include <valid_hostname.h>
+#include <valid_mailhost_addr.h>
+
+/* Global library. */
+
+#include <mail_date.h>
+#include <qmqp_proto.h>
+#include <mail_version.h>
+
+/* 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..4378d5a
--- /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 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 <sys_defs.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <signal.h>
+#include <time.h>
+#include <ctype.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <get_hostname.h>
+#include <listen.h>
+#include <events.h>
+#include <mymalloc.h>
+#include <iostuff.h>
+#include <msg_vstream.h>
+#include <stringops.h>
+#include <sane_accept.h>
+#include <inet_proto.h>
+#include <myaddrinfo.h>
+#include <make_dirs.h>
+#include <myrand.h>
+#include <chroot_uid.h>
+
+/* Global library. */
+
+#include <smtp_stream.h>
+#include <mail_date.h>
+#include <mail_version.h>
+
+/* 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 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 replycode<SPACE>optional-text<CRLF> */
+ 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 <CR><LF>.<CR><LF>");
+ 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"));
+ 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: <foo@myhostname>).
+/* .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: <foo@myhostname>).
+/* .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 <sys_defs.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <netinet/in.h>
+#include <sys/un.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <msg_vstream.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <get_hostname.h>
+#include <split_at.h>
+#include <connect.h>
+#include <mymalloc.h>
+#include <events.h>
+#include <iostuff.h>
+#include <sane_connect.h>
+#include <host_port.h>
+#include <myaddrinfo.h>
+#include <inet_proto.h>
+#include <valid_hostname.h>
+#include <valid_mailhost_addr.h>
+#include <compat_va_copy.h>
+
+/* Global library. */
+
+#include <smtp_stream.h>
+#include <mail_date.h>
+#include <mail_version.h>
+
+/* 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=<wietse@porcupine.org
+Elapsed 31
+
+fist% date; /usr/bin/time ./smtp-source -s 5 -m 100 -t wietse@nipple nipple
+Sat May 9 22:09:13 EDT 1998
+ 5.94 real 0.09 user 0.16 sys
+May 9 22:09:13 nipple wietse[100]: postfix/smtpd[2248]: connect from fist.porcu
+May 9 22:09:40 nipple postfix/local[2260]: 5082FBE00B: to=<wietse@porcupine.org
+Elapsed 27
+
+fist% date; /usr/bin/time ./smtp-source -s 5 -m 100 -t wietse@nipple nipple
+Sat May 9 22:09:43 EDT 1998
+ 5.97 real 0.06 user 0.17 sys
+May 9 22:09:43 nipple postfix/local[2321]: 5082C7E2F3: to=<wietse@porcupine.org
+May 9 22:10:13 nipple postfix/local[2517]: 5082C8772C: to=<wietse@porcupine.org
+Elapsed 30
+
+fist% date; /usr/bin/time ./smtp-source -s 20 -m 100 -t wietse@nipple nipple
+Sat May 9 22:10:30 EDT 1998
+ 6.26 real 0.08 user 0.20 sys
+May 9 22:10:30 nipple wietse[100]: postfix/smtpd[2248]: connect from fist.porcu
+May 9 22:10:58 nipple postfix/local[2330]: 5082C7A2C9: to=<wietse@porcupine.org
+Elapsed 28
+
+fist% date; /usr/bin/time ./smtp-source -s 20 -m 100 -t wietse@nipple nipple
+Sat May 9 22:11:06 EDT 1998
+ 6.04 real 0.05 user 0.25 sys
+May 9 22:11:06 nipple wietse[100]: postfix/smtpd[2675]: connect from fist.porcu
+May 9 22:11:33 nipple postfix/local[2685]: 5082D23842: to=<wietse@porcupine.org
+Elapsed 27
+
+fist% date; /usr/bin/time ./smtp-source -s 20 -m 100 -t wietse@nipple nipple
+Sat May 9 22:11:38 EDT 1998
+ 6.11 real 0.06 user 0.24 sys
+May 9 22:11:38 nipple postfix/local[2686]: 5082E318AD: to=<wietse@porcupine.org
+May 9 22:12:07 nipple postfix/local[2687]: 5082CBF5EC: to=<wietse@porcupine.org
+Elapsed 31
+
+Elapsed: 29, throughput: 3.4 msg/s (local_delivery_concurrency not set)
diff --git a/src/smtpstone/vmail-relay b/src/smtpstone/vmail-relay
new file mode 100644
index 0000000..e9055b5
--- /dev/null
+++ b/src/smtpstone/vmail-relay
@@ -0,0 +1,57 @@
+fist% date; /usr/bin/time ./smtp-source -s 5 -m 100 nipple
+Sat May 9 21:54:48 EDT 1998
+ 7.09 real 0.03 user 0.18 sys
+May 9 21:54:48 nipple wietse[100]: postfix/smtpd[2206]: connect from fist.porcu
+May 9 21:55:05 nipple wietse[100]: postfix/smtp[2215]: 508672B24D: to=<foo@fist
+Elapsed 17
+
+fist% date; /usr/bin/time ./smtp-source -s 5 -m 100 nipple
+Sat May 9 21:55:09 EDT 1998
+ 6.02 real 0.09 user 0.13 sys
+May 9 21:55:09 nipple wietse[100]: postfix/smtpd[2206]: connect from fist.porcu
+May 9 21:55:23 nipple wietse[100]: postfix/smtp[2217]: 50864BCA59: to=<foo@fist
+Elapsed 14
+
+fist% date; /usr/bin/time ./smtp-source -s 5 -m 100 nipple
+Sat May 9 21:55:26 EDT 1998
+ 6.06 real 0.07 user 0.17 sys
+May 9 21:55:27 nipple wietse[100]: postfix/smtpd[2205]: connect from fist.porcu
+May 9 21:55:41 nipple wietse[100]: postfix/smtp[2218]: 508753CB5D: to=<foo@fist
+Elapsed 14
+
+fist% date; /usr/bin/time ./smtp-source -s 10 -m 100 nipple
+Sat May 9 21:55:53 EDT 1998
+ 6.01 real 0.13 user 0.14 sys
+May 9 21:55:53 nipple wietse[100]: postfix/smtpd[2206]: connect from fist.porcu
+May 9 21:56:08 nipple wietse[100]: postfix/smtp[2216]: 50868D8D44: to=<foo@fist
+Elapsed 15
+
+fist% date; /usr/bin/time ./smtp-source -s 10 -m 100 nipple
+Sat May 9 21:56:10 EDT 1998
+ 6.01 real 0.14 user 0.13 sys
+May 9 21:56:10 nipple wietse[100]: postfix/smtpd[2206]: connect from fist.porcu
+May 9 21:56:24 nipple wietse[100]: postfix/smtp[2229]: 50875825BA: to=<foo@fist
+Elapsed 14
+
+fist% date; /usr/bin/time ./smtp-source -s 10 -m 100 nipple
+Sat May 9 21:56:28 EDT 1998
+ 6.10 real 0.03 user 0.23 sys
+May 9 21:56:28 nipple wietse[100]: postfix/smtpd[2205]: connect from fist.porcu
+May 9 21:56:43 nipple wietse[100]: postfix/smtp[2229]: 508651D724: to=<foo@fist
+Elapsed 15
+
+fist% date; /usr/bin/time ./smtp-source -s 20 -m 100 nipple
+Sat May 9 21:56:58 EDT 1998
+ 6.02 real 0.10 user 0.21 sys
+May 9 21:56:58 nipple wietse[100]: postfix/smtpd[2222]: connect from fist.porcu
+May 9 21:57:13 nipple wietse[100]: postfix/smtp[2231]: 5085D279A7: to=<foo@fist
+Elapsed 15
+
+fist% date; /usr/bin/time ./smtp-source -s 20 -m 100 nipple
+Sat May 9 21:57:18 EDT 1998
+ 6.04 real 0.09 user 0.19 sys
+May 9 21:57:18 nipple wietse[100]: postfix/smtpd[2222]: connect from fist.porcu
+May 9 21:57:32 nipple wietse[100]: postfix/smtp[2230]: 508774A28A: to=<foo@fist
+Elapsed 14
+
+Elapsed: 14.4 s, throughput: 6.9 msg/s