/*
 * $Id: 91b0f4d883c3960edfb2b53c90ea0351e1c98f76 $
 *
 * This file is part of nsmtp.
 *
 * Copyright (c) 2007, 2008 Niklas Olmes <niklas@noxa.de>
 *                          http://www.nevm.de/nsmtp/
 *                          NEVM-RIPE
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License Version 2
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include <stdlib.h>
#include <syslog.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <fcntl.h>
#include <stdarg.h>

#include <openssl/ssl.h>
#include <openssl/err.h>

#include "ldap.h"

#include "base64.h"
#include "ntlc.h"
#include "alloc.h"
#include "ntu.h"
#include "dns.h"
#include "spool.h"
#include "log.h"
#include "misc.h"
#include "check.h"
#include "ndn.h"
#include "ssl.h"
#include "proctitle.h"
#include "config.h"

static void do_file(const char *, const char *);
static int try_send(SMTP_CTX *, const char *);
static void setinfo(SMTP_CTX *, const char *, ...);
static void sigterm(int);

static const char rcsid[] = "$Id: 91b0f4d883c3960edfb2b53c90ea0351e1c98f76 $";

static const char abrt[] = "RSET\r\nQUIT\r\n";
static const char quit[] = "QUIT\r\n";

static unsigned long long cycles;
static int want_down = 0;

static LDAP *ld;

extern int ntl_error;

#define ABORT_AND_DEFER()	{											\
					(void)smtp_writeout(ctx, abrt);							\
					smtp_close(ctx);								\
					goto defer;									\
				}
#define EXPECT_OK(okcode)	if (smtp_getline(ctx, buf, sizeof(buf), 0) < 4)						\
					ABORT_AND_DEFER();								\
				if (!(buf[0] == okcode && isdigit((int)buf[1]) && isdigit((int)buf[2])			\
				      && (buf[3] == ' ' || buf[3] == '-')))						\
					ABORT_AND_DEFER();								\
				if (buf[3] == '-') {									\
					/*										\
					 * Multiline Respsose								\
					 * read rest									\
					 */										\
					while (smtp_getline(ctx, buf, sizeof(buf), 0) > 3) {				\
						if (buf[0] != okcode || !(isdigit((int)buf[1]) && isdigit((int)buf[2])))\
							ABORT_AND_DEFER();						\
						if (buf[3] == ' ')							\
							break;								\
					}										\
					if (strlen(buf) < 4 || buf[3] != ' ')						\
						ABORT_AND_DEFER();							\
				}
#define SEND()			if (smtp_writeout(ctx, buf) <= 0)							\
					ABORT_AND_DEFER();
#define EXPECT_ANSWER()		if (smtp_getline(ctx, buf, sizeof(buf), 0) < 4 ||					\
				    !(isdigit((int)buf[0]) && isdigit((int)buf[1]) && isdigit((int)buf[2])		\
				      && (buf[3] == ' ' || buf[3] == '-')))						\
					ABORT_AND_DEFER();
/*
 * return codes:
 * 0  - success
 * 1  - defer
 * 2  - bounced
 * 42 - apparently local
 */
