/*
 * $Id: e8d2ac73170b2b43add50dda1f0885a7c48d6e9a $
 *
 * 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.
 *
 */

/*
 * ENHANCEDSTATUSCODES	RfC2034; RfC1893, RfC3463
 * PIPELINING		RfC2920
 * SIZE			RfC1870
 * STARTTLS		RfC2487
 * AUTH			RfC2554
 * 8BITMIME		RfC1652
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <syslog.h>
#include <string.h>
#include <strings.h>
#include <netdb.h>
#include <poll.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>

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

#include "ntu.h"
#include "alloc.h"
#include "base64.h"
#include "smtp.h"
#include "spool.h"
#include "check.h"
#include "dns.h"
#include "virscan.h"
#include "spamscan.h"
#include "log.h"
#include "signals.h"
#include "misc.h"
#include "ldapauth.h"
#include "legacyauth.h"
#include "relay.h"
#include "greylist.h"
#include "spf.h"
#include "ssl.h"
#include "proctitle.h"
#include "config.h"

#define CONTINUE	0
#define SHUTDOWN	1
#define DEAD		2

#ifndef INFTIM
#define INFTIM		-1
#endif

#define MAX_PORTS	8

#define SMTP_ERROR(se_args)		if ((smtp_error_status = smtp_error se_args) != CONTINUE) { return smtp_error_status; }
#define SMTP_ERROR_DROPOUT(se_args)	if ((smtp_error_status = smtp_error se_args) != CONTINUE) { goto dropout_smtp_error; }

#define WRITEOUT(w_args)		if (smtp_writeout w_args < 1) { return DEAD; }
#define WRITEOUT_DROPOUT(w_args)	if (smtp_writeout w_args < 1) { goto dropout_writeout; }

static int get_next_arg(const char *, char **, int *);
static int do_helo(SMTP_CTX *, const char *);
static int do_ehlo(SMTP_CTX *, const char *);
static int do_noop(SMTP_CTX *);
static int do_rset(SMTP_CTX *);
static int do_mail(SMTP_CTX *, const char *);
static int do_rcpt(SMTP_CTX *, const char *);
static int do_auth(SMTP_CTX *, const char *);
static int do_data(SMTP_CTX *);
static int do_tls(SMTP_CTX *);
static int do_quit(SMTP_CTX *);
static void do_smtp(int, struct clinfo *, int);
static void setinfo(SMTP_CTX *, const char *, ...);
static void socket_setup(int, const char *, const char *);
static void open_sockets(void);
static void handle(void);

static int multi_auth(SMTP_CTX *, const char *, const char *);
static int smtp_error(SMTP_CTX *, const char *, ...);
static void ipc_setup(void);
static void ipc_destroy(void);

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

extern char virscan_last[];

static const char *cmd_err = "502 5.5.2 unknown cmd\r\n";
static const char *nocmd_err = "500 5.5.2 what you say?\r\n";
static const char *hadmail_err = "503 5.5.1 you already did this\r\n";
static const char *badmailsyn_err = "553 5.1.7 we very much dislike this sender address syntax\r\n";
static const char *helofirst_err = "503 5.1.1 dude, what's your name again?  HELO/EHLO first, mail second\r\n";
static const char *invalidhelo_err = "501 5.5.4 please come back with a valid HELO/EHLO, try to ask your peers for your real name or something\r\n";
static const char *nomailbox_sender_err = "550 5.1.1 sorry, sender address is local and no mailbox here by that name\r\n";
static const char *nomailbox_rcpt_err = "550 5.1.1 sorry, no mailbox here by that name\r\n";
static const char *mailfirst_err = "503 5.5.1 please be so kind and specify the sender, first\r\n";
static const char *rcptsfirst_err = "503 5.5.1 would you be a doll and specify any recipients, first?\r\n";
static const char *noaddress_sender_err = "553 5.1.7 actually specifying an e-mail address would be very much appreciated\r\n";
static const char *noaddress_rcpt_err = "553 5.1.3 actually specifying an e-mail address would be very much appreciated\r\n";
static const char *noreturn_sender_err = "550 5.1.8 sender denied because neither return MX nor A record available\r\n";
static const char *noreturn_rcpt_err = "550 5.1.2 recipient denied because neither return MX nor A record available\r\n";
static const char *relaydenied_err = "550 5.7.1 relay access denied\r\n";
static const char *toomanyrcpts_err = "554 5.5.3 too many recipients\r\n";
static const char *toomanynoops_err = "421 4.7.0 too many noops, closing connection\r\n";
static const char *trylater_err = "421 4.3.0 internal wardrobe malfunction, please try again later\r\n";
static const char *buggeroff_err = "451 4.6.1 stray newline (\\n w/o preceding \\r).  come again when you speak smtp.\r\n";
static const char *dipstick_err = "554 5.6.1 what's this business with this long lines?  i never claimed to support BINARY, just 8BITMIME.  pls die.  kthxbye.\r\n";
static const char *authpar_err = "504 5.5.4 auth type not supported\r\n";
static const char *authdatabad_err = "501 5.5.4 bad auth data\r\n";
static const char *authb64bad_err = "501 5.5.4 invalid base64 data\r\n";
static const char *authfailed_err = "535 5.7.0 incorrect username and/or password\r\n";
static const char *virus_err = "554 5.7.0 virus/worm/malware/phishingmail <%s> detected in mail data, message got dropped by request of mailbox owner\r\n";
static const char *spam_err = "554 5.7.0 spam mail detected, message got dropped by user request\r\n";
static const char *tls_err = "454 4.3.0 TLS temporarily not available, don't use STARTTLS or try again later\r\n";
static const char *alreadytls_err = "554 5.5.1 you already have an encrypted connection\r\n";
static const char *loop_err = "554 5.4.6 this message has too many hops, we assume it is stuck in a mail loop\r\n";
static const char *toomanyerrors_err = "421 4.7.0 too many errors\r\n";
static const char *spfgenericfail_err = "550 5.7.1 SPF policy check failed\r\n";
static const char *spfgenericgrey_err = "451 4.3.0 Greylisted: SPF policy check failed\r\n";
static const char *toolarge_err = "552 5.3.4 too much mail data\r\n";

static const char *greylist_msg = "451 4.7.1 this user has greylisting enabled and the combination of your ip address, the envelope from, and this recipient address is currently greylisted.  please come back later.\r\n";
static const char *noop_msg = "250 2.0.0 noopie!\r\n";
static const char *bye_msg = "221 2.0.0 bye\r\n";
static const char *rset_msg = "250 2.0.0 boo!\r\n";
static const char *from_ok_msg = "250 2.1.0 okay\r\n";
static const char *rcpt_ok_msg = "250 2.1.5 okay\r\n";
static const char *data_go_msg = "354 go ahead, end with \\r\\n.\\r\\n\r\n";
static const char *queued_msg = "250 2.6.0 okay, queued lmid <%s>\r\n";
static const char *spoolfail_msg = "451 4.3.0 unable to spool file, try again later pls\r\n";
static const char *hello_msg = "220 %s ESMTP (nsmtp)\r\n";
static const char *goodauth_msg = "235 2.0.0 nice to meet you, <%s>\r\n";
static const char *authid_msg = "334 okay, id please\r\n";
static const char *tlsready_msg = "220 2.7.0 ready to start TLS\r\n";

static struct pollfd serversocket[MAX_PORTS];
static int numserversockets;
static int ipc_qi;

static int smtp_error_status;

static int ports_ssl_start;

int smtpd_childcount;
pid_t *smtpd_childshutdown = NULL;
pid_t *smtpd_childdeath = NULL;
int smtpd_want_down = 0;

static int
get_next_arg(const char *args, char **target, int *offset)
{
	const char *p;
	char buf[512];
	size_t i;

	p = args;

	while (*p == ' ' || *p == '\t')
		p++;

	i = 0;
	while (i < sizeof(buf) - 1 && *p >= 33 && *p <= 126) {
		buf[i++] = *p;
		p++;
	}
	buf[i] = '\0';

	if (i == 0 || (*p != '\0' && *p != '\t' && *p != ' ')) {
		*offset = 0;
		return 1;
	}

	*target = nsm_strdup(buf);
	*offset = i + 1;

	return 0;
}

static int
do_helo(SMTP_CTX *ctx, const char *args)
{
	int offset;

	setinfo(ctx, "helo");

	if (ctx->helo != NULL) {
		xinfo("{%s} info: client protocol misbehavior: already did |HELO|/EHLO", ctx->conn_id);
		SMTP_ERROR((ctx, NULL));
		return CONTINUE;
	} else {
		if (get_next_arg(args, &ctx->helo, &offset) != 0) {
			xinfo("{%s} info: client protocol misbehavior: missing or invalid |HELO| parameter", ctx->conn_id);
			SMTP_ERROR((ctx, invalidhelo_err));
			return CONTINUE;
		}
		xinfo("{%s} info: client identified itself as <%s> via |HELO|", ctx->conn_id, ctx->helo);
		ctx->esmtp = 0;
	}

	/* Don't change this w/o fixing the mailloop check in smtpr.c */
	WRITEOUT((ctx, "250 %s\r\n", nsmc_name));
	return CONTINUE;
}

