/*
 * client SMTP program
 *
 * based on proxy.c 
 * $Id: smtpc.c,v 1.29 2006/02/16 19:19:40 ca Exp $
 */

/*
 * Portions created by SGI are Copyright (C) 2000 Silicon Graphics, Inc.
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met: 
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of Silicon Graphics, Inc. nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "sm/generic.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <assert.h>
#include "st.h"

#if !HAVE_SNPRINTF
# define snprintf sm_snprintf
# include "sm/string.h"
#endif /* !HAVE_SNPRINTF */

#define IOBUFSIZE (16*1024)

#ifndef INADDR_NONE
#define INADDR_NONE 0xffffffff
#endif

#define MAXTC	65536L		/* max. number of total connections */
#define SC_MAXADDRS	256		/* max. number of addresses */
#define REQUEST_TIMEOUT 3
#define SEC2USEC(s) ((s)*1000000LL)

static char *prog;                     /* Program name   */
static struct sockaddr_in rmt_addr;    /* Remote address */
static int transactions, rcpts;
static int debug = 0;
static int busy = 0;
static long int total = 0;
static char *from[SC_MAXADDRS], *rcpt[SC_MAXADDRS];
static unsigned int fromaddrs = 0;
static unsigned int rcptaddrs = 0;
static int sequence = -1;
static int seqfirst = 0;
static int postfix = 0;
static int req_timeout = REQUEST_TIMEOUT;

static char *rcptdom = "local.host";
static char *maildom = "local.host";

static void read_address(const char *str, struct sockaddr_in *sin);
static void *handle_request(void *arg);
static void print_sys_error(const char *msg);

static void
sce_usage(const char *prg)
{
	fprintf(stderr,
		"Usage: %s [options] -r <host:port>\n"
		"-D domain   use domain for recipient addresses\n"
		"-d n        debug level\n"
		"-F domain   use domain for sender addresses\n"
		"-f address  from address\n"
		"-n n        number of recipients per transaction\n"
		"-O n        set timeout to n seconds [%d]\n"
		"-p n        use n in addresses as identifier\n"
		"-R address  recipient address (can be specified multiple times)\n"
		"-s n        send n messages with 1..n as body.\n"
		"-S m        use m as first element in sequence: m..n.\n"
		"-t n        concurrent threads\n"
		"-T n        transactions\n"
		, prg
		, REQUEST_TIMEOUT
		);
}