static int
try_send(SMTP_CTX *ctx, const char *rcpt)
{
	LDAPMessage *res, *entry;
	NTL_ATTR *attrs, *attrs2;
	struct sockaddr_in addr;
	char buf[16384], mlc1[256], mlc2[256];
	const char *domain, *host_p, *state;
	const char **relayhost, **authuser, **authpass;
	char *host;
	char *p;
	int relay;
	int port;
	size_t i, pos;
	unsigned long ip;
	int foo;
	int tls, authmeth_plain, authmeth_login;
	int s, e, flag, mxnum;

	setinfo(ctx, "try <%s>", rcpt);

	(void)snprintf(mlc1, sizeof(mlc1), "250 %s", nsmc_name);
	(void)snprintf(mlc2, sizeof(mlc2), "250-%s", nsmc_name);

	s = -1;
	state = "unknown";
	*buf = '\0';

	if (strchr(rcpt, '@') != NULL)
		domain = strchr(rcpt, '@') + 1;
	else {
		xwarn("{%s}|%s| warn: try_send() => rcpt <%s> has no domain part, defer", ctx->conn_id, ctx->lmid, rcpt);
		setinfo(ctx, "bad rcpt <%s>", rcpt);
		return 1;
	}

	/*
	 * Determine if special mail routing should be put in place
	 */
	relay = 0;
	relayhost = authuser = authpass = NULL;
	attrs = attrs2 = NULL;

	#define RLOOKUP()	if ((res = ntl_search(ld, buf, LDAP_SCOPE_BASE, NULL, NULL)) != NULL \
				    && (entry = ldap_first_entry(ld, res)) != NULL) { \
					relay = 1; \
					attrs = ntl_entry(ld, entry); \
					relayhost = ntl_lookup(attrs, "relayhost"); \
					authuser = ntl_lookup(attrs, "authuser"); \
					authpass = ntl_lookup(attrs, "authpass"); \
				} \
				if (res != NULL) \
					(void)ldap_msgfree(res);

	/*
	 * (a) rcpt-based
	 */
	(void)snprintf(buf, sizeof(buf), "cn=%s,cn=route,cn=%s,ou=config,%s", rcpt, nsmc_name, nsmc_mail_basedn);
	RLOOKUP();
	/*
	 * (b) domain-based
	 */
	if (!relay) {
		(void)snprintf(buf, sizeof(buf), "cn=%s,cn=route,cn=%s,ou=config,%s", domain, nsmc_name, nsmc_mail_basedn);
		RLOOKUP();
	}


	/* reset get_next_mx() */
	host = NULL;
	(void)get_next_mx(NULL);

	mxnum = 0;
	while ((relay && relayhost != NULL && (host_p = relayhost[mxnum]) != NULL) || (!relay && (host_p = get_next_mx(rcpt)) != NULL)) {
		mxnum++;

		if (host != NULL)
			nsm_free(host);
		host = nsm_strdup(host_p);
		port = 25;
		if ((p = strchr(host, ':')) != NULL) {
			*p = '\0';
			port = atoi(p+1);
		}

		/*
		 * check if current MX is us
		 */
		if (is_mx_us(host, port)) {
			if (mxnum == 1) {
				DEBUG(("try_send() => we're best preference MX, nsmtpl is responsible, ignoring"));
				return 42;
			}
			xinfo("{%s}|%s| info: DEFERRED: we're a backup MX for <%s> and we were unable to send to a better preference MX", ctx->conn_id, ctx->lmid, rcpt);
			return 1;
		}

		/*
		 * If special mail routing is in place and we don't yet have SMTP-Auth user/pass,
		 * try to get these out of LDAP for this host.
		 */
		if (relay && (*authuser == NULL || *authpass == NULL)) {
			(void)snprintf(buf, sizeof(buf), "cn=%s,cn=auth,cn=%s,ou=config,%s", host, nsmc_name, nsmc_mail_basedn);
			if ((res = ntl_search(ld, buf, LDAP_SCOPE_BASE, NULL, NULL)) != NULL
			    && (entry = ldap_first_entry(ld, res)) != NULL) {
				attrs2 = ntl_entry(ld, entry);
				authuser = ntl_lookup(attrs2, "authuser");
				authpass = ntl_lookup(attrs2, "authpass");
			}
			if (res != NULL)
				(void)ldap_msgfree(res);
		}

		setinfo(ctx, "try <%s>/<%s:%d>", rcpt, host, port);

		*buf = '\0';
		state = "connect";

		if ((ip = resolve(host)) == INADDR_NONE) {
			xinfo("{%s}|%s| info: unable to resolve MX <%s> for rcpt <%s>, trying next MX, if any", ctx->conn_id, ctx->lmid, host, rcpt);
			continue;
		}

		(void)memset(&addr, 0, sizeof(addr));
		addr.sin_addr.s_addr = ip;
		addr.sin_port = htons(port);
		addr.sin_family = AF_INET;

		if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
			xwarn("{%s}|%s| warn: try_send() => socket() failed, this is a local error, defer", ctx->conn_id, ctx->lmid);
			goto defer;
		}
		foo = 1;
		if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &foo, sizeof(foo)) != 0) {
			xwarn("{%s}|%s| warn: try_send() => setsockopt(SO_REUSEADDR) failed, this is a local error, defer", ctx->conn_id, ctx->lmid);
			(void)close(s);
			goto defer;
		}
		if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
			xinfo("{%s}|%s| info: connect failed to <%s>/%d, MX of rcpt <%s>, trying next MX, if any", ctx->conn_id, ctx->lmid, host, port, rcpt);
			setinfo(ctx, "connect failed <%s>/<%s:%d>", rcpt, host, port);
			(void)close(s);
			s = -1;
			continue;
		}

		ctx->client = s;
		setinfo(ctx, "connected <%s>/<%s:%d>", rcpt, host, port);
		EXPECT_OK('2');

		state = "helo";
		setinfo(ctx, "helo <%s>/<%s:%d>", rcpt, host, port);
		if (strstr(buf, "ESMTP") == NULL)
			(void)snprintf(buf, sizeof(buf), "HELO %s\r\n", nsmc_name);
		else
			(void)snprintf(buf, sizeof(buf), "EHLO %s\r\n", nsmc_name);

		SEND();
		EXPECT_ANSWER();
		if (buf[0] != '2')
			ABORT_AND_DEFER();

		/*
		 * Mailloop check
		 */
		if (strcasecmp(buf, mlc1) == 0 || strcasecmp(buf, mlc2) == 0) {
			xwarn("{%s}|%s| warn: security violation: mail loop detected on |HELO| (remote \"%s\" <=> \"%s\" local), MX for rcpt <%s>", ctx->conn_id, ctx->lmid, buf, nsmc_name, rcpt);
			setinfo(ctx, "mail loop <%s>/<%s:%d>", rcpt, host, port);
			(void)smtp_writeout(ctx, quit);
			smtp_close(ctx);
			return 2;
		}

		/* If we had a multiline response (e.g., on EHLO), get rest before issuing new commands */
		tls = 0;
		authmeth_plain = authmeth_login = 0;
		while (buf[3] == '-') {
			EXPECT_ANSWER();
			if (strstr(buf, "STARTTLS") != NULL)
				tls = 1;
			if (strstr(buf, "AUTH") != NULL) {
				if (strstr(buf, "PLAIN") != NULL)
					authmeth_plain = 1;
				if (strstr(buf, "LOGIN") != NULL)
					authmeth_login = 1;
			}
		}

		if (tls) {
			SSL *ssl;
			SSL_CTX *sslctx;
			int result;

			/*
			 * Initiate STARTTLS
			 */
			state = "starttls";
			xinfo("{%s}|%s| info: initiating STARTTLS", ctx->conn_id, ctx->lmid);
			setinfo(ctx, "starttls <%s>/<%s:%d>", rcpt, host, port);

			(void)snprintf(buf, sizeof(buf), "STARTTLS\r\n");
			SEND();
			EXPECT_OK('2');

			/*
			 * Initialize Library and SSL Context
			 */
			(void)SSL_load_error_strings();
			SSL_library_init();
			if ((sslctx = SSL_CTX_new(SSLv23_client_method())) == NULL) {
				xfatal("{%s}|%s| fatal: try_send() => local error: SSL_CTX_new() failed: %s", ctx->conn_id, ctx->lmid, ssl_error());
				goto defer;
			}
			if ((ssl = SSL_new(sslctx)) == NULL) {
				xfatal("{%s}|%s| fatal: try_send() => local error: SSL_new() failed: %s", ctx->conn_id, ctx->lmid, ssl_error());
				SSL_CTX_free(sslctx);
				goto defer;
			}
			SSL_CTX_free(sslctx);

			if (SSL_set_fd(ssl, ctx->client) != 1) {
				xfatal("{%s}|%s| fatal: try_send() => SSL_set_fd() failed");
				SSL_free(ssl);
				goto defer;
			}

			/*
			 * SSL Handshake
			 */
			if ((result = SSL_connect(ssl)) != 1) {
				xwarn("{%s}|%s| warn: SSL/TLS handshake failed at SSL_connect(), returned (%d), SSL error code (%d): %s", ctx->conn_id, ctx->lmid, result, SSL_get_error(ssl, result), ssl_error());
				SSL_free(ssl);
				goto defer;
			}

			/*
			 * Log Context Information and Setup Environment
			 */
			xinfo("{%s}|%s| info: SSL/TLS handshake success, session is now encrypted: %s", ctx->conn_id, ctx->lmid, ssl_info(ssl));
			ctx->ssl = ssl;

			/*
			 * Redo HELO/EHLO, as result of RfC2487 5.2 or RfC3207 4.2
			 */
			state = "helo";
			setinfo(ctx, "starttls helo <%s>/<%s:%d>", rcpt, host, port);
			(void)snprintf(buf, sizeof(buf), "EHLO %s\r\n", nsmc_name);
			SEND();
			EXPECT_OK('2');
		}

		if (relay && *authpass != NULL && *authuser != NULL) {
			char b64in[256], b64[512];
			size_t b64inlen;

			#define B64ENC()	base64_encode(b64in, b64inlen, b64, sizeof(b64) - 1); \
						b64[sizeof(b64) - 1] = '\0';

			state = "relay-auth";
			setinfo(ctx, "relay-auth user <%s>", authuser);
			xinfo("{%s}|%s| info: relay-auth for <%s> with user <%s> on <%s>/%d, supported meths: plain=%d, login=%d", ctx->conn_id, ctx->lmid, rcpt, *authuser, host, port, authmeth_plain, authmeth_login);

			if (authmeth_plain) {
				/*
				 * AUTH PLAIN [b64]
				 * b64 = base64(\0user\0pass)
				 */
				(void)snprintf(b64in, sizeof(b64in), "\n%s\n%s", *authuser, *authpass);
				b64inlen = strlen(b64in);
				p = strrchr(b64in, '\n');
				*p = '\0';
				p = strchr(b64in, '\n');
				*p = '\0';

				B64ENC();
				(void)snprintf(buf, sizeof(buf), "AUTH PLAIN %s\r\n", b64);
				SEND();
				EXPECT_OK('2');
			} else if (authmeth_login) {
				/*
				 * AUTH LOGIN
				 * 334 ...
				 * base64(user)
				 * 334 ...
				 * base64(pass)
				 */
				(void)snprintf(buf, sizeof(buf), "AUTH LOGIN\r\n");
				SEND();
				EXPECT_OK('3');

				/* User */
				(void)snprintf(b64in, sizeof(b64in), "%s", *authuser);
				b64inlen = strlen(b64in);
				B64ENC();
				(void)snprintf(buf, sizeof(buf), "%s\r\n", b64);
				SEND();
				EXPECT_OK('3');

				/* Pass */
				(void)snprintf(b64in, sizeof(b64in), "%s", *authpass);
				b64inlen = strlen(b64in);
				B64ENC();
				(void)snprintf(buf, sizeof(buf), "%s\r\n", b64);
				SEND();
				EXPECT_OK('2');
			} else {
				xwarn("{%s}|%s| warn: relay-auth for <%s> with user <%s> on <%s>/%d, no supported auth method announced.  will defer.", ctx->conn_id, ctx->lmid, rcpt, *authuser, host, port);
				goto defer;
			}
		}

		state = "mail-from";
		setinfo(ctx, "mail-from <%s>/<%s:%d>", rcpt, host, port);
		(void)snprintf(buf, sizeof(buf), "MAIL FROM:<%s>\r\n", ctx->from);
		SEND();
		EXPECT_OK('2');

		state = "rcpt-to";
		setinfo(ctx, "rcpt-to <%s>/<%s:%d>", rcpt, host, port);
		(void)snprintf(buf, sizeof(buf), "RCPT TO:<%s>\r\n", rcpt);
		SEND();
		EXPECT_OK('2');

		state = "data";
		setinfo(ctx, "data => start <%s>/<%s:%d>", rcpt, host, port);
		(void)snprintf(buf, sizeof(buf), "DATA\r\n");
		SEND();
		EXPECT_OK('3');

		/*
		 * XXX/FIXME
		 *
		 * RfC 2821 & RfC 2822 section 2.1.1 specify no line longer than 998 chars (+ \r\n => 1000)
		 *
		 * Our implementation on the receiving (and storage) side allows for more.
		 * We're not supposed to send out more than 998, though.
		 *
		 * What should we do?
		 * For now, we try to send stuff along as we received it.  If it fails, tough luck.
		 */

		/*
		 * Reconvert to \r\n from \n for DATA
		 */
		flag = 0;
		pos = 0;
		while (pos < ctx->size) {
			/* Read until end of line, buffer full, or end of data */
			flag = 0;
			for (i = 0; pos + i < ctx->size && i < sizeof(buf) + 1; i++)
				if ((buf[i] = ctx->data[pos + i]) == '\n') {
					buf[i] = '\r';
					buf[++i] = '\n';
					flag = 1;
					break;
				}

			if (data_writeout(ctx, buf, i+1) <= 0) {
				xwarn("{%s}|%s| warn: error writing (%u) chars of |DATA| to <%s>/%d, MX for rcpt <%s>, defer",
				     ctx->conn_id, ctx->lmid, i, ctx->size, host, port, rcpt);
				setinfo(ctx, "data => write error <%s>/<%s:%d>", rcpt, host, port);
				smtp_close(ctx);
				goto defer;
			}

			pos += i;
		}

		/*
		 * Finish up:  only send full \r\n.\r\n if for some reason we didn't send \r\n just before.
		 */
		state = "finish";
		setinfo(ctx, "data => finish <%s>/<%s:%d>", rcpt, host, port);
		if (!flag)
			(void)snprintf(buf, sizeof(buf), "\r\n.\r\n");
		else
			(void)snprintf(buf, sizeof(buf), ".\r\n");
		SEND();
		EXPECT_OK('2');

		/*
		 * yieeeeha!
		 */
		/* strip \r\n from server reply */
		if (buf[strlen(buf) - 1] == '\r')
			buf[strlen(buf) - 1] = '\0';
		if (buf[strlen(buf)] == '\n')
			buf[strlen(buf) - 1] = '\0';
		xinfo("{%s}|%s| info: SUCCESS to rcpt <%s>. envelope from=<%s>, MX=<%s>/%d. remote host said: \"%s\"", ctx->conn_id, ctx->lmid, rcpt, ctx->from, host, port, buf);
		setinfo(ctx, "data => success <%s>/<%s:%d>", rcpt, host, port);

		(void)smtp_writeout(ctx, quit);
		smtp_close(ctx);

		if (host != NULL)
			nsm_free(host);

		if (attrs != NULL)
			ntl_entry_free(attrs);
		if (attrs2 != NULL)
			ntl_entry_free(attrs2);

		return 0;
	}

	/*
	 * NOTE:  This mark is only reached if no MX left or by jumping to it from the loop.
	 */
