/*++
/* NAME
/* smtp-sink 8
/* SUMMARY
/* multi-threaded SMTP/LMTP test server
/* SYNOPSIS
/* smtp-sink [-cLpv] [-w delay] [host]:port backlog
/* DESCRIPTION
/* \\\\fIsmtp-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 SMTP client performance, not protocol
/* compliance.
/* This program is the complement of the \\\\fIsmtp-source\\\\fR program.
/* .IP -c
/* Display a running counter that is updated whenever an SMTP
/* QUIT command is executed.
/* .IP -L
/* Speak LMTP rather than SMTP.
/* .IP -p
/* Disable ESMTP command pipelining.
/* .IP -v
/* Show the SMTP conversations.
/* .IP \\\"-w delay\\\"
/* Wait \\\\fIdelay\\\\fR seconds before responding to a DATA command.
/* SEE ALSO
/* smtp-source, SMTP/LMTP test 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
/*--*/
/* System library. */
#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
/* Global library. */
#include
/* Application-specific. */
typedef struct SINK_STATE {
VSTREAM *stream;
int data_state;
int (*read) (struct SINK_STATE *);
int rcpts;
} 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
static int var_tmout;
static int var_max_line_length;
static char *var_myhostname;
static VSTRING *buffer;
static int command_read(SINK_STATE *);
static int data_read(SINK_STATE *);
static void disconnect(SINK_STATE *);
static int count;
static int counter;
static int disable_pipelining;
static int fixed_delay;
static int enable_lmtp;
/* ehlo_response - respond to EHLO command */
static void ehlo_response(SINK_STATE *state)
{
smtp_printf(state->stream, \\\"250-%s\\\", var_myhostname);
if (!disable_pipelining)
smtp_printf(state->stream, \\\"250-PIPELINING\\\");
smtp_printf(state->stream, \\\"250 8BITMIME\\\");
}
/* ok_response - send 250 OK */
static void ok_response(SINK_STATE *state)
{
smtp_printf(state->stream, \\\"250 Ok\\\");
}
/* mail_response - reset recipient count, send 250 OK */
static void mail_response(SINK_STATE *state)
{
state->rcpts = 0;
ok_response(state);
}
/* rcpt_response - bump recipient count, send 250 OK */
static void rcpt_response(SINK_STATE *state)
{
state->rcpts++;
ok_response(state);
}
/* data_response - respond to DATA command */
static void data_response(SINK_STATE *state)
{
state->data_state = ST_CR_LF;
smtp_printf(state->stream, \\\"354 End data with .\\\");
state->read = data_read;
}
/* data_event - delayed response to DATA command */
static void data_event(int unused_event, char *context)
{
SINK_STATE *state = (SINK_STATE *) context;
data_response(state);
}
/* dot_response - response to . command */
static void dot_response(SINK_STATE *state)
{
if (enable_lmtp) {
while (state->rcpts-- > 0) /* XXX this could block */
ok_response(state);
} else {
ok_response(state);
}
}
/* quit_response - respond to QUIT command */
static void quit_response(SINK_STATE *state)
{
smtp_printf(state->stream, \\\"221 Bye\\\");
if (count) {
counter++;
vstream_printf(\\\"%d\\\\r\\\", counter);
vstream_fflush(VSTREAM_OUT);
}
}
/* 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;
/*
* We must avoid blocking I/O, so get out of here as soon as both the
* VSTREAM and kernel read buffers dry up.
*/
while (vstream_peek(state->stream) > 0
|| peekfd(vstream_fileno(state->stream)) > 0) {
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->data_state == ST_CR_LF_DOT_CR_LF) {
if (msg_verbose)
msg_info(\\\".\\\");
dot_response(state);
state->read = command_read;
break;
}
}
return (0);
}
/*
* The table of all SMTP commands that we can handle.
*/
typedef struct SINK_COMMAND {
char *name;
void (*response) (SINK_STATE *);
} SINK_COMMAND;
static SINK_COMMAND command_table[] = {
\\\"helo\\\", ok_response,
\\\"ehlo\\\", ehlo_response,
\\\"lhlo\\\", ehlo_response,
\\\"mail\\\", mail_response,
\\\"rcpt\\\", rcpt_response,
\\\"data\\\", data_response,
\\\"rset\\\", ok_response,
\\\"noop\\\", ok_response,
\\\"vrfy\\\", ok_response,
\\\"quit\\\", quit_response,
0,
};
/* command_read - talk the SMTP protocol, server side */
static int command_read(SINK_STATE *state)
{
char *command;
SINK_COMMAND *cmdp;
smtp_get(buffer, state->stream, var_max_line_length);
if ((command = strtok(vstring_str(buffer), \\\" \\\\t\\\")) == 0) {
smtp_printf(state->stream, \\\"500 Error: unknown command\\\");
return (0);
}
if (msg_verbose)
msg_info(\\\"%s\\\", command);
for (cmdp = command_table; cmdp->name != 0; cmdp++)
if (strcasecmp(command, cmdp->name) == 0)
break;
if (cmdp->name == 0) {
smtp_printf(state->stream, \\\"500 Error: unknown command\\\");
return (0);
}
if (cmdp->response == data_response && fixed_delay > 0) {
event_request_timer(data_event, (char *) state, fixed_delay);
} else {
cmdp->response(state);
if (cmdp->response == quit_response)
return (-1);
}
return (0);
}
/* read_event - handle command or data read events */
static void read_event(int unused_event, char *context)
{
SINK_STATE *state = (SINK_STATE *) context;
do {
switch (vstream_setjmp(state->stream)) {
default:
msg_panic(\\\"unknown error reading input\\\");
case SMTP_ERR_TIME:
msg_panic(\\\"attempt to read non-readable socket\\\");
/* NOTREACHED */
case SMTP_ERR_EOF:
msg_warn(\\\"lost connection\\\");
disconnect(state);
return;
case 0:
if (state->read(state) < 0) {
if (msg_verbose)
msg_info(\\\"disconnect\\\");
disconnect(state);
return;
}
}
} while (vstream_peek(state->stream) > 0);
}
/* disconnect - handle disconnection events */
static void disconnect(SINK_STATE *state)
{
event_disable_readwrite(vstream_fileno(state->stream));
vstream_fclose(state->stream);
myfree((char *) state);
}
/* connect_event - handle connection events */
static void connect_event(int unused_event, char *context)
{
int sock = (int) context;
SINK_STATE *state;
int fd;
if ((fd = accept(sock, (struct sockaddr *) 0, (SOCKADDR_SIZE *) 0)) >= 0) {
if (msg_verbose)
msg_info(\\\"connect\\\");
non_blocking(fd, NON_BLOCKING);
state = (SINK_STATE *) mymalloc(sizeof(*state));
state->stream = vstream_fdopen(fd, O_RDWR);
state->read = command_read;
state->data_state = 0;
smtp_timeout_setup(state->stream, var_tmout);
smtp_printf(state->stream, \\\"220 %s ESMTP\\\", var_myhostname);
event_enable_read(fd, read_event, (char *) state);
}
}
/* usage - explain */
static void usage(char *myname)
{
msg_fatal(\\\"usage: %s [-cLpv] [host]:port backlog\\\", myname);
}
int main(int argc, char **argv)
{
int sock;
int backlog;
int ch;
/*
* Initialize diagnostics.
*/
msg_vstream_init(argv[0], VSTREAM_ERR);
/*
* Parse JCL.
*/
while ((ch = GETOPT(argc, argv, \\\"cLpvw:\\\")) > 0) {
switch (ch) {
case \\\@#c\\\@#:
count++;
break;
case \\\@#L\\\@#:
enable_lmtp = 1;
break;
case \\\@#p\\\@#:
disable_pipelining = 1;
break;
case \\\@#v\\\@#:
msg_verbose++;
break;
case \\\@#w\\\@#:
if ((fixed_delay = atoi(optarg)) <= 0)
usage(argv[0]);
break;
default:
usage(argv[0]);
}
}
if (argc - optind != 2)
usage(argv[0]);
if ((backlog = atoi(argv[optind + 1])) <= 0)
usage(argv[0]);
/*
* Initialize.
*/
buffer = vstring_alloc(1024);
var_myhostname = \\\"smtp-sink\\\";
sock = inet_listen(argv[optind], backlog, BLOCKING);
/*
* Start the event handler.
*/
event_enable_read(sock, connect_event, (char *) sock);
for (;;)
event_loop(-1);
} |