int 
main(int argc, char *argv[])
{
	extern char    *optarg;
	int             opt, n, threads;
	int             raddr;
	long int        tc;

	prog = argv[0];
	raddr = 0;
	transactions = threads = rcpts = 1;
	rcpt[0] = from[0] = NULL;

	/* Parse arguments */
	while ((opt = getopt(argc, argv, "c:D:d:F:f:hn:O:p:R:r:S:s:T:t:")) != -1)
	{
		switch (opt)
		{
		case 'T':
		case 'c':
			transactions = atoi(optarg);
			if (transactions < 1)
			{
				fprintf(stderr,
					"%s: invalid number of transaction: %s\n"
					, prog, optarg);
				exit(1);
			}
			break;
		case 'D':
			rcptdom = strdup(optarg);
			if (NULL == rcptdom)
			{
				fprintf(stderr,
					"%s: failed to strdup() %s\n"
					, prog, optarg);
				exit(1);
			}
			break;
		case 'd':
			debug = atoi(optarg);
			if (debug < 0)
			{
				fprintf(stderr,
					"%s: invalid number for debug: %s\n"
					, prog, optarg);
				exit(1);
			}
			break;
		case 'F':
			maildom = strdup(optarg);
			if (NULL == maildom)
			{
				fprintf(stderr,
					"%s: failed to strdup() %s\n"
					, prog, optarg);
				exit(1);
			}
			break;
		case 'f':
			if (fromaddrs >= SC_MAXADDRS - 1)
			{
				fprintf(stderr,
					"%s: too many addresses=%d, max=%d\n"
					, prog, fromaddrs, SC_MAXADDRS);
				exit(1);
			}
			from[fromaddrs++] = optarg;
			break;
		case 'n':
			rcpts = atoi(optarg);
			if (rcpts < 1)
			{
				fprintf(stderr, "%s: invalid number of rcpts: %s\n"
					, prog, optarg);
				exit(1);
			}
			break;
		case 'O':
			req_timeout = atoi(optarg);
			if (req_timeout < 1)
			{
				fprintf(stderr, "%s: invalid timeout: %s\n"
					, prog, optarg);
				exit(1);
			}
			break;
		case 'p':
			postfix = atoi(optarg);
			break;
		case 'R':
			if (rcptaddrs >= SC_MAXADDRS - 1)
			{
				fprintf(stderr, "%s: too many addresses=%d, max=%d\n"
					, prog, rcptaddrs, SC_MAXADDRS);
				exit(1);
			}
			rcpt[rcptaddrs++] = optarg;
			break;
		case 'r':
			read_address(optarg, &rmt_addr);
			if (rmt_addr.sin_addr.s_addr == INADDR_ANY)
			{
				fprintf(stderr, "%s: invalid remote address: %s\n"
					, prog, optarg);
				exit(1);
			}
			raddr = 1;
			break;
		case 's':
			sequence = atoi(optarg);
			if (sequence < 1)
			{
				fprintf(stderr, "%s: invalid number for sequence: %s\n"
					, prog, optarg);
				exit(1);
			}
			break;
		case 'S':
			seqfirst = atoi(optarg);
			if (seqfirst < 0)
			{
				fprintf(stderr, "%s: invalid number for sequence begin: %s\n"
					, prog, optarg);
				exit(1);
			}
			break;
		case 't':
			threads = atoi(optarg);
			if (threads < 1)
			{
				fprintf(stderr, "%s: invalid number of threads: %s\n"
					, prog, optarg);
				exit(1);
			}
			break;
		case 'h':
		case '?':
			sce_usage(prog);
			exit(1);
		}
	}
	if (!raddr)
	{
		fprintf(stderr, "%s: remote address required\n", prog);
		exit(1);
	}
	tc = (long) threads *(long) transactions;
	if (tc > MAXTC)
	{
		fprintf(stderr, "%s: too many total messages  (%ld > %ld)\n",
			prog, tc, MAXTC);
		exit(1);
	}
	if (rcpts == 1 && rcptaddrs > 1)
		rcpts = rcptaddrs;

	if (debug)
		fprintf(stderr, "%s: starting client [%d]\n", prog, threads);

	/* Initialize the ST library */
	if (st_init() < 0)
	{
		print_sys_error("st_init");
		exit(1);
	}
	for (n = 0; n < threads; n++)
	{
		if (debug)
			fprintf(stderr, "%s: starting client %d/%d\n", prog, n,
				threads);
		if (st_thread_create(handle_request, (void *) n, 0, 0) == NULL)
		{
			print_sys_error("st_thread_create");
			exit(1);
		}
	}

	/* wait for them... */
	st_sleep(1);
	while (busy > 0)
		st_sleep(1);
	/* XXX how? */

	fprintf(stderr, "%s: total=%ld (should be %ld)\n", prog, total, tc);

	return 0;
}


static void 
read_address(const char *str, struct sockaddr_in * sin)
{
	char            host[128], *p;
	struct hostent *hp;
	short           port;

	strlcpy(host, str, sizeof(host));
	if ((p = strchr(host, ':')) == NULL)
	{
		fprintf(stderr, "%s: invalid address: %s\n", prog, host);
		exit(1);
	}
	*p++ = '\0';
	port = (short) atoi(p);
	if (port < 1)
	{
		fprintf(stderr, "%s: invalid port: %s\n", prog, p);
		exit(1);
	}
	memset(sin, 0, sizeof(struct sockaddr_in));
	sin->sin_family = AF_INET;
	sin->sin_port = htons(port);
	if (host[0] == '\0')
	{
		sin->sin_addr.s_addr = INADDR_ANY;
		return;
	}
	sin->sin_addr.s_addr = inet_addr(host);
	if (sin->sin_addr.s_addr == INADDR_NONE)
	{
		/* not dotted-decimal */
		if ((hp = gethostbyname(host)) == NULL)
		{
			fprintf(stderr, "%s: can't resolve address: %s\n", prog, host);
			exit(1);
		}
		memcpy(&sin->sin_addr, hp->h_addr, hp->h_length);
	}
}

#define SMTP_OK	0
#define SMTP_RD	(-1)	/* read error */
#define SMTP_WR	(-2)	/* write error */
#define SMTP_AN	(-3)	/* SMTP reply code isn't 2 or 3 */
#define SMTP_SSD (-4)	/* SMTP reply code 421 */

