/*++ /* NAME /* smtp-sink 1 /* SUMMARY /* parallelized SMTP/LMTP test server /* SYNOPSIS /* .fi /* \fBsmtp-sink\fR [\fIoptions\fR] [\fBinet:\fR][\fIhost\fR]:\fIport\fR /* \fIbacklog\fR /* /* \fBsmtp-sink\fR [\fIoptions\fR] \fBunix:\fR\fIpathname\fR \fIbacklog\fR /* DESCRIPTION /* \fBsmtp-sink\fR listens on the named host (or address) and port. /* It takes SMTP messages from the network and throws them away. /* The purpose is to measure client performance, not protocol /* compliance. /* /* \fBsmtp-sink\fR may also be configured to capture each mail /* delivery transaction to file. Since disk latencies are large /* compared to network delays, this mode of operation can /* reduce the maximal performance by several orders of magnitude. /* /* Connections can be accepted on IPv4 or IPv6 endpoints, or on /* UNIX-domain sockets. /* IPv4 and IPv6 are the default. /* This program is the complement of the \fBsmtp-source\fR(1) program. /* /* Note: this is an unsupported test program. No attempt is made /* to maintain compatibility between successive versions. /* /* Arguments: /* .IP \fB-4\fR /* Support IPv4 only. This option has no effect when /* Postfix is built without IPv6 support. /* .IP \fB-6\fR /* Support IPv6 only. This option is not available when /* Postfix is built without IPv6 support. /* .IP \fB-8\fR /* Do not announce 8BITMIME support. /* .IP \fB-a\fR /* Do not announce SASL authentication support. /* .IP "\fB-A \fIdelay\fR" /* Wait \fIdelay\fR seconds after responding to DATA, then /* abort prematurely with a 550 reply status. Do not read /* further input from the client; this is an attempt to block /* the client before it sends ".". Specify a zero delay value /* to abort immediately. /* .IP "\fB-b \fIsoft-bounce-reply\fR" /* Use \fIsoft-bounce-reply\fR for soft reject responses. The /* default reply is "450 4.3.0 Error: command failed". /* .IP "\fB-B \fIhard-bounce-reply\fR" /* Use \fIhard-bounce-reply\fR for hard reject responses. The /* default reply is "500 5.3.0 Error: command failed". /* .IP \fB-c\fR /* Display running counters that are updated whenever an SMTP /* session ends, a QUIT command is executed, or when "." is /* received. /* .IP \fB-C\fR /* Disable XCLIENT support. /* .IP "\fB-d \fIdump-template\fR" /* Dump each mail transaction to a single-message file whose /* name is created by expanding the \fIdump-template\fR via /* strftime(3) and appending a pseudo-random hexadecimal number /* (example: "%Y%m%d%H/%M." expands into "2006081203/05.809a62e3"). /* If the template contains "/" characters, missing directories /* are created automatically. The message dump format is /* described below. /* .sp /* Note: this option keeps one capture file open for every /* mail transaction in progress. /* .IP "\fB-D \fIdump-template\fR" /* Append mail transactions to a multi-message dump file whose /* name is created by expanding the \fIdump-template\fR via /* strftime(3). /* If the template contains "/" characters, missing directories /* are created automatically. The message dump format is /* described below. /* .sp /* Note: this option keeps one capture file open for every /* mail transaction in progress. /* .IP \fB-e\fR /* Do not announce ESMTP support. /* .IP \fB-E\fR /* Do not announce ENHANCEDSTATUSCODES support. /* .IP "\fB-f \fIcommand,command,...\fR" /* Reject the specified commands with a hard (5xx) error code. /* This option implies \fB-p\fR. /* .sp /* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY, /* DATA, ., RSET, NOOP, and QUIT. Separate command names by /* white space or commas, and use quotes to protect white space /* from the shell. Command names are case-insensitive. /* .IP \fB-F\fR /* Disable XFORWARD support. /* .IP "\fB-h\fI hostname\fR" /* Use \fIhostname\fR in the SMTP greeting, in the HELO response, /* and in the EHLO response. The default hostname is "smtp-sink". /* .IP "\fB-H\fI delay\fR" /* Delay the first read operation after receiving DATA (time /* in seconds). Combine with a large test message and a small /* TCP window size (see the \fB-T\fR option) to test the Postfix /* client write_wait() implementation. /* .IP \fB-L\fR /* Enable LMTP instead of SMTP. /* .IP "\fB-m \fIcount\fR (default: 256)" /* An upper bound on the maximal number of simultaneous /* connections that \fBsmtp-sink\fR will handle. This prevents /* the process from running out of file descriptors. Excess /* connections will stay queued in the TCP/IP stack. /* .IP "\fB-M \fIcount\fR" /* Terminate after receiving \fIcount\fR messages. /* .IP "\fB-n \fIcount\fR" /* Terminate after \fIcount\fR sessions. /* .IP \fB-N\fR /* Do not announce support for DSN. /* .IP \fB-p\fR /* Do not announce support for ESMTP command pipelining. /* .IP \fB-P\fR /* Change the server greeting so that it appears to come through /* a CISCO PIX system. Implies \fB-e\fR. /* .IP "\fB-q \fIcommand,command,...\fR" /* Disconnect (without replying) after receiving one of the /* specified commands. /* .sp /* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY, /* DATA, ., RSET, NOOP, and QUIT. Separate command names by /* white space or commas, and use quotes to protect white space /* from the shell. Command names are case-insensitive. /* .IP "\fB-Q \fIcommand,command,...\fR" /* Send a 421 reply and disconnect after receiving one /* of the specified commands. /* .sp /* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY, /* DATA, ., RSET, NOOP, and QUIT. Separate command names by /* white space or commas, and use quotes to protect white space /* from the shell. Command names are case-insensitive. /* .IP "\fB-r \fIcommand,command,...\fR" /* Reject the specified commands with a soft (4xx) error code. /* This option implies \fB-p\fR. /* .sp /* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY, /* DATA, ., RSET, NOOP, and QUIT. Separate command names by /* white space or commas, and use quotes to protect white space /* from the shell. Command names are case-insensitive. /* .IP "\fB-R \fIroot-directory\fR" /* Change the process root directory to the specified location. /* This option requires super-user privileges. See also the /* \fB-u\fR option. /* .IP "\fB-s \fIcommand,command,...\fR" /* Log the named commands to syslogd. /* .sp /* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY, /* DATA, ., RSET, NOOP, and QUIT. Separate command names by /* white space or commas, and use quotes to protect white space /* from the shell. Command names are case-insensitive. /* .IP "\fB-S start-string\fR" /* An optional string that is prepended to each message that is /* written to a dump file (see the dump file format description /* below). The following C escape sequences are supported: \ea /* (bell), \eb (backspace), \ef (formfeed), \en (newline), \er /* (carriage return), \et (horizontal tab), \ev (vertical tab), /* \e\fIddd\fR (up to three octal digits) and \e\e (the backslash /* character). /* .IP "\fB-t \fItimeout\fR (default: 100)" /* Limit the time for receiving a command or sending a response. /* The time limit is specified in seconds. /* .IP "\fB-T \fIwindowsize\fR" /* Override the default TCP window size. To work around /* broken TCP window scaling implementations, specify a /* value > 0 and < 65536. /* .IP "\fB-u \fIusername\fR" /* Switch to the specified user privileges after opening the /* network socket and optionally changing the process root /* directory. This option is required when the process runs /* with super-user privileges. See also the \fB-R\fR option. /* .IP \fB-v\fR /* Show the SMTP conversations. /* .IP "\fB-w \fIdelay\fR" /* Wait \fIdelay\fR seconds before responding to a DATA command. /* .IP "\fB-W \fIcommand:delay[:odds]\fR" /* Wait \fIdelay\fR seconds before responding to \fIcommand\fR. /* If \fIodds\fR is also specified (a number between 1-99 /* inclusive), wait for a random multiple of \fIdelay\fR. The /* random multiplier is equal to the number of times the program /* needs to roll a dice with a range of 0..99 inclusive, before /* the dice produces a result greater than or equal to \fIodds\fR. /* .IP [\fBinet:\fR][\fIhost\fR]:\fIport\fR /* Listen on network interface \fIhost\fR (default: any interface) /* TCP port \fIport\fR. Both \fIhost\fR and \fIport\fR may be /* specified in numeric or symbolic form. /* .IP \fBunix:\fR\fIpathname\fR /* Listen on the UNIX-domain socket at \fIpathname\fR. /* .IP \fIbacklog\fR /* The maximum length of the queue of pending connections, /* as defined by the \fBlisten\fR(2) system call. /* DUMP FILE FORMAT /* .ad /* .fi /* Each dumped message contains a sequence of text lines, /* terminated with the newline character. The sequence of /* information is as follows: /* .IP \(bu /* The optional string specified with the \fB-S\fR option. /* .IP \(bu /* The \fBsmtp-sink\fR generated headers as documented below. /* .IP \(bu /* The message header and body as received from the SMTP client. /* .IP \(bu /* An empty line. /* .PP /* The format of the \fBsmtp-sink\fR generated headers is as /* follows: /* .IP "\fBX-Client-Addr: \fItext\fR" /* The client IP address without enclosing []. An IPv6 address /* is prefixed with "ipv6:". This record is always present. /* .IP "\fBX-Client-Proto: \fItext\fR" /* The client protocol: SMTP, ESMTP or LMTP. This record is /* always present. /* .IP "\fBX-Helo-Args: \fItext\fR" /* The arguments of the last HELO or EHLO command before this /* mail delivery transaction. This record is present only if /* the client sent a recognizable HELO or EHLO command before /* the DATA command. /* .IP "\fBX-Mail-Args: \fItext\fR" /* The arguments of the MAIL command that started this mail /* delivery transaction. This record is present exactly once. /* .IP "\fBX-Rcpt-Args: \fItext\fR" /* The arguments of an RCPT command within this mail delivery /* transaction. There is one record for each RCPT command, and /* they are in the order as sent by the client. /* .IP "\fBReceived: \fItext\fR" /* A message header for compatibility with mail processing /* software. This three-line header marks the end of the headers /* provided by \fBsmtp-sink\fR, and is formatted as follows: /* .RS /* .IP "\fBfrom \fIhelo\fR ([\fIaddr\fR])" /* The HELO or EHLO command argument and client IP address. /* If the client did not send HELO or EHLO, the client IP /* address is used instead. /* .IP "\fBby \fIhost\fB (smtp-sink) with \fIproto\fB id \fIrandom\fB;\fR" /* The hostname specified with the \fB-h\fR option, the client /* protocol (see \fBX-Client-Proto\fR above), and the pseudo-random /* portion of the per-message capture file name. /* .IP \fItime-stamp\fR /* A time stamp as defined in RFC 2822. /* .RE /* SEE ALSO /* smtp-source(1), SMTP/LMTP message generator /* LICENSE /* .ad /* .fi /* The Secure Mailer license must be distributed with this software. /* AUTHOR(S) /* Wietse Venema /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA /* /* Wietse Venema /* Google, Inc. /* 111 8th Avenue /* New York, NY 10011, USA /*--*/ /* System library. */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef STRCASECMP_IN_STRINGS_H #include #endif /* Utility library. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Global library. */ #include #include #include /* Application-specific. */ typedef struct SINK_STATE { VSTREAM *stream; VSTRING *buffer; int data_state; int (*read_fn) (struct SINK_STATE *); int in_mail; int rcpts; char *push_back_ptr; /* Capture file information for fake Received: header */ MAI_HOSTADDR_STR client_addr; /* IP address */ char *addr_prefix; /* ipv6: or empty */ char *helo_args; /* text after HELO or EHLO */ const char *client_proto; /* SMTP, ESMTP, LMTP */ time_t start_time; /* MAIL command time */ int id; /* pseudo-random */ VSTREAM *dump_file; /* dump file or null */ void (*delayed_response) (struct SINK_STATE *state, const char *); char *delayed_args; } SINK_STATE; #define ST_ANY 0 #define ST_CR 1 #define ST_CR_LF 2 #define ST_CR_LF_DOT 3 #define ST_CR_LF_DOT_CR 4 #define ST_CR_LF_DOT_CR_LF 5 #define PUSH_BACK_PEEK(state) (*(state)->push_back_ptr != 0) #define PUSH_BACK_GET(state) (*(state)->push_back_ptr++) #define PUSH_BACK_SET(state, text) ((state)->push_back_ptr = (text)) #ifndef DEF_MAX_CLIENT_COUNT #define DEF_MAX_CLIENT_COUNT 256 #endif #define SOFT_ERROR_RESP "450 4.3.0 Error: command failed" #define HARD_ERROR_RESP "500 5.3.0 Error: command failed" /* * We can't rely on vstream auto-flushing, so we have to prepare for the * next read request. */ #define SMTP_FLUSH(fp) do { \ if (vstream_peek(fp) <= 0 && readable(vstream_fileno(fp)) <= 0) \ smtp_flush(fp); \ } while (0) static int var_tmout = 100; static int var_max_line_length = 2048; static char *var_myhostname; static char *soft_error_resp = SOFT_ERROR_RESP; static char *hard_error_resp = HARD_ERROR_RESP; static int command_read(SINK_STATE *); static int data_read(SINK_STATE *); static void disconnect(SINK_STATE *); static void read_timeout(int, void *); static void read_event(int, void *); static int show_count; static int sess_count; static int quit_count; static int mesg_count; static int max_quit_count; static int max_msg_quit_count; static int disable_pipelining; static int disable_8bitmime; static int disable_esmtp; static int enable_lmtp; static int pretend_pix; static int disable_saslauth; static int disable_xclient; static int disable_xforward; static int disable_enh_status; static int disable_dsn; static int max_client_count = DEF_MAX_CLIENT_COUNT; static int client_count; static int sock; static int abort_delay = -1; static int data_read_delay = 0; static char *single_template; /* individual template */ static char *shared_template; /* shared template */ static VSTRING *start_string; /* dump content prefix */ static const INET_PROTO_INFO *proto_info; #define STR(x) vstring_str(x) /* do_stats - show counters */ static void do_stats(void) { vstream_printf("sess=%d quit=%d mesg=%d\r", sess_count, quit_count, mesg_count); vstream_fflush(VSTREAM_OUT); } /* hard_err_resp - generic hard error response */ static void hard_err_resp(SINK_STATE *state) { smtp_printf(state->stream, "%s", hard_error_resp); SMTP_FLUSH(state->stream); } /* soft_err_resp - generic soft error response */ static void soft_err_resp(SINK_STATE *state) { smtp_printf(state->stream, "%s", soft_error_resp); SMTP_FLUSH(state->stream); } /* exp_path_template - expand template pathname, static result */ static VSTRING *exp_path_template(const char *template, time_t start_time) { static VSTRING *path_buf = 0; struct tm *lt; if (path_buf == 0) path_buf = vstring_alloc(100); else VSTRING_RESET(path_buf); lt = localtime(&start_time); while (strftime(STR(path_buf), vstring_avail(path_buf), template, lt) == 0) VSTRING_SPACE(path_buf, vstring_avail(path_buf) + 100); VSTRING_SKIP(path_buf); return (path_buf); } /* make_parent_dir - create parent directory or bust */ static void make_parent_dir(const char *path, mode_t mode) { const char *parent; parent = sane_dirname((VSTRING *) 0, path); if (make_dirs(parent, mode) < 0) msg_fatal("mkdir %s: %m", parent); } /* mail_file_open - open mail capture file */ static void mail_file_open(SINK_STATE *state) { const char *myname = "mail_file_open"; VSTRING *path_buf; ssize_t len; int tries = 0; /* * Save the start time for later. */ time(&(state->start_time)); /* * Expand the per-message dumpfile pathname template. */ path_buf = exp_path_template(single_template, state->start_time); /* * Append a random hexadecimal string to the pathname and create a new * file. Retry with a different path if the file already exists. Create * intermediate directories on the fly when the template specifies * multiple pathname segments. */ #define ID_FORMAT "%08x" for (len = VSTRING_LEN(path_buf); /* void */ ; vstring_truncate(path_buf, len)) { if (++tries > 100) msg_fatal("%s: something is looping", myname); state->id = myrand(); vstring_sprintf_append(path_buf, ID_FORMAT, state->id); if ((state->dump_file = vstream_fopen(STR(path_buf), O_RDWR | O_CREAT | O_EXCL, 0644)) != 0) { break; } else if (errno == EEXIST) { continue; } else if (errno == ENOENT) { make_parent_dir(STR(path_buf), 0755); continue; } else { msg_fatal("open %s: %m", STR(path_buf)); } } /* * Don't leave temporary files behind. */ if (shared_template != 0 && unlink(STR(path_buf)) < 0) msg_fatal("unlink %s: %m", STR(path_buf)); /* * Do initial header records. */ if (start_string) vstream_fprintf(state->dump_file, "%s", STR(start_string)); vstream_fprintf(state->dump_file, "X-Client-Addr: %s%s\n", state->addr_prefix, state->client_addr.buf); vstream_fprintf(state->dump_file, "X-Client-Proto: %s\n", state->client_proto); if (state->helo_args) vstream_fprintf(state->dump_file, "X-Helo-Args: %s\n", state->helo_args); /* Note: there may be more than one recipient. */ } /* mail_file_finish_header - do final smtp-sink generated header records */ static void mail_file_finish_header(SINK_STATE *state) { if (state->helo_args) vstream_fprintf(state->dump_file, "Received: from %s ([%s%s])\n", state->helo_args, state->addr_prefix, state->client_addr.buf); else vstream_fprintf(state->dump_file, "Received: from [%s%s] ([%s%s])\n", state->addr_prefix, state->client_addr.buf, state->addr_prefix, state->client_addr.buf); vstream_fprintf(state->dump_file, "\tby %s (smtp-sink)" " with %s id " ID_FORMAT ";\n", var_myhostname, state->client_proto, state->id); vstream_fprintf(state->dump_file, "\t%s\n", mail_date(state->start_time)); } /* mail_file_cleanup - common cleanup for capture file */ static void mail_file_cleanup(SINK_STATE *state) { (void) vstream_fclose(state->dump_file); state->dump_file = 0; } /* mail_file_finish - handle message completion for capture file */ static void mail_file_finish(SINK_STATE *state) { /* * Optionally append the captured message to a shared dumpfile. */ if (shared_template) { const char *out_path; VSTREAM *out_fp; ssize_t count; /* * Expand the shared dumpfile pathname template. */ out_path = STR(exp_path_template(shared_template, state->start_time)); /* * Open the shared dump file. */ #define OUT_OPEN_FLAGS (O_WRONLY | O_CREAT | O_APPEND) #define OUT_OPEN_MODE 0644 if ((out_fp = vstream_fopen(out_path, OUT_OPEN_FLAGS, OUT_OPEN_MODE)) == 0 && errno == ENOENT) { make_parent_dir(out_path, 0755); out_fp = vstream_fopen(out_path, OUT_OPEN_FLAGS, OUT_OPEN_MODE); } if (out_fp == 0) msg_fatal("open %s: %m", out_path); /* * Append message content from single-message dump file. */ if (vstream_fseek(state->dump_file, 0L, SEEK_SET) < 0) msg_fatal("seek file %s: %m", VSTREAM_PATH(state->dump_file)); VSTRING_RESET(state->buffer); for (;;) { count = vstream_fread(state->dump_file, STR(state->buffer), vstring_avail(state->buffer)); if (count <= 0) break; if (vstream_fwrite(out_fp, STR(state->buffer), count) != count) msg_fatal("append file %s: %m", out_path); } if (vstream_ferror(state->dump_file)) msg_fatal("read file %s: %m", VSTREAM_PATH(state->dump_file)); if (vstream_fclose(out_fp)) msg_fatal("append file %s: %m", out_path); } mail_file_cleanup(state); } /* mail_file_reset - abort mail to capture file */ static void mail_file_reset(SINK_STATE *state) { if (shared_template == 0 && unlink(VSTREAM_PATH(state->dump_file)) < 0 && errno != ENOENT) msg_fatal("unlink %s: %m", VSTREAM_PATH(state->dump_file)); mail_file_cleanup(state); } /* mail_cmd_reset - reset mail transaction information */ static void mail_cmd_reset(SINK_STATE *state) { state->in_mail = 0; /* Not: state->rcpts = 0. This breaks the DOT reply with LMTP. */ if (state->dump_file) mail_file_reset(state); } /* ehlo_response - respond to EHLO command */ static void ehlo_response(SINK_STATE *state, const char *args) { #define SKIP(cp, cond) do { \ for (/* void */; *cp && (cond); cp++) \ /* void */; \ } while (0) /* EHLO aborts a mail transaction in progress. */ mail_cmd_reset(state); if (enable_lmtp == 0) state->client_proto = "ESMTP"; smtp_printf(state->stream, "250-%s", var_myhostname); if (!disable_pipelining) smtp_printf(state->stream, "250-PIPELINING"); if (!disable_8bitmime) smtp_printf(state->stream, "250-8BITMIME"); if (!disable_saslauth) smtp_printf(state->stream, "250-AUTH PLAIN LOGIN"); if (!disable_xclient) smtp_printf(state->stream, "250-XCLIENT NAME HELO"); if (!disable_xforward) smtp_printf(state->stream, "250-XFORWARD NAME ADDR PROTO HELO"); if (!disable_enh_status) smtp_printf(state->stream, "250-ENHANCEDSTATUSCODES"); if (!disable_dsn) smtp_printf(state->stream, "250-DSN"); /* RFC 821/2821/5321: Format is replycodeoptional-text */ smtp_printf(state->stream, "250 "); SMTP_FLUSH(state->stream); if (single_template) { if (state->helo_args) myfree(state->helo_args); SKIP(args, ISSPACE(*args)); state->helo_args = mystrdup(args); } } /* helo_response - respond to HELO command */ static void helo_response(SINK_STATE *state, const char *args) { /* HELO aborts a mail transaction in progress. */ mail_cmd_reset(state); state->client_proto = "SMTP"; smtp_printf(state->stream, "250 %s", var_myhostname); SMTP_FLUSH(state->stream); if (single_template) { if (state->helo_args) myfree(state->helo_args); SKIP(args, ISSPACE(*args)); state->helo_args = mystrdup(args); } } /* ok_response - send 250 OK */ static void ok_response(SINK_STATE *state, const char *unused_args) { smtp_printf(state->stream, "250 2.0.0 Ok"); SMTP_FLUSH(state->stream); } /* rset_response - reset, send 250 OK */ static void rset_response(SINK_STATE *state, const char *unused_args) { mail_cmd_reset(state); smtp_printf(state->stream, "250 2.1.0 Ok"); SMTP_FLUSH(state->stream); } /* mail_response - reset recipient count, send 250 OK */ static void mail_response(SINK_STATE *state, const char *args) { if (state->in_mail) { smtp_printf(state->stream, "503 5.5.1 Error: nested MAIL command"); SMTP_FLUSH(state->stream); return; } state->in_mail++; state->rcpts = 0; smtp_printf(state->stream, "250 2.1.0 Ok"); SMTP_FLUSH(state->stream); if (single_template) { mail_file_open(state); SKIP(args, *args != ':'); SKIP(args, *args == ':'); SKIP(args, ISSPACE(*args)); vstream_fprintf(state->dump_file, "X-Mail-Args: %s\n", args); } } /* rcpt_response - bump recipient count, send 250 OK */ static void rcpt_response(SINK_STATE *state, const char *args) { if (state->in_mail == 0) { smtp_printf(state->stream, "503 5.5.1 Error: need MAIL command"); SMTP_FLUSH(state->stream); return; } state->rcpts++; smtp_printf(state->stream, "250 2.1.5 Ok"); SMTP_FLUSH(state->stream); /* Note: there may be more than one recipient per mail transaction. */ if (state->dump_file) { SKIP(args, *args != ':'); SKIP(args, *args == ':'); SKIP(args, ISSPACE(*args)); vstream_fprintf(state->dump_file, "X-Rcpt-Args: %s\n", args); } } /* abort_event - delayed abort after DATA command */ static void abort_event(int unused_event, void *context) { SINK_STATE *state = (SINK_STATE *) context; smtp_printf(state->stream, "550 This violates SMTP"); SMTP_FLUSH(state->stream); disconnect(state); } /* delay_read_event - resume input event handling */ static void delay_read_event(int event, void *context) { SINK_STATE *state = (SINK_STATE *) context; if (event != EVENT_TIME) msg_panic("delay_read_event: non-timer event %d", event); event_enable_read(vstream_fileno(state->stream), read_event, (void *) state); event_request_timer(read_timeout, (void *) state, var_tmout); } /* delay_read - temporarily suspend input event handling */ static void delay_read(SINK_STATE *state, int delay) { event_disable_readwrite(vstream_fileno(state->stream)); event_cancel_timer(read_timeout, (void *) state); event_request_timer(delay_read_event, (void *) state, delay); } /* data_response - respond to DATA command */ static void data_response(SINK_STATE *state, const char *unused_args) { if (state->in_mail == 0 || state->rcpts == 0) { smtp_printf(state->stream, "503 5.5.1 Error: need RCPT command"); SMTP_FLUSH(state->stream); return; } /* Not: ST_ANY. */ state->data_state = ST_CR_LF; smtp_printf(state->stream, "354 End data with ."); SMTP_FLUSH(state->stream); if (abort_delay < 0) { state->read_fn = data_read; /* Todo: move into code that invokes the command response function. */ if (data_read_delay > 0) delay_read(state, data_read_delay); } else { /* Stop reading, send premature 550, and disconnect. */ event_disable_readwrite(vstream_fileno(state->stream)); event_cancel_timer(read_event, (void *) state); event_request_timer(abort_event, (void *) state, abort_delay); } if (state->dump_file) mail_file_finish_header(state); } /* dot_resp_hard - hard error response to . command */ static void dot_resp_hard(SINK_STATE *state) { if (enable_lmtp) { while (state->rcpts-- > 0) /* XXX this could block */ smtp_printf(state->stream, "%s", hard_error_resp); } else { smtp_printf(state->stream, "%s", hard_error_resp); } SMTP_FLUSH(state->stream); } /* dot_resp_soft - soft error response to . command */ static void dot_resp_soft(SINK_STATE *state) { if (enable_lmtp) { while (state->rcpts-- > 0) /* XXX this could block */ smtp_printf(state->stream, "%s", soft_error_resp); } else { smtp_printf(state->stream, "%s", soft_error_resp); } SMTP_FLUSH(state->stream); } /* dot_response - response to . command */ static void dot_response(SINK_STATE *state, const char *unused_args) { if (enable_lmtp) { while (state->rcpts-- > 0) /* XXX this could block */ smtp_printf(state->stream, "250 2.2.0 Ok"); } else { smtp_printf(state->stream, "250 2.0.0 Ok"); } SMTP_FLUSH(state->stream); } /* quit_response - respond to QUIT command */ static void quit_response(SINK_STATE *state, const char *unused_args) { smtp_printf(state->stream, "221 Bye"); smtp_flush(state->stream); /* not: SMTP_FLUSH */ if (show_count) quit_count++; } /* conn_response - respond to connect command */ static void conn_response(SINK_STATE *state, const char *unused_args) { if (pretend_pix) smtp_printf(state->stream, "220 ********"); else if (disable_esmtp) smtp_printf(state->stream, "220 %s", var_myhostname); else smtp_printf(state->stream, "220 %s ESMTP", var_myhostname); SMTP_FLUSH(state->stream); } /* delay_event - delayed command response */ static void delay_event(int unused_event, void *context) { SINK_STATE *state = (SINK_STATE *) context; switch (vstream_setjmp(state->stream)) { default: msg_panic("unknown read/write error"); /* NOTREACHED */ case SMTP_ERR_TIME: msg_warn("write timeout"); disconnect(state); return; case SMTP_ERR_EOF: msg_warn("lost connection"); disconnect(state); return; case 0: state->delayed_response(state, state->delayed_args); myfree(state->delayed_args); state->delayed_args = 0; break; } if (state->delayed_response == quit_response) { disconnect(state); return; } state->delayed_response = 0; /* Resume input event handling after the delayed response. */ event_enable_read(vstream_fileno(state->stream), read_event, (void *) state); event_request_timer(read_timeout, (void *) state, var_tmout); } /* data_read - read data from socket */ static int data_read(SINK_STATE *state) { int ch; struct data_trans { int state; int want; int next_state; }; static struct data_trans data_trans[] = { ST_ANY, '\r', ST_CR, ST_CR, '\n', ST_CR_LF, ST_CR_LF, '.', ST_CR_LF_DOT, ST_CR_LF_DOT, '\r', ST_CR_LF_DOT_CR, ST_CR_LF_DOT_CR, '\n', ST_CR_LF_DOT_CR_LF, }; struct data_trans *dp; /* * A read may result in EOF, but is never supposed to time out - a time * out means that we were trying to read when no data was available. */ for (;;) { if ((ch = VSTREAM_GETC(state->stream)) == VSTREAM_EOF) return (-1); for (dp = data_trans; dp->state != state->data_state; dp++) /* void */ ; /* * Try to match the current character desired by the state machine. * If that fails, try to restart the machine with a match for its * first state. This covers the case of a CR/LF/CR/LF sequence * (empty line) right before the end of the message data. */ if (ch == dp->want) state->data_state = dp->next_state; else if (ch == data_trans[0].want) state->data_state = data_trans[0].next_state; else state->data_state = ST_ANY; if (state->dump_file) { if (ch != '\r' && state->data_state != ST_CR_LF_DOT) VSTREAM_PUTC(ch, state->dump_file); if (vstream_ferror(state->dump_file)) msg_fatal("append file %s: %m", VSTREAM_PATH(state->dump_file)); } if (state->data_state == ST_CR_LF_DOT_CR_LF) { PUSH_BACK_SET(state, ".\r\n"); state->read_fn = command_read; state->data_state = ST_ANY; if (state->dump_file) mail_file_finish(state); mail_cmd_reset(state); if (show_count || max_msg_quit_count > 0) { mesg_count++; if (show_count) do_stats(); if (max_msg_quit_count > 0 && mesg_count >= max_msg_quit_count) exit(0); } break; } /* * We must avoid blocking I/O, so get out of here as soon as both the * VSTREAM and kernel read buffers dry up. */ if (vstream_peek(state->stream) <= 0 && readable(vstream_fileno(state->stream)) <= 0) return (0); } return (0); } /* * The table of all SMTP commands that we can handle. */ typedef struct SINK_COMMAND { const char *name; void (*response) (SINK_STATE *, const char *); void (*hard_response) (SINK_STATE *); void (*soft_response) (SINK_STATE *); int flags; int delay; int delay_odds; } SINK_COMMAND; #define FLAG_ENABLE (1<<0) /* command is enabled */ #define FLAG_SYSLOG (1<<1) /* log the command */ #define FLAG_HARD_ERR (1<<2) /* report hard error */ #define FLAG_SOFT_ERR (1<<3) /* report soft error */ #define FLAG_DISCONNECT (1<<4) /* disconnect */ #define FLAG_CLOSE (1<<5) /* say goodbye and disconnect */ static SINK_COMMAND command_table[] = { "connect", conn_response, hard_err_resp, soft_err_resp, 0, 0, 0, "helo", helo_response, hard_err_resp, soft_err_resp, 0, 0, 0, "ehlo", ehlo_response, hard_err_resp, soft_err_resp, 0, 0, 0, "lhlo", ehlo_response, hard_err_resp, soft_err_resp, 0, 0, 0, "xclient", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, "xforward", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, "auth", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, "mail", mail_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, "rcpt", rcpt_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, "data", data_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, ".", dot_response, dot_resp_hard, dot_resp_soft, FLAG_ENABLE, 0, 0, "rset", rset_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, "noop", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, "vrfy", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, "quit", quit_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, 0, }; /* reset_cmd_flags - reset per-command command flags */ static void reset_cmd_flags(const char *cmd, int flags) { SINK_COMMAND *cmdp; for (cmdp = command_table; cmdp->name != 0; cmdp++) if (strcasecmp(cmd, cmdp->name) == 0) break; if (cmdp->name == 0) msg_fatal("unknown command: %s", cmd); cmdp->flags &= ~flags; } /* set_cmd_flags - set per-command command flags */ static void set_cmd_flags(const char *cmd, int flags) { SINK_COMMAND *cmdp; for (cmdp = command_table; cmdp->name != 0; cmdp++) if (strcasecmp(cmd, cmdp->name) == 0) break; if (cmdp->name == 0) msg_fatal("unknown command: %s", cmd); cmdp->flags |= flags; } /* set_cmds_flags - set per-command flags for multiple commands */ static void set_cmds_flags(const char *cmds, int flags) { char *saved_cmds; char *cp; char *cmd; saved_cmds = cp = mystrdup(cmds); while ((cmd = mystrtok(&cp, CHARS_COMMA_SP)) != 0) set_cmd_flags(cmd, flags); myfree(saved_cmds); } /* set_cmd_delay - set per-command delay */ static void set_cmd_delay(const char *cmd, int delay, int odds) { SINK_COMMAND *cmdp; for (cmdp = command_table; cmdp->name != 0; cmdp++) if (strcasecmp(cmd, cmdp->name) == 0) break; if (cmdp->name == 0) msg_fatal("unknown command: %s", cmd); if (delay <= 0) msg_fatal("non-positive '%s' delay", cmd); if (odds < 0 || odds > 99) msg_fatal("delay odds for '%s' out of range", cmd); cmdp->delay = delay; cmdp->delay_odds = odds; } /* set_cmd_delay_arg - set per-command delay from option argument */ static void set_cmd_delay_arg(char *arg) { char *cp; char *saved_arg; char *cmd; char *delay; char *odds; saved_arg = cp = mystrdup(arg); cmd = mystrtok(&cp, ":"); delay = mystrtok(&cp, ":"); if (cmd == 0 || delay == 0) msg_fatal("invalid command delay argument: %s", arg); odds = mystrtok(&cp, ""); set_cmd_delay(cmd, atoi(delay), odds ? atoi(odds) : 0); myfree(saved_arg); } /* command_resp - respond to command */ static int command_resp(SINK_STATE *state, SINK_COMMAND *cmdp, const char *command, const char *args) { /* We use raw syslog. Sanitize data content and length. */ if (cmdp->flags & FLAG_SYSLOG) syslog(LOG_INFO, "%s %.100s", command, args); if (cmdp->flags & FLAG_DISCONNECT) return (-1); if (cmdp->flags & FLAG_CLOSE) { smtp_printf(state->stream, "421 4.0.0 Server closing connection"); return (-1); } if (cmdp->flags & FLAG_HARD_ERR) { cmdp->hard_response(state); return (0); } if (cmdp->flags & FLAG_SOFT_ERR) { cmdp->soft_response(state); return (0); } if (cmdp->delay > 0) { int delay = cmdp->delay; if (cmdp->delay_odds > 0) for (delay = 0; ((int) (100.0 * rand() / (RAND_MAX + 1.0))) < cmdp->delay_odds; delay += cmdp->delay) /* NOP */ ; /* Suspend input event handling while delaying the command response. */ event_disable_readwrite(vstream_fileno(state->stream)); event_cancel_timer(read_timeout, (void *) state); event_request_timer(delay_event, (void *) state, delay); state->delayed_response = cmdp->response; state->delayed_args = mystrdup(args); } else { cmdp->response(state, args); if (cmdp->response == quit_response) return (-1); } return (0); } /* command_read - talk the SMTP protocol, server side */ static int command_read(SINK_STATE *state) { char *command; SINK_COMMAND *cmdp; int ch; struct cmd_trans { int state; int want; int next_state; }; static struct cmd_trans cmd_trans[] = { ST_ANY, '\r', ST_CR, ST_CR, '\n', ST_CR_LF, 0, 0, 0, }; struct cmd_trans *cp; char *ptr; /* * A read may result in EOF, but is never supposed to time out - a time * out means that we were trying to read when no data was available. */ #define NEXT_CHAR(state) \ (PUSH_BACK_PEEK(state) ? PUSH_BACK_GET(state) : VSTREAM_GETC(state->stream)) if (state->data_state == ST_CR_LF) state->data_state = ST_ANY; /* XXX */ for (;;) { if ((ch = NEXT_CHAR(state)) == VSTREAM_EOF) return (-1); /* * Sanity check. We don't want to store infinitely long commands. */ if (VSTRING_LEN(state->buffer) >= var_max_line_length) { msg_warn("command line too long"); return (-1); } VSTRING_ADDCH(state->buffer, ch); /* * Try to match the current character desired by the state machine. * If that fails, try to restart the machine with a match for its * first state. */ for (cp = cmd_trans; cp->state != state->data_state; cp++) if (cp->want == 0) msg_panic("command_read: unknown state: %d", state->data_state); if (ch == cp->want) state->data_state = cp->next_state; else if (ch == cmd_trans[0].want) state->data_state = cmd_trans[0].next_state; else state->data_state = ST_ANY; if (state->data_state == ST_CR_LF) break; /* * We must avoid blocking I/O, so get out of here as soon as both the * VSTREAM and kernel read buffers dry up. * * XXX Solaris non-blocking read() may fail on a socket when ioctl * FIONREAD reports there is unread data. Diagnosis by Max Pashkov. * As a workaround we use readable() (which uses poll or select()) * instead of peek_fd() (which uses ioctl FIONREAD). Workaround added * 20020604. */ if (PUSH_BACK_PEEK(state) == 0 && vstream_peek(state->stream) <= 0 && readable(vstream_fileno(state->stream)) <= 0) return (0); } /* * Properly terminate the result, and reset the buffer write pointer for * reading the next command. This is ugly, but not as ugly as trying to * deal with all the early returns below. */ vstring_truncate(state->buffer, VSTRING_LEN(state->buffer) - 2); VSTRING_TERMINATE(state->buffer); state->data_state = ST_CR_LF; VSTRING_RESET(state->buffer); /* * Got a complete command line. Parse it. */ ptr = vstring_str(state->buffer); if (msg_verbose) msg_info("%s", ptr); if ((command = mystrtok(&ptr, " \t")) == 0) { smtp_printf(state->stream, "500 5.5.2 Error: unknown command"); SMTP_FLUSH(state->stream); return (0); } for (cmdp = command_table; cmdp->name != 0; cmdp++) if (strcasecmp(command, cmdp->name) == 0) break; if (cmdp->name == 0 || (cmdp->flags & FLAG_ENABLE) == 0) { smtp_printf(state->stream, "500 5.5.1 Error: unknown command"); SMTP_FLUSH(state->stream); return (0); } return (command_resp(state, cmdp, command, printable(ptr, '?'))); } /* read_timeout - handle timer event */ static void read_timeout(int unused_event, void *context) { SINK_STATE *state = (SINK_STATE *) context; /* * We don't send anything to the client, because we would have to set up * an smtp_stream exception handler first. And that is just too much * trouble. */ msg_warn("read timeout"); disconnect(state); } /* read_event - handle command or data read events */ static void read_event(int unused_event, void *context) { SINK_STATE *state = (SINK_STATE *) context; /* * The input reading routine not only reads input (with vstream calls) * but also produces output (with smtp_stream calls). Because the output * routines can raise timeout or EOF exceptions with vstream_longjmp(), * the input reading routine needs to set up corresponding exception * handlers with vstream_setjmp(). Guarding the input operations in the * same manner is not useful: we must read input in non-blocking mode, so * we never get called when the socket stays unreadable too long. And EOF * is already trivial to detect with the vstream calls. */ do { switch (vstream_setjmp(state->stream)) { default: msg_panic("unknown read/write error"); /* NOTREACHED */ case SMTP_ERR_TIME: msg_warn("write timeout"); disconnect(state); return; case SMTP_ERR_EOF: msg_warn("lost connection"); disconnect(state); return; case 0: if (state->read_fn(state) < 0) { if (msg_verbose) msg_info("disconnect"); disconnect(state); return; } } } while (PUSH_BACK_PEEK(state) != 0 || vstream_peek(state->stream) > 0); /* * Reset the idle timer. Wait until the next input event, or until the * idle timer goes off. */ event_request_timer(read_timeout, (void *) state, var_tmout); } static void connect_event(int, void *); /* disconnect - handle disconnection events */ static void disconnect(SINK_STATE *state) { event_disable_readwrite(vstream_fileno(state->stream)); event_cancel_timer(read_timeout, (void *) state); if (show_count) { sess_count++; do_stats(); } vstream_fclose(state->stream); vstring_free(state->buffer); /* Clean up file capture attributes. */ if (state->helo_args) myfree(state->helo_args); /* Delete incomplete mail transaction. */ mail_cmd_reset(state); if (state->delayed_args) myfree(state->delayed_args); myfree((void *) state); if (max_quit_count > 0 && quit_count >= max_quit_count) exit(0); if (client_count-- == max_client_count) event_enable_read(sock, connect_event, (void *) 0); } /* connect_event - handle connection events */ static void connect_event(int unused_event, void *unused_context) { struct sockaddr_storage ss; SOCKADDR_SIZE len = sizeof(ss); struct sockaddr *sa = (struct sockaddr *) &ss; SINK_STATE *state; int fd; if ((fd = sane_accept(sock, sa, &len)) >= 0) { /* Safety: limit the number of open sockets and capture files. */ if (++client_count == max_client_count) event_disable_readwrite(sock); state = (SINK_STATE *) mymalloc(sizeof(*state)); if (strchr((char *) proto_info->sa_family_list, sa->sa_family)) SOCKADDR_TO_HOSTADDR(sa, len, &state->client_addr, (MAI_SERVPORT_STR *) 0, sa->sa_family); else strncpy(state->client_addr.buf, "local", sizeof("local") + 0); if (msg_verbose) msg_info("connect (%s %s)", #ifdef AF_LOCAL sa->sa_family == AF_LOCAL ? "AF_LOCAL" : #else sa->sa_family == AF_UNIX ? "AF_UNIX" : #endif sa->sa_family == AF_INET ? "AF_INET" : #ifdef AF_INET6 sa->sa_family == AF_INET6 ? "AF_INET6" : #endif "unknown protocol family", state->client_addr.buf); non_blocking(fd, NON_BLOCKING); state->stream = vstream_fdopen(fd, O_RDWR); vstream_tweak_sock(state->stream); state->buffer = vstring_alloc(1024); state->read_fn = command_read; state->data_state = ST_ANY; PUSH_BACK_SET(state, ""); smtp_timeout_setup(state->stream, var_tmout); state->in_mail = 0; state->rcpts = 0; state->delayed_response = 0; state->delayed_args = 0; /* Initialize file capture attributes. */ #ifdef AF_INET6 if (sa->sa_family == AF_INET6) state->addr_prefix = "ipv6:"; else #endif state->addr_prefix = ""; state->helo_args = 0; state->client_proto = enable_lmtp ? "LMTP" : "SMTP"; state->start_time = 0; state->id = 0; state->dump_file = 0; /* * We use the smtp_stream module to produce output. That module * throws an exception via vstream_longjmp() in case of a timeout or * lost connection error. Therefore we must prepare to handle these * exceptions with vstream_setjmp(). */ switch (vstream_setjmp(state->stream)) { default: msg_panic("unknown read/write error"); /* NOTREACHED */ case SMTP_ERR_TIME: msg_warn("write timeout"); disconnect(state); return; case SMTP_ERR_EOF: msg_warn("lost connection"); disconnect(state); return; case 0: if (command_resp(state, command_table, "connect", "") < 0) disconnect(state); else if (command_table->delay == 0) { event_enable_read(fd, read_event, (void *) state); event_request_timer(read_timeout, (void *) state, var_tmout); } } } } /* usage - explain */ static void usage(char *myname) { msg_fatal("usage: %s [-468acCeEFLpPv] [-A abort_delay] [-b soft_bounce_reply] [-B hard_bounce_reply] [-d dump-template] [-D dump-template] [-f commands] [-h hostname] [-m max_concurrency] [-M message_quit_count] [-n quit_count] [-q commands] [-r commands] [-R root-dir] [-s commands] [-S start-string] [-u user_privs] [-w delay] [host]:port backlog", myname); } MAIL_VERSION_STAMP_DECLARE; int main(int argc, char **argv) { int backlog; int ch; int delay; const char *protocols = INET_PROTO_NAME_ALL; const char *root_dir = 0; const char *user_privs = 0; /* * Fingerprint executables and core dumps. */ MAIL_VERSION_STAMP_ALLOCATE; /* * Fix 20051207. */ signal(SIGPIPE, SIG_IGN); /* * Initialize diagnostics. */ msg_vstream_init(argv[0], VSTREAM_ERR); /* * Parse JCL. */ while ((ch = GETOPT(argc, argv, "468aA:b:B:cCd:D:eEf:Fh:H:Ln:m:M:NpPq:Q:r:R:s:S:t:T:u:vw:W:")) > 0) { switch (ch) { case '4': protocols = INET_PROTO_NAME_IPV4; break; case '6': protocols = INET_PROTO_NAME_IPV6; break; case '8': disable_8bitmime = 1; break; case 'a': disable_saslauth = 1; break; case 'A': if (!alldig(optarg) || (abort_delay = atoi(optarg)) < 0) usage(argv[0]); break; case 'b': if (optarg[0] != '4' || strspn(optarg, "0123456789") != 3) { msg_error("bad soft error reply: %s", optarg); usage(argv[0]); } else soft_error_resp = optarg; break; case 'B': if (optarg[0] != '5' || strspn(optarg, "0123456789") != 3) { msg_error("bad hard error reply: %s", optarg); usage(argv[0]); } else hard_error_resp = optarg; break; case 'c': show_count++; break; case 'C': disable_xclient = 1; reset_cmd_flags("xclient", FLAG_ENABLE); break; case 'd': single_template = optarg; break; case 'D': shared_template = optarg; break; case 'e': disable_esmtp = 1; break; case 'E': disable_enh_status = 1; break; case 'f': set_cmds_flags(optarg, FLAG_HARD_ERR); disable_pipelining = 1; break; case 'F': disable_xforward = 1; reset_cmd_flags("xforward", FLAG_ENABLE); break; case 'h': var_myhostname = optarg; break; case 'H': if ((data_read_delay = atoi(optarg)) <= 0) msg_fatal("bad data read delay: %s", optarg); break; case 'L': enable_lmtp = 1; break; case 'm': if ((max_client_count = atoi(optarg)) <= 0) msg_fatal("bad concurrency limit: %s", optarg); break; case 'M': if ((max_msg_quit_count = atoi(optarg)) <= 0) msg_fatal("bad message quit count: %s", optarg); break; case 'n': if ((max_quit_count = atoi(optarg)) <= 0) msg_fatal("bad quit count: %s", optarg); break; case 'N': disable_dsn = 1; break; case 'p': disable_pipelining = 1; break; case 'P': pretend_pix = 1; disable_esmtp = 1; break; case 'q': set_cmds_flags(optarg, FLAG_DISCONNECT); break; case 'Q': set_cmds_flags(optarg, FLAG_CLOSE); break; case 'r': set_cmds_flags(optarg, FLAG_SOFT_ERR); disable_pipelining = 1; break; case 'R': root_dir = optarg; break; case 's': openlog(basename(argv[0]), LOG_PID, LOG_MAIL); set_cmds_flags(optarg, FLAG_SYSLOG); break; case 'S': start_string = vstring_alloc(10); unescape(start_string, optarg); break; case 't': if ((var_tmout = atoi(optarg)) <= 0) msg_fatal("bad timeout: %s", optarg); break; case 'T': if ((inet_windowsize = atoi(optarg)) <= 0) msg_fatal("bad TCP window size: %s", optarg); break; case 'u': user_privs = optarg; break; case 'v': msg_verbose++; break; case 'w': if ((delay = atoi(optarg)) <= 0) usage(argv[0]); set_cmd_delay("data", delay, 0); break; case 'W': set_cmd_delay_arg(optarg); break; default: usage(argv[0]); } } if (argc - optind != 2) usage(argv[0]); if ((backlog = atoi(argv[optind + 1])) <= 0) usage(argv[0]); if (single_template && shared_template) msg_fatal("use only one of -d or -D, but not both"); if (geteuid() == 0 && user_privs == 0) msg_fatal("-u option is required if running as root"); /* * Initialize. */ if (var_myhostname == 0) var_myhostname = "smtp-sink"; set_cmds_flags(enable_lmtp ? "lhlo" : disable_esmtp ? "helo" : "helo, ehlo", FLAG_ENABLE); proto_info = inet_proto_init("protocols", protocols); if (strncmp(argv[optind], "unix:", 5) == 0) { sock = unix_listen(argv[optind] + 5, backlog, BLOCKING); } else { if (strncmp(argv[optind], "inet:", 5) == 0) argv[optind] += 5; sock = inet_listen(argv[optind], backlog, BLOCKING); } if (user_privs) chroot_uid(root_dir, user_privs); if (single_template) mysrand((int) time((time_t *) 0)); else if (shared_template) single_template = shared_template; /* * Start the event handler. */ event_enable_read(sock, connect_event, (void *) 0); for (;;) event_loop(-1); }