defer:
	e = 1;

	if (host == NULL) {
		xinfo("{%s}|%s| info: DEFERRED: unable to send to rcpt <%s>. no MX left", ctx->conn_id, ctx->lmid, rcpt);
		setinfo(ctx, "defer: no mx left <%s>", rcpt);
	} else if (isdigit((int)buf[0]) && isdigit((int)buf[1]) && isdigit((int)buf[2]) && (buf[3] == '-' || buf[3] == ' ')) {
		if (strcmp(state, "helo") == 0 || strcmp(state, "starttls") == 0
		    || ((strcmp(state, "mail-from") == 0 || strcmp(state, "rcpt-to") == 0 || strcmp(state, "data") == 0) && buf[0] < '5')) {
			xinfo("{%s}|%s| info: DEFERRED: unable to send to rcpt <%s>. envelope from=<%s>, MX=<%s>. remote host said \"%s\" at state \"%s\"", ctx->conn_id, ctx->lmid, rcpt, ctx->from, host, buf, state);
			setinfo(ctx, "defer: remote host %c%c%c at state %s <%s>/<%s>", buf[0], buf[1], buf[2], state, rcpt, host);
		} else if (buf[0] == '5') {
			const char *rcptlist[2];
			xinfo("{%s}|%s| info: BOUNCED: unable to send to rcpt <%s>. envelope from=<%s>, MX=<%s>. remote host said \"%s\" at state \"%s\"; giving up", ctx->conn_id, ctx->lmid, rcpt, ctx->from, host, buf, state);
			setinfo(ctx, "bounce: remote host %c%c%c at state %s <%s>/<%s>", buf[0], buf[1], buf[2], state, rcpt, host);
			e = 2;
			rcptlist[0] = rcpt;
			rcptlist[1] = NULL;
			if (ndn_bounce(ctx, rcptlist, host, buf, state, time(NULL)) != 0)
				e = 1;
		} else {
			xinfo("{%s}|%s| info: DEFERRED: unable to send to rcpt <%s>. envelope from=<%s>, MX=<%s>. remote host said \"%s\" at state \"%s\"", ctx->conn_id, ctx->lmid, rcpt, ctx->from, host, buf, state);
			setinfo(ctx, "defer: remote host %c%c%c at state %s <%s>/<%s>", buf[0], buf[1], buf[2], state, rcpt, host);
		}
	} else {
		xinfo("{%s}|%s| info: DEFERRED: unable to send to rcpt <%s>. envelope from=<%s>, MX=<%s>. remote host returned garbage at state \"%s\"", ctx->conn_id, ctx->lmid, rcpt, ctx->from, host, state);
		setinfo(ctx, "defer: remote host garbage at state %s <%s>/<%s>", state, rcpt, host);
	}
	smtp_close(ctx);

	if (host != NULL)
		nsm_free(host);

	if (attrs != NULL)
		ntl_entry_free(attrs);
	if (attrs2 != NULL)
		ntl_entry_free(attrs2);

	return e;
}

