/*++ /* 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 #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 *); } 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 ."); 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); }