#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>
#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>
#include <smtp_stream.h>
#include <mail_date.h>
#include <mail_version.h>
typedef struct SESSION {
int xfer_count;
int rcpt_done;
int rcpt_count;
int rcpt_accepted;
VSTREAM *stream;
int connect_count;
struct SESSION *next;
} SESSION;
static SESSION *last_session;
typedef struct {
int code;
char *str;
VSTRING *buf;
} 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;
static char *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 number_rcpts = 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 *);
static int random_interval(int interval)
{
return (rand() % (interval + 1));
}
static void command(VSTREAM *stream, char *fmt,...)
{
va_list ap;
va_start(ap, fmt);
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);
}
static int socket_error(int sock)
{
int error;
SOCKOPT_SIZE error_len;
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);
}
return (0);
}
static RESPONSE *response(VSTREAM *stream, VSTRING *buf)
{
static RESPONSE rdata;
int more;
char *cp;
if (rdata.buf == 0) {
rdata.buf = vstring_alloc(100);
vstring_ctl(rdata.buf, CA_VSTRING_CTL_MAXLEN(var_line_limit), 0);
}
#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++;
vstring_strcat(rdata.buf, cp);
if (more == 0)
break;
VSTRING_ADDCH(rdata.buf, '\n');
}
VSTRING_TERMINATE(rdata.buf);
rdata.str = vstring_str(rdata.buf);
return (&rdata);
}
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);
}
}
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);
}
}
static void start_event(int unused_event, void *context)
{
SESSION *session = (SESSION *) context;
startup(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);
}
}
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;
}
}
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);
}
}
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);
}
static void start_connect(SESSION *session)
{
int fd;
struct linger linger;
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);
}
static void connect_done(int unused_event, void *context)
{
SESSION *session = (SESSION *) context;
int fd = vstream_fileno(session->stream);
if (socket_error(fd) < 0) {
fail_connect(session);
} else {
non_blocking(fd, BLOCKING);
event_disable_readwrite(fd);
event_enable_read(fd, read_banner, (void *) session);
dequeue_connect(session);
if (sa->sa_family == AF_INET
#ifdef AF_INET6
|| sa->sa_family == AF_INET6
#endif
)
vstream_tweak_tcp(session->stream);
}
}
static void read_banner(int unused_event, void *context)
{
SESSION *session = (SESSION *) context;
RESPONSE *resp;
int except;
if ((except = vstream_setjmp(session->stream)) != 0)
msg_fatal("%s while reading server greeting", exception_text(except));
if (((resp = response(session->stream, buffer))->code / 100) == 2) {
;
} 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);
}
if (send_helo_first)
send_helo(session);
else
send_mail(session);
}
static void send_helo(SESSION *session)
{
int except;
const char *NOCLOBBER protocol = (talk_lmtp ? "LHLO" : "HELO");
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);
event_enable_read(vstream_fileno(session->stream), helo_done, (void *) session);
}
static void helo_done(int unused_event, void *context)
{
SESSION *session = (SESSION *) context;
RESPONSE *resp;
int except;
const char *protocol = (talk_lmtp ? "LHLO" : "HELO");
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) {
;
} 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);
}
static void send_mail(SESSION *session)
{
int except;
if ((except = vstream_setjmp(session->stream)) != 0)
msg_fatal("%s while sending sender", exception_text(except));
command(session->stream, "MAIL FROM:<%s>", sender);
event_enable_read(vstream_fileno(session->stream), mail_done, (void *) session);
}
static void mail_done(int unused, void *context)
{
SESSION *session = (SESSION *) context;
RESPONSE *resp;
int except;
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);
}
}
static void send_rcpt(int unused_event, void *context)
{
SESSION *session = (SESSION *) context;
int except;
if ((except = vstream_setjmp(session->stream)) != 0)
msg_fatal("%s while sending recipient", exception_text(except));
if (session->rcpt_count > 1 || number_rcpts > 0)
command(session->stream, "RCPT TO:<%d%s>",
number_rcpts ? number_rcpts++ : session->rcpt_count,
recipient);
else
command(session->stream, "RCPT TO:<%s>", recipient);
session->rcpt_count--;
session->rcpt_done++;
event_enable_read(vstream_fileno(session->stream), rcpt_done, (void *) session);
}
static void rcpt_done(int unused, void *context)
{
SESSION *session = (SESSION *) context;
RESPONSE *resp;
int except;
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);
}
if (session->rcpt_count > 0)
send_rcpt(unused, context);
else if (session->rcpt_accepted > 0)
send_data(unused, context);
else
send_rset(unused, context);
}
static void send_data(int unused_event, void *context)
{
SESSION *session = (SESSION *) context;
int except;
if ((except = vstream_setjmp(session->stream)) != 0)
msg_fatal("%s while sending DATA command", exception_text(except));
command(session->stream, "DATA");
event_enable_read(vstream_fileno(session->stream), data_done, (void *) session);
}
static void data_done(int unused, void *context)
{
SESSION *session = (SESSION *) context;
RESPONSE *resp;
int except;
static const char *mydate;
static int mypid;
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) {
;
} 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);
}
if (send_headers) {
if (mydate == 0) {
mydate = mail_date(time((time_t *) 0));
mypid = getpid();
}
smtp_printf(session->stream, "From: <%s>", sender);
smtp_printf(session->stream, "To: <%s>", recipient);
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);
}
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 {
smtp_fputs(message_data, message_length, session->stream);
}
command(session->stream, ".");
if (count) {
counter++;
vstream_printf("%d\r", counter);
vstream_fflush(VSTREAM_OUT);
}
event_enable_read(vstream_fileno(session->stream), dot_done, (void *) session);
}
static void dot_done(int unused_event, void *context)
{
SESSION *session = (SESSION *) context;
RESPONSE *resp;
int except;
if ((except = vstream_setjmp(session->stream)) != 0)
msg_fatal("%s while sending message", exception_text(except));
do {
if ((resp = response(session->stream, buffer))->code / 100 == 2) {
;
} 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++;
if (disconnect || message_count < 1) {
send_quit(session);
} else {
event_disable_readwrite(vstream_fileno(session->stream));
start_another(session);
}
}
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);
}
static void rset_done(int unused_event, void *context)
{
SESSION *session = (SESSION *) context;
RESPONSE *resp;
int except;
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) {
} 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);
}
if (disconnect || message_count < 1) {
send_quit(session);
} else {
event_disable_readwrite(vstream_fileno(session->stream));
start_another(session);
}
}
static void send_quit(SESSION *session)
{
command(session->stream, "QUIT");
event_enable_read(vstream_fileno(session->stream), quit_done, (void *) session);
}
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);
}
static void close_session(SESSION *session)
{
event_disable_readwrite(vstream_fileno(session->stream));
vstream_fclose(session->stream);
session->stream = 0;
start_another(session);
}
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;
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;
MAIL_VERSION_STAMP_ALLOCATE;
signal(SIGPIPE, SIG_IGN);
msg_vstream_init(argv[0], VSTREAM_ERR);
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':
number_rcpts = 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);
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 = 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());
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';
}
}
(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);
}
if (buffer == 0) {
buffer = vstring_alloc(100);
vstring_ctl(buffer, CA_VSTRING_CTL_MAXLEN(var_line_limit), 0);
}
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 = defaddr;
}
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);
}
}
}