/*++
/* NAME
/* smtp-source 1
/* SUMMARY
/* parallelized SMTP/LMTP test generator
/* SYNOPSIS
/* .fi
/* \fBsmtp-source\fR [\fIoptions\fR] [\fBinet:\fR]\fIhost\fR[:\fIport\fR]
/*
/* \fBsmtp-source\fR [\fIoptions\fR] \fBunix:\fIpathname\fR
/* DESCRIPTION
/* \fBsmtp-source\fR connects to the named \fIhost\fR and TCP \fIport\fR
/* (default: port 25)
/* and sends one or more messages to it, either sequentially
/* or in parallel. The program speaks either SMTP (default) or
/* LMTP.
/* Connections can be made to UNIX-domain and IPv4 or IPv6 servers.
/* IPv4 and IPv6 are the default.
/*
/* Note: this is an unsupported test program. No attempt is made
/* to maintain compatibility between successive versions.
/*
/* Arguments:
/* .IP \fB-4\fR
/* Connect to the server with IPv4. This option has no effect when
/* Postfix is built without IPv6 support.
/* .IP \fB-6\fR
/* Connect to the server with IPv6. This option is not available when
/* Postfix is built without IPv6 support.
/* .IP "\fB-A\fR"
/* Don't abort when the server sends something other than the
/* expected positive reply code.
/* .IP \fB-c\fR
/* Display a running counter that is incremented each time
/* an SMTP DATA command completes.
/* .IP "\fB-C \fIcount\fR"
/* 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 \fB-d\fR
/* Don't disconnect after sending a message; send the next
/* message over the same connection.
/* .IP "\fB-f \fIfrom\fR"
/* Use the specified sender address (default: <foo@my-hostname>).
/* .IP "\fB-F \fIfile\fR"
/* Send the pre-formatted message header and body in the
/* specified \fIfile\fR, while prepending '.' before lines that
/* begin with '.', and while appending CRLF after each line.
/* .IP "\fB-l \fIlength\fR"
/* Send \fIlength\fR bytes as message payload. The length does not
/* include message headers.
/* .IP \fB-L\fR
/* Speak LMTP rather than SMTP.
/* .IP "\fB-m \fImessage_count\fR"
/* Send the specified number of messages (default: 1).
/* .IP "\fB-M \fImy-hostname\fR"
/* Use the specified hostname or [address] in the HELO command
/* and in the default sender and recipient addresses, instead
/* of the machine hostname.
/* .IP "\fB-N\fR"
/* Generate each recipient address by appending a number (a
/* per-process recipient counter) to the recipient address
/* localpart specified with the \fB-t\fR option.
/*
/* Note: to use the number as an address extension, specify
/* an explicit address delimiter at the end of the recipient
/* localpart, as in "\fB-t localpart+@domain\fR" or "\fB-t
/* localpart+\fR", where "\fB+\fR" is a Postfix recipient
/* address delimiter.
/*
/* Benefits:
/* .RS
/* .IP \(bu
/* A non-constant recipient address avoids an unrealistic 100%
/* cache hit rate in clients of the Postfix trivial-rewrite
/* service, better approximating performance under real-life
/* work-loads.
/* .IP \(bu
/* A fixed recipient address local-part with a non-constant
/* address extension avoids the need to configure a large
/* number of valid recipient addresses in the receiving Postfix
/* server.
/* .RE
/* .IP \fB-o\fR
/* Old mode: don't send HELO, and don't send message headers.
/* .IP "\fB-r \fIrecipient_count\fR"
/* Send the specified number of recipients per transaction
/* (default: 1), and generate recipient addresses as described
/* under the \fB-N\fR option.
/* .IP "\fB-R \fIinterval\fR"
/* Wait a random time (0 <= n <= \fIinterval\fR) between messages.
/* Suspending one thread does not affect other delivery threads.
/* .IP "\fB-s \fIsession_count\fR"
/* Run the specified number of SMTP sessions in parallel (default: 1).
/* .IP "\fB-S \fIsubject\fR"
/* Send mail with the named subject line (default: none).
/* .IP "\fB-t \fIto\fR"
/* Use the specified recipient address (default: <foo@my-hostname>).
/* .IP "\fB-T \fIwindowsize\fR"
/* Override the default TCP window size. To work around
/* broken TCP window scaling implementations, specify a
/* value > 0 and < 65536.
/* .IP \fB-v\fR
/* Make the program more verbose, for debugging purposes.
/* .IP "\fB-w \fIinterval\fR"
/* Wait a fixed time between messages.
/* Suspending one thread does not affect other delivery threads.
/* .IP [\fBinet:\fR]\fIhost\fR[:\fIport\fR]
/* Connect via TCP to host \fIhost\fR, port \fIport\fR. The default
/* port is \fBsmtp\fR.
/* .IP \fBunix:\fIpathname\fR
/* Connect to the UNIX-domain socket at \fIpathname\fR.
/* BUGS
/* No SMTP command pipelining support.
/* SEE ALSO
/* smtp-sink(1), SMTP/LMTP message dump
/* 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
/*
/* Wietse Venema
/* Google, Inc.
/* 111 8th Avenue
/* New York, NY 10011, USA
/*--*/
/* System library. */
#include <sys_defs.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <sys/un.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>
/* Utility library. */
#include <msg.h>
#include <msg_vstream.h>
#include <vstring.h>
#include <vstream.h>
#include <vstring_vstream.h>
#include <get_hostname.h>
#include <split_at.h>
#include <connect.h>
#include <mymalloc.h>
#include <events.h>
#include <iostuff.h>
#include <sane_connect.h>
#include <host_port.h>
#include <myaddrinfo.h>
#include <inet_proto.h>
#include <valid_hostname.h>
#include <valid_mailhost_addr.h>
#include <compat_va_copy.h>
/* Global library. */
#include <smtp_stream.h>
#include <mail_date.h>
#include <mail_version.h>
/* Application-specific. */
/*
* Per - session data structure with state .
*
* This software can maintain multiple parallel connections to the same SMTP
* server . However , it makes no more than one connection request at a time
* to avoid overwhelming the server with SYN packets and having to back off .
* Back - off would screw up the benchmark . Pending connection requests are
* kept in a linear list .
*/
typedef struct SESSION {
int xfer_count; /* # of xfers in session */
int rcpt_done; /* # of recipients done */
int rcpt_count; /* # of recipients to go */
int rcpt_accepted; /* # of recipients accepted */
int rcpt_sample; /* Sample recipient # for To: header */
VSTREAM *stream; /* open connection */
int connect_count; /* # of connect()s to retry */
struct SESSION *next; /* connect() queue linkage */
} SESSION;
static SESSION *last_session; /* connect() queue tail */
/*
* 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_storage ss;
#undef sun
static struct sockaddr_un sun;
static struct sockaddr *sa;
static int sa_length;
static int recipients = 1 ;
static char *defaddr;
typedef struct {
char *local;
char *at_domain;
} RECIPIENT;
static RECIPIENT *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 int talk_lmtp = 0 ;
static char *subject = 0 ;
static int global_rcpt_suffix = 0 ;
static int global_rcpt_done = 0 ;
static int allow_reject = 0 ;
static void enqueue_connect(SESSION *);
static void start_connect(SESSION *);
static void connect_done(int , void *);
static void read_banner(int , void *);
static void send_helo(SESSION *);
static void helo_done(int , void *);
static void send_mail(SESSION *);
static void mail_done(int , void *);
static void send_rcpt(int , void *);
static void rcpt_done(int , void *);
static void send_data(int , void *);
static void data_done(int , void *);
static void dot_done(int , void *);
static void send_rset(int , void *);
static void rset_done(int , void *);
static void send_quit(SESSION *);
static void quit_done(int , void *);
static void close_session(SESSION *);
/* make_recipient - parse recipient into localpart and at_domain */
static RECIPIENT *make_recipient(const char *address)
{
RECIPIENT *rp = (RECIPIENT *) mymalloc(sizeof (*rp));
const char *at;
if ((at = strrchr(address, '@' )) == 0 )
at = address + strlen(address);
rp->local = mystrndup(address, at - address);
rp->at_domain = mystrdup(at);
return (rp);
}
/* 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,...)
{
va_list ap;
va_start(ap, fmt);
/*
* Optionally , log the command before actually sending , so we can see
* what the program is trying to do .
*/
if (msg_verbose) {
va_list ap2;
VA_COPY(ap2, ap);
vmsg_info(fmt, ap2);
va_end(ap2);
}
smtp_vprintf(stream, fmt, ap);
va_end(ap);
smtp_flush(stream);
}
/* socket_error - look up and reset the last socket error */
static int socket_error(int sock)
{
int error;
SOCKOPT_SIZE error_len;
/*
* Some Solaris 2 versions have getsockopt ( ) itself return the error ,
* instead of returning it via the parameter list .
*/
error = 0 ;
error_len = sizeof (error);
if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void *) &error, &error_len) < 0 )
return (-1 );
if (error) {
errno = error;
return (-1 );
}
/*
* No problems .
*/
return (0 );
}
/* 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 . smtp_get ( ) defends against a
* denial of service attack by limiting the amount of single - line text ,
* and the loop below limits the amount of multi - line text that we are
* willing to store .
*/
if (rdata.buf == 0 )
rdata.buf = vstring_alloc(100 );
/*
* 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, SMTP_GET_FLAG_SKIP);
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++;
if (VSTRING_LEN(rdata.buf) < var_line_limit)
vstring_strcat(rdata.buf, cp);
if (more == 0 )
break ;
if (VSTRING_LEN(rdata.buf) < var_line_limit)
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)
{
if (message_count-- <= 0 ) {
myfree((void *) session);
session_count--;
return ;
}
if (session->stream == 0 ) {
enqueue_connect(session);
} else {
send_mail(session);
}
}
/* start_event - invoke startup from timer context */
static void start_event(int unused_event, void *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, (void *) session,
random_interval(random_delay));
} else if (fixed_delay > 0 ) {
event_request_timer(start_event, (void *) session, fixed_delay);
} else {
startup(session);
}
}
/* enqueue_connect - queue a connection request */
static void enqueue_connect(SESSION *session)
{
session->next = 0 ;
if (last_session == 0 ) {
last_session = session;
start_connect(session);
} else {
last_session->next = session;
last_session = session;
}
}
/* dequeue_connect - connection request completed */
static void dequeue_connect(SESSION *session)
{
if (session == last_session) {
if (session->next != 0 )
msg_panic("dequeue_connect: queue ends after last" );
last_session = 0 ;
} else {
if (session->next == 0 )
msg_panic("dequeue_connect: queue ends before last" );
start_connect(session->next);
}
}
/* fail_connect - handle failed startup */
static void fail_connect(SESSION *session)
{
if (session->connect_count-- == 1 )
msg_fatal("connect: %m" );
msg_warn("connect: %m" );
event_disable_readwrite(vstream_fileno(session->stream));
vstream_fclose(session->stream);
session->stream = 0 ;
#ifdef MISSING_USLEEP
doze(10 );
#else
usleep(10 );
#endif
start_connect(session);
}
/* start_connect - start TCP handshake */
static void start_connect(SESSION *session)
{
int fd;
struct linger linger;
/*
* Some systems don ' t set the socket error when connect ( ) fails early
* ( loopback ) so we must deal with the error immediately , rather than
* retrieving it later with getsockopt ( ) . We can ' t use MSG_PEEK to
* distinguish between server disconnect and connection refused .
*/
if ((fd = socket(sa->sa_family, SOCK_STREAM, 0 )) < 0 )
msg_fatal("socket: %m" );
(void ) non_blocking(fd, NON_BLOCKING);
linger.l_onoff = 1 ;
linger.l_linger = 0 ;
if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (void *) &linger,
sizeof (linger)) < 0 )
msg_warn("setsockopt SO_LINGER %d: %m" , linger.l_linger);
session->stream = vstream_fdopen(fd, O_RDWR);
event_enable_write(fd, connect_done, (void *) session);
smtp_timeout_setup(session->stream, var_timeout);
if (inet_windowsize > 0 )
set_inet_windowsize(fd, inet_windowsize);
if (sane_connect(fd, sa, sa_length) < 0 && errno != EINPROGRESS)
fail_connect(session);
}
/* connect_done - send message sender info */
static void connect_done(int unused_event, void *context)
{
SESSION *session = (SESSION *) context;
int fd = vstream_fileno(session->stream);
/*
* Try again after some delay when the connection failed , in case they
* run a Mickey Mouse protocol stack .
*/
if (socket_error(fd) < 0 ) {
fail_connect(session);
} else {
non_blocking(fd, BLOCKING);
/* Disable write events. */
event_disable_readwrite(fd);
event_enable_read(fd, read_banner, (void *) session);
dequeue_connect(session);
/* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */
if (sa->sa_family == AF_INET
#ifdef AF_INET6
|| sa->sa_family == AF_INET6
#endif
)
vstream_tweak_tcp(session->stream);
}
}
/* read_banner - receive SMTP server greeting */
static void read_banner(int unused_event, void *context)
{
SESSION *session = (SESSION *) context;
RESPONSE *resp;
int except;
/*
* Prepare for disaster .
*/
if ((except = vstream_setjmp(session->stream)) != 0 )
msg_fatal("%s while reading server greeting" , exception_text(except));
/*
* Read and parse the server ' s SMTP greeting banner .
*/
if (((resp = response(session->stream, buffer))->code / 100 ) == 2 ) {
/* void */ ;
} else if (allow_reject) {
msg_warn("rejected at server banner: %d %s" , resp->code, resp->str);
} else {
msg_fatal("rejected at server banner: %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;
const char *NOCLOBBER protocol = (talk_lmtp ? "LHLO" : "HELO" );
/*
* Send the standard greeting with our hostname
*/
if ((except = vstream_setjmp(session->stream)) != 0 )
msg_fatal("%s while sending %s" , exception_text(except), protocol);
command(session->stream, "%s %s" , protocol, var_myhostname);
/*
* Prepare for the next event .
*/
event_enable_read(vstream_fileno(session->stream), helo_done, (void *) session);
}
/* helo_done - handle HELO response */
static void helo_done(int unused_event, void *context)
{
SESSION *session = (SESSION *) context;
RESPONSE *resp;
int except;
const char *protocol = (talk_lmtp ? "LHLO" : "HELO" );
/*
* Get response to HELO command .
*/
if ((except = vstream_setjmp(session->stream)) != 0 )
msg_fatal("%s while sending %s" , exception_text(except), protocol);
if ((resp = response(session->stream, buffer))->code / 100 == 2 ) {
/* void */ ;
} else if (allow_reject) {
msg_warn("%s rejected: %d %s" , protocol, resp->code, resp->str);
if (resp->code == 421 || resp->code == 521 ) {
close_session(session);
return ;
}
} else {
msg_fatal("%s rejected: %d %s" , protocol, 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 = vstream_setjmp(session->stream)) != 0 )
msg_fatal("%s while sending sender" , exception_text(except));
command(session->stream, "MAIL FROM:<%s>" , sender);
/*
* Prepare for the next event .
*/
event_enable_read(vstream_fileno(session->stream), mail_done, (void *) session);
}
/* mail_done - handle MAIL response */
static void mail_done(int unused, void *context)
{
SESSION *session = (SESSION *) context;
RESPONSE *resp;
int except;
/*
* Get response to MAIL command .
*/
if ((except = vstream_setjmp(session->stream)) != 0 )
msg_fatal("%s while sending sender" , exception_text(except));
if ((resp = response(session->stream, buffer))->code / 100 == 2 ) {
session->rcpt_count = recipients;
session->rcpt_done = 0 ;
session->rcpt_accepted = 0 ;
send_rcpt(unused, context);
} else if (allow_reject) {
msg_warn("sender rejected: %d %s" , resp->code, resp->str);
if (resp->code == 421 || resp->code == 521 ) {
close_session(session);
return ;
}
send_rset(unused, context);
} else {
msg_fatal("sender rejected: %d %s" , resp->code, resp->str);
}
}
/* send_rcpt - send recipient address */
static void send_rcpt(int unused_event, void *context)
{
SESSION *session = (SESSION *) context;
int except;
/*
* Send envelope recipient address .
*/
if ((except = vstream_setjmp(session->stream)) != 0 )
msg_fatal("%s while sending recipient" , exception_text(except));
if (global_rcpt_suffix)
command(session->stream, "RCPT TO:<%s%d%s>" ,
recipient->local, session->rcpt_sample = global_rcpt_done++,
recipient->at_domain);
else
command(session->stream, "RCPT TO:<%s%s>" ,
recipient->local, recipient->at_domain);
session->rcpt_count--;
session->rcpt_done++;
/*
* Prepare for the next event .
*/
event_enable_read(vstream_fileno(session->stream), rcpt_done, (void *) session);
}
/* rcpt_done - handle RCPT completion */
static void rcpt_done(int unused, void *context)
{
SESSION *session = (SESSION *) context;
RESPONSE *resp;
int except;
/*
* Get response to RCPT command .
*/
if ((except = vstream_setjmp(session->stream)) != 0 )
msg_fatal("%s while sending recipient" , exception_text(except));
if ((resp = response(session->stream, buffer))->code / 100 == 2 ) {
session->rcpt_accepted++;
} else if (allow_reject) {
msg_warn("recipient rejected: %d %s" , resp->code, resp->str);
if (resp->code == 421 || resp->code == 521 ) {
close_session(session);
return ;
}
} else {
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 if (session->rcpt_accepted > 0 )
send_data(unused, context);
else
send_rset(unused, context);
}
/* send_data - send DATA command */
static void send_data(int unused_event, void *context)
{
SESSION *session = (SESSION *) context;
int except;
/*
* Request data transmission .
*/
if ((except = vstream_setjmp(session->stream)) != 0 )
msg_fatal("%s while sending DATA command" , exception_text(except));
command(session->stream, "DATA" );
/*
* Prepare for the next event .
*/
event_enable_read(vstream_fileno(session->stream), data_done, (void *) session);
}
/* data_done - send message content */
static void data_done(int unused, void *context)
{
SESSION *session = (SESSION *) context;
RESPONSE *resp;
int except;
static const char *mydate;
static int mypid;
/*
* Get response to DATA command .
*/
if ((except = vstream_setjmp(session->stream)) != 0 )
msg_fatal("%s while sending DATA command" , exception_text(except));
if ((resp = response(session->stream, buffer))->code == 354 ) {
/* see below */ ;
} else if (allow_reject) {
msg_warn("data rejected: %d %s" , resp->code, resp->str);
if (resp->code == 421 || resp->code == 521 ) {
close_session(session);
return ;
}
send_rset(unused, context);
return ;
} else {
msg_fatal("data rejected: %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);
if (global_rcpt_suffix)
smtp_printf(session->stream, "To: <%s%d%s>" , recipient->local,
session->rcpt_sample, recipient->at_domain);
else
smtp_printf(session->stream, "To: <%s%s>" ,
recipient->local, recipient->at_domain);
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);
if (subject)
smtp_printf(session->stream, "Subject: %s" , subject);
smtp_fputs("" , 0 , session->stream);
}
/*
* Send some garbage .
*/
if ((except = vstream_setjmp(session->stream)) != 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 {
/*
* XXX This may cause the process to block with message content
* larger than VSTREAM_BUFIZ bytes .
*/
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_enable_read(vstream_fileno(session->stream), dot_done, (void *) session);
}
/* dot_done - send QUIT or start another transaction */
static void dot_done(int unused_event, void *context)
{
SESSION *session = (SESSION *) context;
RESPONSE *resp;
int except;
/*
* Get response to " . " command .
*/
if ((except = vstream_setjmp(session->stream)) != 0 )
msg_fatal("%s while sending message" , exception_text(except));
do { /* XXX this could block */
if ((resp = response(session->stream, buffer))->code / 100 == 2 ) {
/* void */ ;
} else if (allow_reject) {
msg_warn("end of data rejected: %d %s" , resp->code, resp->str);
if (resp->code == 421 || resp->code == 521 ) {
close_session(session);
return ;
}
} else {
msg_fatal("end of data rejected: %d %s" , resp->code, resp->str);
}
} while (talk_lmtp && --session->rcpt_done > 0 );
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_rset - send RSET command */
static void send_rset(int unused_event, void *context)
{
SESSION *session = (SESSION *) context;
command(session->stream, "RSET" );
event_enable_read(vstream_fileno(session->stream), rset_done, (void *) session);
}
/* rset_done - handle RSET reply */
static void rset_done(int unused_event, void *context)
{
SESSION *session = (SESSION *) context;
RESPONSE *resp;
int except;
/*
* Get response to RSET command .
*/
if ((except = vstream_setjmp(session->stream)) != 0 )
msg_fatal("%s while sending message" , exception_text(except));
if ((resp = response(session->stream, buffer))->code / 100 == 2 ) {
/* void */
} else if (allow_reject) {
msg_warn("rset rejected: %d %s" , resp->code, resp->str);
if (resp->code == 421 || resp->code == 521 ) {
close_session(session);
return ;
}
} else {
msg_fatal("rset rejected: %d %s" , resp->code, resp->str);
}
/*
* 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_enable_read(vstream_fileno(session->stream), quit_done, (void *) session);
}
/* quit_done - disconnect */
static void quit_done(int unused_event, void *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);
}
/* close_session - disconnect, for example after 421 or 521 reply */
static void close_session(SESSION *session)
{
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 -cdLNov -s sess -l msglen -m msgs -C count -M myhostname -f from -t to -r rcptcount -R delay -w delay host[:port]" , myname);
}
MAIL_VERSION_STAMP_DECLARE;
/* main - parse JCL and start the machine */
int main(int argc, char **argv)
{
SESSION *session;
char *host;
char *port;
char *path;
int path_len;
int sessions = 1 ;
int ch;
int i;
char *buf;
const char *parse_err;
struct addrinfo *res;
int aierr;
const char *protocols = INET_PROTO_NAME_ALL;
char *message_file = 0 ;
/*
* Fingerprint executables and core dumps .
*/
MAIL_VERSION_STAMP_ALLOCATE;
signal(SIGPIPE, SIG_IGN);
msg_vstream_init(argv[0 ], VSTREAM_ERR);
/*
* Parse JCL .
*/
while ((ch = GETOPT(argc, argv, "46AcC:df:F:l:Lm:M:Nor:R:s:S:t:T:vw:" )) > 0 ) {
switch (ch) {
case '4' :
protocols = INET_PROTO_NAME_IPV4;
break ;
case '6' :
protocols = INET_PROTO_NAME_IPV6;
break ;
case 'A' :
allow_reject = 1 ;
break ;
case 'c' :
count++;
break ;
case 'C' :
if ((connect_count = atoi(optarg)) <= 0 )
msg_fatal("bad connection count: %s" , optarg);
break ;
case 'd' :
disconnect = 0 ;
break ;
case 'f' :
sender = optarg;
break ;
case 'F' :
if (message_file == 0 && message_length > 0 )
msg_fatal("-l option cannot be used with -F" );
message_file = optarg;
break ;
case 'l' :
if (message_file != 0 )
msg_fatal("-l option cannot be used with -F" );
if ((message_length = atoi(optarg)) <= 0 )
msg_fatal("bad message length: %s" , optarg);
break ;
case 'L' :
talk_lmtp = 1 ;
break ;
case 'm' :
if ((message_count = atoi(optarg)) <= 0 )
msg_fatal("bad message count: %s" , optarg);
break ;
case 'M' :
if (*optarg == '[' ) {
if (!valid_mailhost_literal(optarg, DO_GRIPE))
msg_fatal("bad address literal: %s" , optarg);
} else {
if (!valid_hostname(optarg, DO_GRIPE))
msg_fatal("bad hostname: %s" , optarg);
}
var_myhostname = optarg;
break ;
case 'N' :
global_rcpt_suffix = 1 ;
break ;
case 'o' :
send_helo_first = 0 ;
send_headers = 0 ;
break ;
case 'r' :
if ((recipients = atoi(optarg)) <= 0 )
msg_fatal("bad recipient count: %s" , optarg);
global_rcpt_suffix = 1 ;
break ;
case 'R' :
if (fixed_delay > 0 )
msg_fatal("do not use -w and -R options at the same time" );
if ((random_delay = atoi(optarg)) <= 0 )
msg_fatal("bad random delay: %s" , optarg);
break ;
case 's' :
if ((sessions = atoi(optarg)) <= 0 )
msg_fatal("bad session count: %s" , optarg);
break ;
case 'S' :
subject = optarg;
break ;
case 't' :
recipient = make_recipient(optarg);
break ;
case 'T' :
if ((inet_windowsize = atoi(optarg)) <= 0 )
msg_fatal("bad TCP window size: %s" , optarg);
break ;
case 'v' :
msg_verbose++;
break ;
case 'w' :
if (random_delay > 0 )
msg_fatal("do not use -w and -R options at the same time" );
if ((fixed_delay = atoi(optarg)) <= 0 )
msg_fatal("bad fixed delay: %s" , optarg);
break ;
default :
usage(argv[0 ]);
}
}
if (argc - optind != 1 )
usage(argv[0 ]);
if (random_delay > 0 )
srand(getpid());
/*
* Initialize the message content , SMTP encoded . smtp_fputs ( ) will append
* another \ r \ n but we don ' t care .
*/
if (message_file != 0 ) {
VSTREAM *fp;
VSTRING *buf = vstring_alloc(100 );
VSTRING *msg = vstring_alloc(100 );
if ((fp = vstream_fopen(message_file, O_RDONLY, 0 )) == 0 )
msg_fatal("open %s: %m" , message_file);
while (vstring_get_nonl(buf, fp) != VSTREAM_EOF) {
if (*vstring_str(buf) == '.' )
VSTRING_ADDCH(msg, '.' );
vstring_memcat(msg, vstring_str(buf), VSTRING_LEN(buf));
vstring_memcat(msg, "\r\n" , 2 );
}
if (vstream_ferror(fp))
msg_fatal("read %s: %m" , message_file);
vstream_fclose(fp);
vstring_free(buf);
message_length = VSTRING_LEN(msg);
message_data = vstring_export(msg);
send_headers = 0 ;
} else if (message_length > 0 ) {
message_data = mymalloc(message_length);
memset(message_data, 'X' , message_length);
for (i = 80 ; i < message_length; i += 80 ) {
message_data[i - 80 ] = "0123456789" [(i / 80 ) % 10 ];
message_data[i - 2 ] = '\r' ;
message_data[i - 1 ] = '\n' ;
}
}
/*
* Translate endpoint address to internal form .
*/
(void ) inet_proto_init("protocols" , protocols);
if (strncmp(argv[optind], "unix:" , 5 ) == 0 ) {
path = argv[optind] + 5 ;
path_len = strlen(path);
if (path_len >= (int ) sizeof (sun.sun_path))
msg_fatal("unix-domain name too long: %s" , path);
memset((void *) &sun, 0 , sizeof (sun));
sun.sun_family = AF_UNIX;
#ifdef HAS_SUN_LEN
sun.sun_len = path_len + 1 ;
#endif
memcpy(sun.sun_path, path, path_len);
sa = (struct sockaddr *) &sun;
sa_length = sizeof (sun);
} else {
if (strncmp(argv[optind], "inet:" , 5 ) == 0 )
argv[optind] += 5 ;
buf = mystrdup(argv[optind]);
if ((parse_err = host_port(buf, &host, (char *) 0 , &port, "smtp" )) != 0 )
msg_fatal("%s: %s" , argv[optind], parse_err);
if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0 )
msg_fatal("%s: %s" , argv[optind], MAI_STRERROR(aierr));
myfree(buf);
sa = (struct sockaddr *) &ss;
if (res->ai_addrlen > sizeof (ss))
msg_fatal("address length %d > buffer length %d" ,
(int ) res->ai_addrlen, (int ) sizeof (ss));
memcpy((void *) sa, res->ai_addr, res->ai_addrlen);
sa_length = res->ai_addrlen;
#ifdef HAS_SA_LEN
sa->sa_len = sa_length;
#endif
freeaddrinfo(res);
}
/*
* smtp_get ( ) makes sure the SMTP server cannot run us out of memory by
* sending never - ending lines of text .
*/
if (buffer == 0 )
buffer = vstring_alloc(100 );
/*
* Make sure we have sender and recipient addresses .
*/
if (var_myhostname == 0 )
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 = make_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->next = 0 ;
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 );
}
}
}
Messung V0.5 in Prozent C=82 H=94 G=88
¤ Dauer der Verarbeitung: 0.21 Sekunden
(vorverarbeitet am 2026-08-08)
¤
*© Formatika GbR, Deutschland