static int
do_ehlo(SMTP_CTX *ctx, const char *args)
{
	int offset;

	setinfo(ctx, "ehlo");

	if (ctx->helo != NULL) {
		xinfo("{%s} info: client local policy violation: already did HELO/|EHLO|", ctx->conn_id);
		SMTP_ERROR((ctx, NULL));
	} else {
		if (get_next_arg(args, &ctx->helo, &offset) != 0) {
			xinfo("{%s} info: client protocol misbehavior: missing or invalid |EHLO| parameter", ctx->conn_id);
			SMTP_ERROR((ctx, invalidhelo_err));
			return CONTINUE;
		}
		xinfo("{%s} info: client identified itself as <%s> via |EHLO|", ctx->conn_id, ctx->helo);
		ctx->esmtp = 1;
	}

	WRITEOUT((ctx,
	    "250-%s\r\n"
	    "250-SIZE %ld\r\n"
	    "250-8BITMIME\r\n"
	    "250-AUTH LOGIN PLAIN\r\n"
	    "250-STARTTLS\r\n"
	    "250-ENHANCEDSTATUSCODES\r\n"
	    "250 PIPELINING\r\n", nsmc_name, MAX_SIZE));
	return CONTINUE;
}

static int
do_quit(SMTP_CTX *ctx)
{

	setinfo(ctx, "quit");

	xinfo("{%s} info: client |QUIT|", ctx->conn_id);
	WRITEOUT((ctx, bye_msg));
	return SHUTDOWN;
}

static int
do_noop(SMTP_CTX *ctx)
{

	setinfo(ctx, "noop");

	xinfo("{%s} info: client |NOOP|s, letting us know he is still there", ctx->conn_id);

	ctx->noops++;
	if (ctx->noops >= nsmc_smtpd_noop_threshold) {
		xinfo("{%s} info: client local policy violation: too many |NOOP|s, over treshold (%d >= %d), shutting down", ctx->conn_id, ctx->noops, nsmc_smtpd_noop_threshold);
		SMTP_ERROR((ctx, toomanynoops_err));
		return SHUTDOWN;
	}

	WRITEOUT((ctx, noop_msg));
	return CONTINUE;
}

static int
do_rset(SMTP_CTX *ctx)
{

	setinfo(ctx, "rset");

	xinfo("{%s} info: client |RSET|, previous MAIL FROM and RCPT TO up to last DATA (if any) in this session are void", ctx->conn_id);
	reset_smtp_ctx(ctx);
	WRITEOUT((ctx, rset_msg));
	return CONTINUE;
}