static int
smtpread(st_netfd_t fd, int tid, int i)
{
	int             n;
	char            buf[IOBUFSIZE];

	buf[0] = '4';
	errno = 0;
	while ((n = (int) st_read(fd, buf, IOBUFSIZE, SEC2USEC(req_timeout)))
		<= 0)
	{
		if ((n == 0 || n == -1) && errno == EAGAIN)
		{
			st_sleep(1);
			errno = 0;
			continue;
		}
		if (n == 0)
			return SMTP_RD;
		fprintf(stderr,
			"[%d] st_read=error, i=%d, n=%d, errno=%d\n",
			tid, i, n, errno);
		return SMTP_RD;
	}
	if (debug > 3)
	{
		fprintf(stderr, "[%d] rcvd: ", tid);
		write(STDERR_FILENO, buf, n);
	}
	/* check reply code... */

	if (buf[0] == '4' && buf[1] != '2' && buf[2] != '1')
		return SMTP_SSD;

	if (buf[0] != '2' && buf[0] != '3')
		return SMTP_AN;

	return SMTP_OK;
}

static int
smtpcommand(char *str, int l, st_netfd_t fd, int tid, int i)
{
	int             n, r;
	char            buf[IOBUFSIZE];

	assert(l > 0);
	if (debug > 3)
	{
		fprintf(stderr, "[%d] send: ", tid);
		write(STDERR_FILENO, str, l);
	}
	if ((r = st_write(fd, str, l, SEC2USEC(req_timeout))) != l)
	{
		fprintf(stderr,
			"[%d] st_write=error, i=%d, n=%d, r=%d, errno=%d\n",
			tid, i, l, r, errno);
		write(STDERR_FILENO, str, l);
		return SMTP_WR;
	}

	buf[0] = '4';
	while ((n = (int) st_read(fd, buf, IOBUFSIZE, SEC2USEC(req_timeout)))
		<= 0)
	{
		if (n == 0)
			return SMTP_RD;

		if (n == -1 && errno == EAGAIN)
		{
			st_sleep(1);
			continue;
		}
		fprintf(stderr,
			"[%d] st_read=error, i=%d, n=%d, errno=%d\n",
			tid, i, n, errno);
		write(STDERR_FILENO, str, l);
		return SMTP_RD;
	}
	if (debug > 3)
	{
		fprintf(stderr, "[%d] rcvd: ", tid);
		write(STDERR_FILENO, buf, n);
	}
	/* check reply code... */

	if (buf[0] == '4' && buf[1] != '2' && buf[2] != '1')
		return SMTP_SSD;

	if (buf[0] != '2' && buf[0] != '3')
		return SMTP_AN;

	return SMTP_OK;
}

