1/*	$NetBSD: smtp-sink.c,v 1.3 2022/10/08 16:12:49 christos Exp $	*/
2
3/*++
4/* NAME
5/*	smtp-sink 1
6/* SUMMARY
7/*	parallelized SMTP/LMTP test server
8/* SYNOPSIS
9/* .fi
10/*	\fBsmtp-sink\fR [\fIoptions\fR] [\fBinet:\fR][\fIhost\fR]:\fIport\fR
11/*	\fIbacklog\fR
12/*
13/*	\fBsmtp-sink\fR [\fIoptions\fR] \fBunix:\fR\fIpathname\fR \fIbacklog\fR
14/* DESCRIPTION
15/*	\fBsmtp-sink\fR listens on the named host (or address) and port.
16/*	It takes SMTP messages from the network and throws them away.
17/*	The purpose is to measure client performance, not protocol
18/*	compliance.
19/*
20/*	\fBsmtp-sink\fR may also be configured to capture each mail
21/*	delivery transaction to file. Since disk latencies are large
22/*	compared to network delays, this mode of operation can
23/*	reduce the maximal performance by several orders of magnitude.
24/*
25/*	Connections can be accepted on IPv4 or IPv6 endpoints, or on
26/*	UNIX-domain sockets.
27/*	IPv4 and IPv6 are the default.
28/*	This program is the complement of the \fBsmtp-source\fR(1) program.
29/*
30/*	Note: this is an unsupported test program. No attempt is made
31/*	to maintain compatibility between successive versions.
32/*
33/*	Arguments:
34/* .IP \fB-4\fR
35/*	Support IPv4 only. This option has no effect when
36/*	Postfix is built without IPv6 support.
37/* .IP \fB-6\fR
38/*	Support IPv6 only. This option is not available when
39/*	Postfix is built without IPv6 support.
40/* .IP \fB-8\fR
41/*	Do not announce 8BITMIME support.
42/* .IP \fB-a\fR
43/*	Do not announce SASL authentication support.
44/* .IP "\fB-A \fIdelay\fR"
45/*	Wait \fIdelay\fR seconds after responding to DATA, then
46/*	abort prematurely with a 550 reply status.  Do not read
47/*	further input from the client; this is an attempt to block
48/*	the client before it sends ".".  Specify a zero delay value
49/*	to abort immediately.
50/* .IP "\fB-b \fIsoft-bounce-reply\fR"
51/*	Use \fIsoft-bounce-reply\fR for soft reject responses.  The
52/*	default reply is "450 4.3.0 Error: command failed".
53/* .IP "\fB-B \fIhard-bounce-reply\fR"
54/*	Use \fIhard-bounce-reply\fR for hard reject responses.  The
55/*	default reply is "500 5.3.0 Error: command failed".
56/* .IP \fB-c\fR
57/*	Display running counters that are updated whenever an SMTP
58/*	session ends, a QUIT command is executed, or when "." is
59/*	received.
60/* .IP \fB-C\fR
61/*	Disable XCLIENT support.
62/* .IP "\fB-d \fIdump-template\fR"
63/*	Dump each mail transaction to a single-message file whose
64/*	name is created by expanding the \fIdump-template\fR via
65/*	strftime(3) and appending a pseudo-random hexadecimal number
66/*	(example: "%Y%m%d%H/%M." expands into "2006081203/05.809a62e3").
67/*	If the template contains "/" characters, missing directories
68/*	are created automatically.  The message dump format is
69/*	described below.
70/* .sp
71/*	Note: this option keeps one capture file open for every
72/*	mail transaction in progress.
73/* .IP "\fB-D \fIdump-template\fR"
74/*	Append mail transactions to a multi-message dump file whose
75/*	name is created by expanding the \fIdump-template\fR via
76/*	strftime(3).
77/*	If the template contains "/" characters, missing directories
78/*	are created automatically.  The message dump format is
79/*	described below.
80/* .sp
81/*	Note: this option keeps one capture file open for every
82/*	mail transaction in progress.
83/* .IP \fB-e\fR
84/*	Do not announce ESMTP support.
85/* .IP \fB-E\fR
86/*	Do not announce ENHANCEDSTATUSCODES support.
87/* .IP "\fB-f \fIcommand,command,...\fR"
88/*	Reject the specified commands with a hard (5xx) error code.
89/*	This option implies \fB-p\fR.
90/* .sp
91/*	Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY,
92/*	DATA, ., RSET, NOOP, and QUIT. Separate command names by
93/*	white space or commas, and use quotes to protect white space
94/*	from the shell. Command names are case-insensitive.
95/* .IP \fB-F\fR
96/*	Disable XFORWARD support.
97/* .IP "\fB-h\fI hostname\fR"
98/*	Use \fIhostname\fR in the SMTP greeting, in the HELO response,
99/*	and in the EHLO response. The default hostname is "smtp-sink".
100/* .IP "\fB-H\fI delay\fR"
101/*	Delay the first read operation after receiving DATA (time
102/*	in seconds). Combine with a large test message and a small
103/*	TCP window size (see the \fB-T\fR option) to test the Postfix
104/*	client write_wait() implementation.
105/* .IP \fB-L\fR
106/*	Enable LMTP instead of SMTP.
107/* .IP "\fB-m \fIcount\fR (default: 256)"
108/*	An upper bound on the maximal number of simultaneous
109/*	connections that \fBsmtp-sink\fR will handle. This prevents
110/*	the process from running out of file descriptors. Excess
111/*	connections will stay queued in the TCP/IP stack.
112/* .IP "\fB-M \fIcount\fR"
113/*	Terminate after receiving \fIcount\fR messages.
114/* .IP "\fB-n \fIcount\fR"
115/*	Terminate after \fIcount\fR sessions.
116/* .IP \fB-N\fR
117/*	Do not announce support for DSN.
118/* .IP \fB-p\fR
119/*	Do not announce support for ESMTP command pipelining.
120/* .IP \fB-P\fR
121/*	Change the server greeting so that it appears to come through
122/*	a CISCO PIX system. Implies \fB-e\fR.
123/* .IP "\fB-q \fIcommand,command,...\fR"
124/*	Disconnect (without replying) after receiving one of the
125/*	specified commands.
126/* .sp
127/*	Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY,
128/*	DATA, ., RSET, NOOP, and QUIT. Separate command names by
129/*	white space or commas, and use quotes to protect white space
130/*	from the shell. Command names are case-insensitive.
131/* .IP "\fB-Q \fIcommand,command,...\fR"
132/*	Send a 421 reply and disconnect after receiving one
133/*	of the specified commands.
134/* .sp
135/*	Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY,
136/*	DATA, ., RSET, NOOP, and QUIT. Separate command names by
137/*	white space or commas, and use quotes to protect white space
138/*	from the shell. Command names are case-insensitive.
139/* .IP "\fB-r \fIcommand,command,...\fR"
140/*	Reject the specified commands with a soft (4xx) error code.
141/*	This option implies \fB-p\fR.
142/* .sp
143/*	Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY,
144/*	DATA, ., RSET, NOOP, and QUIT. Separate command names by
145/*	white space or commas, and use quotes to protect white space
146/*	from the shell. Command names are case-insensitive.
147/* .IP "\fB-R \fIroot-directory\fR"
148/*	Change the process root directory to the specified location.
149/*	This option requires super-user privileges. See also the
150/*	\fB-u\fR option.
151/* .IP "\fB-s \fIcommand,command,...\fR"
152/*	Log the named commands to syslogd.
153/* .sp
154/*	Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY,
155/*	DATA, ., RSET, NOOP, and QUIT. Separate command names by
156/*	white space or commas, and use quotes to protect white space
157/*	from the shell. Command names are case-insensitive.
158/* .IP "\fB-S start-string\fR"
159/*	An optional string that is prepended to each message that is
160/*	written to a dump file (see the dump file format description
161/*	below). The following C escape sequences are supported: \ea
162/*	(bell), \eb (backspace), \ef (formfeed), \en (newline), \er
163/*	(carriage return), \et (horizontal tab), \ev (vertical tab),
164/*	\e\fIddd\fR (up to three octal digits) and \e\e (the backslash
165/*	character).
166/* .IP "\fB-t \fItimeout\fR (default: 100)"
167/*	Limit the time for receiving a command or sending a response.
168/*	The time limit is specified in seconds.
169/* .IP "\fB-T \fIwindowsize\fR"
170/*	Override the default TCP window size. To work around
171/*	broken TCP window scaling implementations, specify a
172/*	value > 0 and < 65536.
173/* .IP "\fB-u \fIusername\fR"
174/*	Switch to the specified user privileges after opening the
175/*	network socket and optionally changing the process root
176/*	directory. This option is required when the process runs
177/*	with super-user privileges. See also the \fB-R\fR option.
178/* .IP \fB-v\fR
179/*	Show the SMTP conversations.
180/* .IP "\fB-w \fIdelay\fR"
181/*	Wait \fIdelay\fR seconds before responding to a DATA command.
182/* .IP "\fB-W \fIcommand:delay[:odds]\fR"
183/*	Wait \fIdelay\fR seconds before responding to \fIcommand\fR.
184/*	If \fIodds\fR is also specified (a number between 1-99
185/*	inclusive), wait for a random multiple of \fIdelay\fR. The
186/*	random multiplier is equal to the number of times the program
187/*	needs to roll a dice with a range of 0..99 inclusive, before
188/*	the dice produces a result greater than or equal to \fIodds\fR.
189/* .IP [\fBinet:\fR][\fIhost\fR]:\fIport\fR
190/*	Listen on network interface \fIhost\fR (default: any interface)
191/*	TCP port \fIport\fR. Both \fIhost\fR and \fIport\fR may be
192/*	specified in numeric or symbolic form.
193/* .IP \fBunix:\fR\fIpathname\fR
194/*	Listen on the UNIX-domain socket at \fIpathname\fR.
195/* .IP \fIbacklog\fR
196/*	The maximum length of the queue of pending connections,
197/*	as defined by the \fBlisten\fR(2) system call.
198/* DUMP FILE FORMAT
199/* .ad
200/* .fi
201/*	Each dumped message contains a sequence of text lines,
202/*	terminated with the newline character. The sequence of
203/*	information is as follows:
204/* .IP \(bu
205/*	The optional string specified with the \fB-S\fR option.
206/* .IP \(bu
207/*	The \fBsmtp-sink\fR generated headers as documented below.
208/* .IP \(bu
209/*	The message header and body as received from the SMTP client.
210/* .IP \(bu
211/*	An empty line.
212/* .PP
213/*	The format of the \fBsmtp-sink\fR generated headers is as
214/*	follows:
215/* .IP "\fBX-Client-Addr: \fItext\fR"
216/*	The client IP address without enclosing []. An IPv6 address
217/*	is prefixed with "ipv6:". This record is always present.
218/* .IP "\fBX-Client-Proto: \fItext\fR"
219/*	The client protocol: SMTP, ESMTP or LMTP. This record is
220/*	always present.
221/* .IP "\fBX-Helo-Args: \fItext\fR"
222/*	The arguments of the last HELO or EHLO command before this
223/*	mail delivery transaction. This record is present only if
224/*	the client sent a recognizable HELO or EHLO command before
225/*	the DATA command.
226/* .IP "\fBX-Mail-Args: \fItext\fR"
227/*	The arguments of the MAIL command that started this mail
228/*	delivery transaction. This record is present exactly once.
229/* .IP "\fBX-Rcpt-Args: \fItext\fR"
230/*	The arguments of an RCPT command within this mail delivery
231/*	transaction. There is one record for each RCPT command, and
232/*	they are in the order as sent by the client.
233/* .IP "\fBReceived: \fItext\fR"
234/*	A message header for compatibility with mail processing
235/*	software. This three-line header marks the end of the headers
236/*	provided by \fBsmtp-sink\fR, and is formatted as follows:
237/* .RS
238/* .IP "\fBfrom \fIhelo\fR ([\fIaddr\fR])"
239/*	The HELO or EHLO command argument and client IP address.
240/*	If the client did not send HELO or EHLO, the client IP
241/*	address is used instead.
242/* .IP "\fBby \fIhost\fB (smtp-sink) with \fIproto\fB id \fIrandom\fB;\fR"
243/*	The hostname specified with the \fB-h\fR option, the client
244/*	protocol (see \fBX-Client-Proto\fR above), and the pseudo-random
245/*	portion of the per-message capture file name.
246/* .IP \fItime-stamp\fR
247/*	A time stamp as defined in RFC 2822.
248/* .RE
249/* SEE ALSO
250/*	smtp-source(1), SMTP/LMTP message generator
251/* LICENSE
252/* .ad
253/* .fi
254/*	The Secure Mailer license must be distributed with this software.
255/* AUTHOR(S)
256/*	Wietse Venema
257/*	IBM T.J. Watson Research
258/*	P.O. Box 704
259/*	Yorktown Heights, NY 10598, USA
260/*
261/*	Wietse Venema
262/*	Google, Inc.
263/*	111 8th Avenue
264/*	New York, NY 10011, USA
265/*--*/
266
267/* System library. */
268
269#include <sys_defs.h>
270#include <sys/socket.h>
271#include <sys/wait.h>
272#include <sys/stat.h>
273#include <unistd.h>
274#include <string.h>
275#include <stdlib.h>
276#include <fcntl.h>
277#include <syslog.h>
278#include <signal.h>
279#include <time.h>
280#include <ctype.h>
281
282#ifdef STRCASECMP_IN_STRINGS_H
283#include <strings.h>
284#endif
285
286/* Utility library. */
287
288#include <msg.h>
289#include <vstring.h>
290#include <vstream.h>
291#include <vstring_vstream.h>
292#include <get_hostname.h>
293#include <listen.h>
294#include <events.h>
295#include <mymalloc.h>
296#include <iostuff.h>
297#include <msg_vstream.h>
298#include <stringops.h>
299#include <sane_accept.h>
300#include <inet_proto.h>
301#include <myaddrinfo.h>
302#include <make_dirs.h>
303#include <myrand.h>
304#include <chroot_uid.h>
305
306/* Global library. */
307
308#include <smtp_stream.h>
309#include <mail_date.h>
310#include <mail_version.h>
311
312/* Application-specific. */
313
314typedef struct SINK_STATE {
315    VSTREAM *stream;
316    VSTRING *buffer;
317    int     data_state;
318    int     (*read_fn) (struct SINK_STATE *);
319    int     in_mail;
320    int     rcpts;
321    char   *push_back_ptr;
322    /* Capture file information for fake Received: header */
323    MAI_HOSTADDR_STR client_addr;	/* IP address */
324    char   *addr_prefix;		/* ipv6: or empty */
325    char   *helo_args;			/* text after HELO or EHLO */
326    const char *client_proto;		/* SMTP, ESMTP, LMTP */
327    time_t  start_time;			/* MAIL command time */
328    int     id;				/* pseudo-random */
329    VSTREAM *dump_file;			/* dump file or null */
330    void    (*delayed_response) (struct SINK_STATE *state, const char *);
331    char   *delayed_args;
332} SINK_STATE;
333
334#define ST_ANY			0
335#define ST_CR			1
336#define ST_CR_LF		2
337#define ST_CR_LF_DOT		3
338#define ST_CR_LF_DOT_CR		4
339#define ST_CR_LF_DOT_CR_LF	5
340
341#define PUSH_BACK_PEEK(state)		(*(state)->push_back_ptr != 0)
342#define PUSH_BACK_GET(state)		(*(state)->push_back_ptr++)
343#define PUSH_BACK_SET(state, text)	((state)->push_back_ptr = (text))
344
345#ifndef DEF_MAX_CLIENT_COUNT
346#define DEF_MAX_CLIENT_COUNT	256
347#endif
348
349#define SOFT_ERROR_RESP		"450 4.3.0 Error: command failed"
350#define HARD_ERROR_RESP		"500 5.3.0 Error: command failed"
351
352 /*
353  * We can't rely on vstream auto-flushing, so we have to prepare for the
354  * next read request.
355  */
356#define SMTP_FLUSH(fp) do { \
357    if (vstream_peek(fp) <= 0 && readable(vstream_fileno(fp)) <= 0) \
358        smtp_flush(fp); \
359    } while (0)
360
361static int var_tmout = 100;
362static int var_max_line_length = 2048;
363static char *var_myhostname;
364static char *soft_error_resp = SOFT_ERROR_RESP;
365static char *hard_error_resp = HARD_ERROR_RESP;
366static int command_read(SINK_STATE *);
367static int data_read(SINK_STATE *);
368static void disconnect(SINK_STATE *);
369static void read_timeout(int, void *);
370static void read_event(int, void *);
371static int show_count;
372static int sess_count;
373static int quit_count;
374static int mesg_count;
375static int max_quit_count;
376static int max_msg_quit_count;
377static int disable_pipelining;
378static int disable_8bitmime;
379static int disable_esmtp;
380static int enable_lmtp;
381static int pretend_pix;
382static int disable_saslauth;
383static int disable_xclient;
384static int disable_xforward;
385static int disable_enh_status;
386static int disable_dsn;
387static int max_client_count = DEF_MAX_CLIENT_COUNT;
388static int client_count;
389static int sock;
390static int abort_delay = -1;
391static int data_read_delay = 0;
392
393static char *single_template;		/* individual template */
394static char *shared_template;		/* shared template */
395static VSTRING *start_string;		/* dump content prefix */
396
397static const INET_PROTO_INFO *proto_info;
398
399#define STR(x)	vstring_str(x)
400
401/* do_stats - show counters */
402
403static void do_stats(void)
404{
405    vstream_printf("sess=%d quit=%d mesg=%d\r",
406		   sess_count, quit_count, mesg_count);
407    vstream_fflush(VSTREAM_OUT);
408}
409
410/* hard_err_resp - generic hard error response */
411
412static void hard_err_resp(SINK_STATE *state)
413{
414    smtp_printf(state->stream, "%s", hard_error_resp);
415    SMTP_FLUSH(state->stream);
416}
417
418/* soft_err_resp - generic soft error response */
419
420static void soft_err_resp(SINK_STATE *state)
421{
422    smtp_printf(state->stream, "%s", soft_error_resp);
423    SMTP_FLUSH(state->stream);
424}
425
426/* exp_path_template - expand template pathname, static result */
427
428static VSTRING *exp_path_template(const char *template, time_t start_time)
429{
430    static VSTRING *path_buf = 0;
431    struct tm *lt;
432
433    if (path_buf == 0)
434	path_buf = vstring_alloc(100);
435    else
436	VSTRING_RESET(path_buf);
437    lt = localtime(&start_time);
438    while (strftime(STR(path_buf), vstring_avail(path_buf), template, lt) == 0)
439	VSTRING_SPACE(path_buf, vstring_avail(path_buf) + 100);
440    VSTRING_SKIP(path_buf);
441    return (path_buf);
442}
443
444/* make_parent_dir - create parent directory or bust */
445
446static void make_parent_dir(const char *path, mode_t mode)
447{
448    const char *parent;
449
450    parent = sane_dirname((VSTRING *) 0, path);
451    if (make_dirs(parent, mode) < 0)
452	msg_fatal("mkdir %s: %m", parent);
453}
454
455/* mail_file_open - open mail capture file */
456
457static void mail_file_open(SINK_STATE *state)
458{
459    const char *myname = "mail_file_open";
460    VSTRING *path_buf;
461    ssize_t len;
462    int     tries = 0;
463
464    /*
465     * Save the start time for later.
466     */
467    time(&(state->start_time));
468
469    /*
470     * Expand the per-message dumpfile pathname template.
471     */
472    path_buf = exp_path_template(single_template, state->start_time);
473
474    /*
475     * Append a random hexadecimal string to the pathname and create a new
476     * file. Retry with a different path if the file already exists. Create
477     * intermediate directories on the fly when the template specifies
478     * multiple pathname segments.
479     */
480#define ID_FORMAT	"%08x"
481
482    for (len = VSTRING_LEN(path_buf); /* void */ ; vstring_truncate(path_buf, len)) {
483	if (++tries > 100)
484	    msg_fatal("%s: something is looping", myname);
485	state->id = myrand();
486	vstring_sprintf_append(path_buf, ID_FORMAT, state->id);
487	if ((state->dump_file = vstream_fopen(STR(path_buf),
488					      O_RDWR | O_CREAT | O_EXCL,
489					      0644)) != 0) {
490	    break;
491	} else if (errno == EEXIST) {
492	    continue;
493	} else if (errno == ENOENT) {
494	    make_parent_dir(STR(path_buf), 0755);
495	    continue;
496	} else {
497	    msg_fatal("open %s: %m", STR(path_buf));
498	}
499    }
500
501    /*
502     * Don't leave temporary files behind.
503     */
504    if (shared_template != 0 && unlink(STR(path_buf)) < 0)
505	msg_fatal("unlink %s: %m", STR(path_buf));
506
507    /*
508     * Do initial header records.
509     */
510    if (start_string)
511	vstream_fprintf(state->dump_file, "%s", STR(start_string));
512    vstream_fprintf(state->dump_file, "X-Client-Addr: %s%s\n",
513		    state->addr_prefix, state->client_addr.buf);
514    vstream_fprintf(state->dump_file, "X-Client-Proto: %s\n", state->client_proto);
515    if (state->helo_args)
516	vstream_fprintf(state->dump_file, "X-Helo-Args: %s\n", state->helo_args);
517    /* Note: there may be more than one recipient. */
518}
519
520/* mail_file_finish_header - do final smtp-sink generated header records */
521
522static void mail_file_finish_header(SINK_STATE *state)
523{
524    if (state->helo_args)
525	vstream_fprintf(state->dump_file, "Received: from %s ([%s%s])\n",
526			state->helo_args, state->addr_prefix,
527			state->client_addr.buf);
528    else
529	vstream_fprintf(state->dump_file, "Received: from [%s%s] ([%s%s])\n",
530			state->addr_prefix, state->client_addr.buf,
531			state->addr_prefix, state->client_addr.buf);
532    vstream_fprintf(state->dump_file, "\tby %s (smtp-sink)"
533		    " with %s id " ID_FORMAT ";\n",
534		    var_myhostname, state->client_proto, state->id);
535    vstream_fprintf(state->dump_file, "\t%s\n", mail_date(state->start_time));
536}
537
538/* mail_file_cleanup - common cleanup for capture file */
539
540static void mail_file_cleanup(SINK_STATE *state)
541{
542    (void) vstream_fclose(state->dump_file);
543    state->dump_file = 0;
544}
545
546/* mail_file_finish - handle message completion for capture file */
547
548static void mail_file_finish(SINK_STATE *state)
549{
550
551    /*
552     * Optionally append the captured message to a shared dumpfile.
553     */
554    if (shared_template) {
555	const char *out_path;
556	VSTREAM *out_fp;
557	ssize_t count;
558
559	/*
560	 * Expand the shared dumpfile pathname template.
561	 */
562	out_path = STR(exp_path_template(shared_template, state->start_time));
563
564	/*
565	 * Open the shared dump file.
566	 */
567#define OUT_OPEN_FLAGS	(O_WRONLY | O_CREAT | O_APPEND)
568#define OUT_OPEN_MODE	0644
569
570	if ((out_fp = vstream_fopen(out_path, OUT_OPEN_FLAGS, OUT_OPEN_MODE))
571	    == 0 && errno == ENOENT) {
572	    make_parent_dir(out_path, 0755);
573	    out_fp = vstream_fopen(out_path, OUT_OPEN_FLAGS, OUT_OPEN_MODE);
574	}
575	if (out_fp == 0)
576	    msg_fatal("open %s: %m", out_path);
577
578	/*
579	 * Append message content from single-message dump file.
580	 */
581	if (vstream_fseek(state->dump_file, 0L, SEEK_SET) < 0)
582	    msg_fatal("seek file %s: %m", VSTREAM_PATH(state->dump_file));
583	VSTRING_RESET(state->buffer);
584	for (;;) {
585	    count = vstream_fread(state->dump_file, STR(state->buffer),
586				  vstring_avail(state->buffer));
587	    if (count <= 0)
588		break;
589	    if (vstream_fwrite(out_fp, STR(state->buffer), count) != count)
590		msg_fatal("append file %s: %m", out_path);
591	}
592	if (vstream_ferror(state->dump_file))
593	    msg_fatal("read file %s: %m", VSTREAM_PATH(state->dump_file));
594	if (vstream_fclose(out_fp))
595	    msg_fatal("append file %s: %m", out_path);
596    }
597    mail_file_cleanup(state);
598}
599
600/* mail_file_reset - abort mail to capture file */
601
602static void mail_file_reset(SINK_STATE *state)
603{
604    if (shared_template == 0
605	&& unlink(VSTREAM_PATH(state->dump_file)) < 0
606	&& errno != ENOENT)
607	msg_fatal("unlink %s: %m", VSTREAM_PATH(state->dump_file));
608    mail_file_cleanup(state);
609}
610
611/* mail_cmd_reset - reset mail transaction information */
612
613static void mail_cmd_reset(SINK_STATE *state)
614{
615    state->in_mail = 0;
616    /* Not: state->rcpts = 0. This breaks the DOT reply with LMTP. */
617    if (state->dump_file)
618	mail_file_reset(state);
619}
620
621/* ehlo_response - respond to EHLO command */
622
623static void ehlo_response(SINK_STATE *state, const char *args)
624{
625#define SKIP(cp, cond) do { \
626	for (/* void */; *cp && (cond); cp++) \
627	    /* void */; \
628    } while (0)
629
630    /* EHLO aborts a mail transaction in progress. */
631    mail_cmd_reset(state);
632    if (enable_lmtp == 0)
633	state->client_proto = "ESMTP";
634    smtp_printf(state->stream, "250-%s", var_myhostname);
635    if (!disable_pipelining)
636	smtp_printf(state->stream, "250-PIPELINING");
637    if (!disable_8bitmime)
638	smtp_printf(state->stream, "250-8BITMIME");
639    if (!disable_saslauth)
640	smtp_printf(state->stream, "250-AUTH PLAIN LOGIN");
641    if (!disable_xclient)
642	smtp_printf(state->stream, "250-XCLIENT NAME HELO");
643    if (!disable_xforward)
644	smtp_printf(state->stream, "250-XFORWARD NAME ADDR PROTO HELO");
645    if (!disable_enh_status)
646	smtp_printf(state->stream, "250-ENHANCEDSTATUSCODES");
647    if (!disable_dsn)
648	smtp_printf(state->stream, "250-DSN");
649    /* RFC 821/2821/5321: Format is replycode<SPACE>optional-text<CRLF> */
650    smtp_printf(state->stream, "250 ");
651    SMTP_FLUSH(state->stream);
652    if (single_template) {
653	if (state->helo_args)
654	    myfree(state->helo_args);
655	SKIP(args, ISSPACE(*args));
656	state->helo_args = mystrdup(args);
657    }
658}
659
660/* helo_response - respond to HELO command */
661
662static void helo_response(SINK_STATE *state, const char *args)
663{
664    /* HELO aborts a mail transaction in progress. */
665    mail_cmd_reset(state);
666    state->client_proto = "SMTP";
667    smtp_printf(state->stream, "250 %s", var_myhostname);
668    SMTP_FLUSH(state->stream);
669    if (single_template) {
670	if (state->helo_args)
671	    myfree(state->helo_args);
672	SKIP(args, ISSPACE(*args));
673	state->helo_args = mystrdup(args);
674    }
675}
676
677/* ok_response - send 250 OK */
678
679static void ok_response(SINK_STATE *state, const char *unused_args)
680{
681    smtp_printf(state->stream, "250 2.0.0 Ok");
682    SMTP_FLUSH(state->stream);
683}
684
685/* rset_response - reset, send 250 OK */
686
687static void rset_response(SINK_STATE *state, const char *unused_args)
688{
689    mail_cmd_reset(state);
690    smtp_printf(state->stream, "250 2.1.0 Ok");
691    SMTP_FLUSH(state->stream);
692}
693
694/* mail_response - reset recipient count, send 250 OK */
695
696static void mail_response(SINK_STATE *state, const char *args)
697{
698    if (state->in_mail) {
699	smtp_printf(state->stream, "503 5.5.1 Error: nested MAIL command");
700	SMTP_FLUSH(state->stream);
701	return;
702    }
703    state->in_mail++;
704    state->rcpts = 0;
705    smtp_printf(state->stream, "250 2.1.0 Ok");
706    SMTP_FLUSH(state->stream);
707    if (single_template) {
708	mail_file_open(state);
709	SKIP(args, *args != ':');
710	SKIP(args, *args == ':');
711	SKIP(args, ISSPACE(*args));
712	vstream_fprintf(state->dump_file, "X-Mail-Args: %s\n", args);
713    }
714}
715
716/* rcpt_response - bump recipient count, send 250 OK */
717
718static void rcpt_response(SINK_STATE *state, const char *args)
719{
720    if (state->in_mail == 0) {
721	smtp_printf(state->stream, "503 5.5.1 Error: need MAIL command");
722	SMTP_FLUSH(state->stream);
723	return;
724    }
725    state->rcpts++;
726    smtp_printf(state->stream, "250 2.1.5 Ok");
727    SMTP_FLUSH(state->stream);
728    /* Note: there may be more than one recipient per mail transaction. */
729    if (state->dump_file) {
730	SKIP(args, *args != ':');
731	SKIP(args, *args == ':');
732	SKIP(args, ISSPACE(*args));
733	vstream_fprintf(state->dump_file, "X-Rcpt-Args: %s\n", args);
734    }
735}
736
737/* abort_event - delayed abort after DATA command */
738
739static void abort_event(int unused_event, void *context)
740{
741    SINK_STATE *state = (SINK_STATE *) context;
742
743    smtp_printf(state->stream, "550 This violates SMTP");
744    SMTP_FLUSH(state->stream);
745    disconnect(state);
746}
747
748/* delay_read_event - resume input event handling */
749
750static void delay_read_event(int event, void *context)
751{
752    SINK_STATE *state = (SINK_STATE *) context;
753
754    if (event != EVENT_TIME)
755	msg_panic("delay_read_event: non-timer event %d", event);
756
757    event_enable_read(vstream_fileno(state->stream), read_event, (void *) state);
758    event_request_timer(read_timeout, (void *) state, var_tmout);
759}
760
761/* delay_read - temporarily suspend input event handling */
762
763static void delay_read(SINK_STATE *state, int delay)
764{
765    event_disable_readwrite(vstream_fileno(state->stream));
766    event_cancel_timer(read_timeout, (void *) state);
767    event_request_timer(delay_read_event, (void *) state, delay);
768}
769
770/* data_response - respond to DATA command */
771
772static void data_response(SINK_STATE *state, const char *unused_args)
773{
774    if (state->in_mail == 0 || state->rcpts == 0) {
775	smtp_printf(state->stream, "503 5.5.1 Error: need RCPT command");
776	SMTP_FLUSH(state->stream);
777	return;
778    }
779    /* Not: ST_ANY. */
780    state->data_state = ST_CR_LF;
781    smtp_printf(state->stream, "354 End data with <CR><LF>.<CR><LF>");
782    SMTP_FLUSH(state->stream);
783    if (abort_delay < 0) {
784	state->read_fn = data_read;
785	/* Todo: move into code that invokes the command response function. */
786	if (data_read_delay > 0)
787	    delay_read(state, data_read_delay);
788    } else {
789	/* Stop reading, send premature 550, and disconnect. */
790	event_disable_readwrite(vstream_fileno(state->stream));
791	event_cancel_timer(read_event, (void *) state);
792	event_request_timer(abort_event, (void *) state, abort_delay);
793    }
794    if (state->dump_file)
795	mail_file_finish_header(state);
796}
797
798/* dot_resp_hard - hard error response to . command */
799
800static void dot_resp_hard(SINK_STATE *state)
801{
802    if (enable_lmtp) {
803	while (state->rcpts-- > 0)	/* XXX this could block */
804	    smtp_printf(state->stream, "%s", hard_error_resp);
805    } else {
806	smtp_printf(state->stream, "%s", hard_error_resp);
807    }
808    SMTP_FLUSH(state->stream);
809}
810
811/* dot_resp_soft - soft error response to . command */
812
813static void dot_resp_soft(SINK_STATE *state)
814{
815    if (enable_lmtp) {
816	while (state->rcpts-- > 0)	/* XXX this could block */
817	    smtp_printf(state->stream, "%s", soft_error_resp);
818    } else {
819	smtp_printf(state->stream, "%s", soft_error_resp);
820    }
821    SMTP_FLUSH(state->stream);
822}
823
824/* dot_response - response to . command */
825
826static void dot_response(SINK_STATE *state, const char *unused_args)
827{
828    if (enable_lmtp) {
829	while (state->rcpts-- > 0)	/* XXX this could block */
830	    smtp_printf(state->stream, "250 2.2.0 Ok");
831    } else {
832	smtp_printf(state->stream, "250 2.0.0 Ok");
833    }
834    SMTP_FLUSH(state->stream);
835}
836
837/* quit_response - respond to QUIT command */
838
839static void quit_response(SINK_STATE *state, const char *unused_args)
840{
841    smtp_printf(state->stream, "221 Bye");
842    smtp_flush(state->stream);			/* not: SMTP_FLUSH */
843    if (show_count)
844	quit_count++;
845}
846
847/* conn_response - respond to connect command */
848
849static void conn_response(SINK_STATE *state, const char *unused_args)
850{
851    if (pretend_pix)
852	smtp_printf(state->stream, "220 ********");
853    else if (disable_esmtp)
854	smtp_printf(state->stream, "220 %s", var_myhostname);
855    else
856	smtp_printf(state->stream, "220 %s ESMTP", var_myhostname);
857    SMTP_FLUSH(state->stream);
858}
859
860/* delay_event - delayed command response */
861
862static void delay_event(int unused_event, void *context)
863{
864    SINK_STATE *state = (SINK_STATE *) context;
865
866    switch (vstream_setjmp(state->stream)) {
867
868    default:
869	msg_panic("unknown read/write error");
870	/* NOTREACHED */
871
872    case SMTP_ERR_TIME:
873	msg_warn("write timeout");
874	disconnect(state);
875	return;
876
877    case SMTP_ERR_EOF:
878	msg_warn("lost connection");
879	disconnect(state);
880	return;
881
882    case 0:
883	state->delayed_response(state, state->delayed_args);
884	myfree(state->delayed_args);
885	state->delayed_args = 0;
886	break;
887    }
888
889    if (state->delayed_response == quit_response) {
890	disconnect(state);
891	return;
892    }
893    state->delayed_response = 0;
894
895    /* Resume input event handling after the delayed response. */
896    event_enable_read(vstream_fileno(state->stream), read_event, (void *) state);
897    event_request_timer(read_timeout, (void *) state, var_tmout);
898}
899
900/* data_read - read data from socket */
901
902static int data_read(SINK_STATE *state)
903{
904    int     ch;
905    struct data_trans {
906	int     state;
907	int     want;
908	int     next_state;
909    };
910    static struct data_trans data_trans[] = {
911	ST_ANY, '\r', ST_CR,
912	ST_CR, '\n', ST_CR_LF,
913	ST_CR_LF, '.', ST_CR_LF_DOT,
914	ST_CR_LF_DOT, '\r', ST_CR_LF_DOT_CR,
915	ST_CR_LF_DOT_CR, '\n', ST_CR_LF_DOT_CR_LF,
916    };
917    struct data_trans *dp;
918
919    /*
920     * A read may result in EOF, but is never supposed to time out - a time
921     * out means that we were trying to read when no data was available.
922     */
923    for (;;) {
924	if ((ch = VSTREAM_GETC(state->stream)) == VSTREAM_EOF)
925	    return (-1);
926	for (dp = data_trans; dp->state != state->data_state; dp++)
927	     /* void */ ;
928
929	/*
930	 * Try to match the current character desired by the state machine.
931	 * If that fails, try to restart the machine with a match for its
932	 * first state.  This covers the case of a CR/LF/CR/LF sequence
933	 * (empty line) right before the end of the message data.
934	 */
935	if (ch == dp->want)
936	    state->data_state = dp->next_state;
937	else if (ch == data_trans[0].want)
938	    state->data_state = data_trans[0].next_state;
939	else
940	    state->data_state = ST_ANY;
941	if (state->dump_file) {
942	    if (ch != '\r' && state->data_state != ST_CR_LF_DOT)
943		VSTREAM_PUTC(ch, state->dump_file);
944	    if (vstream_ferror(state->dump_file))
945		msg_fatal("append file %s: %m", VSTREAM_PATH(state->dump_file));
946	}
947	if (state->data_state == ST_CR_LF_DOT_CR_LF) {
948	    PUSH_BACK_SET(state, ".\r\n");
949	    state->read_fn = command_read;
950	    state->data_state = ST_ANY;
951	    if (state->dump_file)
952		mail_file_finish(state);
953	    mail_cmd_reset(state);
954	    if (show_count || max_msg_quit_count > 0) {
955		mesg_count++;
956		if (show_count)
957		    do_stats();
958		if (max_msg_quit_count > 0 && mesg_count >= max_msg_quit_count)
959		    exit(0);
960	    }
961	    break;
962	}
963
964	/*
965	 * We must avoid blocking I/O, so get out of here as soon as both the
966	 * VSTREAM and kernel read buffers dry up.
967	 */
968	if (vstream_peek(state->stream) <= 0
969	    && readable(vstream_fileno(state->stream)) <= 0)
970	    return (0);
971    }
972    return (0);
973}
974
975 /*
976  * The table of all SMTP commands that we can handle.
977  */
978typedef struct SINK_COMMAND {
979    const char *name;
980    void    (*response) (SINK_STATE *, const char *);
981    void    (*hard_response) (SINK_STATE *);
982    void    (*soft_response) (SINK_STATE *);
983    int     flags;
984    int     delay;
985    int     delay_odds;
986} SINK_COMMAND;
987
988#define FLAG_ENABLE	(1<<0)		/* command is enabled */
989#define FLAG_SYSLOG	(1<<1)		/* log the command */
990#define FLAG_HARD_ERR	(1<<2)		/* report hard error */
991#define FLAG_SOFT_ERR	(1<<3)		/* report soft error */
992#define FLAG_DISCONNECT	(1<<4)		/* disconnect */
993#define FLAG_CLOSE	(1<<5)		/* say goodbye and disconnect */
994
995static SINK_COMMAND command_table[] = {
996    "connect", conn_response, hard_err_resp, soft_err_resp, 0, 0, 0,
997    "helo", helo_response, hard_err_resp, soft_err_resp, 0, 0, 0,
998    "ehlo", ehlo_response, hard_err_resp, soft_err_resp, 0, 0, 0,
999    "lhlo", ehlo_response, hard_err_resp, soft_err_resp, 0, 0, 0,
1000    "xclient", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
1001    "xforward", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
1002    "auth", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
1003    "mail", mail_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
1004    "rcpt", rcpt_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
1005    "data", data_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
1006    ".", dot_response, dot_resp_hard, dot_resp_soft, FLAG_ENABLE, 0, 0,
1007    "rset", rset_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
1008    "noop", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
1009    "vrfy", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
1010    "quit", quit_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0,
1011    0,
1012};
1013
1014/* reset_cmd_flags - reset per-command command flags */
1015
1016static void reset_cmd_flags(const char *cmd, int flags)
1017{
1018    SINK_COMMAND *cmdp;
1019
1020    for (cmdp = command_table; cmdp->name != 0; cmdp++)
1021	if (strcasecmp(cmd, cmdp->name) == 0)
1022	    break;
1023    if (cmdp->name == 0)
1024	msg_fatal("unknown command: %s", cmd);
1025    cmdp->flags &= ~flags;
1026}
1027
1028/* set_cmd_flags - set per-command command flags */
1029
1030static void set_cmd_flags(const char *cmd, int flags)
1031{
1032    SINK_COMMAND *cmdp;
1033
1034    for (cmdp = command_table; cmdp->name != 0; cmdp++)
1035	if (strcasecmp(cmd, cmdp->name) == 0)
1036	    break;
1037    if (cmdp->name == 0)
1038	msg_fatal("unknown command: %s", cmd);
1039    cmdp->flags |= flags;
1040}
1041
1042/* set_cmds_flags - set per-command flags for multiple commands */
1043
1044static void set_cmds_flags(const char *cmds, int flags)
1045{
1046    char   *saved_cmds;
1047    char   *cp;
1048    char   *cmd;
1049
1050    saved_cmds = cp = mystrdup(cmds);
1051    while ((cmd = mystrtok(&cp, CHARS_COMMA_SP)) != 0)
1052	set_cmd_flags(cmd, flags);
1053    myfree(saved_cmds);
1054}
1055
1056/* set_cmd_delay - set per-command delay */
1057
1058static void set_cmd_delay(const char *cmd, int delay, int odds)
1059{
1060    SINK_COMMAND *cmdp;
1061
1062    for (cmdp = command_table; cmdp->name != 0; cmdp++)
1063	if (strcasecmp(cmd, cmdp->name) == 0)
1064	    break;
1065    if (cmdp->name == 0)
1066	msg_fatal("unknown command: %s", cmd);
1067
1068    if (delay <= 0)
1069	msg_fatal("non-positive '%s' delay", cmd);
1070    if (odds < 0 || odds > 99)
1071	msg_fatal("delay odds for '%s' out of range", cmd);
1072
1073    cmdp->delay = delay;
1074    cmdp->delay_odds = odds;
1075}
1076
1077/* set_cmd_delay_arg - set per-command delay from option argument */
1078
1079static void set_cmd_delay_arg(char *arg)
1080{
1081    char   *cp;
1082    char   *saved_arg;
1083    char   *cmd;
1084    char   *delay;
1085    char   *odds;
1086
1087    saved_arg = cp = mystrdup(arg);
1088    cmd = mystrtok(&cp, ":");
1089    delay = mystrtok(&cp, ":");
1090    if (cmd == 0 || delay == 0)
1091	msg_fatal("invalid command delay argument: %s", arg);
1092    odds = mystrtok(&cp, "");
1093    set_cmd_delay(cmd, atoi(delay), odds ? atoi(odds) : 0);
1094    myfree(saved_arg);
1095}
1096
1097/* command_resp - respond to command */
1098
1099static int command_resp(SINK_STATE *state, SINK_COMMAND *cmdp,
1100			        const char *command, const char *args)
1101{
1102    /* We use raw syslog. Sanitize data content and length. */
1103    if (cmdp->flags & FLAG_SYSLOG)
1104	syslog(LOG_INFO, "%s %.100s", command, args);
1105    if (cmdp->flags & FLAG_DISCONNECT)
1106	return (-1);
1107    if (cmdp->flags & FLAG_CLOSE) {
1108	smtp_printf(state->stream, "421 4.0.0 Server closing connection");
1109	return (-1);
1110    }
1111    if (cmdp->flags & FLAG_HARD_ERR) {
1112	cmdp->hard_response(state);
1113	return (0);
1114    }
1115    if (cmdp->flags & FLAG_SOFT_ERR) {
1116	cmdp->soft_response(state);
1117	return (0);
1118    }
1119    if (cmdp->delay > 0) {
1120	int     delay = cmdp->delay;
1121
1122	if (cmdp->delay_odds > 0)
1123	    for (delay = 0;
1124	     ((int) (100.0 * rand() / (RAND_MAX + 1.0))) < cmdp->delay_odds;
1125		 delay += cmdp->delay)
1126		 /* NOP */ ;
1127	/* Suspend input event handling while delaying the command response. */
1128	event_disable_readwrite(vstream_fileno(state->stream));
1129	event_cancel_timer(read_timeout, (void *) state);
1130	event_request_timer(delay_event, (void *) state, delay);
1131	state->delayed_response = cmdp->response;
1132	state->delayed_args = mystrdup(args);
1133    } else {
1134	cmdp->response(state, args);
1135	if (cmdp->response == quit_response)
1136	    return (-1);
1137    }
1138    return (0);
1139}
1140
1141/* command_read - talk the SMTP protocol, server side */
1142
1143static int command_read(SINK_STATE *state)
1144{
1145    char   *command;
1146    SINK_COMMAND *cmdp;
1147    int     ch;
1148    struct cmd_trans {
1149	int     state;
1150	int     want;
1151	int     next_state;
1152    };
1153    static struct cmd_trans cmd_trans[] = {
1154	ST_ANY, '\r', ST_CR,
1155	ST_CR, '\n', ST_CR_LF,
1156	0, 0, 0,
1157    };
1158    struct cmd_trans *cp;
1159    char   *ptr;
1160
1161    /*
1162     * A read may result in EOF, but is never supposed to time out - a time
1163     * out means that we were trying to read when no data was available.
1164     */
1165#define NEXT_CHAR(state) \
1166    (PUSH_BACK_PEEK(state) ? PUSH_BACK_GET(state) : VSTREAM_GETC(state->stream))
1167
1168    if (state->data_state == ST_CR_LF)
1169	state->data_state = ST_ANY;		/* XXX */
1170    for (;;) {
1171	if ((ch = NEXT_CHAR(state)) == VSTREAM_EOF)
1172	    return (-1);
1173
1174	/*
1175	 * Sanity check. We don't want to store infinitely long commands.
1176	 */
1177	if (VSTRING_LEN(state->buffer) >= var_max_line_length) {
1178	    msg_warn("command line too long");
1179	    return (-1);
1180	}
1181	VSTRING_ADDCH(state->buffer, ch);
1182
1183	/*
1184	 * Try to match the current character desired by the state machine.
1185	 * If that fails, try to restart the machine with a match for its
1186	 * first state.
1187	 */
1188	for (cp = cmd_trans; cp->state != state->data_state; cp++)
1189	    if (cp->want == 0)
1190		msg_panic("command_read: unknown state: %d", state->data_state);
1191	if (ch == cp->want)
1192	    state->data_state = cp->next_state;
1193	else if (ch == cmd_trans[0].want)
1194	    state->data_state = cmd_trans[0].next_state;
1195	else
1196	    state->data_state = ST_ANY;
1197	if (state->data_state == ST_CR_LF)
1198	    break;
1199
1200	/*
1201	 * We must avoid blocking I/O, so get out of here as soon as both the
1202	 * VSTREAM and kernel read buffers dry up.
1203	 *
1204	 * XXX Solaris non-blocking read() may fail on a socket when ioctl
1205	 * FIONREAD reports there is unread data. Diagnosis by Max Pashkov.
1206	 * As a workaround we use readable() (which uses poll or select())
1207	 * instead of peek_fd() (which uses ioctl FIONREAD). Workaround added
1208	 * 20020604.
1209	 */
1210	if (PUSH_BACK_PEEK(state) == 0 && vstream_peek(state->stream) <= 0
1211	    && readable(vstream_fileno(state->stream)) <= 0)
1212	    return (0);
1213    }
1214
1215    /*
1216     * Properly terminate the result, and reset the buffer write pointer for
1217     * reading the next command. This is ugly, but not as ugly as trying to
1218     * deal with all the early returns below.
1219     */
1220    vstring_truncate(state->buffer, VSTRING_LEN(state->buffer) - 2);
1221    VSTRING_TERMINATE(state->buffer);
1222    state->data_state = ST_CR_LF;
1223    VSTRING_RESET(state->buffer);
1224
1225    /*
1226     * Got a complete command line. Parse it.
1227     */
1228    ptr = vstring_str(state->buffer);
1229    if (msg_verbose)
1230	msg_info("%s", ptr);
1231    if ((command = mystrtok(&ptr, " \t")) == 0) {
1232	smtp_printf(state->stream, "500 5.5.2 Error: unknown command");
1233	SMTP_FLUSH(state->stream);
1234	return (0);
1235    }
1236    for (cmdp = command_table; cmdp->name != 0; cmdp++)
1237	if (strcasecmp(command, cmdp->name) == 0)
1238	    break;
1239    if (cmdp->name == 0 || (cmdp->flags & FLAG_ENABLE) == 0) {
1240	smtp_printf(state->stream, "500 5.5.1 Error: unknown command");
1241	SMTP_FLUSH(state->stream);
1242	return (0);
1243    }
1244    return (command_resp(state, cmdp, command, printable(ptr, '?')));
1245}
1246
1247/* read_timeout - handle timer event */
1248
1249static void read_timeout(int unused_event, void *context)
1250{
1251    SINK_STATE *state = (SINK_STATE *) context;
1252
1253    /*
1254     * We don't send anything to the client, because we would have to set up
1255     * an smtp_stream exception handler first. And that is just too much
1256     * trouble.
1257     */
1258    msg_warn("read timeout");
1259    disconnect(state);
1260}
1261
1262/* read_event - handle command or data read events */
1263
1264static void read_event(int unused_event, void *context)
1265{
1266    SINK_STATE *state = (SINK_STATE *) context;
1267
1268    /*
1269     * The input reading routine not only reads input (with vstream calls)
1270     * but also produces output (with smtp_stream calls). Because the output
1271     * routines can raise timeout or EOF exceptions with vstream_longjmp(),
1272     * the input reading routine needs to set up corresponding exception
1273     * handlers with vstream_setjmp(). Guarding the input operations in the
1274     * same manner is not useful: we must read input in non-blocking mode, so
1275     * we never get called when the socket stays unreadable too long. And EOF
1276     * is already trivial to detect with the vstream calls.
1277     */
1278    do {
1279	switch (vstream_setjmp(state->stream)) {
1280
1281	default:
1282	    msg_panic("unknown read/write error");
1283	    /* NOTREACHED */
1284
1285	case SMTP_ERR_TIME:
1286	    msg_warn("write timeout");
1287	    disconnect(state);
1288	    return;
1289
1290	case SMTP_ERR_EOF:
1291	    msg_warn("lost connection");
1292	    disconnect(state);
1293	    return;
1294
1295	case 0:
1296	    if (state->read_fn(state) < 0) {
1297		if (msg_verbose)
1298		    msg_info("disconnect");
1299		disconnect(state);
1300		return;
1301	    }
1302	}
1303    } while (PUSH_BACK_PEEK(state) != 0 || vstream_peek(state->stream) > 0);
1304
1305    /*
1306     * Reset the idle timer. Wait until the next input event, or until the
1307     * idle timer goes off.
1308     */
1309    event_request_timer(read_timeout, (void *) state, var_tmout);
1310}
1311
1312static void connect_event(int, void *);
1313
1314/* disconnect - handle disconnection events */
1315
1316static void disconnect(SINK_STATE *state)
1317{
1318    event_disable_readwrite(vstream_fileno(state->stream));
1319    event_cancel_timer(read_timeout, (void *) state);
1320    if (show_count) {
1321	sess_count++;
1322	do_stats();
1323    }
1324    vstream_fclose(state->stream);
1325    vstring_free(state->buffer);
1326    /* Clean up file capture attributes. */
1327    if (state->helo_args)
1328	myfree(state->helo_args);
1329    /* Delete incomplete mail transaction. */
1330    mail_cmd_reset(state);
1331    if (state->delayed_args)
1332	myfree(state->delayed_args);
1333    myfree((void *) state);
1334    if (max_quit_count > 0 && quit_count >= max_quit_count)
1335	exit(0);
1336    if (client_count-- == max_client_count)
1337	event_enable_read(sock, connect_event, (void *) 0);
1338}
1339
1340/* connect_event - handle connection events */
1341
1342static void connect_event(int unused_event, void *unused_context)
1343{
1344    struct sockaddr_storage ss;
1345    SOCKADDR_SIZE len = sizeof(ss);
1346    struct sockaddr *sa = (struct sockaddr *) &ss;
1347    SINK_STATE *state;
1348    int     fd;
1349
1350    if ((fd = sane_accept(sock, sa, &len)) >= 0) {
1351	/* Safety: limit the number of open sockets and capture files. */
1352	if (++client_count == max_client_count)
1353	    event_disable_readwrite(sock);
1354	state = (SINK_STATE *) mymalloc(sizeof(*state));
1355	if (strchr((char *) proto_info->sa_family_list, sa->sa_family))
1356	    SOCKADDR_TO_HOSTADDR(sa, len, &state->client_addr,
1357				 (MAI_SERVPORT_STR *) 0, sa->sa_family);
1358	else
1359	    strncpy(state->client_addr.buf, "local", sizeof("local") + 0);
1360	if (msg_verbose)
1361	    msg_info("connect (%s %s)",
1362#ifdef AF_LOCAL
1363		     sa->sa_family == AF_LOCAL ? "AF_LOCAL" :
1364#else
1365		     sa->sa_family == AF_UNIX ? "AF_UNIX" :
1366#endif
1367		     sa->sa_family == AF_INET ? "AF_INET" :
1368#ifdef AF_INET6
1369		     sa->sa_family == AF_INET6 ? "AF_INET6" :
1370#endif
1371		     "unknown protocol family",
1372		     state->client_addr.buf);
1373	non_blocking(fd, NON_BLOCKING);
1374	state->stream = vstream_fdopen(fd, O_RDWR);
1375	vstream_tweak_sock(state->stream);
1376	state->buffer = vstring_alloc(1024);
1377	state->read_fn = command_read;
1378	state->data_state = ST_ANY;
1379	PUSH_BACK_SET(state, "");
1380	smtp_timeout_setup(state->stream, var_tmout);
1381	state->in_mail = 0;
1382	state->rcpts = 0;
1383	state->delayed_response = 0;
1384	state->delayed_args = 0;
1385	/* Initialize file capture attributes. */
1386#ifdef AF_INET6
1387	if (sa->sa_family == AF_INET6)
1388	    state->addr_prefix = "ipv6:";
1389	else
1390#endif
1391	    state->addr_prefix = "";
1392
1393	state->helo_args = 0;
1394	state->client_proto = enable_lmtp ? "LMTP" : "SMTP";
1395	state->start_time = 0;
1396	state->id = 0;
1397	state->dump_file = 0;
1398
1399	/*
1400	 * We use the smtp_stream module to produce output. That module
1401	 * throws an exception via vstream_longjmp() in case of a timeout or
1402	 * lost connection error. Therefore we must prepare to handle these
1403	 * exceptions with vstream_setjmp().
1404	 */
1405	switch (vstream_setjmp(state->stream)) {
1406
1407	default:
1408	    msg_panic("unknown read/write error");
1409	    /* NOTREACHED */
1410
1411	case SMTP_ERR_TIME:
1412	    msg_warn("write timeout");
1413	    disconnect(state);
1414	    return;
1415
1416	case SMTP_ERR_EOF:
1417	    msg_warn("lost connection");
1418	    disconnect(state);
1419	    return;
1420
1421	case 0:
1422	    if (command_resp(state, command_table, "connect", "") < 0)
1423		disconnect(state);
1424	    else if (command_table->delay == 0) {
1425		event_enable_read(fd, read_event, (void *) state);
1426		event_request_timer(read_timeout, (void *) state, var_tmout);
1427	    }
1428	}
1429    }
1430}
1431
1432/* usage - explain */
1433
1434static void usage(char *myname)
1435{
1436    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);
1437}
1438
1439MAIL_VERSION_STAMP_DECLARE;
1440
1441int     main(int argc, char **argv)
1442{
1443    int     backlog;
1444    int     ch;
1445    int     delay;
1446    const char *protocols = INET_PROTO_NAME_ALL;
1447    const char *root_dir = 0;
1448    const char *user_privs = 0;
1449
1450    /*
1451     * Fingerprint executables and core dumps.
1452     */
1453    MAIL_VERSION_STAMP_ALLOCATE;
1454
1455    /*
1456     * Fix 20051207.
1457     */
1458    signal(SIGPIPE, SIG_IGN);
1459
1460    /*
1461     * Initialize diagnostics.
1462     */
1463    msg_vstream_init(argv[0], VSTREAM_ERR);
1464
1465    /*
1466     * Parse JCL.
1467     */
1468    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) {
1469	switch (ch) {
1470	case '4':
1471	    protocols = INET_PROTO_NAME_IPV4;
1472	    break;
1473	case '6':
1474	    protocols = INET_PROTO_NAME_IPV6;
1475	    break;
1476	case '8':
1477	    disable_8bitmime = 1;
1478	    break;
1479	case 'a':
1480	    disable_saslauth = 1;
1481	    break;
1482	case 'A':
1483	    if (!alldig(optarg) || (abort_delay = atoi(optarg)) < 0)
1484		usage(argv[0]);
1485	    break;
1486	case 'b':
1487	    if (optarg[0] != '4' || strspn(optarg, "0123456789") != 3) {
1488		msg_error("bad soft error reply: %s", optarg);
1489		usage(argv[0]);
1490	    } else
1491		soft_error_resp = optarg;
1492	    break;
1493	case 'B':
1494	    if (optarg[0] != '5' || strspn(optarg, "0123456789") != 3) {
1495		msg_error("bad hard error reply: %s", optarg);
1496		usage(argv[0]);
1497	    } else
1498		hard_error_resp = optarg;
1499	    break;
1500	case 'c':
1501	    show_count++;
1502	    break;
1503	case 'C':
1504	    disable_xclient = 1;
1505	    reset_cmd_flags("xclient", FLAG_ENABLE);
1506	    break;
1507	case 'd':
1508	    single_template = optarg;
1509	    break;
1510	case 'D':
1511	    shared_template = optarg;
1512	    break;
1513	case 'e':
1514	    disable_esmtp = 1;
1515	    break;
1516	case 'E':
1517	    disable_enh_status = 1;
1518	    break;
1519	case 'f':
1520	    set_cmds_flags(optarg, FLAG_HARD_ERR);
1521	    disable_pipelining = 1;
1522	    break;
1523	case 'F':
1524	    disable_xforward = 1;
1525	    reset_cmd_flags("xforward", FLAG_ENABLE);
1526	    break;
1527	case 'h':
1528	    var_myhostname = optarg;
1529	    break;
1530	case 'H':
1531	    if ((data_read_delay = atoi(optarg)) <= 0)
1532		msg_fatal("bad data read delay: %s", optarg);
1533	    break;
1534	case 'L':
1535	    enable_lmtp = 1;
1536	    break;
1537	case 'm':
1538	    if ((max_client_count = atoi(optarg)) <= 0)
1539		msg_fatal("bad concurrency limit: %s", optarg);
1540	    break;
1541	case 'M':
1542	    if ((max_msg_quit_count = atoi(optarg)) <= 0)
1543		msg_fatal("bad message quit count: %s", optarg);
1544	    break;
1545	case 'n':
1546	    if ((max_quit_count = atoi(optarg)) <= 0)
1547		msg_fatal("bad quit count: %s", optarg);
1548	    break;
1549	case 'N':
1550	    disable_dsn = 1;
1551	    break;
1552	case 'p':
1553	    disable_pipelining = 1;
1554	    break;
1555	case 'P':
1556	    pretend_pix = 1;
1557	    disable_esmtp = 1;
1558	    break;
1559	case 'q':
1560	    set_cmds_flags(optarg, FLAG_DISCONNECT);
1561	    break;
1562	case 'Q':
1563	    set_cmds_flags(optarg, FLAG_CLOSE);
1564	    break;
1565	case 'r':
1566	    set_cmds_flags(optarg, FLAG_SOFT_ERR);
1567	    disable_pipelining = 1;
1568	    break;
1569	case 'R':
1570	    root_dir = optarg;
1571	    break;
1572	case 's':
1573	    openlog(basename(argv[0]), LOG_PID, LOG_MAIL);
1574	    set_cmds_flags(optarg, FLAG_SYSLOG);
1575	    break;
1576	case 'S':
1577	    start_string = vstring_alloc(10);
1578	    unescape(start_string, optarg);
1579	    break;
1580	case 't':
1581	    if ((var_tmout = atoi(optarg)) <= 0)
1582		msg_fatal("bad timeout: %s", optarg);
1583	    break;
1584	case 'T':
1585	    if ((inet_windowsize = atoi(optarg)) <= 0)
1586		msg_fatal("bad TCP window size: %s", optarg);
1587	    break;
1588	case 'u':
1589	    user_privs = optarg;
1590	    break;
1591	case 'v':
1592	    msg_verbose++;
1593	    break;
1594	case 'w':
1595	    if ((delay = atoi(optarg)) <= 0)
1596		usage(argv[0]);
1597	    set_cmd_delay("data", delay, 0);
1598	    break;
1599	case 'W':
1600	    set_cmd_delay_arg(optarg);
1601	    break;
1602	default:
1603	    usage(argv[0]);
1604	}
1605    }
1606    if (argc - optind != 2)
1607	usage(argv[0]);
1608    if ((backlog = atoi(argv[optind + 1])) <= 0)
1609	usage(argv[0]);
1610    if (single_template && shared_template)
1611	msg_fatal("use only one of -d or -D, but not both");
1612    if (geteuid() == 0 && user_privs == 0)
1613	msg_fatal("-u option is required if running as root");
1614
1615    /*
1616     * Initialize.
1617     */
1618    if (var_myhostname == 0)
1619	var_myhostname = "smtp-sink";
1620    set_cmds_flags(enable_lmtp ? "lhlo" :
1621		   disable_esmtp ? "helo" :
1622		   "helo, ehlo", FLAG_ENABLE);
1623    proto_info = inet_proto_init("protocols", protocols);
1624    if (strncmp(argv[optind], "unix:", 5) == 0) {
1625	sock = unix_listen(argv[optind] + 5, backlog, BLOCKING);
1626    } else {
1627	if (strncmp(argv[optind], "inet:", 5) == 0)
1628	    argv[optind] += 5;
1629	sock = inet_listen(argv[optind], backlog, BLOCKING);
1630    }
1631    if (user_privs)
1632	chroot_uid(root_dir, user_privs);
1633
1634    if (single_template)
1635	mysrand((int) time((time_t *) 0));
1636    else if (shared_template)
1637	single_template = shared_template;
1638
1639    /*
1640     * Start the event handler.
1641     */
1642    event_enable_read(sock, connect_event, (void *) 0);
1643    for (;;)
1644	event_loop(-1);
1645}
1646