static int
do_mail(SMTP_CTX *ctx, const char *args)
{
	char *f, *rcpt;
	int offset, n;

	setinfo(ctx, "mail");

	/*
	 * XXX/GCC: GCC 4.1 bug
	 *          warning: 'rcpt' may be used uninitialized in this function
	 */
	rcpt = NULL;

	if (ctx->helo == NULL) {
		xinfo("{%s} info: client protocol misbehavior: never did HELO/EHLO in this session before specifying |MAIL FROM|", ctx->conn_id);
		SMTP_ERROR((ctx, helofirst_err));
		return CONTINUE;
	}

	if (ctx->from != NULL) {
		xinfo("{%s} info: client protocol misbehavior: already specified |MAIL FROM|", ctx->conn_id);
		SMTP_ERROR((ctx, hadmail_err));
		return CONTINUE;
	}

	if (get_next_arg(args, &f, &offset) != 0) {
		xinfo("{%s} info: client protocol misbehavior: missing or invalid |MAIL FROM| parameter", ctx->conn_id);
		SMTP_ERROR((ctx, noaddress_sender_err));
		return CONTINUE;
	}

	if (strcmp(f, "<>") == 0) {
		ctx->from = nsm_strdup("");
		goto skip_checks;
	}

	if (strlen(f) < 3) {
		xinfo("{%s} info: client local policy violation: impossible short |MAIL FROM| address", ctx->conn_id);
		SMTP_ERROR_DROPOUT((ctx, badmailsyn_err));
		goto dropout;
	}

	/* NOTE: we ensured above that strlen(f) >= 3 */
	n = 0;
	if (f[n] == '<')
		n++;
	if (f[strlen(f) - 1] == '>')
		f[strlen(f) - 1] = '\0';
	rcpt = f + n;

	setinfo(ctx, "mail check <%s>", rcpt);
	switch (check_mailaddress(rcpt, 1)) {
	case -1:
		xinfo("{%s} info: unable to check |MAIL FROM| at this time, defer", ctx->conn_id);
		SMTP_ERROR_DROPOUT((ctx, trylater_err));
		goto dropout;
	case 0:
		/* good */
		break;
	case 1:
		xinfo("{%s} info: bad |MAIL FROM|=<%s> does not pass basic formal checks", ctx->conn_id, rcpt);
		SMTP_ERROR_DROPOUT((ctx, badmailsyn_err));
		goto dropout;
	case 2:
		xinfo("{%s} info: bad |MAIL FROM|=<%s> is known to us and no such mailbox by that name", ctx->conn_id, rcpt);
		SMTP_ERROR_DROPOUT((ctx, nomailbox_sender_err));
		goto dropout;
	case 3:
		xinfo("{%s} info: bad |MAIL FROM|=<%s> is remote but does not have any MX or A DNS record associated with it, so we couldn't send back mail", ctx->conn_id, rcpt);
		SMTP_ERROR_DROPOUT((ctx, noreturn_sender_err));
		goto dropout;
	case 4:
		break;
	default:
		xfatal("{%s} fatal: internal error: do_mail() received unknown and unhandled result from check_mailaddress()", ctx->conn_id);
		SMTP_ERROR_DROPOUT((ctx, trylater_err));
		nsm_free(f);
		return SHUTDOWN;
	}

	if (!ctx->relay) {
		switch (check_spf_mail_from(ctx, rcpt)) {
		case 0:
			/* good */
			break;
		case 1:
			if (greylist_spf(ctx->client_ip, rcpt) == 0) {
				xinfo("{%s} info: bad |MAIL FROM|=<%s> SPF policy check returned: SoftFail and sender is greylisted", ctx->conn_id, rcpt);
				SMTP_ERROR_DROPOUT((ctx, ctx->spfsmtp != NULL ? ctx->spfsmtp : spfgenericgrey_err));
				goto dropout;
			}
			xinfo("{%s} info: |MAIL FROM|=<%s> SPF policy check returned: SoftFail", ctx->conn_id, rcpt);
			break;
		case 2:
			xinfo("{%s} info: bad |MAIL FROM|=<%s> SPF policy check returned: Fail", ctx->conn_id, rcpt);
			SMTP_ERROR_DROPOUT((ctx, ctx->spfsmtp != NULL ? ctx->spfsmtp : spfgenericfail_err));
			goto dropout;
		default:
			xfatal("{%s} fatal: internal error: do_mail() received unknown and unhandled result from check_spf_mail_from()", ctx->conn_id);
			SMTP_ERROR_DROPOUT((ctx, trylater_err));
			nsm_free(f);
			return SHUTDOWN;
		}
	}

	ctx->from = nsm_strdup(rcpt);
skip_checks:
	xinfo("{%s} info: valid |MAIL FROM|=<%s>", ctx->conn_id, ctx->from);
	setinfo(ctx, "mail valid <%s>", rcpt);
	WRITEOUT_DROPOUT((ctx, from_ok_msg));

dropout:
	setinfo(ctx, "mail bad <%s>", rcpt);
	nsm_free(f);
	return CONTINUE;

dropout_smtp_error:
	setinfo(ctx, "mail bad <%s>", rcpt);
	nsm_free(f);
	return smtp_error_status;

dropout_writeout:
	nsm_free(f);
	return DEAD;
}


/*
 * NOTE:  RfC2821 part 4.5.3.1:
 *              "The minimum total number of recipients that must be buffered is 100 recipients."
 *        Which we break in default configuration.
 *
 *        Also:  "If the server has a configured site-policy limitation on the
 *                number of RCPT commands, it MAY instead use a 5XX response code."
 *
 *        And it shouldn't be a 552 (we use 554 by default), because:
 *               "Clients SHOULD treat a 552 code in this case as a temporary, rather than permanent,
 *                failure so the logic below works."
 */
static int
do_rcpt(SMTP_CTX *ctx, const char *args)
{
	NTL_ATTR *d_attrs, *u_attrs;
	char *f, *rcpt;
	int offset, i, n;

	setinfo(ctx, "rcpt");

	/*
	 * XXX/GCC: GCC 4.1 bug
	 *          warning: 'rcpt' may be used uninitialized in this function
	 */
	rcpt = NULL;

	if (ctx->from == NULL) {
		xinfo("{%s} info: client protocol misbehavior: did not supply sender address first before supplying recipient via |RCPT TO|", ctx->conn_id);
		SMTP_ERROR((ctx, mailfirst_err));
		return CONTINUE;
	}

	if (get_next_arg(args, &f, &offset) != 0) {
		xinfo("{%s} info: client protocol misbehavior: missing or invalid |RCPT TO| parameter", ctx->conn_id);
		SMTP_ERROR((ctx, noaddress_rcpt_err));
		return CONTINUE;
	}

	if (strlen(f) < 3) {
		xinfo("{%s} info: client local policy violation: impossible short |RCPT TO| address", ctx->conn_id);
		SMTP_ERROR_DROPOUT((ctx, badmailsyn_err));
		goto dropout;
	}

	/* NOTE: we ensured above that strlen(f) >= 3 */
	n = 0;
	if (f[n] == '<')
		n++;
	if (f[strlen(f) - 1] == '>')
		f[strlen(f) - 1] = '\0';
	rcpt = f + n;

	setinfo(ctx, "rcpt check <%s>", rcpt);
	switch (check_mailaddress(rcpt, ctx->relay)) {
	case -1:
		xinfo("{%s} info: unable to check |RCPT TO| at this time, defer", ctx->conn_id);
		SMTP_ERROR_DROPOUT((ctx, trylater_err));
		goto dropout;
	case 0:
		/* good */
		break;
	case 1:
		xinfo("{%s} info: bad |RCPT TO|=<%s>: does not pass basic formal checks", ctx->conn_id, rcpt);
		SMTP_ERROR_DROPOUT((ctx, badmailsyn_err));
		goto dropout;
	case 2:
		xinfo("{%s} info: bad |RCPT TO|=<%s>: domain is known to us and no such mailbox by that name", ctx->conn_id, rcpt);
		SMTP_ERROR_DROPOUT((ctx, nomailbox_rcpt_err));
		goto dropout;
	case 3:
		xinfo("{%s} info: bad |RCPT TO|=<%s>: is remote but does not have any MX or A DNS record associated with it, so we couldn't deliver this mail if we took it", ctx->conn_id, rcpt);
		SMTP_ERROR_DROPOUT((ctx, noreturn_rcpt_err));
		goto dropout;
	case 4:
		xinfo("{%s} info: bad |RCPT TO|=<%s>: is remote but client is not allowed to relay", ctx->conn_id, rcpt);
		SMTP_ERROR_DROPOUT((ctx, relaydenied_err));
		goto dropout;
	default:
		xfatal("{%s] fatal: internal error: do_rcpt() received unknown and unhandled result from check_mailaddress()", ctx->conn_id);
		SMTP_ERROR_DROPOUT((ctx, trylater_err));
		nsm_free(f);
		return SHUTDOWN;
	}

	d_attrs = u_attrs = NULL;
	if (!ctx->relay && want_greylisting(rcpt, &d_attrs, &u_attrs, 0) > 0) {
		if (whitelist_host(ctx->client_ip) == 1)
			xinfo("{%s} info: whitelisted host ip [%s]", ctx->conn_id, ctx->client_ip);
		else if (greylist_rcpt(ctx->client_ip, ctx->from, rcpt) == 0) {
			xinfo("{%s} info: valid but greylisted |RCPT TO|=<%s>", ctx->conn_id, rcpt);
			WRITEOUT_DROPOUT((ctx, greylist_msg));
			goto dropout;
		}
	}

	i = 0;
	if (ctx->rcpts == NULL) {
		ctx->rcpts = nsm_calloc(2, sizeof(char *));
		ctx->rcpts[0] = NULL;
	} else {
		while (ctx->rcpts[i] != NULL)
			i++;
		if (i >= MAX_RECIPIENTS) {
			xwarn("{%s} warn: client local policy violation: too many recipients (%d >= %d) on |RCPT TO|", ctx->conn_id, i, MAX_RECIPIENTS);
			SMTP_ERROR_DROPOUT((ctx, toomanyrcpts_err));
			goto dropout;
		}
		ctx->rcpts = nsm_realloc(ctx->rcpts, (i + 2) * sizeof(char *));
	}

	ctx->rcpts[i] = nsm_strdup(rcpt);
	ctx->rcpts[i+1] = NULL;
	xinfo("{%s} info: valid |RCPT TO|=<%s>", ctx->conn_id, ctx->rcpts[i]);
	setinfo(ctx, "rcpt valid <%s>", rcpt);
	WRITEOUT_DROPOUT((ctx, rcpt_ok_msg));

dropout:
	setinfo(ctx, "rcpt bad <%s>", rcpt);
	nsm_free(f);
	return CONTINUE;

dropout_smtp_error:
	setinfo(ctx, "rcpt bad <%s>", rcpt);
	nsm_free(f);
	return smtp_error_status;

dropout_writeout:
	nsm_free(f);
	return DEAD;
}