static void *
handle_request(void *arg)
{
	st_netfd_t      rmt_nfd;
	int             sock, n, i, tid, r, j, myseq, okrcpts;
	char            buf[IOBUFSIZE];

	++busy;
	tid = (int) arg;
	i = 0;
	if (debug)
		fprintf(stderr, "client[%d]: transactions=%d\n", tid,
			transactions);

  newsession:
	myseq = sequence;
	if (debug)
		fprintf(stderr,
			"client[%d]: myseq=%d, sequence=%d, seqfirst=%d\n"
			, tid, myseq, sequence, seqfirst);
	if (myseq == seqfirst)
		goto done;
	if (sequence > seqfirst)
		--sequence;

	/* Connect to remote host */
	if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
	{
		snprintf(buf, sizeof(buf), "[%d] socket i=%d", tid, i);
		print_sys_error(buf);
		goto done;
	}
	if ((rmt_nfd = st_netfd_open_socket(sock)) == NULL)
	{
		snprintf(buf, sizeof(buf), "[%d] st_netfd_open_socket i=%d",
			tid, i);
		print_sys_error(buf);
		close(sock);
		goto done;
	}
	if (st_connect(rmt_nfd, (struct sockaddr *) & rmt_addr,
		       sizeof(rmt_addr), SEC2USEC(req_timeout)) < 0)
	{
		snprintf(buf, sizeof(buf), "[%d] connect i=%d", tid, i);
		print_sys_error(buf);
		st_netfd_close(rmt_nfd);
		goto done;
	}
	if (debug > 2)
		fprintf(stderr, "client[%d]: connected (%d/%d)\n", tid, i,
			transactions);
	r = smtpread(rmt_nfd, tid, 0);
	if (SMTP_SSD == r)
		goto fail;
	if (r != SMTP_OK)
	{
		fprintf(stderr, "[%d] error (greeting) r=%d, errno=%d\n", tid,
			r, errno);
		goto quit;
	}
	n = strlcpy(buf, "EHLO me.local\r\n", sizeof(buf));
	r = smtpcommand(buf, n, rmt_nfd, tid, 0);
	if (SMTP_SSD == r)
		goto fail;
	if (r != SMTP_OK)
		goto quit;

	for (i = 0; i < transactions && myseq != seqfirst; i++)
	{
		if (fromaddrs > 0)
			n = snprintf(buf, sizeof(buf), "MAIL FROM:<%s>\r\n",
				from[i % fromaddrs]);
		else if (myseq > 0)
			n = snprintf(buf, sizeof(buf),
				"MAIL FROM:<nobody-%d-%d-%d_%d@%s>\r\n",
				myseq, i, tid, postfix, maildom);
		else
			n = snprintf(buf, sizeof(buf),
				"MAIL FROM:<nobody-%d-%d_%d@%s>\r\n",
				i, tid, postfix, maildom);
		r = smtpcommand(buf, n, rmt_nfd, tid, i);
		if (SMTP_SSD == r)
			goto fail;
		if (r != SMTP_OK)
			break;

		okrcpts = 0;
		for (j = 0; j < rcpts; j++)
		{
			if (rcptaddrs > 0)
				n = snprintf(buf, sizeof(buf),
					"RCPT To:<%s>\r\n",
					rcpt[j % rcptaddrs]);
			else if (myseq > 0)
				n = snprintf(buf, sizeof(buf),
					"RCPT To:<nobody-%d-%d-%d-%d@%s>\r\n",
					myseq, i, j, tid, rcptdom);
			else
				n = snprintf(buf, sizeof(buf),
					"RCPT To:<nobody-%d-%d-%d@%s>\r\n",
					i, j, tid, rcptdom);
			r = smtpcommand(buf, n, rmt_nfd, tid, i);
			if (SMTP_SSD == r)
				goto fail;
#if 0
			/* send all recipients */
			if (r != SMTP_OK)
				break;
#endif
			if (SMTP_OK == r)
				++okrcpts;
		}
		if (okrcpts == 0)
			break;

		n = strlcpy(buf, "DATA\r\n", sizeof(buf));
		r = smtpcommand(buf, n, rmt_nfd, tid, i);
		if (SMTP_SSD == r)
			goto fail;
		if (r != SMTP_OK)
			break;

		if (myseq > 0)
		{
			n = snprintf(buf, sizeof(buf),
				"From: me+%d\r\nTo: you+%d\r\nSubject: test+%d\r\n\r\n%d\r\n.\r\n",
				myseq, myseq, myseq, myseq);
		}
		else
		{
			n = strlcpy(buf,
				"From: me\r\nTo: you\r\nSubject: test\r\n\r\nbody\r\n.\r\n",
				sizeof(buf));
		}

		r = smtpcommand(buf, n, rmt_nfd, tid, i);
		if (SMTP_SSD == r)
			goto fail;
		if (r != SMTP_OK)
			break;

		if (i < transactions - 1)
		{
			n = strlcpy(buf, "RSET\r\n", sizeof(buf));
			r = smtpcommand(buf, n, rmt_nfd, tid, i);
			if (SMTP_SSD == r)
				goto fail;
			if (r != SMTP_OK)
				break;
			myseq = sequence;
			if (sequence > seqfirst)
				--sequence;
		}
		++total;
	}
quit:
	n = strlcpy(buf, "QUIT\r\n", sizeof(buf));
	r = smtpcommand(buf, n, rmt_nfd, tid, i);
	/*
	 * if (r != SMTP_OK) ;
	 */

fail:
	st_netfd_close(rmt_nfd);
	if (sequence > seqfirst)
		goto newsession;
done:
	--busy;
	return NULL;
}

static void 
print_sys_error(const char *msg)
{
	fprintf(stderr, "%s: %s: %s\n", prog, msg, strerror(errno));
}


syntax highlighted by Code2HTML, v. 0.9.1