/*++ /* NAME /* smtp-source 8 /* SUMMARY /* multi-threaded SMTP test generator /* SYNOPSIS /* smtp-source [options] host[:port] /* DESCRIPTION /* smtp-source connects to the named host and port (default 25) /* and sends one or more little messages to it, either sequentially /* or in parallel. /* /* Options: /* .IP -c /* Display a running counter that is incremented each time /* an SMTP DATA command completes. /* .IP "-C count" /* 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 -d /* Don't disconnect after sending a message; send the next /* message over the same connection. /* .IP "-f from" /* Use the specified sender address (default: ). /* .IP -o /* Old mode: don't send HELO, and don't send message headers. /* .IP "-l length" /* Send \fIlength\fR bytes as message payload. /* .IP "-m message_count" /* Send the specified number of messages (default: 1). /* .IP "-r recipient_count" /* Send the specified number of recipients per transaction (default: 1). /* Recipient names are generated by prepending a number to the /* recipient address. The default is one recipient per transaction. /* .IP "-s session_count" /* Run the specified number of SMTP sessions in parallel (default: 1). /* .IP "-t to" /* Use the specified recipient address (default: ). /* .IP "-R interval" /* Wait for a random period of time 0 <= n <= interval between messages. /* Suspending one thread does not affect other delivery threads. /* .IP "-w interval" /* Wait a fixed time between messages. /* Suspending one thread does not affect other delivery threads. /* 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 /*--*/ /* System library. */ #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 /* Global library. */ #include #include /* Application-specific. */ /* * Per-session data structure with state. */ typedef struct { int xfer_count; /* # of xfers in session */ int rcpt_count; /* # of recipients to go */ VSTREAM *stream; /* open connection */ int connect_count; /* # of connect()s to retry */ } SESSION; /* * 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_in sin; 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 void connect_done(int, char *); static void send_helo(SESSION *); static void helo_done(int, char *); static void send_mail(SESSION *); static void mail_done(int, char *); static void send_rcpt(int, char *); static void rcpt_done(int, char *); static void send_data(int, char *); static void data_done(int, char *); static void dot_done(int, char *); static void send_quit(SESSION *); static void quit_done(int, char *); /* 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,...) { VSTRING *buf; va_list ap; /* * Optionally, log the command before actually sending, so we can see * what the program is trying to do. */ if (msg_verbose) { buf = vstring_alloc(100); va_start(ap, fmt); vstring_vsprintf(buf, fmt, ap); va_end(ap); msg_info("%s", vstring_str(buf)); vstring_free(buf); } va_start(ap, fmt); smtp_vprintf(stream, fmt, ap); va_end(ap); } /* 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. Defend against a denial of * service attack by limiting the amount of multi-line text that we are * willing to store. */ if (rdata.buf == 0) { rdata.buf = vstring_alloc(100); vstring_ctl(rdata.buf, VSTRING_CTL_MAXLEN, var_line_limit, 0); } /* * 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); 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++; vstring_strcat(rdata.buf, cp); if (more == 0) break; 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) { int fd; if (message_count-- <= 0) { myfree((char *) session); session_count--; return; } if (session->stream == 0) { if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) msg_fatal("socket: %m"); for (;;) { if (session->connect_count == 0) msg_fatal("connect: %m"); if (!connect(fd, (struct sockaddr *) & sin, sizeof(sin))) break; if (session->connect_count-- > 1) #ifdef MISSING_USLEEP doze(10); #else usleep(10); #endif } session->stream = vstream_fdopen(fd, O_RDWR); smtp_timeout_setup(session->stream, var_timeout); event_enable_read(vstream_fileno(session->stream), connect_done, (char *) session); } else { send_mail(session); } } /* start_event - invoke startup from timer context */ static void start_event(int unused_event, char *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, (char *) session, random_interval(random_delay)); } else if (fixed_delay > 0) { event_request_timer(start_event, (char *) session, fixed_delay); } else { startup(session); } } /* connect_done - send message sender info */ static void connect_done(int unused_event, char *context) { SESSION *session = (SESSION *) context; RESPONSE *resp; int except; /* * Prepare for disaster. */ if ((except = setjmp(smtp_timeout_buf)) != 0) msg_fatal("%s while reading HELO", exception_text(except)); /* * Read and parse the server's SMTP greeting banner. */ if (((resp = response(session->stream, buffer))->code / 100) != 2) msg_fatal("bad startup: %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; /* * Send the standard greeting with our hostname */ if ((except = setjmp(smtp_timeout_buf)) != 0) msg_fatal("%s while sending HELO", exception_text(except)); command(session->stream, "HELO %s", var_myhostname); /* * Prepare for the next event. */ event_disable_readwrite(vstream_fileno(session->stream)); event_enable_read(vstream_fileno(session->stream), helo_done, (char *) session); } /* helo_done - handle HELO response */ static void helo_done(int unused_event, char *context) { SESSION *session = (SESSION *) context; RESPONSE *resp; int except; /* * Get response to HELO command. */ if ((except = setjmp(smtp_timeout_buf)) != 0) msg_fatal("%s while sending HELO", exception_text(except)); if ((resp = response(session->stream, buffer))->code / 100 != 2) msg_fatal("HELO rejected: %d %s", 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 = setjmp(smtp_timeout_buf)) != 0) msg_fatal("%s while sending sender", exception_text(except)); command(session->stream, "MAIL FROM:<%s>", sender); /* * Prepare for the next event. */ event_disable_readwrite(vstream_fileno(session->stream)); event_enable_read(vstream_fileno(session->stream), mail_done, (char *) session); } /* mail_done - handle MAIL response */ static void mail_done(int unused, char *context) { SESSION *session = (SESSION *) context; RESPONSE *resp; int except; /* * Get response to MAIL command. */ if ((except = setjmp(smtp_timeout_buf)) != 0) msg_fatal("%s while sending sender", exception_text(except)); if ((resp = response(session->stream, buffer))->code / 100 != 2) msg_fatal("sender rejected: %d %s", resp->code, resp->str); session->rcpt_count = recipients; send_rcpt(unused, context); } /* send_rcpt - send recipient address */ static void send_rcpt(int unused_event, char *context) { SESSION *session = (SESSION *) context; int except; /* * Send envelope recipient address. */ if ((except = setjmp(smtp_timeout_buf)) != 0) msg_fatal("%s while sending recipient", exception_text(except)); if (session->rcpt_count > 1) command(session->stream, "RCPT TO:<%d%s>", session->rcpt_count, recipient); else command(session->stream, "RCPT TO:<%s>", recipient); session->rcpt_count--; /* * Prepare for the next event. */ event_disable_readwrite(vstream_fileno(session->stream)); event_enable_read(vstream_fileno(session->stream), rcpt_done, (char *) session); } /* rcpt_done - handle RCPT completion */ static void rcpt_done(int unused, char *context) { SESSION *session = (SESSION *) context; RESPONSE *resp; int except; /* * Get response to RCPT command. */ if ((except = setjmp(smtp_timeout_buf)) != 0) msg_fatal("%s while sending recipient", exception_text(except)); if ((resp = response(session->stream, buffer))->code / 100 != 2) 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 send_data(unused, context); } /* send_data - send DATA command */ static void send_data(int unused_event, char *context) { SESSION *session = (SESSION *) context; int except; /* * Request data transmission. */ if ((except = setjmp(smtp_timeout_buf)) != 0) msg_fatal("%s while sending DATA command", exception_text(except)); command(session->stream, "DATA"); /* * Prepare for the next event. */ event_disable_readwrite(vstream_fileno(session->stream)); event_enable_read(vstream_fileno(session->stream), data_done, (char *) session); } /* data_done - send message content */ static void data_done(int unused_event, char *context) { SESSION *session = (SESSION *) context; RESPONSE *resp; int except; static const char *mydate; static int mypid; /* * Get response to DATA command. */ if ((except = setjmp(smtp_timeout_buf)) != 0) msg_fatal("%s while sending DATA command", exception_text(except)); if ((resp = response(session->stream, buffer))->code != 354) msg_fatal("data %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); smtp_fputs("", 0, session->stream); } /* * Send some garbage. */ if ((except = setjmp(smtp_timeout_buf)) != 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 { 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_disable_readwrite(vstream_fileno(session->stream)); event_enable_read(vstream_fileno(session->stream), dot_done, (char *) session); } /* dot_done - send QUIT */ static void dot_done(int unused_event, char *context) { SESSION *session = (SESSION *) context; RESPONSE *resp; int except; /* * Get response to "." command. */ if ((except = setjmp(smtp_timeout_buf)) != 0) msg_fatal("%s while sending message", exception_text(except)); if ((resp = response(session->stream, buffer))->code / 100 != 2) msg_fatal("data %d %s", resp->code, resp->str); 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_quit - send QUIT command */ static void send_quit(SESSION *session) { command(session->stream, "QUIT"); event_disable_readwrite(vstream_fileno(session->stream)); event_enable_read(vstream_fileno(session->stream), quit_done, (char *) session); } /* quit_done - disconnect */ static void quit_done(int unused_event, char *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); } /* usage - explain */ static void usage(char *myname) { msg_fatal("usage: %s -s sess -l msglen -m msgs -c -C count -d -f from -o -t to -R delay -v -w delay host[:port]", myname); } /* main - parse JCL and start the machine */ int main(int argc, char **argv) { SESSION *session; char *host; char *port; int sessions = 1; int ch; int i; signal(SIGPIPE, SIG_IGN); msg_vstream_init(argv[0], VSTREAM_ERR); /* * Parse JCL. */ while ((ch = GETOPT(argc, argv, "cC:df:l:m:or:R:s:t:vw:")) > 0) { switch (ch) { case 'c': count++; break; case 'C': if ((connect_count = atoi(optarg)) <= 0) usage(argv[0]); break; case 'd': disconnect = 0; break; case 'f': sender = optarg; break; case 'l': if ((message_length = atoi(optarg)) <= 0) usage(argv[0]); message_data = mymalloc(message_length); memset(message_data, 'X', message_length); for (i = 80; i < message_length; i += 80) { message_data[i - 2] = '\r'; message_data[i - 1] = '\n'; } break; case 'm': if ((message_count = atoi(optarg)) <= 0) usage(argv[0]); break; case 'o': send_helo_first = 0; send_headers = 0; break; case 'r': if ((recipients = atoi(optarg)) <= 0) usage(argv[0]); break; case 'R': if (fixed_delay > 0 || (random_delay = atoi(optarg)) <= 0) usage(argv[0]); break; case 's': if ((sessions = atoi(optarg)) <= 0) usage(argv[0]); break; case 't': recipient = optarg; break; case 'v': msg_verbose++; break; case 'w': if (random_delay > 0 || (fixed_delay = atoi(optarg)) <= 0) usage(argv[0]); break; default: usage(argv[0]); } } if (argc - optind != 1) usage(argv[0]); if ((port = split_at(host = argv[optind], ':')) == 0) port = "smtp"; if (random_delay > 0) srand(getpid()); /* * Translate endpoint address to internal form. */ memset((char *) &sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = find_inet_addr(host); sin.sin_port = find_inet_port(port, "tcp"); /* * Make sure the SMTP server cannot run us out of memory by sending * never-ending lines of text. */ if (buffer == 0) { buffer = vstring_alloc(100); vstring_ctl(buffer, VSTRING_CTL_MAXLEN, var_line_limit, 0); } /* * Make sure we have sender and recipient addresses. */ 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_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); } } }