static int
multi_auth(SMTP_CTX *ctx, const char *u, const char *p)
{
	int e1, e2;

	if ((e1 = ldapauth(u, p)) == 0 || (e2 = legacyauth(u, p)) == 0) {
		if (ctx->authinfo != NULL)
			nsm_free(ctx->authinfo);
		(void)nsm_asprintf(&(ctx->authinfo), "SMTP-AUTH authenticated user <%s>", u);
		xinfo("{%s} info: client |AUTH| as <%s> succeeded", ctx->conn_id, u);
		(void)smtp_writeout(ctx, goodauth_msg, u);
		ctx->relay = 1;
		return 0;
	}

	if ((e1 == 1 && e2 != 1) || (e1 != 1 && e2 == 1)) {
		xinfo("{%s} info: client |AUTH| as <%s> not possible at this moment", ctx->conn_id, u);
		(void)smtp_error(ctx, trylater_err);
		return -1;
	}

	xinfo("{%s} info: client |AUTH| as <%s> failed", ctx->conn_id, u);
	(void)smtp_error(ctx, authfailed_err);

	return 1;
}

#define NZOD()		while (i < dlen - 1 && decoded[i] != '\0') \
				i++; \
			if (i + 1 >= dlen ) { \
				xwarn("{%s} warn: client provided malformed base64 input for |AUTH|", ctx->conn_id); \
				SMTP_ERROR((ctx, authdatabad_err)); \
				return CONTINUE; \
			} \
			i++;
#define AUTH_B64DEC(encoded)	dlen = sizeof(decoded) - 1; \
				if (base64_decode(encoded, strlen(encoded), decoded, &dlen) != 1) { \
					xwarn("{%s} warn: client provided bad base64 encoded data for |AUTH|, unable to decode", ctx->conn_id); \
					SMTP_ERROR((ctx, authb64bad_err)); \
					return CONTINUE; \
				} \
				decoded[dlen] = '\0';
#define AUTH_INPUT()	if (smtp_getline(ctx, buf, sizeof(buf), 0) < 2) { \
				xinfo("{%s} info: bad or none |AUTH| input provided by client", ctx->conn_id); \
				SMTP_ERROR((ctx, authb64bad_err)); \
				return CONTINUE; \
			}
#define AUTH_CHECK(auth_user, auth_pass)	(void)multi_auth(ctx, auth_user, auth_pass);
static int
do_auth(SMTP_CTX *ctx, const char *args)
{
	char decoded[512], buf[512];
	char aluser[256], alpass[256];
	const char *user, *pass;
	char *p;
	size_t dlen, i;
	int offset;

	setinfo(ctx, "auth");

	if (ctx->helo == NULL) {
		xinfo("{%s} info: client protocol misbehavior: never did HELO/EHLO in this session before trying |AUTH|", ctx->conn_id);
		SMTP_ERROR((ctx, helofirst_err));
		return CONTINUE;
	}

	if (get_next_arg(args, &p, &offset) != 0) {
		xinfo("{%s} info: client protocol misbehavior: missing or invalid |AUTH| parameter", ctx->conn_id);
		SMTP_ERROR((ctx, authpar_err));
		return CONTINUE;
	}

	if (strcasecmp(p, "PLAIN") == 0) {
		nsm_free(p);
		if (get_next_arg(args + offset, &p, &offset) == 0) {
			/*
			 * Inline (AUTH PLAIN [base64])
			 */
			AUTH_B64DEC(p);
			nsm_free(p);
		} else {
			/*
			 * Seperate (AUTH PLAIN\r\n[base64])
			 */
			WRITEOUT((ctx, authid_msg));
			AUTH_INPUT();
			AUTH_B64DEC(buf);
		}

		user = pass = NULL;
		i = 0;
		NZOD();
		user = decoded + i;
		NZOD();
		pass = decoded + i;

		AUTH_CHECK(user, pass);
	} else if (strcasecmp(p, "LOGIN") == 0) {
		nsm_free(p);
		if (get_next_arg(args + offset, &p, &offset) == 0) {
			/*
			 * Half-Inline (AUTH LOGIN [base64])
			 */
			AUTH_B64DEC(p);
			nsm_free(p);
		} else {
			/*
			 * Seperate
			 */
			WRITEOUT((ctx, "334 VXNlcm5hbWU6\r\n")); /* Username: */
			AUTH_INPUT();
			AUTH_B64DEC(buf);
		}
		(void)snprintf(aluser, sizeof(aluser), "%s", decoded);

		WRITEOUT((ctx, "334 UGFzc3dvcmQ6\r\n")); /* Password: */
		AUTH_INPUT();
		AUTH_B64DEC(buf);
		(void)snprintf(alpass, sizeof(alpass), "%s", decoded);

		AUTH_CHECK(aluser, alpass);
	} else {
		nsm_free(p);
		xwarn("{%s} warn: client protocol misbehavior: unsupported |AUTH| type <%s>", ctx->conn_id, args);
		SMTP_ERROR((ctx, authpar_err));
	}

	return CONTINUE;
}

/*
 * NOTE:  RfC2821 part 4.5.3.1:
 *               "The maximum total length of a text line including the <CRLF> is
 *                1000 characters (not counting the leading dot duplicated for
 *                transparency).  This number may be increased by the use of SMTP
 *                Service Extensions."
 */
