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 
314 typedef 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 
361 static int var_tmout = 100;
362 static int var_max_line_length = 2048;
363 static char *var_myhostname;
364 static char *soft_error_resp = SOFT_ERROR_RESP;
365 static char *hard_error_resp = HARD_ERROR_RESP;
366 static int command_read(SINK_STATE *);
367 static int data_read(SINK_STATE *);
368 static void disconnect(SINK_STATE *);
369 static void read_timeout(int, void *);
370 static void read_event(int, void *);
371 static int show_count;
372 static int sess_count;
373 static int quit_count;
374 static int mesg_count;
375 static int max_quit_count;
376 static int max_msg_quit_count;
377 static int disable_pipelining;
378 static int disable_8bitmime;
379 static int disable_esmtp;
380 static int enable_lmtp;
381 static int pretend_pix;
382 static int disable_saslauth;
383 static int disable_xclient;
384 static int disable_xforward;
385 static int disable_enh_status;
386 static int disable_dsn;
387 static int max_client_count = DEF_MAX_CLIENT_COUNT;
388 static int client_count;
389 static int sock;
390 static int abort_delay = -1;
391 static int data_read_delay = 0;
392 
393 static char *single_template;		/* individual template */
394 static char *shared_template;		/* shared template */
395 static VSTRING *start_string;		/* dump content prefix */
396 
397 static const INET_PROTO_INFO *proto_info;
398 
399 #define STR(x)	vstring_str(x)
400 
401 /* do_stats - show counters */
402 
do_stats(void)403 static 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 
hard_err_resp(SINK_STATE * state)412 static 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 
soft_err_resp(SINK_STATE * state)420 static 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 
exp_path_template(const char * template,time_t start_time)428 static 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 
make_parent_dir(const char * path,mode_t mode)446 static 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 
mail_file_open(SINK_STATE * state)457 static 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 
mail_file_finish_header(SINK_STATE * state)522 static 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 
mail_file_cleanup(SINK_STATE * state)540 static 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 
mail_file_finish(SINK_STATE * state)548 static 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 
mail_file_reset(SINK_STATE * state)602 static 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 
mail_cmd_reset(SINK_STATE * state)613 static 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 
ehlo_response(SINK_STATE * state,const char * args)623 static 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 
helo_response(SINK_STATE * state,const char * args)662 static 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 
ok_response(SINK_STATE * state,const char * unused_args)679 static 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 
rset_response(SINK_STATE * state,const char * unused_args)687 static 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 
mail_response(SINK_STATE * state,const char * args)696 static 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 
rcpt_response(SINK_STATE * state,const char * args)718 static 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 
abort_event(int unused_event,void * context)739 static 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 
delay_read_event(int event,void * context)750 static 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 
delay_read(SINK_STATE * state,int delay)763 static 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 
data_response(SINK_STATE * state,const char * unused_args)772 static 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 
dot_resp_hard(SINK_STATE * state)800 static 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 
dot_resp_soft(SINK_STATE * state)813 static 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 
dot_response(SINK_STATE * state,const char * unused_args)826 static 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 
quit_response(SINK_STATE * state,const char * unused_args)839 static 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 
conn_response(SINK_STATE * state,const char * unused_args)849 static 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 
delay_event(int unused_event,void * context)862 static 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 
data_read(SINK_STATE * state)902 static 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   */
978 typedef 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 
995 static 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 
reset_cmd_flags(const char * cmd,int flags)1016 static 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 
set_cmd_flags(const char * cmd,int flags)1030 static 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 
set_cmds_flags(const char * cmds,int flags)1044 static 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 
set_cmd_delay(const char * cmd,int delay,int odds)1058 static 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 
set_cmd_delay_arg(char * arg)1079 static 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 
command_resp(SINK_STATE * state,SINK_COMMAND * cmdp,const char * command,const char * args)1099 static 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 
command_read(SINK_STATE * state)1143 static 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 
read_timeout(int unused_event,void * context)1249 static 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 
read_event(int unused_event,void * context)1264 static 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 
1312 static void connect_event(int, void *);
1313 
1314 /* disconnect - handle disconnection events */
1315 
disconnect(SINK_STATE * state)1316 static 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 
connect_event(int unused_event,void * unused_context)1342 static 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 
usage(char * myname)1434 static 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 
1439 MAIL_VERSION_STAMP_DECLARE;
1440 
main(int argc,char ** argv)1441 int     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