/*++ /* 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: ). /* .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 \fImy-hostname\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" /* Generate each recipient address by appending a number (a /* per-process recipient counter) to the recipient address /* localpart specified with the \fB-t\fR option. /* /* Note: to use the number as an address extension, specify /* an explicit address delimiter at the end of the recipient /* localpart, as in "\fB-t localpart+@domain\fR" or "\fB-t /* localpart+\fR", where "\fB+\fR" is a Postfix recipient /* address delimiter. /* /* Benefits: /* .RS /* .IP \(bu /* A non-constant recipient address avoids an unrealistic 100% /* cache hit rate in clients of the Postfix trivial-rewrite /* service, better approximating performance under real-life /* work-loads. /* .IP \(bu /* A fixed recipient address local-part with a non-constant /* address extension avoids the need to configure a large /* number of valid recipient addresses in the receiving Postfix /* server. /* .RE /* .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), and generate recipient addresses as described /* under the \fB-N\fR option. /* .IP "\fB-R \fIinterval\fR" /* Wait a random time (0 <= n <= \fIinterval\fR) 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: ). /* .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 #include #include #include #include #include #include #include #include #include #include #include #include /* Utility library. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Global library. */ #include #include #include /* 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 */ int rcpt_sample; /* Sample recipient # for To: header */ 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; typedef struct { char *local; char *at_domain; } RECIPIENT; static RECIPIENT *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 global_rcpt_suffix = 0; static int global_rcpt_done = 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 *); /* make_recipient - parse recipient into localpart and at_domain */ static RECIPIENT *make_recipient(const char *address) { RECIPIENT *rp = (RECIPIENT *) mymalloc(sizeof(*rp)); const char *at; if ((at = strrchr(address, '@')) == 0) at = address + strlen(address); rp->local = mystrndup(address, at - address); rp->at_domain = mystrdup(at); return (rp); } /* 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 (global_rcpt_suffix) command(session->stream, "RCPT TO:<%s%d%s>", recipient->local, session->rcpt_sample = global_rcpt_done++, recipient->at_domain); else command(session->stream, "RCPT TO:<%s%s>", recipient->local, recipient->at_domain); 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); if (global_rcpt_suffix) smtp_printf(session->stream, "To: <%s%d%s>", recipient->local, session->rcpt_sample, recipient->at_domain); else smtp_printf(session->stream, "To: <%s%s>", recipient->local, recipient->at_domain); 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': global_rcpt_suffix = 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); global_rcpt_suffix = 1; 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 = make_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 = make_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); } } }