static int
do_data(SMTP_CTX *ctx)
{
	NTL_ATTR *d_attrs, *u_attrs;
	char *data, *newdata, *spamuser;
	ssize_t len;
	size_t alloced, rd;
	size_t newdatasize;
	int flag, e;

	setinfo(ctx, "data => start");

	if (ctx->from == NULL) {
		xinfo("{%s} info: client protocol misbehavior: never supplied sender address before starting |DATA|", ctx->conn_id);
		SMTP_ERROR((ctx, mailfirst_err));
		return CONTINUE;
	}

	if (ctx->rcpts == NULL) {
		xinfo("{%s} info: client protocol misbehavior: never supplied any recipients before starting |DATA|", ctx->conn_id);
		SMTP_ERROR((ctx, rcptsfirst_err));
		return CONTINUE;
	}

	WRITEOUT((ctx, data_go_msg));

	alloced = 65536;
	data = nsm_calloc(alloced, sizeof(char));

	flag = 0;
	rd = 0;
	while (rd <= MAX_SIZE) {
		if ((len = smtp_getline(ctx, data + rd, alloced - rd, 1)) <= 0) {
			flag = -1;
			break;
		}
		rd += len;

		if (len > 8192) {
			xwarn("{%s} warn: insanely long line (>=8k) received from client <%s>[%s] while |DATA|, shutting down", ctx->conn_id, ctx->client_rn != NULL ? ctx->client_rn : "", ctx->client_ip);
			nsm_free(data);
			WRITEOUT((ctx, dipstick_err));
			return SHUTDOWN;
		}

		/*
		 * We use the properties of smtp_getline(), that is:
		 *  - if there's a \n in data, it is at the very end
		 * to detect bare naked newlines.
		 *
		 * phew, phew, phew!
		 */
		if (rd >= 2 && data[rd-1] == '\n' && data[rd-2] != '\r') {
			xwarn("{%s} warn: bare naked newline from client <%s>[%s] while |DATA|", ctx->conn_id, ctx->client_rn != NULL ? ctx->client_rn : "", ctx->client_ip);
			nsm_free(data);
			WRITEOUT((ctx, buggeroff_err));
			return SHUTDOWN;
		}

		/*
		 * Replace \r\n with \n.
		 */
		if (rd >= 2 && data[rd-1] == '\n') {
			data[rd-2] = '\n';
			rd--;
		}

		/*
		 * Search for \n.\n (\r\n.\r\n).
		 *
		 * NOTE:  We allow .\r\n, that is, zero length messages, as other MTAs do.
		 */
		if (rd >= 3 && data[rd-1] == '\n' && data[rd-2] == '.' && data[rd-3] == '\n') {
			rd -= 3;
			flag = 1;
			break;
		} else if (rd == 2 && data[1] == '\n' && data[0] == '.') {
			rd -= 2;
			flag = 2;
			break;
		}

		/*
		 * Still data to read.
		 * (Pre)alloc if needed.
		 */
		if (rd > alloced / 2) {
			setinfo(ctx, "data => (%lu) chars read yet", rd);
			alloced += 524288;
			data = nsm_realloc(data, alloced * sizeof(char));
		}
	}

	if (flag == -1) {
		xwarn("{%s} warn: short read, possibly client <%s>[%s] starvation on |DATA|", ctx->conn_id, ctx->client_rn != NULL ? ctx->client_rn : "", ctx->client_ip);
		setinfo(ctx, "data => short read");
		nsm_free(data);
		return SHUTDOWN;
	}

	if (rd > MAX_SIZE) {
		xwarn("{%s} warn: |DATA| too large, client provided (%d) > (%d) chars of mail data", ctx->conn_id, rd, MAX_SIZE);
		setinfo(ctx, "data => too large");
		nsm_free(data);
		SMTP_ERROR((ctx, toolarge_err));
		return CONTINUE;
	}

	if (!flag) {
		xfatal("{%s} fatal: internal error: |DATA| neither too large ((%d) <= (%d) chars) nor eod flag set, which is considered impossible", ctx->conn_id, rd, MAX_SIZE);
		setinfo(ctx, "data => internal error");
		nsm_free(data);
		SMTP_ERROR((ctx, trylater_err));
		return SHUTDOWN;
	}

	ctx->data = data;
	ctx->size = rd;
	ctx->lmid = make_mid(ctx);

	xinfo("{%s} info: read (%lu) chars of mail |DATA|", ctx->conn_id, (unsigned long)ctx->size);
	setinfo(ctx, "data => read done");

	/*
	 * Okay, we need to make a choice here:
	 * We have potential n recipients for this piece of mail.
	 *
	 * If n = 1:  Easy, check if user wants us to do an online virscan and/or spamscan or not
	 *
	 * If n > 1:  XXX/TODO:  For now, we ignore any n > 1
	 *            The fate of that piece of mail is decided solely on the configuration
	 *            options chosen by the user that happens to be the first recipient.
	 */
	d_attrs = u_attrs = NULL;
	if (want_onlinevirscan(ctx->rcpts[0], &d_attrs, &u_attrs, 1) > 0) {
		setinfo(ctx, "data => virscan");
		if (virscan(ctx->data, ctx->size) > 0) {
			xinfo("{%s} info: client local security violation: mail |DATA| was bad/infected says virscan(), user wants us to deny/drop that mail", ctx->conn_id);
			setinfo(ctx, "data => infected");
			SMTP_ERROR_DROPOUT((ctx, virus_err, virscan_last));
			goto fast_out;
		}
	}
	if (want_onlinespamscan(ctx->rcpts[0], &d_attrs, &u_attrs, 1) > 0) {
		if ((spamuser = get_spamassassin_user(ctx->rcpts[0], &d_attrs, &u_attrs, 1)) == NULL)
			spamuser = nsm_strdup(ctx->rcpts[0]);
		setinfo(ctx, "data => spamscan");
		if ((e = spamscan(spamuser, ctx->data, ctx->size, &newdata, &newdatasize)) == 1) {
			ntl_entry_free(d_attrs);
			ntl_entry_free(u_attrs);
			nsm_free(spamuser);
			nsm_free(newdata);
			xinfo("{%s} info: client local policy violation: mail |DATA| is to be considered spam according to spamscan(), user wants us to deny/drop that mail", ctx->conn_id);
			setinfo(ctx, "data => considered spam");
			SMTP_ERROR_DROPOUT((ctx, spam_err));
			goto fast_out;
		} else if (e == 0) {
			DEBUG(("do_data() => spamscan() returned size (%lu) for data", (unsigned long)newdatasize));
			nsm_free(ctx->data);
			ctx->data = newdata;
			ctx->size = newdatasize;
		}
		nsm_free(spamuser);
	}

	ntl_entry_free(d_attrs);
	ntl_entry_free(u_attrs);

	setinfo(ctx, "data => spool");
	if ((e = spool("incoming", ctx)) == 0) {
		xinfo("{%s} info: queued LMID=<%s> size=(%lu) envelope from=<%s>", ctx->conn_id, ctx->lmid, ctx->size, ctx->from);
		if (smtp_writeout(ctx, queued_msg, ctx->lmid) < 1) {
			xwarn("{%s} warn: remote dropped off after I spooled the file for |DATA| but before I could write my 2xx queued message", ctx->conn_id);
			setinfo(ctx, "data => client dropoff");
			reset_smtp_ctx(ctx);
			return DEAD;
		}
	} else if (e == 13) {
		xfatal("{%s} fatal: client local security violation: mail loop detected on |DATA|", ctx->conn_id);
		setinfo(ctx, "data => mailloop detected");
		SMTP_ERROR((ctx, loop_err));
	} else {
		xwarn("{%s} warn: local error: unable to spool mail after |DATA|, defer", ctx->conn_id);
		setinfo(ctx, "data => spool error");
		if (smtp_writeout(ctx, spoolfail_msg) < 1) {
			xwarn("{%s} warn: remote dropped off after |DATA| but before I could write my 4xx local error message", ctx->conn_id);
			reset_smtp_ctx(ctx);
			return DEAD;
		}
	}

	setinfo(ctx, "data => spooled");

fast_out:
	reset_smtp_ctx(ctx);
	return CONTINUE;

dropout_smtp_error:
	reset_smtp_ctx(ctx);
	return smtp_error_status;
}