static void
do_file(const char *subdir, const char *fn)
{
	SMTP_CTX *ctx;
	long long oac;
	int i, e;

	DEBUG(("do_file() => spool subdir <%s>, file <%s>", subdir, fn));

	oac = nsm_alloc_reset(0);

	if ((ctx = read_spoolfile(subdir, fn)) == NULL) {
		xwarn("warn: do_file() => unable to read file: spool subdir <%s>, file <%s>", subdir, fn);
		goto dropout;
	}

	if (ctx->rcpts == NULL) {
		xwarn("warn: do_file() => spool file <%s> has no rcpts", fn);
		goto dropout;
	}

	DEBUG(("do_file() => ctx: lmid <%s>, from <%s>, size (%lu)", ctx->lmid, ctx->from, (unsigned long)ctx->size));

	for (i = 0; ctx->rcpts[i] != NULL; i++) {
		DEBUG(("do_file() => for-each-recipient, current <%s>", ctx->rcpts[i]));

		e = try_send(ctx, ctx->rcpts[i]);
		(void)get_next_mx(NULL);
		switch (e) {
		case 0:
			DEBUG(("do_file() => success for rcpt <%s>", ctx->rcpts[i]));
			*(ctx->rcpts[i]) = '\0';
			break;
		case 2:
			DEBUG(("do_file() => bounced for rcpt <%s>", ctx->rcpts[i]));
			*(ctx->rcpts[i]) = '\0';
			break;
		case 42:
			DEBUG(("do_file() => rcpt judging by best preference MX record is local <%s>, task is up to nsmtpl", ctx->rcpts[i]));
			break;
		default:
			DEBUG(("do_file() => deferred for rcpt <%s>", ctx->rcpts[i]));
			defer_rcpt(subdir, fn, ctx, ctx->rcpts[i]);
			*(ctx->rcpts[i]) = '\0';
			break;
		}

	}
	remove_empty_rcpts(ctx->rcpts);

	if (update_spool(subdir, fn, ctx) != 0)
		xfatal("{%s}|%s| fatal: do_file() => update_spool() failed after creating facts, this is bad", ctx->conn_id, ctx->lmid);

dropout:
	if (ctx != NULL)
		destroy_smtp_ctx(ctx);

	if (!nsm_alloc_check())
		xwarn("warn: do_file() => DEBUG/QA: nsm_alloc_check() reports failure");
	(void)nsm_alloc_reset(oac);
}

