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