/*
 * NOTE:  RfC2821 part 4.5.3.1:
 *             "The maximum total length of a command line including the command word and the <CRLF> is 512 characters.
 *              SMTP extensions may be used to increase this limit."
 *        So 2k oughta be enough.
 */
static void
do_smtp(int cl, struct clinfo *clinfo, int sslflag)
{
	SMTP_CTX *ctx;
	char buf[2048];
	ssize_t len;
	int e;

	ctx = init_smtp_ctx();
	ctx->client = cl;
	ctx->client_rn = clinfo->name;
	ctx->client_ip = clinfo->ip;
	(void)nsm_asprintf(&ctx->conn_id, "%d.%d", time(NULL), random() % 1000000 + 1);

	if (relaystatus(ctx->client_ip) == 0)
		ctx->relay = 1;

	xinfo("{%s} info: connection from <%s>[%s]", ctx->conn_id, ctx->client_rn != NULL ? ctx->client_rn : "", ctx->client_ip);
	setinfo(ctx, "connected");

	if (sslflag) {
		SSL *ssl;
		SSL_CTX *sslctx;

		xinfo("{%s} info: setting up TLS/SSL", ctx->conn_id);
		setinfo(ctx, "ssl setup");

		e = 0;
		(void)SSL_load_error_strings();
		(void)SSL_library_init();

		/*
		 * Create and Initialize SSL Context
		 */
		if ((sslctx = SSL_CTX_new(SSLv23_server_method())) == NULL) {
			xfatal("{%s} fatal: do_smtp() => local error: SSL_CTX_new() failed: %s", ctx->conn_id, ssl_error());
			goto dropout;
		}
		if (SSL_CTX_use_RSAPrivateKey_file(sslctx, nsmc_ssl_server_pem, SSL_FILETYPE_PEM) != 1) {
			xfatal("{%s} fatal: do_smtp() => local error: SSL_CTX_use_RSAPrivateKey() failed, check server pem file <%s> for valid private key; %s", ctx->conn_id, nsmc_ssl_server_pem, ssl_error());
			SSL_CTX_free(sslctx);
			goto dropout;
		}
		if (SSL_CTX_use_certificate_chain_file(sslctx, nsmc_ssl_server_pem) != 1) {
			xfatal("{%s} fatal: do_smtp() => local error: SSL_CTX_use_certficate_chain_file() failed, check server cert pem file <%s>; %s", ctx->conn_id, nsmc_ssl_server_pem, ssl_error());
			SSL_CTX_free(sslctx);
			goto dropout;
		}
		ssl_callbacks(sslctx);

		if ((ssl = SSL_new(sslctx)) == NULL) {
			xfatal("{%s} fatal: do_smtp() => local error: SSL_new() failed: %s", ctx->conn_id, ssl_error());
			SSL_CTX_free(sslctx);
			goto dropout;
		}
		SSL_CTX_free(sslctx);

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

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

		ctx->sslinfo = nsm_strdup(ssl_info(ssl));
		ctx->ssl = ssl;
		xinfo("{%s} info: SSL/TLS handshake success, session is encrypted: %s", ctx->conn_id, ctx->sslinfo);
	}

	if (smtp_writeout(ctx, hello_msg, nsmc_name) < 1) {
		xinfo("{%s} info: going off, remote <%s>[%s] dropped off before I could even say hello", ctx->conn_id, ctx->client_rn != NULL ? ctx->client_rn : "", ctx->client_ip);
		smtp_close(ctx);
		destroy_smtp_ctx(ctx);
		return;
	}

	for (;;) {
		e = 0;
		if ((len = smtp_getline(ctx, buf, sizeof(buf), 0)) == -1) {
			xinfo("{%s} info: client <%s>[%s] did not answer or died", ctx->conn_id, ctx->client_rn != NULL ? ctx->client_rn : "", ctx->client_ip);
			break;
		} else if (len == 0)
			e = smtp_error(ctx, nocmd_err);
		else if (strncasecmp(buf, "HELO", 4) == 0)
			e = do_helo(ctx, buf + 4);
		else if (strncasecmp(buf, "EHLO", 4) == 0)
			e = do_ehlo(ctx, buf + 4);
		else if (strncasecmp(buf, "NOOP", 4) == 0)
			e = do_noop(ctx);
		else if (strncasecmp(buf, "RSET", 4) == 0)
			e = do_rset(ctx);
		else if (strncasecmp(buf, "MAIL FROM:", 10) == 0)
			e = do_mail(ctx, buf + 10);
		else if (strncasecmp(buf, "RCPT TO:", 8) == 0)
			e = do_rcpt(ctx, buf + 8);
		else if (strncasecmp(buf, "DATA", 4) == 0)
			e = do_data(ctx);
		else if (strncasecmp(buf, "AUTH", 4) == 0)
			e = do_auth(ctx, buf + 4);
		else if (strncasecmp(buf, "STARTTLS", 8) == 0)
			e = do_tls(ctx);
		else if (strncasecmp(buf, "QUIT", 4) == 0) {
			e = do_quit(ctx);
			break;
		} else {
			buf[9] = '\0';
			xinfo("{%s} info: client talks funny, unknown cmd <%s> received", ctx->conn_id, buf);
			e = smtp_error(ctx, cmd_err);
		}

		if (e == SHUTDOWN || e == DEAD)
			break;
	}

dropout:
	setinfo(ctx, "going off");

	if (e != DEAD)
		xinfo("{%s} info: going off, closing connection from <%s>[%s]", ctx->conn_id, ctx->client_rn != NULL ? ctx->client_rn : "", ctx->client_ip);
	else
		xinfo("{%s} info: going off, connection from <%s>[%s] already dead", ctx->conn_id, ctx->client_rn != NULL ? ctx->client_rn : "", ctx->client_ip);

	smtp_close(ctx);
	destroy_smtp_ctx(ctx);
}

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));
	setproctitle("@%lu <%s>[%s]%s%s", time(NULL),
	    ctx->client_rn != NULL ? ctx->client_rn : "", ctx->client_ip,
	    state != NULL ? " " : "", state != NULL ? state : "");
}

