SMTP压力测试工具:客户端程序smtp-client.c

- 中国WEB开发者网络 (http://www.webasp.net)
-- 技术教程 (http://www.webasp.net/article/)
--- SMTP压力测试工具:客户端程序smtp-client.c (http://www.webasp.net/article/10/9925.htm)
-- 作者:未知
-- 发布日期: 2004-06-10
SMTP压力测试工具:客户端程序smtp-client.c

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

webasp.net