NetBSD/gnu/dist/postfix/smtpstone/smtp-sink.c

369 lines
8.4 KiB
C

/*++
/* NAME
/* smtp-sink 8
/* SUMMARY
/* multi-threaded smtp test server
/* SYNOPSIS
/* smtp-sink [-c] [-p] [-v] [-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 -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 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 <sys_defs.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <setjmp.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#ifdef STRCASECMP_IN_STRINGS_H
#include <strings.h>
#endif
/* Utility library. */
#include <msg.h>
#include <vstring.h>
#include <vstream.h>
#include <vstring_vstream.h>
#include <get_hostname.h>
#include <listen.h>
#include <events.h>
#include <mymalloc.h>
#include <iostuff.h>
#include <msg_vstream.h>
/* Global library. */
#include <smtp_stream.h>
/* Application-specific. */
typedef struct SINK_STATE {
VSTREAM *stream;
int data_state;
int (*read) (struct SINK_STATE *);
} 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;
/* 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");
}
/* 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 <CR><LF>.<CR><LF>");
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);
}
/* 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(".");
ok_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,
"mail", ok_response,
"rcpt", ok_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 (setjmp(smtp_timeout_buf)) {
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 [-c] [-p] [-v] [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, "cpvw:")) > 0) {
switch (ch) {
case 'c':
count++;
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);
}