static void
socket_setup(int n, const char *address, const char *port)
{
	struct addrinfo hints, *res;
	int foo;
	int e;

	(void)memset(&serversocket[n], 0, sizeof(struct pollfd));
	(void)memset(&hints, 0, sizeof(struct addrinfo));
	hints.ai_family = PF_INET;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE;

	if ((e = getaddrinfo(address, port, &hints, &res)))
		xdie("fatal: socket_setup() => getaddrinfo() failed for [%s]/%s: \"%s\"", address, port, gai_strerror(e));

	if ((serversocket[n].fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) < 0) {
		freeaddrinfo(res);
		xdie("fatal: socket_setup() => socket() failed for [%s]/%s: \"%\"", address, port, strerror(errno));
	}

	foo = 1;
	if (setsockopt(serversocket[n].fd, SOL_SOCKET, SO_REUSEADDR, &foo, sizeof(foo)) != 0) {
		(void)close(serversocket[n].fd);
		freeaddrinfo(res);
		xdie("fatal: socket_setup() => setsockopt(SO_REUSEADDR) failed for [%s]/%s: \"%s\"", address, port, strerror(errno));
	}

	if (bind(serversocket[n].fd, (struct sockaddr *)res->ai_addr, res->ai_addrlen) < 0) {
		(void)close(serversocket[n].fd);
		freeaddrinfo(res);
		xdie("fatal: socket_setup() => bind() failed for [%s]/%s: \"%s\"", address, port, strerror(errno));
	}

	if (listen(serversocket[n].fd, 10) != 0) {
		(void)close(serversocket[n].fd);
		freeaddrinfo(res);
		xdie("fatal: socket_setup() => listen() failed for [%s]/%s: \"%s\"", address, port, strerror(errno));
	}

	freeaddrinfo(res);

	serversocket[n].events = POLLIN;
}

static void
open_sockets(void)
{
	int i, n;

	n = 0;
	for (i = 0; n < MAX_PORTS && nsmc_ports != NULL && nsmc_ports[i] != NULL; i++) {
		socket_setup(n++, nsmc_ip, nsmc_ports[i]);
		xinfo("info: Listening on [%s]/%s", nsmc_ip, nsmc_ports[i]);
	}
	ports_ssl_start = n;
	for (i = 0; n < MAX_PORTS && nsmc_ports_ssl != NULL && nsmc_ports_ssl[i] != NULL; i++) {
		socket_setup(n++, nsmc_ip, nsmc_ports_ssl[i]);
		xinfo("info: Listening on [%s]/%s (SSL)", nsmc_ip, nsmc_ports_ssl[i]);
	}

	numserversockets = n;
}

static void
handle(void)
{
	char logname[32];
	struct sockaddr claddr;
	struct clinfo *clinfo;
	socklen_t claddr_len;
	pid_t pid;
	pid_t childpid;
	int sslflag;
	int st, cl;
	int i, j;

	xinfo("info: waiting for connections");

	smtpd_childcount = 0;
	smtpd_childshutdown = nsm_calloc((size_t)nsmc_smtpd_maxchilds, sizeof(pid_t));
	smtpd_childdeath = nsm_calloc((size_t)nsmc_smtpd_maxchilds, sizeof(pid_t));
	for (i = 0; i < nsmc_smtpd_maxchilds; i++)
		smtpd_childshutdown[i] = smtpd_childdeath[i] = 0;

	claddr_len = sizeof(claddr);
	cl = 0;

	while (!smtpd_want_down) {
		if (smtpd_childcount >= nsmc_smtpd_maxchilds) {
			(void)usleep(100000);
			continue;
		}

		/*
		 * Record clean child process shutdowns
		 */
		childpid = 0;
		while (msgrcv(ipc_qi, &childpid, sizeof(childpid), 0L, IPC_NOWAIT) != -1) {
			i = 0;
			while (i < nsmc_smtpd_maxchilds && smtpd_childshutdown[i] != 0)
				i++;
			if (i >= nsmc_smtpd_maxchilds)
				xwarn("warn: DEBUG/QA: more childshutdown IPC reports than we could save.  Current PID (%lu).", childpid);
			else
				smtpd_childshutdown[i] = childpid;
		}

		/*
		 * Match child process deaths with clean child process shutdowns
		 * and find unclean shutdowns
		 */
		for (i = 0; i < nsmc_smtpd_maxchilds; i++)
			if (smtpd_childdeath[i] > 0) {
				j = 0;
				while (j < nsmc_smtpd_maxchilds && smtpd_childshutdown[j] != smtpd_childdeath[i])
					j++;
				if (j >= nsmc_smtpd_maxchilds)
					xwarn("warn: DEBUG/QA: unclean child shutdown, sudden death for PID (%lu)", smtpd_childdeath[i]);
				else
					smtpd_childshutdown[j] = 0;
				smtpd_childdeath[i] = 0;
			}

		setproctitle("MASTER: [%d/%d]", smtpd_childcount, nsmc_smtpd_maxchilds);

		if (poll(serversocket, (nfds_t)numserversockets, 250) < 1)
			continue;

		sslflag = 0;
		st = -1;
		for (i = 0; i < numserversockets; i++) {
			if (serversocket[i].revents == POLLIN) {
				st = serversocket[i].fd;
				if (i >= ports_ssl_start)
					sslflag = 1;
				break;
			}
		}
		if (st == -1) {
			(void)usleep(50000);
			continue;
		}

		if ((cl = accept(st, (struct sockaddr *)&claddr, &claddr_len)) == -1) {
			xwarn("warn: handle() => accept() failed");
			continue;
		}

		clinfo = identify(&claddr, sizeof(struct sockaddr_in));

		if ((pid = fork()) == -1) {
			xwarn("warn: handle() => fork() failed");
			nsm_free(clinfo);
			(void)close(cl);
		} else if (pid == 0) {
			/*
			 * child process
			 */

			setproctitle("started");

			nsm_free(smtpd_childshutdown);
			nsm_free(smtpd_childdeath);

			(void)signal(SIGPIPE, SIG_IGN);
			for (i = 0; i < numserversockets; i++)
				(void)close(serversocket[i].fd);

			(void)snprintf(logname, sizeof(logname), "nsmtp(smtpd/slave)[%d/%d]", smtpd_childcount+1, nsmc_smtpd_maxchilds);
			openlog(logname, LOG_PID, LOG_MAIL);

			if ((ipc_qi = msgget((key_t)getppid(), 0)) == -1) {
				xfatal("fatal: unable to get IPC msg queue id, msgget() failed: \"%s\"", strerror(errno));
				nsm_free(clinfo);
				exit(EXIT_FAILURE);
			}

			ntu_seed();
			do_smtp(cl, clinfo, sslflag);

			nsm_free(clinfo);
			nsm_free(nsmc_ip);
			free_ldap_cfg();

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

			pid = getpid();
			if (msgsnd(ipc_qi, &pid, sizeof(pid), 0) != 0)
				xfatal("fatal: unable to send IPC msg, master process still running?  msgsnd() failed: \"%s\"", strerror(errno));
			(void)usleep(50000); /* Give the message some time to propagate */

			exit(EXIT_SUCCESS);
		} else {
			/*
			 * parent (master) process
			 */
			smtpd_childcount++;
			nsm_free(clinfo);
			(void)close(cl);
		}
	}

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

	nsm_free(smtpd_childshutdown);
	nsm_free(smtpd_childdeath);
}

