diff options
Diffstat (limited to '')
-rw-r--r-- | src/smtpstone/smtp-source.c | 1188 |
1 files changed, 1188 insertions, 0 deletions
diff --git a/src/smtpstone/smtp-source.c b/src/smtpstone/smtp-source.c new file mode 100644 index 0000000..be388d6 --- /dev/null +++ b/src/smtpstone/smtp-source.c @@ -0,0 +1,1188 @@ +/*++ +/* NAME +/* smtp-source 1 +/* SUMMARY +/* parallelized SMTP/LMTP test generator +/* SYNOPSIS +/* .fi +/* \fBsmtp-source\fR [\fIoptions\fR] [\fBinet:\fR]\fIhost\fR[:\fIport\fR] +/* +/* \fBsmtp-source\fR [\fIoptions\fR] \fBunix:\fIpathname\fR +/* DESCRIPTION +/* \fBsmtp-source\fR connects to the named \fIhost\fR and TCP \fIport\fR +/* (default: port 25) +/* and sends one or more messages to it, either sequentially +/* or in parallel. The program speaks either SMTP (default) or +/* LMTP. +/* Connections can be made to UNIX-domain and IPv4 or IPv6 servers. +/* IPv4 and IPv6 are the default. +/* +/* Note: this is an unsupported test program. No attempt is made +/* to maintain compatibility between successive versions. +/* +/* Arguments: +/* .IP \fB-4\fR +/* Connect to the server with IPv4. This option has no effect when +/* Postfix is built without IPv6 support. +/* .IP \fB-6\fR +/* Connect to the server with IPv6. This option is not available when +/* Postfix is built without IPv6 support. +/* .IP "\fB-A\fR" +/* Don't abort when the server sends something other than the +/* expected positive reply code. +/* .IP \fB-c\fR +/* Display a running counter that is incremented each time +/* an SMTP DATA command completes. +/* .IP "\fB-C \fIcount\fR" +/* When a host sends RESET instead of SYN|ACK, try \fIcount\fR times +/* before giving up. The default count is 1. Specify a larger count in +/* order to work around a problem with TCP/IP stacks that send RESET +/* when the listen queue is full. +/* .IP \fB-d\fR +/* Don't disconnect after sending a message; send the next +/* message over the same connection. +/* .IP "\fB-f \fIfrom\fR" +/* Use the specified sender address (default: <foo@myhostname>). +/* .IP "\fB-F \fIfile\fR" +/* Send the pre-formatted message header and body in the +/* specified \fIfile\fR, while prepending '.' before lines that +/* begin with '.', and while appending CRLF after each line. +/* .IP "\fB-l \fIlength\fR" +/* Send \fIlength\fR bytes as message payload. The length does not +/* include message headers. +/* .IP \fB-L\fR +/* Speak LMTP rather than SMTP. +/* .IP "\fB-m \fImessage_count\fR" +/* Send the specified number of messages (default: 1). +/* .IP "\fB-M \fImyhostname\fR" +/* Use the specified hostname or [address] in the HELO command +/* and in the default sender and recipient addresses, instead +/* of the machine hostname. +/* .IP "\fB-N\fR" +/* Prepend a non-repeating sequence number to each recipient +/* address. This avoids the artificial 100% hit rate in the +/* resolve and rewrite client caches and exercises the +/* trivial-rewrite daemon, better approximating Postfix +/* performance under real-life work-loads. +/* .IP \fB-o\fR +/* Old mode: don't send HELO, and don't send message headers. +/* .IP "\fB-r \fIrecipient_count\fR" +/* Send the specified number of recipients per transaction (default: 1). +/* Recipient names are generated by prepending a number to the +/* recipient address. +/* .IP "\fB-R \fIinterval\fR" +/* Wait for a random period of time 0 <= n <= interval between messages. +/* Suspending one thread does not affect other delivery threads. +/* .IP "\fB-s \fIsession_count\fR" +/* Run the specified number of SMTP sessions in parallel (default: 1). +/* .IP "\fB-S \fIsubject\fR" +/* Send mail with the named subject line (default: none). +/* .IP "\fB-t \fIto\fR" +/* Use the specified recipient address (default: <foo@myhostname>). +/* .IP "\fB-T \fIwindowsize\fR" +/* Override the default TCP window size. To work around +/* broken TCP window scaling implementations, specify a +/* value > 0 and < 65536. +/* .IP \fB-v\fR +/* Make the program more verbose, for debugging purposes. +/* .IP "\fB-w \fIinterval\fR" +/* Wait a fixed time between messages. +/* Suspending one thread does not affect other delivery threads. +/* .IP [\fBinet:\fR]\fIhost\fR[:\fIport\fR] +/* Connect via TCP to host \fIhost\fR, port \fIport\fR. The default +/* port is \fBsmtp\fR. +/* .IP \fBunix:\fIpathname\fR +/* Connect to the UNIX-domain socket at \fIpathname\fR. +/* BUGS +/* No SMTP command pipelining support. +/* SEE ALSO +/* smtp-sink(1), SMTP/LMTP message dump +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <netinet/in.h> +#include <sys/un.h> +#include <stdarg.h> +#include <string.h> +#include <ctype.h> +#include <stdlib.h> +#include <unistd.h> +#include <signal.h> +#include <fcntl.h> +#include <errno.h> + +/* Utility library. */ + +#include <msg.h> +#include <msg_vstream.h> +#include <vstring.h> +#include <vstream.h> +#include <vstring_vstream.h> +#include <get_hostname.h> +#include <split_at.h> +#include <connect.h> +#include <mymalloc.h> +#include <events.h> +#include <iostuff.h> +#include <sane_connect.h> +#include <host_port.h> +#include <myaddrinfo.h> +#include <inet_proto.h> +#include <valid_hostname.h> +#include <valid_mailhost_addr.h> +#include <compat_va_copy.h> + +/* Global library. */ + +#include <smtp_stream.h> +#include <mail_date.h> +#include <mail_version.h> + +/* Application-specific. */ + + /* + * Per-session data structure with state. + * + * This software can maintain multiple parallel connections to the same SMTP + * server. However, it makes no more than one connection request at a time + * to avoid overwhelming the server with SYN packets and having to back off. + * Back-off would screw up the benchmark. Pending connection requests are + * kept in a linear list. + */ +typedef struct SESSION { + int xfer_count; /* # of xfers in session */ + int rcpt_done; /* # of recipients done */ + int rcpt_count; /* # of recipients to go */ + int rcpt_accepted; /* # of recipients accepted */ + VSTREAM *stream; /* open connection */ + int connect_count; /* # of connect()s to retry */ + struct SESSION *next; /* connect() queue linkage */ +} SESSION; + +static SESSION *last_session; /* connect() queue tail */ + + /* + * Structure with broken-up SMTP server response. + */ +typedef struct { /* server response */ + int code; /* status */ + char *str; /* text */ + VSTRING *buf; /* origin of text */ +} RESPONSE; + +static VSTRING *buffer; +static int var_line_limit = 10240; +static int var_timeout = 300; +static const char *var_myhostname; +static int session_count; +static int message_count = 1; +static struct sockaddr_storage ss; + +#undef sun +static struct sockaddr_un sun; +static struct sockaddr *sa; +static int sa_length; +static int recipients = 1; +static char *defaddr; +static char *recipient; +static char *sender; +static char *message_data; +static int message_length; +static int disconnect = 1; +static int count = 0; +static int counter = 0; +static int send_helo_first = 1; +static int send_headers = 1; +static int connect_count = 1; +static int random_delay = 0; +static int fixed_delay = 0; +static int talk_lmtp = 0; +static char *subject = 0; +static int number_rcpts = 0; +static int allow_reject = 0; + +static void enqueue_connect(SESSION *); +static void start_connect(SESSION *); +static void connect_done(int, void *); +static void read_banner(int, void *); +static void send_helo(SESSION *); +static void helo_done(int, void *); +static void send_mail(SESSION *); +static void mail_done(int, void *); +static void send_rcpt(int, void *); +static void rcpt_done(int, void *); +static void send_data(int, void *); +static void data_done(int, void *); +static void dot_done(int, void *); +static void send_rset(int, void *); +static void rset_done(int, void *); +static void send_quit(SESSION *); +static void quit_done(int, void *); +static void close_session(SESSION *); + +/* random_interval - generate a random value in 0 .. (small) interval */ + +static int random_interval(int interval) +{ + return (rand() % (interval + 1)); +} + +/* command - send an SMTP command */ + +static void command(VSTREAM *stream, char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + + /* + * Optionally, log the command before actually sending, so we can see + * what the program is trying to do. + */ + if (msg_verbose) { + va_list ap2; + + VA_COPY(ap2, ap); + vmsg_info(fmt, ap2); + va_end(ap2); + } + smtp_vprintf(stream, fmt, ap); + va_end(ap); + smtp_flush(stream); +} + +/* socket_error - look up and reset the last socket error */ + +static int socket_error(int sock) +{ + int error; + SOCKOPT_SIZE error_len; + + /* + * Some Solaris 2 versions have getsockopt() itself return the error, + * instead of returning it via the parameter list. + */ + error = 0; + error_len = sizeof(error); + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void *) &error, &error_len) < 0) + return (-1); + if (error) { + errno = error; + return (-1); + } + + /* + * No problems. + */ + return (0); +} + +/* response - read and process SMTP server response */ + +static RESPONSE *response(VSTREAM *stream, VSTRING *buf) +{ + static RESPONSE rdata; + int more; + char *cp; + + /* + * Initialize the response data buffer. smtp_get() defends against a + * denial of service attack by limiting the amount of single-line text, + * and the loop below limits the amount of multi-line text that we are + * willing to store. + */ + if (rdata.buf == 0) + rdata.buf = vstring_alloc(100); + + /* + * Censor out non-printable characters in server responses. Concatenate + * multi-line server responses. Separate the status code from the text. + * Leave further parsing up to the application. + */ +#define BUF ((char *) vstring_str(buf)) + VSTRING_RESET(rdata.buf); + for (;;) { + smtp_get(buf, stream, var_line_limit, SMTP_GET_FLAG_SKIP); + for (cp = BUF; *cp != 0; cp++) + if (!ISPRINT(*cp) && !ISSPACE(*cp)) + *cp = '?'; + cp = BUF; + if (msg_verbose) + msg_info("<<< %s", cp); + while (ISDIGIT(*cp)) + cp++; + rdata.code = (cp - BUF == 3 ? atoi(BUF) : 0); + if ((more = (*cp == '-')) != 0) + cp++; + while (ISSPACE(*cp)) + cp++; + if (VSTRING_LEN(rdata.buf) < var_line_limit) + vstring_strcat(rdata.buf, cp); + if (more == 0) + break; + if (VSTRING_LEN(rdata.buf) < var_line_limit) + VSTRING_ADDCH(rdata.buf, '\n'); + } + VSTRING_TERMINATE(rdata.buf); + rdata.str = vstring_str(rdata.buf); + return (&rdata); +} + +/* exception_text - translate exceptions from the smtp_stream module */ + +static char *exception_text(int except) +{ + switch (except) { + case SMTP_ERR_EOF: + return ("lost connection"); + case SMTP_ERR_TIME: + return ("timeout"); + default: + msg_panic("exception_text: unknown exception %d", except); + } + /* NOTREACHED */ +} + +/* startup - connect to server but do not wait */ + +static void startup(SESSION *session) +{ + if (message_count-- <= 0) { + myfree((void *) session); + session_count--; + return; + } + if (session->stream == 0) { + enqueue_connect(session); + } else { + send_mail(session); + } +} + +/* start_event - invoke startup from timer context */ + +static void start_event(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + + startup(session); +} + +/* start_another - start another session */ + +static void start_another(SESSION *session) +{ + if (random_delay > 0) { + event_request_timer(start_event, (void *) session, + random_interval(random_delay)); + } else if (fixed_delay > 0) { + event_request_timer(start_event, (void *) session, fixed_delay); + } else { + startup(session); + } +} + +/* enqueue_connect - queue a connection request */ + +static void enqueue_connect(SESSION *session) +{ + session->next = 0; + if (last_session == 0) { + last_session = session; + start_connect(session); + } else { + last_session->next = session; + last_session = session; + } +} + +/* dequeue_connect - connection request completed */ + +static void dequeue_connect(SESSION *session) +{ + if (session == last_session) { + if (session->next != 0) + msg_panic("dequeue_connect: queue ends after last"); + last_session = 0; + } else { + if (session->next == 0) + msg_panic("dequeue_connect: queue ends before last"); + start_connect(session->next); + } +} + +/* fail_connect - handle failed startup */ + +static void fail_connect(SESSION *session) +{ + if (session->connect_count-- == 1) + msg_fatal("connect: %m"); + msg_warn("connect: %m"); + event_disable_readwrite(vstream_fileno(session->stream)); + vstream_fclose(session->stream); + session->stream = 0; +#ifdef MISSING_USLEEP + doze(10); +#else + usleep(10); +#endif + start_connect(session); +} + +/* start_connect - start TCP handshake */ + +static void start_connect(SESSION *session) +{ + int fd; + struct linger linger; + + /* + * Some systems don't set the socket error when connect() fails early + * (loopback) so we must deal with the error immediately, rather than + * retrieving it later with getsockopt(). We can't use MSG_PEEK to + * distinguish between server disconnect and connection refused. + */ + if ((fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) + msg_fatal("socket: %m"); + (void) non_blocking(fd, NON_BLOCKING); + linger.l_onoff = 1; + linger.l_linger = 0; + if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (void *) &linger, + sizeof(linger)) < 0) + msg_warn("setsockopt SO_LINGER %d: %m", linger.l_linger); + session->stream = vstream_fdopen(fd, O_RDWR); + event_enable_write(fd, connect_done, (void *) session); + smtp_timeout_setup(session->stream, var_timeout); + if (inet_windowsize > 0) + set_inet_windowsize(fd, inet_windowsize); + if (sane_connect(fd, sa, sa_length) < 0 && errno != EINPROGRESS) + fail_connect(session); +} + +/* connect_done - send message sender info */ + +static void connect_done(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + int fd = vstream_fileno(session->stream); + + /* + * Try again after some delay when the connection failed, in case they + * run a Mickey Mouse protocol stack. + */ + if (socket_error(fd) < 0) { + fail_connect(session); + } else { + non_blocking(fd, BLOCKING); + /* Disable write events. */ + event_disable_readwrite(fd); + event_enable_read(fd, read_banner, (void *) session); + dequeue_connect(session); + /* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */ + if (sa->sa_family == AF_INET +#ifdef AF_INET6 + || sa->sa_family == AF_INET6 +#endif + ) + vstream_tweak_tcp(session->stream); + } +} + +/* read_banner - receive SMTP server greeting */ + +static void read_banner(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + RESPONSE *resp; + int except; + + /* + * Prepare for disaster. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while reading server greeting", exception_text(except)); + + /* + * Read and parse the server's SMTP greeting banner. + */ + if (((resp = response(session->stream, buffer))->code / 100) == 2) { + /* void */ ; + } else if (allow_reject) { + msg_warn("rejected at server banner: %d %s", resp->code, resp->str); + } else { + msg_fatal("rejected at server banner: %d %s", resp->code, resp->str); + } + + /* + * Send helo or send the envelope sender address. + */ + if (send_helo_first) + send_helo(session); + else + send_mail(session); +} + +/* send_helo - send hostname */ + +static void send_helo(SESSION *session) +{ + int except; + const char *NOCLOBBER protocol = (talk_lmtp ? "LHLO" : "HELO"); + + /* + * Send the standard greeting with our hostname + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending %s", exception_text(except), protocol); + + command(session->stream, "%s %s", protocol, var_myhostname); + + /* + * Prepare for the next event. + */ + event_enable_read(vstream_fileno(session->stream), helo_done, (void *) session); +} + +/* helo_done - handle HELO response */ + +static void helo_done(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + RESPONSE *resp; + int except; + const char *protocol = (talk_lmtp ? "LHLO" : "HELO"); + + /* + * Get response to HELO command. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending %s", exception_text(except), protocol); + + if ((resp = response(session->stream, buffer))->code / 100 == 2) { + /* void */ ; + } else if (allow_reject) { + msg_warn("%s rejected: %d %s", protocol, resp->code, resp->str); + if (resp->code == 421 || resp->code == 521) { + close_session(session); + return; + } + } else { + msg_fatal("%s rejected: %d %s", protocol, resp->code, resp->str); + } + + send_mail(session); +} + +/* send_mail - send envelope sender */ + +static void send_mail(SESSION *session) +{ + int except; + + /* + * Send the envelope sender address. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending sender", exception_text(except)); + + command(session->stream, "MAIL FROM:<%s>", sender); + + /* + * Prepare for the next event. + */ + event_enable_read(vstream_fileno(session->stream), mail_done, (void *) session); +} + +/* mail_done - handle MAIL response */ + +static void mail_done(int unused, void *context) +{ + SESSION *session = (SESSION *) context; + RESPONSE *resp; + int except; + + /* + * Get response to MAIL command. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending sender", exception_text(except)); + + if ((resp = response(session->stream, buffer))->code / 100 == 2) { + session->rcpt_count = recipients; + session->rcpt_done = 0; + session->rcpt_accepted = 0; + send_rcpt(unused, context); + } else if (allow_reject) { + msg_warn("sender rejected: %d %s", resp->code, resp->str); + if (resp->code == 421 || resp->code == 521) { + close_session(session); + return; + } + send_rset(unused, context); + } else { + msg_fatal("sender rejected: %d %s", resp->code, resp->str); + } +} + +/* send_rcpt - send recipient address */ + +static void send_rcpt(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + int except; + + /* + * Send envelope recipient address. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending recipient", exception_text(except)); + + if (session->rcpt_count > 1 || number_rcpts > 0) + command(session->stream, "RCPT TO:<%d%s>", + number_rcpts ? number_rcpts++ : session->rcpt_count, + recipient); + else + command(session->stream, "RCPT TO:<%s>", recipient); + session->rcpt_count--; + session->rcpt_done++; + + /* + * Prepare for the next event. + */ + event_enable_read(vstream_fileno(session->stream), rcpt_done, (void *) session); +} + +/* rcpt_done - handle RCPT completion */ + +static void rcpt_done(int unused, void *context) +{ + SESSION *session = (SESSION *) context; + RESPONSE *resp; + int except; + + /* + * Get response to RCPT command. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending recipient", exception_text(except)); + + if ((resp = response(session->stream, buffer))->code / 100 == 2) { + session->rcpt_accepted++; + } else if (allow_reject) { + msg_warn("recipient rejected: %d %s", resp->code, resp->str); + if (resp->code == 421 || resp->code == 521) { + close_session(session); + return; + } + } else { + msg_fatal("recipient rejected: %d %s", resp->code, resp->str); + } + + /* + * Send another RCPT command or send DATA. + */ + if (session->rcpt_count > 0) + send_rcpt(unused, context); + else if (session->rcpt_accepted > 0) + send_data(unused, context); + else + send_rset(unused, context); +} + +/* send_data - send DATA command */ + +static void send_data(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + int except; + + /* + * Request data transmission. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending DATA command", exception_text(except)); + command(session->stream, "DATA"); + + /* + * Prepare for the next event. + */ + event_enable_read(vstream_fileno(session->stream), data_done, (void *) session); +} + +/* data_done - send message content */ + +static void data_done(int unused, void *context) +{ + SESSION *session = (SESSION *) context; + RESPONSE *resp; + int except; + static const char *mydate; + static int mypid; + + /* + * Get response to DATA command. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending DATA command", exception_text(except)); + if ((resp = response(session->stream, buffer))->code == 354) { + /* see below */ ; + } else if (allow_reject) { + msg_warn("data rejected: %d %s", resp->code, resp->str); + if (resp->code == 421 || resp->code == 521) { + close_session(session); + return; + } + send_rset(unused, context); + return; + } else { + msg_fatal("data rejected: %d %s", resp->code, resp->str); + } + + /* + * Send basic header to keep mailers that bother to examine them happy. + */ + if (send_headers) { + if (mydate == 0) { + mydate = mail_date(time((time_t *) 0)); + mypid = getpid(); + } + smtp_printf(session->stream, "From: <%s>", sender); + smtp_printf(session->stream, "To: <%s>", recipient); + smtp_printf(session->stream, "Date: %s", mydate); + smtp_printf(session->stream, "Message-Id: <%04x.%04x.%04x@%s>", + mypid, vstream_fileno(session->stream), message_count, var_myhostname); + if (subject) + smtp_printf(session->stream, "Subject: %s", subject); + smtp_fputs("", 0, session->stream); + } + + /* + * Send some garbage. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending message", exception_text(except)); + if (message_length == 0) { + smtp_fputs("La de da de da 1.", 17, session->stream); + smtp_fputs("La de da de da 2.", 17, session->stream); + smtp_fputs("La de da de da 3.", 17, session->stream); + smtp_fputs("La de da de da 4.", 17, session->stream); + } else { + + /* + * XXX This may cause the process to block with message content + * larger than VSTREAM_BUFIZ bytes. + */ + smtp_fputs(message_data, message_length, session->stream); + } + + /* + * Send end of message and process the server response. + */ + command(session->stream, "."); + + /* + * Update the running counter. + */ + if (count) { + counter++; + vstream_printf("%d\r", counter); + vstream_fflush(VSTREAM_OUT); + } + + /* + * Prepare for the next event. + */ + event_enable_read(vstream_fileno(session->stream), dot_done, (void *) session); +} + +/* dot_done - send QUIT or start another transaction */ + +static void dot_done(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + RESPONSE *resp; + int except; + + /* + * Get response to "." command. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending message", exception_text(except)); + do { /* XXX this could block */ + if ((resp = response(session->stream, buffer))->code / 100 == 2) { + /* void */ ; + } else if (allow_reject) { + msg_warn("end of data rejected: %d %s", resp->code, resp->str); + if (resp->code == 421 || resp->code == 521) { + close_session(session); + return; + } + } else { + msg_fatal("end of data rejected: %d %s", resp->code, resp->str); + } + } while (talk_lmtp && --session->rcpt_done > 0); + session->xfer_count++; + + /* + * Say goodbye or send the next message. + */ + if (disconnect || message_count < 1) { + send_quit(session); + } else { + event_disable_readwrite(vstream_fileno(session->stream)); + start_another(session); + } +} + +/* send_rset - send RSET command */ + +static void send_rset(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + + command(session->stream, "RSET"); + event_enable_read(vstream_fileno(session->stream), rset_done, (void *) session); +} + +/* rset_done - handle RSET reply */ + +static void rset_done(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + RESPONSE *resp; + int except; + + /* + * Get response to RSET command. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending message", exception_text(except)); + if ((resp = response(session->stream, buffer))->code / 100 == 2) { + /* void */ + } else if (allow_reject) { + msg_warn("rset rejected: %d %s", resp->code, resp->str); + if (resp->code == 421 || resp->code == 521) { + close_session(session); + return; + } + } else { + msg_fatal("rset rejected: %d %s", resp->code, resp->str); + } + + /* + * Say goodbye or send the next message. + */ + if (disconnect || message_count < 1) { + send_quit(session); + } else { + event_disable_readwrite(vstream_fileno(session->stream)); + start_another(session); + } +} + +/* send_quit - send QUIT command */ + +static void send_quit(SESSION *session) +{ + command(session->stream, "QUIT"); + event_enable_read(vstream_fileno(session->stream), quit_done, (void *) session); +} + +/* quit_done - disconnect */ + +static void quit_done(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + + (void) response(session->stream, buffer); + event_disable_readwrite(vstream_fileno(session->stream)); + vstream_fclose(session->stream); + session->stream = 0; + start_another(session); +} + +/* close_session - disconnect, for example after 421 or 521 reply */ + +static void close_session(SESSION *session) +{ + event_disable_readwrite(vstream_fileno(session->stream)); + vstream_fclose(session->stream); + session->stream = 0; + start_another(session); +} + +/* usage - explain */ + +static void usage(char *myname) +{ + msg_fatal("usage: %s -cdLNov -s sess -l msglen -m msgs -C count -M myhostname -f from -t to -r rcptcount -R delay -w delay host[:port]", myname); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - parse JCL and start the machine */ + +int main(int argc, char **argv) +{ + SESSION *session; + char *host; + char *port; + char *path; + int path_len; + int sessions = 1; + int ch; + int i; + char *buf; + const char *parse_err; + struct addrinfo *res; + int aierr; + const char *protocols = INET_PROTO_NAME_ALL; + char *message_file = 0; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + signal(SIGPIPE, SIG_IGN); + msg_vstream_init(argv[0], VSTREAM_ERR); + + /* + * Parse JCL. + */ + while ((ch = GETOPT(argc, argv, "46AcC:df:F:l:Lm:M:Nor:R:s:S:t:T:vw:")) > 0) { + switch (ch) { + case '4': + protocols = INET_PROTO_NAME_IPV4; + break; + case '6': + protocols = INET_PROTO_NAME_IPV6; + break; + case 'A': + allow_reject = 1; + break; + case 'c': + count++; + break; + case 'C': + if ((connect_count = atoi(optarg)) <= 0) + msg_fatal("bad connection count: %s", optarg); + break; + case 'd': + disconnect = 0; + break; + case 'f': + sender = optarg; + break; + case 'F': + if (message_file == 0 && message_length > 0) + msg_fatal("-l option cannot be used with -F"); + message_file = optarg; + break; + case 'l': + if (message_file != 0) + msg_fatal("-l option cannot be used with -F"); + if ((message_length = atoi(optarg)) <= 0) + msg_fatal("bad message length: %s", optarg); + break; + case 'L': + talk_lmtp = 1; + break; + case 'm': + if ((message_count = atoi(optarg)) <= 0) + msg_fatal("bad message count: %s", optarg); + break; + case 'M': + if (*optarg == '[') { + if (!valid_mailhost_literal(optarg, DO_GRIPE)) + msg_fatal("bad address literal: %s", optarg); + } else { + if (!valid_hostname(optarg, DO_GRIPE)) + msg_fatal("bad hostname: %s", optarg); + } + var_myhostname = optarg; + break; + case 'N': + number_rcpts = 1; + break; + case 'o': + send_helo_first = 0; + send_headers = 0; + break; + case 'r': + if ((recipients = atoi(optarg)) <= 0) + msg_fatal("bad recipient count: %s", optarg); + break; + case 'R': + if (fixed_delay > 0) + msg_fatal("do not use -w and -R options at the same time"); + if ((random_delay = atoi(optarg)) <= 0) + msg_fatal("bad random delay: %s", optarg); + break; + case 's': + if ((sessions = atoi(optarg)) <= 0) + msg_fatal("bad session count: %s", optarg); + break; + case 'S': + subject = optarg; + break; + case 't': + recipient = optarg; + break; + case 'T': + if ((inet_windowsize = atoi(optarg)) <= 0) + msg_fatal("bad TCP window size: %s", optarg); + break; + case 'v': + msg_verbose++; + break; + case 'w': + if (random_delay > 0) + msg_fatal("do not use -w and -R options at the same time"); + if ((fixed_delay = atoi(optarg)) <= 0) + msg_fatal("bad fixed delay: %s", optarg); + break; + default: + usage(argv[0]); + } + } + if (argc - optind != 1) + usage(argv[0]); + + if (random_delay > 0) + srand(getpid()); + + /* + * Initialize the message content, SMTP encoded. smtp_fputs() will append + * another \r\n but we don't care. + */ + if (message_file != 0) { + VSTREAM *fp; + VSTRING *buf = vstring_alloc(100); + VSTRING *msg = vstring_alloc(100); + + if ((fp = vstream_fopen(message_file, O_RDONLY, 0)) == 0) + msg_fatal("open %s: %m", message_file); + while (vstring_get_nonl(buf, fp) != VSTREAM_EOF) { + if (*vstring_str(buf) == '.') + VSTRING_ADDCH(msg, '.'); + vstring_memcat(msg, vstring_str(buf), VSTRING_LEN(buf)); + vstring_memcat(msg, "\r\n", 2); + } + if (vstream_ferror(fp)) + msg_fatal("read %s: %m", message_file); + vstream_fclose(fp); + vstring_free(buf); + message_length = VSTRING_LEN(msg); + message_data = vstring_export(msg); + send_headers = 0; + } else if (message_length > 0) { + message_data = mymalloc(message_length); + memset(message_data, 'X', message_length); + for (i = 80; i < message_length; i += 80) { + message_data[i - 80] = "0123456789"[(i / 80) % 10]; + message_data[i - 2] = '\r'; + message_data[i - 1] = '\n'; + } + } + + /* + * Translate endpoint address to internal form. + */ + (void) inet_proto_init("protocols", protocols); + if (strncmp(argv[optind], "unix:", 5) == 0) { + path = argv[optind] + 5; + path_len = strlen(path); + if (path_len >= (int) sizeof(sun.sun_path)) + msg_fatal("unix-domain name too long: %s", path); + memset((void *) &sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; +#ifdef HAS_SUN_LEN + sun.sun_len = path_len + 1; +#endif + memcpy(sun.sun_path, path, path_len); + sa = (struct sockaddr *) &sun; + sa_length = sizeof(sun); + } else { + if (strncmp(argv[optind], "inet:", 5) == 0) + argv[optind] += 5; + buf = mystrdup(argv[optind]); + if ((parse_err = host_port(buf, &host, (char *) 0, &port, "smtp")) != 0) + msg_fatal("%s: %s", argv[optind], parse_err); + if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0) + msg_fatal("%s: %s", argv[optind], MAI_STRERROR(aierr)); + myfree(buf); + sa = (struct sockaddr *) &ss; + if (res->ai_addrlen > sizeof(ss)) + msg_fatal("address length %d > buffer length %d", + (int) res->ai_addrlen, (int) sizeof(ss)); + memcpy((void *) sa, res->ai_addr, res->ai_addrlen); + sa_length = res->ai_addrlen; +#ifdef HAS_SA_LEN + sa->sa_len = sa_length; +#endif + freeaddrinfo(res); + } + + /* + * smtp_get() makes sure the SMTP server cannot run us out of memory by + * sending never-ending lines of text. + */ + if (buffer == 0) + buffer = vstring_alloc(100); + + /* + * Make sure we have sender and recipient addresses. + */ + if (var_myhostname == 0) + var_myhostname = get_hostname(); + if (sender == 0 || recipient == 0) { + vstring_sprintf(buffer, "foo@%s", var_myhostname); + defaddr = mystrdup(vstring_str(buffer)); + if (sender == 0) + sender = defaddr; + if (recipient == 0) + recipient = defaddr; + } + + /* + * Start sessions. + */ + while (sessions-- > 0) { + session = (SESSION *) mymalloc(sizeof(*session)); + session->stream = 0; + session->xfer_count = 0; + session->connect_count = connect_count; + session->next = 0; + session_count++; + startup(session); + } + for (;;) { + event_loop(-1); + if (session_count <= 0 && message_count <= 0) { + if (count) { + VSTREAM_PUTC('\n', VSTREAM_OUT); + vstream_fflush(VSTREAM_OUT); + } + exit(0); + } + } +} |