static void
setinfo(SMTP_CTX *ctx, const char *fmt, ...)
{
	char state[512];
	va_list ap;

	va_start(ap, fmt);
	(void)vsnprintf(state, sizeof(state), fmt, ap);
	va_end(ap);

	DEBUG(("setinfo() => state change <%s>", state));

	if (ctx == NULL) {
		setproctitle("@%lu %s", time(NULL), state);
		return;
	}

	setproctitle("@%lu |%s|<%s>%s%s", time(NULL), ctx->lmid, ctx->from, state != NULL ? " " : "", state != NULL ? state : "");
}

static void
sigterm(int ignore)
{

	(void)ignore;
	want_down = 1;
}

int
main(int argc, char *argv[])
{
	char subdir[128], progname[128];
	const char *cfgfile;
	int ch;

	setprogname(argv[0]);
	cfgfile = NSMTP_DEFAULT_CONFIG;

	while ((ch = getopt(argc, argv, "c:hv")) != -1)
		switch (ch) {
		case 'c':
			cfgfile = optarg;
			break;
		case 'v':
			(void)fprintf(stderr, "nsmtp Version %s\n", NSMTP_VERSION);
			(void)fprintf(stderr, "Copyright (c) 2007, 2008 Niklas Olmes <niklas@noxa.de>\n"
				      "                         http://www.nevm.de/nsmtp/\n"
				      "                         NEVM-RIPE\n"
				      "Licensed under the GNU GPL Version 2.\n");
			exit(EXIT_SUCCESS);
		case '?': /* FALLTHRU */
		case 'h': /* FALLTHRU */
		default:
			(void)fprintf(stderr, "Usage: %s [-c cfgfile] [-hv] spoolsubdir\n", getprogname());
			exit(EXIT_FAILURE);
		}

	if (argc - optind != 1) {
		(void)fprintf(stderr, "Usage error:  Missing parameter spoolsubdir on command line.\n");
		(void)fprintf(stderr, "%s -h for Usage.\n", getprogname());
		exit(EXIT_FAILURE);
	}

	(void)snprintf(subdir, sizeof(subdir), "%s", argv[optind]);
	(void)snprintf(progname, sizeof(progname), "%s %s", argv[0], subdir);
	setprogname(progname);

	openlog("nsmtp(smtpr)", LOG_PID, LOG_MAIL);
	if (nsm_config_read(cfgfile) != 0)
		nsm_config_perror("smtpr");
	read_ldap_cfg();
	xinfo("info: nsmtp smtpr started for host <%s>[%s] alias <%s> domain <%s>, initializing. spoolsubdir <%s>", nsmc_name, nsmc_ip, nsmc_alias, nsmc_domain, subdir);

	(void)signal(SIGTERM, sigterm);
	(void)signal(SIGPIPE, SIG_IGN);

	ntu_seed();
	drop_privileges(nsmc_smtpr_uid, nsmc_smtpr_gid, nsmc_smtpr_chroot);
	init_smtp_sighandlers();
	ldap_connect(&ld);
	daemonize();

	initproctitle(argc, argv);
	setproctitle("started");

	cycles = 0;
	while (!want_down) {
		if (++cycles % 10000 == 0)
			xinfo("info: still alive after (%llu) cycles", cycles);

		if (ntl_error == LDAP_SERVER_DOWN) {
			setinfo(NULL, "ldap reconnect");
			if (ldap_reconnect(&ld) != 0) {
				setinfo(NULL, "ldap unavailable, waiting");
				(void)sleep((unsigned int)((random() % 60) + 30));
				continue;
			}
		}

		setproctitle("run");
		do_dir(subdir, do_file);

		setproctitle("idle");
		(void)sleep((unsigned int)((random() % 5) + 1));
	}

	xinfo("info: SIGTERM received, shutdown");
	setproctitle("shutdown");

	nsm_config_free();
	free_ldap_cfg();

	if (!nsm_alloc_check())
		xwarn("warn: main() => DEBUG/QA: nsm_alloc_check() reports failure");

	xinfo("info: shutdown complete");

	exit(EXIT_SUCCESS);
}