static int
do_tls(SMTP_CTX *ctx)
{
	SSL *ssl;
	SSL_CTX *sslctx;
	int e;

	setinfo(ctx, "starttls");

	if (ctx->ssl != NULL) {
		xinfo("{%s} info: client protocol misbehaviour: already did |STARTTLS| or client is using a SSL port, connection is encrypted", ctx->conn_id);
		SMTP_ERROR((ctx, alreadytls_err));
		return CONTINUE;
	}

	xinfo("{%s} info: client |STARTTLS|", ctx->conn_id);

	if (ctx->sslinfo != NULL) {
		xfatal("{%s} fatal: internal error: ssl == NULL and sslinfo != NULL, this is impossible", ctx->conn_id);
		SMTP_ERROR((ctx, tls_err));
		return SHUTDOWN;
	}

	(void)SSL_load_error_strings();
	(void)SSL_library_init();

	/*
	 * (1) Create and Initialize SSL Context
	 */
	if ((sslctx = SSL_CTX_new(SSLv23_server_method())) == NULL) {
		xfatal("{%s} fatal: do_tls() => local error: SSL_CTX_new() failed: %s", ctx->conn_id, ssl_error());
		goto fail;
	}
	if (SSL_CTX_use_RSAPrivateKey_file(sslctx, nsmc_ssl_server_pem, SSL_FILETYPE_PEM) != 1) {
		xfatal("{%s} fatal: do_tls() => local error: SSL_CTX_use_RSAPrivateKey() failed, check server pem file <%s> for valid private key; %s", ctx->conn_id, nsmc_ssl_server_pem, ssl_error());
		SSL_CTX_free(sslctx);
		goto fail;
	}
	if (SSL_CTX_use_certificate_chain_file(sslctx, nsmc_ssl_server_pem) != 1) {
		xfatal("{%s} fatal: do_tls() => local error: SSL_CTX_use_certficate_chain_file() failed, check server cert pem file <%s>; %s", ctx->conn_id, nsmc_ssl_server_pem, ssl_error());
		SSL_CTX_free(sslctx);
		goto fail;
	}
	ssl_callbacks(sslctx);

	if ((ssl = SSL_new(sslctx)) == NULL) {
		xfatal("{%s} fatal: do_tls() => local error: SSL_new() failed: %s", ctx->conn_id, ssl_error());
		SSL_CTX_free(sslctx);
		goto fail;
	}
	SSL_CTX_free(sslctx);

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

	/*
	 * (2) Reset SMTP Context, including HELO/EHLO,
	 *     as per RfC2487 5.2 or RfC3207 4.2
	 */
	reset_smtp_ctx(ctx);
	if (ctx->helo != NULL) {
		nsm_free(ctx->helo);
		ctx->helo = NULL;
	}
	ctx->esmtp = 0;

	/*
	 * (3) STARTTLS TLS/SSL Handshake
	 */
	if (smtp_writeout(ctx, tlsready_msg) < 1) {
		xwarn("{%s} warn: remote died before I could write out tls ready message after |STARTTLS|");
		SSL_free(ssl);
		return DEAD;
	}

	if ((e = SSL_accept(ssl)) != 1) {
		xwarn("{%s} warn: do_tls() => TLS/SSL handshake failed at SSL_accept(), returned (%d), SSL error code (%d): %s", ctx->conn_id, e, SSL_get_error(ssl, e), ssl_error());
		SSL_free(ssl);
		goto fail;
	}

	/*
	 * (4) Success, Retrieve and Log Version and Cipher Info
	 */
	ctx->sslinfo = nsm_strdup(ssl_info(ssl));
	ctx->ssl = ssl;
	xinfo("{%s} info: SSL/TLS handshake success, session is now encrypted: %s", ctx->conn_id, ctx->sslinfo);

	return CONTINUE;

fail:
	SMTP_ERROR((ctx, tls_err));
	return CONTINUE;
}

static int
smtp_error(SMTP_CTX *ctx, const char *fmt, ...)
{
	va_list ap;
	char msg[1024];

	if (fmt != NULL) {
		va_start(ap, fmt);
		(void)vsnprintf(msg, sizeof(msg), fmt, ap);
		va_end(ap);
	}

	ctx->errors++;

	if (ctx->errors >= nsmc_smtpd_error_threshold_hard) {
		/* die, die, die, my darling */
		xwarn("{%s} warn: client policy violation: client is over hard error threshold (%d >= %d), shutting down <%s>[%s]", ctx->conn_id, ctx->errors, nsmc_smtpd_error_threshold_hard, ctx->client_rn, ctx->client_ip);
		setinfo(ctx, "kick");
		WRITEOUT((ctx, toomanyerrors_err));
		return SHUTDOWN;
	}

	if (ctx->errors >= nsmc_smtpd_error_threshold_soft) {
		/* slow down that sucker */
		xinfo("{%s} info: client policy violation: client is over soft error threshold (%d >= %d), slow down", ctx->conn_id, ctx->errors, nsmc_smtpd_error_threshold_soft);
		setinfo(ctx, "throttle");
		(void)sleep((unsigned int)(1 + ctx->errors - nsmc_smtpd_error_threshold_soft));
	}

	if (fmt != NULL)
		WRITEOUT((ctx, msg));

	return CONTINUE;
}

static void
ipc_setup(void)
{

	if ((ipc_qi = msgget((key_t)getpid(), IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR)) == -1)
		xdie("fatal: IPC setup failed at msgget(): \"%s\"", strerror(errno));
}

static void
ipc_destroy(void)
{

	if (msgctl(ipc_qi, IPC_RMID, NULL) != 0)
		xfatal("fatal: msgctl(IPC_RMID) to remove the IPC msg queue failed: \"%s\"", strerror(errno));
}

int
main(int argc, char *argv[])
{
	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]\n", getprogname());
			exit(EXIT_FAILURE);
		}

	if (argc - optind != 0) {
		(void)fprintf(stderr, "Usage error:  Unhandled \"%s\" on command line.\n", argv[optind]);
		(void)fprintf(stderr, "%s -h for Usage.\n", getprogname());
		exit(EXIT_FAILURE);
	}

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

	open_sockets();
	drop_privileges(nsmc_smtpd_uid, nsmc_smtpd_gid, nsmc_smtpd_chroot);
	init_sighandlers();
	init_smtp_sighandlers();
	daemonize();
	initproctitle(argc, argv);
	setproctitle("MASTER: ready");
	ipc_setup();
	write_pid_file("smtpd_master");
	handle();

	ipc_destroy();
	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);
}
