/*
#ident	"@(#)smail/src:RELEASE-3_2_0_121:smtprecv.c,v 1.295 2005/11/03 00:13:23 woods Exp"
 */

/*
 *    Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
 *    Copyright (C) 1992  Ronald S. Karr
 * 
 * See the file COPYING, distributed with smail, for restriction
 * and warranty information.
 */

/*
 * smtprecv.c:
 *	Receive mail using the SMTP protocol.
 */

#include "defs.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include <ctype.h>
#include <signal.h>
#include <limits.h>

#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
#  include <stdlib.h>
# endif
#endif

#ifdef HAVE_STRING_H
# if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H)
#  include <memory.h>
# endif
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif

#ifdef __STDC__
# include <stdarg.h>
#else
# include <varargs.h>
#endif

#if defined(UNIX_SYS5_4)
# include <sys/sysmacros.h>		/* for MIN() & MAX() */
#endif

#if defined(UNIX_BSD) && !defined(POSIX_OS)
# include <sys/ioctl.h>
#endif
#if defined(UNIX_SYS5) || defined(POSIX_OS) || defined(USE_FCNTL)
# include <fcntl.h>
#else
# if defined(UNIX_BSD)
#  include <sys/file.h>
# endif
#endif

#if defined(HAVE_UNISTD_H)
# include <unistd.h>
#endif

#if defined(HAVE_LIBWHOSON)
# include <whoson.h>
#endif

#include "smail.h"
#include "smailsock.h"

#if defined(HAVE_RFC1413)
# include <ident.h>			/* declarations for ident protocol lookups */
#endif

#include <pcre.h>

#include "config.h"
#include "main.h"
#include "parse.h"
#include "addr.h"
#include "hash.h"
#include "direct.h"
#include "route.h"
#include "alloc.h"
#include "list.h"
#include "smailstring.h"
#include "dys.h"
#include "log.h"
#include "spool.h"
#include "iobpeek.h"
#include "lookup.h"
#include "match.h"
#if defined(HAVE_BSD_NETWORKING) && defined(HAVE_BIND)
# include "bindsmtpth.h"
# include "bindlib.h"
#endif
#include "drivertab.h"
#include "transport.h"
#if defined(HAVE_BSD_NETWORKING)
# include "transports/smtplib.h"	/* for rfc821_is_*_string() */
#endif
#include "extern.h"
#include "debug.h"
#include "exitcodes.h"
#include "error.h"
#include "smailport.h"

/* Declare the ident variables, even if HAVE_RFC1413 is not defined, so that
 * configs can be kept consistant (also used in expand.c and queue.c)
 */
char *ident_sender = NULL;		/* The calculated identity of the sender */
char *ident_method = NULL;		/* Method used to get identity */

char *smtp_local_addr = NULL;		/* ascii representation of addr from getsockname() */
char *smtp_local_port = NULL;		/* ascii representation of port from getsockname() */

int peer_is_localhost = FALSE;		/* endpoint addresses are the same? */

char *ehlo_errstr;

/*
 * a guess at the classic (non-CIDR) of the local network, in the form produced
 * by inet_net_ntop() (used by match_ip())
 */
char *smtp_local_net = NULL;

/* XXX smtp_sess_deny should probably just replace smtp_sess_deny_reason.... */
static unsigned int smtp_sess_deny = FALSE; 	/* reject the SMTP session outright */
static unsigned int sent_refused_data = FALSE;	/* drop the SMTP session forcefully unless RSET or HELO/EHLO */

#define SMTP_SESS_DENY_DNSBL		001	/* one of the DNSBLs matched */
#define SMTP_SESS_DENY_REJECT		002	/* smtp_reject_hosts matched */
#define SMTP_SESS_DENY_REJECT_PTR	004	/* smtp_host_reject_hostnames matched */

static int smtp_sess_deny_reason = 0;	/* one of SMTP_SESS_DENY_* */
static char *first_smtp_sess_deny_reason;

static char *smtp_sess_deny_msg = NULL;	/* details for reject (may contain newlines!) */

static char *smtp_dnsbl_match = NULL;	/* full domain name matched by a DNSBL */
static char *smtp_dnsbl_addr = NULL;	/* ascii formatted address value of DNSBL A RR */

/* types local to this file */
enum e_smtp_cmds {
    HELO_CMD,				/* HELO domain */
    EHLO_CMD,				/* EHLO domain */
    MAIL_CMD,				/* MAIL FROM:<sender> */
    RCPT_CMD,				/* RCPT TO:<recipient> */
    DATA_CMD,				/* DATA */
    VRFY_CMD,				/* VRFY */
    EXPN_CMD,				/* EXPN */
    QUIT_CMD,				/* QUIT */
    RSET_CMD,				/* RSET */
    NOOP_CMD,				/* NOOP */
    DEBUG_CMD,				/* DEBUG [level] */
    VERB_CMD,				/* VERB */
    HELP_CMD,				/* HELP */
    EOF_CMD,				/* end of file encountered (special) */
    BOGUS_CMD,				/* invalid characters encountered (special) */
    TOOLONG_CMD,			/* maximum command length exceeded (special) */
    OTHER_CMD				/* unknown command (special) */
};

struct smtp_command_list {
	char *name;
	enum e_smtp_cmds cmd;
};
typedef struct smtp_command_list smtp_cmd_list_t;

/* functions local to this file */
static char *check_addr_operand __P((FILE *, int, char **));
static void send_smtp_msg __P((FILE *, int, char *, int, const char *));
static void do_greeting __P((FILE *, void *, void *));
static void deny_greeting __P((FILE *, int, char *));
static void send_session_denied_reply __P((FILE *, char *, char *));
static void non_compliant_reply __P((FILE *, int, int, int, char *, char *));
static void invalid_operand_warning __P((int, char *, char *));
static void reset_state __P((void));
static enum e_smtp_cmds read_smtp_command __P((FILE *, FILE *));
static int decode_mail_options __P((char *, FILE *));
static void expand_addr __P((char *, FILE *));
static int verify_addr_form __P((char *, char *, FILE *, enum e_smtp_cmds, char **));
static int verify_sender __P((char *, char *, FILE *));
static int verify_addr __P((char *, FILE *, enum e_smtp_cmds));
static char *verify_host __P((char *, void *, char **, int *));
static void check_smtp_remote_allow __P((struct addr *, struct addr **, struct addr **, struct addr **));
static void smtp_input_signals __P((void));
static void smtp_processing_signals __P((void));
static void smtp_reset_signals __P((void));
static void smtp_reload_sig __P((int));
static void smtp_receive_timeout_sig __P((int));
static void smtp_sig_unlink __P((int));
static off_t computed_max_msg_size __P((void));
#if defined(HAVE_BSD_NETWORKING) || defined(HAVE_BIND) /* XXX can they ever be "or"? */
static void invalid_relay_error __P((struct addr *, char *));
#endif

/* variables local to this file */
static char *data;			/* interesting data within input */
static int got_sighup = FALSE;

/*
 * NOTE: this is indexed by enum e_smtp_cmds and must be kept in the same order!
 */
static smtp_cmd_list_t smtp_cmd_list[] = {
	{ "HELO",	HELO_CMD },
	{ "EHLO",	EHLO_CMD },
	{ "MAIL FROM:",	MAIL_CMD },
	{ "RCPT TO:",	RCPT_CMD },
	{ "DATA",	DATA_CMD },
	{ "VRFY",	VRFY_CMD },
	{ "EXPN",	EXPN_CMD },
	{ "QUIT",	QUIT_CMD },
	{ "RSET",	RSET_CMD },
	{ "NOOP",	NOOP_CMD },
	{ "DEBUG",	DEBUG_CMD },
	{ "VERB",	VERB_CMD },
	{ "HELP",	HELP_CMD },
	/* NOTE: do not include "special" commands in this list! */
};

static int smtp_remove_on_timeout;
static FILE *out_file;
static char *help_msg[] = {
    "250-2.0.0 The following SMTP commands are recognized:",
    "250-2.0.0",
    "250-2.0.0    HELO hostname                   - startup and give your hostname",
    "250-2.0.0    EHLO hostname                   - startup with extension info",
    "250-2.0.0    MAIL FROM:<sender-address>      - start transaction from sender",
    "250-2.0.0    RCPT TO:<recipient-address>     - name recipient for message",
    "250-2.0.0    VRFY <address>                  - verify deliverability of address",
#ifndef NO_SMTP_EXPN
    "250-2.0.0    EXPN <address>                  - expand mailing list address",
#endif
    "250-2.0.0    DATA                            - start text of mail message",
    "250-2.0.0    .                               - end-of-DATA, on a line by itself",
    "250-2.0.0    RSET                            - reset state, drop transaction",
    "250-2.0.0    NOOP                            - do nothing",
#ifndef NODEBUG
    "250-2.0.0    DEBUG [level]                   - set debugging level, default 1",
#endif
    "250-2.0.0    VERB on|off                     - be a little more verbose",
    "250-2.0.0    HELP                            - produce this help message",
    "250-2.0.0    QUIT                            - close SMTP connection",
    "250-2.0.0",
    "250-2.0.0 The normal sequence of events when sending a message is to first",
    "250-2.0.0 introduce yourself with your the true canonical hostname of your",
    "250-2.0.0 client using either the 'HELO' or 'EHLO' command, then state the",
    "250-2.0.0 sender address with a 'MAIL FROM:' command, specify the recipients",
    "250-2.0.0 with as many 'RCPT TO:' commands as are required (one address per",
    "250-2.0.0 command), then give the DATA command before sending the mail message",
    "250-2.0.0 headers and body text; and end the message with the `.' command.",
    "250 2.0.0 Multiple messages may be specified.  End the last one with a QUIT.",
    NULL					/* array terminator */
};

static char *no_files_by_email_msg = "Please do not send large files by e-mail!\n\
If you are sending a message with a MIME-capable mailer please set it so that\
 it splits messages larger than 64KB into multiple parts.  Otherwise please\
 learn to transfer files with a proper file transfer protocol.";

static char *non_compliant_client_msg = "\nIf you are seeing this message in\
 a bounce, or in an alert box from your mailer client, etc., then your mailer\
 software may not be compliant with the SMTP protocol (RFC 821, etc.)"
/* XXX warning C89 string concatenation required here! */
#ifdef HAVE_ESMTP_PIPELINING		/* defined in iobpeek.h if support is possible */
", or it may not be showing you the correct and most meaningful error message."
#endif
"\n\nPlease report this error to the technical support personnel responsible for\
 your mailer software and ask them to reproduce it while doing a full packet\
 capture so that they may find and fix this problem.";

char *sender_host_invalid = NULL;	/* short error text if sender_host is not actually valid */
char *sender_host_really = NULL;	/* result of PTR lookup */
char *sender_host_really_invalid = NULL;/* short error text if sender_host_really is not actually valid */

unsigned int num_smtp_recipients = 0;	/* number of recipient addresses */

off_t accepted_msg_size = -1;		/* what we're currently willing to try
					 * to take...  the lesser of either
					 * free space (minus reserved space),
					 * or max_message_size (if set)
					 */


/*
 * receive_smtp - receive mail over SMTP.
 *
 * Take SMTP commands on the `in' file.  Send reply messages
 * to the `out' file.  If `out' is NULL, then don't send reply
 * messages (i.e., read batch SMTP commands).
 *
 * return an array of spool files which were created in this SMTP
 * conversation.
 *
 * The last spooled message is left open as an efficiency move, so the
 * caller must arrange to close it or process it to completion.  As
 * well, it is the callers responsibility to close the input and
 * output channels.
 */
char **
receive_smtp(in, out, peer)
    FILE *in;				/* stream of SMTP commands */
    FILE *out;				/* channel for responses */
    void *peer				/* peer address from accept() */
#ifndef HAVE_BSD_NETWORKING
		__attribute__((unused))
#endif
	;
{
    static char **files = NULL;		/* array of names of spool files to return */
    static int file_cnt = 7;		/* initially put 7 parts in array */
    int file_i = 0;			/* index always starts at the beginning */
    char *errstr = NULL;		/* temp to hold error messages */
    struct addr *cur;			/* address pointer -- current recipient */
    char *rest;				/* ptr used in parsing */
    char *p;				/* just a pointer */
    FILE *save_errfile = errfile;	/* might get overwritten by DEBUG... */
    int save_debug = debug;		/* might get overwritten by DEBUG */
#ifdef HAVE_BSD_NETWORKING
    struct sockaddr_in from_sa;		/* result of getpeername() */
    struct hostent *shp = NULL;		/* result of gethostbyaddr() */
#endif

    if (out && peer) {
	/* impossible to determine the client's user's true identity */
	if (local_sender) {
	    xfree(local_sender);
	}
	local_sender = COPY_STRING(nobody);
	if (sender_name) {
	    xfree(sender_name);
	}
	sender_name = NULL;
	/*
	 * Note that messages received by SMTP from any remote peer will still
	 * have a recorded real_uid and prog_euid matching that of the process
	 * listening on the SMTP port (normally 0,0 (until the day when smtpd
	 * finally drops root and runs as nobody, at which time this code here
	 * will have to spoof the "smail" real-UID)), so the sender address
	 * (and any Sender: headers) specified by SMTP clients will still be
	 * trusted.
	 */
    }

    ident_sender = NULL;
    ident_method = NULL;
    ehlo_errstr = NULL;
    first_smtp_sess_deny_reason = NULL;

    /* be paranoid about setting other variables too... */
    sender_host = NULL;
    sender_host_invalid = NULL;
    sender_host_addr = NULL;
    sender_host_port = NULL;
    sender_proto = NULL;
    smtp_local_addr = NULL;
    smtp_local_port = NULL;
    peer_is_localhost = FALSE;
    smtp_local_net = NULL;
    smtp_sess_deny = FALSE;
    sent_refused_data = FALSE;
    smtp_sess_deny_reason = 0;
    smtp_sess_deny_msg = NULL;
    smtp_dnsbl_match = NULL;
    smtp_dnsbl_addr = NULL;
    sender_host_really = NULL;
    num_smtp_recipients = 0;
    accepted_msg_size = -1;

    out_file = out;			/* store pointer to output file handle in a static global for signal handlers */

    smtp_processing_signals();

    /* allocate an initial chunk of spool filename slots */
    if (files == NULL) {
	files = (char **)xmalloc((file_cnt + 1) * sizeof(*files));
    }

    DEBUG(DBG_REMOTE_HI, "receive_smtp() called.\n");

    /*
     * output the startup banner line
     *
     * NOTE:  we do this as soon as possible to avoid having the client
     * timeout...
     *
     * XXX Too bad there's no easy RPC-like call-back style alternative API to
     * gethostbyaddr() so that we could kick off the PTR lookup in parallel
     * with everything else...  maybe someday with BIND-9's LWRES daemon.
     */
    if (out) {
	char *s;
#if defined(O_NONBLOCK) && defined(HAVE_BSD_NETWORKING)
	struct sockaddr_in *peer_sa = (struct sockaddr_in *) peer;

	/*
	 * but first if we're talking to a remote peer then check to see if any
	 * input has been received yet...
	 *
	 * Spamware, e-mail worms, some SMTP proxies, etc., all often
	 * referred to as "slammers", will just spew commands out right
	 * after connecting and without reading any responses, not even the
	 * 220 greeting (presumably in order to work faster and be
	 * simpler).  If we receive input before the banner has been sent
	 * then the sender isn't a real MTA and we can just delay them and
	 * then drop them.
	 */
	if (peer && peer_sa->sin_addr.s_addr != htonl((in_addr_t) INADDR_LOOPBACK)) {
	    int c;
	    int flags;


	    /* we have to sleep a a sec first to give them a chance to screw up.... */
	    sleep(smtp_greeting_delay > 1 ? smtp_greeting_delay : 1);
	    if ((flags = fcntl(fileno(in), F_GETFL, 0)) == -1) {
		panic(EX_OSERR, "fcntl(ls, F_GETFL, 0): failed: %s", strerror(errno));
		/* NOTREACHED */
	    }
	    if (fcntl(fileno(in), F_SETFL, flags | O_NONBLOCK) == -1) {
		panic(EX_OSERR, "fcntl(in, F_SETFL, flags | O_NONBLOCK): failed: %s", strerror(errno));
		/* NOTREACHED */
	    }
	    if ((c = getc(in)) != EOF) {
		ungetc(c, in);
	    }
	    if (fcntl(fileno(in), F_SETFL, flags & ~O_NONBLOCK) == -1) {
		panic(EX_OSERR, "fcntl(in, F_SETFL, flags & ~O_NONBLOCK): failed: %s", strerror(errno));
		/* NOTREACHED */
	    }
	    if (c != EOF) {
		smtp_reset_signals();
		sleep((smtp_error_delay > 2) ? (smtp_error_delay / 2) : 2);
		fputs("554-You must not send commands before you have\r\n", out);
		fflush(out);
		sleep((smtp_error_delay > 2) ? (smtp_error_delay / 2) : 2);
		fputs("554 read my 220 greeting banner!  Goodbye.\r\n", out);
		fflush(out);
		write_log(WRITE_LOG_SYS, "remote ERROR: input received before greeting sent, dropped connection from [%s]",
			  inet_ntoa(peer_sa->sin_addr));
		files[file_i] = NULL;
		exitvalue = EX_PROTOCOL;

		return files;
	    }
	}
#endif

	DEBUG(DBG_REMOTE_HI, "receive_smtp() sending smtp_banner.\n");

	s = expand_string(smtp_banner, (struct addr *)NULL,
			  (char *)NULL, (char *)NULL);
	if (!s) {
	    static int done_it = FALSE;

	    if (!done_it) {
		write_log(WRITE_LOG_PANIC, "expand_string(): failed for smtp_banner='%v'",
			  smtp_banner);
		done_it = TRUE;
	    }
	    /* XXX this will leak, but fixing it adds too much guck */
	    s = xprintf("%v", "invalid smtp_banner definition -- please notify my postmaster!\nSmail ready");
	}
#ifdef HAVE_EHLO
	p = xprintf("%q %s\nESMTP supported", primary_name, s);
#else
	p = xprintf("%q %s", primary_name, s);
#endif
	send_smtp_msg(out, 220, "", TRUE, p);
	xfree(p);
    }

#ifdef HAVE_BSD_NETWORKING
    if (out && peer) {
	struct sockaddr_in *peer_sa = (struct sockaddr_in *) peer;
	socklen_t from_sa_len = sizeof(from_sa);
	struct sockaddr_in my_sa;
	socklen_t my_sa_len = sizeof(my_sa);
	struct in_addr netaddr;
	int netbits;
	char mynet[INET_ADDRSTRLEN+3];
	int rc;

	if (getsockname(fileno(in), (struct sockaddr *) &my_sa, &my_sa_len) != 0 ||
	    my_sa_len == 0 ||
	    my_sa.sin_family != AF_INET) {
	    /*
	     * If errno is ENOTCONN (or ECONNRESET on FreeBSD-4.x) (or on older
	     * 4.4BSD systems, EINVAL) the peer may already have disconnected,
	     * and with '-bs' we'll be looking at STDIN_FILENO...
	     */
	    if (errno != ENOTCONN && errno != ECONNRESET && errno != EINVAL && errno != ENOTSOCK) {
		write_log(WRITE_LOG_PANIC, "getsockname(): %s", strerror(errno));
	    }
	    my_sa.sin_family = AF_INET;
	    my_sa.sin_port = htons((in_port_t) 25);	/* as good a "guess" as any!  ;-) */
	    my_sa.sin_addr.s_addr = htonl((in_addr_t) INADDR_LOOPBACK);
	}
	smtp_local_addr = COPY_STRING(inet_ntoa(my_sa.sin_addr));
	smtp_local_port = xprintf("%u", (unsigned int) ntohs((in_port_t) my_sa.sin_port));
	/*
	 * NOTE: there is absolutely no way to get the actual interface netmask
	 * on your average non-BSD UNIX machine.  RFC 1122 forbids sending a
	 * mask reply unless the system has been administratively explictly
	 * configured as an authoritative agent for address masks so we can't
	 * use the ICMP MASKREQ hack.  On BSD there's a SIOCGIFNETMASK ioctl,
	 * but you need to know the interface name in order to use it, and to
	 * find the interface name to which a socket is receiving packets from
	 * would require asking the kernel for the route to the remote host via
	 * the routing socket.  This would be incredibly complicated!  (Now if
	 * SIOCGIFNETMASK were "fixed" so to work on any socket, i.e. if the
	 * complication were put in the kernel, then it might be worth using it
	 * here.)  In these days of CIDR and drastically sub-netted Class-A
	 * networks in widespread use, this probably means that the whole
	 * "localnet" hack should just be ripped right out and forgotten....
	 * Maybe when/if I finally have to give up my own proper Class-C
	 * network! ;-)
	 * 
	 * We could also do the same trick BIND and Postfix do and simply
	 * initially scan for all interfaces and use SIOCGIFNETMASK to build up
	 * a list of "my networks" instead of trying to figure it out for every
	 * connection based on the local endpoint address as we do now.
	 */
	netaddr = inet_makeaddr(inet_netof(my_sa.sin_addr), (in_addr_t) 0);
	if (IN_CLASSA(ntohl(netaddr.s_addr))) {
	    netbits = 32 - IN_CLASSA_NSHIFT;
	} else if (IN_CLASSB(ntohl(netaddr.s_addr))) {
	    netbits = 32 - IN_CLASSB_NSHIFT;
	} else if (IN_CLASSC(ntohl(netaddr.s_addr))) {
	    netbits = 32 - IN_CLASSC_NSHIFT;
	} else {
	    netbits = 32;
	}
	DEBUG3(DBG_REMOTE_MID, "netaddr=0x%x[%s], netbits=%d\n",
	       ntohl(netaddr.s_addr),
	       inet_ntoa(netaddr),
	       netbits);
	if (!inet_net_ntop(my_sa.sin_family, (void *) &netaddr, netbits, mynet, sizeof(mynet))) {
	    DEBUG3(DBG_REMOTE_MID, "inet_net_ntop(netaddr=0x%x, netbits=%d): %s\n",
		   ntohl(netaddr.s_addr), netbits, strerror(errno));
	    p = inet_ntoa(inet_makeaddr((in_addr_t) ntohl(netaddr.s_addr), (in_addr_t) 0));
	    (void) sprintf(mynet, "%s/%d", p, netbits);
	}
	smtp_local_net = COPY_STRING(mynet);

	errno = 0;
	if (((rc = getpeername(fileno(in), (struct sockaddr *) &from_sa, &from_sa_len)) == 0) &&
	    (from_sa_len > 0) &&
	    (from_sa.sin_family == AF_INET)) {
	    /*
	     * for now we'll compare what getpeername() gives us to what we
	     * were handed (which should be the peer address returned by
	     * accept()), and simply complain very loudly if they differ....
	     */
	    sender_host_port = xprintf("%u", (unsigned int) ntohs((in_port_t) from_sa.sin_port));
	    sender_host_addr = COPY_STRING(inet_ntoa(from_sa.sin_addr));
	    if (memcmp((char *) &(peer_sa->sin_addr), (char *) &(from_sa.sin_addr), sizeof(peer_sa->sin_addr)) != 0) {
		write_log(WRITE_LOG_PANIC | WRITE_LOG_SYS,
			  "getpeername(): [%s] does not match addr from accept(): [%s]!",
			  sender_host_addr, inet_ntoa(peer_sa->sin_addr));
	    }
	} else {
	    /*
	     * If the failure is ENOTCONN (or ECONNRESET on FreeBSD-4.x or newer)
	     * the peer may already have disconnected.
	     *
	     * Note that we may, or may not, still be able to read something
	     * (eg. "QUIT<CR><LF>") from the buffer.
	     */
	    if (rc == -1 && errno != ENOTCONN && errno != ECONNRESET && errno != ENOTSOCK) {
		/* XXX maybe this should be a panic()? */
		write_log(WRITE_LOG_PANIC, "getpeername(): %s", strerror(errno));
	    } else {
		/* XXX if (peer_sa && errno == ENOTSOCK) then very weird things are happening! */
		DEBUG1(DBG_REMOTE_MID, "getpeername(): %s, will try to use peer_sa\n", strerror(errno));
	    }
	    /*
	     * if possible use the supplied "peer" value (from accept()) if
	     * getpeername() fails, assuming the original value is valid...
	     */
	    if (peer_sa->sin_addr.s_addr != 0 && peer_sa->sin_family == AF_INET) {
		memcpy((char *) &from_sa, (char *) peer_sa, sizeof(from_sa));
		DEBUG1(DBG_REMOTE_MID, "using peer_sa from accept(): %s\n", inet_ntoa(from_sa.sin_addr));
	    } else {
		if (peer_sa->sin_family != AF_LOCAL) {
		    panic(EX_OSERR, "peer socket info from accept() has invalid address family %d", peer_sa->sin_family);
		}
		DEBUG(DBG_REMOTE_MID, "peer_sa from accept() is AF_LOCAL, pretending they used loopback\n");
		from_sa.sin_family = AF_INET;
		from_sa.sin_port = htons((in_port_t) 65535);
		from_sa.sin_addr.s_addr = htonl((in_addr_t) INADDR_LOOPBACK);
	    }
	    sender_host_port = xprintf("%u", (unsigned int) ntohs((in_port_t) from_sa.sin_port));
	    sender_host_addr = COPY_STRING(inet_ntoa(from_sa.sin_addr));
	}

	/* finally we can check who's who... */
	if (my_sa.sin_family == from_sa.sin_family &&
	    my_sa.sin_addr.s_addr == from_sa.sin_addr.s_addr) {
	    peer_is_localhost = TRUE;
	}
	DEBUG3(DBG_REMOTE_MID, "local addr is [%s], local net might be [%s]%s.\n",
	       inet_ntoa(my_sa.sin_addr),
	       smtp_local_net ? smtp_local_net : "<unset>",
	       peer_is_localhost ? ", peer is the local host" : "");

	if (sender_host_addr) {
	    if (!(shp = gethostbyaddr((char *) &(from_sa.sin_addr),
				      (socklen_t) sizeof(from_sa.sin_addr), from_sa.sin_family))) {
		DEBUG2(DBG_REMOTE_LO, "gethostbyaddr(%s) failed: %s.\n", sender_host_addr, hstrerror(h_errno));
		sender_host_really = NULL;
	    } else {
		/* the use of %q shouldn't be necessary here, but for consistency... */
		sender_host_really = xprintf("%q", shp->h_name);
	    }
	}
	DEBUG2(DBG_REMOTE_MID, "sender_host_addr is [%s], sender_host_really might be '%s'\n",
	       sender_host_addr ? sender_host_addr : "(unset)",
	       sender_host_really ? sender_host_really : "(unknown)");

    } else if (out) {
	peer_is_localhost = TRUE;
	sender_host_really = "interactive-SMTP-on-stdin"; /* or maybe a UNIX socket */
    } else {
	peer_is_localhost = TRUE;
	sender_host_really = "SMTP-on-stdin";	/* or maybe a UNIX socket */
    }

    /* do this least expensive check first.... */
    if (sender_host_addr && !smtp_sess_deny) {
	if (match_ip(sender_host_addr, smtp_reject_hosts, ':', ';', FALSE, &errstr)) {
	    smtp_sess_deny = TRUE;
	    smtp_sess_deny_reason = SMTP_SESS_DENY_REJECT;
	    smtp_sess_deny_msg = errstr;
	}
    }

    /*
     * then this next not-so-expensive check....
     *
     * This is a special case where we call expand_string() so that we can
     * use variable names in a list, since in this case it makes the most
     * sense and is the least error prone to just always include
     * "${rxquote:hostnames}" in the list.
     *
     * Note that if the expand_string() fails then the result is as if
     * smtp_host_reject_hostnames is not set (though maybe a paniclog
     * entry will be written as a warning).
     *
     * Note also there is no user-controllable exception list here (other than
     * the WHOSON hack).  A quick hack would be to bypass this check if the
     * client was in the smtp_hello_broken_allow list, but that would greatly
     * overload an already far too overloaded control.  Adding yet another
     * bypass list though should also not really be necessary since usually a
     * wee bit of fancy regex logic will allow most offenders to be temporarily
     * bypassed without too much risk of mis-identification.  Just be sure you
     * only bypass with completely matched names.
     */
    if (sender_host_really && smtp_host_reject_hostnames && !smtp_sess_deny && !peer_is_localhost && 
	match_hostname(sender_host_really,
		       expand_string(smtp_host_reject_hostnames, (struct addr *) NULL, (char *) NULL, (char *) NULL),
		       &errstr) == MATCH_MATCHED) {
	smtp_sess_deny = TRUE;
	smtp_sess_deny_reason = SMTP_SESS_DENY_REJECT_PTR;
	smtp_sess_deny_msg = errstr;
    }

    /* then, if necessary, do the RBL dirty work */
    if (sender_host_addr && !smtp_sess_deny) {
	if (! match_ip(sender_host_addr, smtp_rbl_except, ':', ';', FALSE, (char **) NULL)) {
	    char *revaddr = flip_inet_addr(sender_host_addr);

	    if (match_dnsbl(revaddr, smtp_rbl_domains, "smtp_rbl_domains", &smtp_dnsbl_match, &smtp_dnsbl_addr, &errstr)) {
		smtp_sess_deny = TRUE;
		smtp_sess_deny_reason = SMTP_SESS_DENY_DNSBL;
		smtp_sess_deny_msg = errstr;
	    }
	    xfree(revaddr);
	}
    }

    /* and then, if necessary, some more DNSBL dirty work */
    if (sender_host_addr && sender_host_really && !smtp_sess_deny) {
	if (! match_ip(sender_host_addr, smtp_host_dnsbl_except, ':', ';', FALSE, (char **) NULL)) {
	    if (match_dnsbl(sender_host_really, smtp_host_dnsbl_domains, "smtp_host_dnsbl_domains", &smtp_dnsbl_match, &smtp_dnsbl_addr, &errstr)) {
		smtp_sess_deny = TRUE;
		smtp_sess_deny_reason = SMTP_SESS_DENY_DNSBL;
		smtp_sess_deny_msg = errstr;
	    }
	} else {
	    DEBUG1(DBG_REMOTE_MID, "do_smtp(): not checking for %s in smtp_host_dnsbl_domains.\n", sender_host_really);
	}
    }

# if defined(HAVE_LIBWHOSON)
    if (smtp_sess_deny && sender_host_addr) {
	int wso;
	char retbuf[BUFSIZ];

	DEBUG1(DBG_REMOTE_HI,
	       "(checking if '%s' is listed in the 'whoson' database)\n", sender_host_addr);
	*retbuf = '\0';
	wso = wso_query(sender_host_addr, retbuf, (int) sizeof(retbuf));
	retbuf[sizeof(retbuf)-1] = '\0'; /* just in case... */
	if (wso < 0 && *retbuf) {
	    /* XXX hope this doesn't flood the log! */
	    write_log(WRITE_LOG_PANIC, "wso_query(%s): whoson query failed: %v", sender_host_addr, retbuf);
	} else if (wso == 0) {
	    DEBUG2(DBG_REMOTE_MID, "ip '%s' verified by whoson as '%v'\n", sender_host_addr, retbuf);
	    /*
	     * WARNING!!!!
	     *
	     * We must have already done all checks by now that can set or
	     * change smtp_sess_deny in any way that wouldn't be allowed for
	     * WHOSON authenticated users
	     */
	    smtp_sess_deny = FALSE;
	    smtp_sess_deny_reason = 0;
	    smtp_sess_deny_msg = NULL;
	} else {
	    DEBUG1(DBG_REMOTE_MID, "ip '%s' NOT verified by whoson\n", sender_host_addr);
	}
    }
# endif /* HAVE_LIBWHOSON */
#else /* !HAVE_BSD_NETWORKING */
    if (peer) {
	sender_host_really = "unknowable.and.unverified";
	peer_is_localhost = FALSE;
    } else if (out) {
	peer_is_localhost = TRUE;
	sender_host_really = "interactive-SMTP-on-stdin";
    } else {
	peer_is_localhost = TRUE;
	sender_host_really = "SMTP-on-stdin";
    }
#endif /* HAVE_BSD_NETWORKING */

#ifdef HAVE_RFC1413			/* and presumably BSD_NETWORKING too! ;-) */
    if (out && rfc1413_query_timeout > 0) { /* switch off RFC1413 by setting timeout <= 0 */
	DEBUG(DBG_REMOTE_HI, "receive_smtp() getting remote identity.\n");
	/* 
	 * This code does an Ident/RFC1413 lookup on the connecting client (if
	 * possible).
	 *
	 * It is down here because it may cause a delay and it is necessary to
	 * have the delay after the connection is established and the 220
	 * message is sent, rather than at the start, otherwise clients may
	 * timeout.
	 *
	 * A call to ident_lookup() could be used to obtain additional info
	 * available from an RFC1413 ident call, but I don't know what we would
	 * do with the info, so don't bother to obtain it!
	 */
	if ((ident_sender = ident_id(fileno(in), (int) rfc1413_query_timeout))) {
	    ident_sender = xprintf("%q", ident_sender);
	    ident_method = "rfc1413";
	}
    }
#endif /* HAVE_RFC1413 */

    /* if there's no connection to query... */
    if (!out) {
	ident_sender = xprintf("%q", local_sender);
	ident_method = "UNIX";
    }

    /* Set the initual guess at the protocol used */
    if (sender_proto == NULL) {
        sender_proto = (out ? "smtp" : "bsmtp");
    }

    accepted_msg_size = computed_max_msg_size();
    if (out) {
	/* XXX it would be nice if we could use the time of the accept(), along
	 * with the PTR target hostname, in this log entry.... */
	write_log(WRITE_LOG_SYS, "remote connection from %s%s%s%s%s%s",
		  ident_sender ? ident_sender : "",
		  ident_sender ? "@" : "",
		  sender_host_really ? sender_host_really : "(unknown)",
		  sender_host_addr ? "[" : "",
		  sender_host_addr ? sender_host_addr : "",
		  sender_host_addr ? "]" : "");
    } else if (debug) {
	write_log(WRITE_LOG_SYS, "bsmtp session started on file#%d", fileno(in));
    }
    /*
     * clear the alarm() timer in preparation for reading commands, and then
     * arrange to always catch timeouts, even in batch mode where the timer is
     * never started, so that a manual SIGALARM still has the desired effect
     */
    alarm((unsigned int) 0);
    (void) signal(SIGALRM, smtp_receive_timeout_sig);

    /*
     * prepare for SIGHUP, sent from the parent when it reloads...
     */
    got_sighup = FALSE;
    (void) signal(SIGHUP, smtp_reload_sig);

    /*
     * read commands until we're done, or something bad happens causing a
     * return or exit() inside the loop....
     */
    while (TRUE) {
	int smtp_cmd_id;
	char *saved_sender;		/* protected from read_message() */
	char **argv;			/* args from read_message() */
	int i;

	/* in batch mode we will wait forever... */
	if (out) {
	    /*
	     * for a remote connection we reset the timer after every command
	     */
	    alarm((unsigned int) smtp_receive_command_timeout);
	}
	/*
	 * read another command from the client
	 */
	smtp_cmd_id = read_smtp_command(in, out);

	/*
	 * if we've already sent an error response to the DATA command and the
	 * next command is not RSET_CMD or HELO_CMD then drop the connection
	 * immediately!
	 */
	if (sent_refused_data &&
	    smtp_cmd_id != EHLO_CMD &&
#ifdef HAVE_EHLO
	    smtp_cmd_id != EHLO_CMD &&
#endif
	    smtp_cmd_id != QUIT_CMD &&
	    smtp_cmd_id != RSET_CMD) {

	    smtp_reset_signals();
	    if (out) {
		fputs("521 5.3.0 You have ignored one too many errors!  Goodbye.\r\n", out);
		fflush(out);
	    }
	    write_log(WRITE_LOG_SYS, "remote DATA: error ignored, dropped connection forcefully from %s%s%s%s%s%s%s%s%s%s",
		      (sender_host || sender_host_addr) ? "" : "stdin on ",
		      ident_sender ? ident_sender : "",
		      ident_sender ? "@" : "",
		      sender_host ? sender_host : (sender_host_addr ? "" : "localhost"),
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
		      sender_host_addr ? "[" : "",
		      sender_host_addr ? sender_host_addr : "",
		      sender_host_addr ? "]" : "");
	    files[file_i] = NULL;
	    errfile = save_errfile;
	    debug = save_debug;
	    exitvalue = EX_PROTOCOL;

	    return files;
	}
	switch (smtp_cmd_id) {
	case EHLO_CMD:
#ifdef HAVE_EHLO
	    sender_proto = (out ? "esmtp" : "ebsmtp"); /* reset as appropriate */
# ifdef HAVE_BSD_NETWORKING
	    do_greeting(out, peer, (void *) &from_sa);
# else
	    do_greeting(out, peer, (void *) NULL);
# endif
#else /* !HAVE_EHLO */
	    if (out) {
		sleep(1);
		fprintf(out, "501-ESMTP support not enabled.\r\n");
		fflush(out);
		sleep(smtp_error_delay > 0 ? smtp_error_delay : 2);
		fprintf(out, "501 ESMTP Where did you get the idea it was?!?!?\r\n");
		fflush(out);
	    }
# ifdef NO_LOG_EHLO /* XXX overloaded from meaning in transports/smtplib.c */
	    DEBUG4(DBG_REMOTE_LO, "received unsupported EHLO from %s%s%s%s%s%s.\n",
		   ident_sender ? ident_sender : "", ident_sender ? "@" : "",
		   sender_host_really ? sender_host_really : "(unknown)",
		   sender_host_addr ? "[" : "",
		   sender_host_addr ? sender_host_addr : "",
		   sender_host_addr ? "]" : "");
# else /* !NO_LOG_EHLO */
	    write_log(WRITE_LOG_SYS, "remote EHLO: not unsupported, from %s%s%s%s%s%s",
		      ident_sender ? ident_sender : "",
		      ident_sender ? "@" : "",
		      sender_host_really ? sender_host_really : "(unknown)",
		      sender_host_addr ? "[" : "",
		      sender_host_addr ? sender_host_addr : "",
		      sender_host_addr ? "]" : "");
# endif /* NO_LOG_EHLO */
	    exitvalue = EX_USAGE;
#endif /* HAVE_EHLO */
	    break;

	case HELO_CMD:
	    sender_proto = (out ? "smtp" : "bsmtp"); /* reset as appropriate */
#ifdef HAVE_BSD_NETWORKING
	    do_greeting(out, peer, (void *) &from_sa);
#else
	    do_greeting(out, peer, (void *) NULL);
#endif
	    break;

	case MAIL_CMD: {
	    int is_bounce = FALSE;

	    if (smtp_sess_deny) {
		send_session_denied_reply(out, smtp_cmd_list[smtp_cmd_id].name, data);
		exitvalue = EX_UNAVAILABLE;
		break;
	    }
	    if (!sender_host && out /* XXX && peer */) {
	        /* DO NOT send enhanced status as we are apparently not past HELO/EHLO. */
		non_compliant_reply(out, smtp_cmd_id, TRUE, 503, "", "Command must be preceded by HELO/EHLO command.");
		break;
	    }
	    if (sender) {
		non_compliant_reply(out, smtp_cmd_id, TRUE, 503, "5.5.1", "Sender already specified");
		exitvalue = EX_USAGE;
		break;
	    }
	    sender = check_addr_operand(out, smtp_cmd_id, &rest);
	    if (! sender) {
		/* NOTE: error reply was printed and logged by check_addr_operand() */
		break;
	    }
	    is_bounce = FALSE;
	    if (*sender == '\0') {
		/* special error sender form <> given */
		xfree(sender);
		sender = COPY_STRING("<>");
		if (smtp_max_bounce_recipients > 0) {
		    smtp_max_recipients = smtp_max_bounce_recipients;
		}
		is_bounce = TRUE;
	    } else if (EQ(sender, "+")) {
		/* special smail-internal <+> was given */
		xfree(sender);
		sender = COPY_STRING("<+>");
		smtp_max_recipients = 1;	/* XXX multiples impossible? (see notify.c?) */
		is_bounce = TRUE;
	    } else if (!verify_sender(sender, data, out)) {
		/* NOTE: error reply was printed and logged by verify_sender() */
		xfree(sender);
		sender = NULL;
		exitvalue = EX_NOUSER;
		break;
	    }
	    /* XXX should sender be re-quoted with angle brackets? */
#ifdef HAVE_BSD_NETWORKING
	    if (is_bounce) {
		char *reject_msg = NULL;

		if ((smtp_local_port && atoi(smtp_local_port) != 25) ||	/* incoming message submission? */
		    (smtp_mua_only_hosts && sender_host_addr &&
		     match_ip(sender_host_addr, smtp_mua_only_hosts, ':', ';', FALSE, &reject_msg))) {
		    if (out) {
			char *esc = "5.4.3";

			if (smtp_local_port && atoi(smtp_local_port) != 25) {
			    /*
			     * We assume anything not on the standard SMTP port
			     * is actually only doing "message submission" in
			     * the spirit of RFC 2476 and that it isn't a true
			     * MTA (since true MTAs only use official SMTP on
			     * port 25 :-).  Many sites use port 2525 (which is
			     * also often used for LMTP, but which has been
			     * assigned to Micro$oft for ms-v-worlds, whatever
			     * the hell that is), or 2025 (unassigned?), or
			     * even 26 (unassigned), instead of the official
			     * port 587 for message submission.
			     *
			     * Note that RFC 2476 says that null return paths
			     * "MUST" be accepted by the message submission
			     * agent.  However since too many common clients
			     * now allow ignorant users to fake bounces of spam
			     * and other unwanted mail (which then usually just
			     * goes to to faked or forged sender addresses),
			     * and a growing amount of spamware also fakes
			     * bounces, we simply cannot allow anything
			     * resembling a fake bounce to be sent as it will
			     * either annoy the wrong person, or end up in our
			     * error queue.
			     *
			     * The sending of MDNs (RFC 2298) is also a _very_
			     * bad idea in the first place anyway (many, many,
			     * many privacy concerns!), and that's the only
			     * other known reason for an MUA to use a null
			     * return path when talking to an MSA.
			     */
			    reject_msg = "Bounces (NDNs) and message disposition notifications (MDNs, RFC 2298) are not relayed by this site when using message the mail submission service.\r\n";
			    esc = "5.6.2";
			}
			sleep((smtp_error_delay > 2) ? (smtp_error_delay / 2) : 2);
			dprintf(out, "550-%s Your client is not authorized to use a null return path.\r\n", esc);
			sleep(1);
			if (reject_msg) {
			    dprintf(out, "550-%s Administrative reason given was:\r\n", esc);
			    send_smtp_msg(out, 550, esc, FALSE, reject_msg);
			}
			sleep(smtp_error_delay > 0 ? smtp_error_delay : 2);
			dprintf(out, "550 %s Please use a valid sender address.\r\n", esc);
			fflush(out);
		    }
		    write_log(WRITE_LOG_SYS, "remote %s: '%q' rejecting bounce from %s%s%s%s%s%s%s%s%s: %s",
			      smtp_cmd_list[smtp_cmd_id].name,
			      data,
			      ident_sender ? ident_sender : "",
			      ident_sender ? "@" : "",
			      sender_host ? sender_host : "",
			      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
			      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
			      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
			      sender_host_addr ? "[" : "",
			      sender_host_addr ? sender_host_addr : "",
			      sender_host_addr ? "]" : "",
			      (smtp_local_port && atoi(smtp_local_port) != 25) ? "via message submission" : "matched in smtp_mua_only_hosts");
		    xfree(sender);
		    sender = NULL;
		    exitvalue = EX_NOUSER;
		    break;
		}
	    }
#endif /* HAVE_BSD_NETWORKING */

	    if (decode_mail_options(rest, out) < 0) {
		xfree(sender);
		sender = NULL;
		exitvalue = EX_DATAERR;
		break;
	    }

	    if (out) {
		fprintf(out, "250 2.1.0 %s%s%s Sender Okay.\r\n",
			is_bounce ? "" : "<",
			sender,
			is_bounce ? "" : ">");
		fflush(out);
	    } else {
		DEBUG1(DBG_REMOTE_LO, "Sender '%q' okay.\n", data);
	    }
	    break;
	}
	case RCPT_CMD: {
	    char *newrecip;

	    if (smtp_sess_deny) {
		send_session_denied_reply(out, smtp_cmd_list[smtp_cmd_id].name, data);
		exitvalue = EX_UNAVAILABLE;
		break;
	    }
	    if (!sender) {
		/* don't bother logging if ESMTP PIPELINING is likely happening */
		non_compliant_reply(out, smtp_cmd_id, ((*sender_proto == 'e') ? FALSE : TRUE),
				    503, "5.5.1", "Command must be preceded by 'MAIL FROM:' command.");
		break;
	    }
	    if (smtp_max_recipients && (num_smtp_recipients++ >= smtp_max_recipients)) {
		if (out) {
		    fprintf(out, "452-4.5.3 Too many recipients.\r\n");
		    fflush(out);
		    sleep(smtp_error_delay > 0 ? smtp_error_delay : 2);
		    fprintf(out, "452 4.5.3 Administrative limit exceeded.\r\n");
		    fflush(out);
		} else {
		    DEBUG(DBG_REMOTE_LO, "Too many recipients specified!  Ignoring this one!\n");
		}
		num_smtp_recipients--;
		exitvalue = EX_TEMPFAIL;
		break;
	    }
	    newrecip = check_addr_operand(out, smtp_cmd_id, &rest);
	    if (! newrecip) {
		/* NOTE: error reply was printed and logged by check_addr_operand() */
		num_smtp_recipients--;
		break;
	    }
	    if (rest && *rest) {
		char *msg;

/* XXX TODO implement RFC 3461 "NOTIFY=" (should not be seen unless we include DSN in EHLO response) */
/* XXX TODO implement RFC 3461 "ORCPT=" (should not be seen unless we include DSN in EHLO response) */

		/* NOTE: ESMTP options without EHLO is an error in check_addr_operand() */
		msg = xprintf("syntax error in parameters: unknown ESMTP option(s): %v", rest);
		non_compliant_reply(out, smtp_cmd_id, TRUE, 501, "5.5.2", msg);
		xfree(msg);
		xfree(newrecip);
		num_smtp_recipients--;
		exitvalue = EX_DATAERR;
		break;
	    }
	    /*
	     * NOTE: error/OK reply is printed to client by verify_addr()
	     */
	    if (! verify_addr(newrecip, out, RCPT_CMD)) {
		xfree(newrecip);
		num_smtp_recipients--;
		exitvalue = EX_NOUSER;
		break;
	    }
	    /*
	     * create a new address for this recipient and add to the recipients list
	     */
	    cur = alloc_addr();
	    /*
	     * always surround the recorded address in angle brackets again,
	     * mostly to be consistent with the null-addr forms above...
	     */
	    cur->in_addr = xprintf("<%s>", newrecip);
	    xfree(newrecip);
	    cur->succ = recipients;
	    recipients = cur;
	    break;
	}
	case DATA_CMD:
	    if (smtp_sess_deny) {
		send_session_denied_reply(out, smtp_cmd_list[smtp_cmd_id].name, data);
		exitvalue = EX_UNAVAILABLE;
		break;
	    }
	    if (sender == NULL) {
		if (out) {
		    /* don't bother logging if ESMTP PIPELINING is likely happening */
		    non_compliant_reply(out, smtp_cmd_id, ((*sender_proto == 'e') ? FALSE : TRUE),
					503, "5.5.1", "Command must be preceded by 'MAIL FROM:' command.");
		    sent_refused_data = TRUE;
		} else {
		    /* devnull the message for the sake of further batched cmds */
		    if (spool_fn) {
			close_spool();
		    }
		    swallow_smtp(in);
		}
		break;
	    }
	    if (recipients == NULL) {
		if (out) {
		    /* don't bother logging if ESMTP PIPELINING is likely happening */
		    non_compliant_reply(out, smtp_cmd_id, ((*sender_proto == 'e') ? FALSE : TRUE),
					503, "5.5.1", "Command must be preceded by at least one 'RCPT TO:' command.");
		    sent_refused_data = TRUE;
		} else {
		    DEBUG(DBG_REMOTE_LO, "503-5.5.1 DATA must be preceded by at least one 'RCPT TO:' command.\n");
		    /* devnull the message for the sake of further batched cmds */
		    if (spool_fn) {
			close_spool();
		    }
		    swallow_smtp(in);
		}
		break;
	    }
	    if (out) {
		fprintf(out, "354 Enter mail, end with \".\" on a line by itself...\r\n");
		fflush(out);
		alarm((unsigned int) 0);
	    } else {
		DEBUG(DBG_REMOTE_LO, "354 Enter mail, end with `.' on a line by itself...\n");
	    }

	    /*
	     * if we had the previous spool file opened, close it
	     * before creating a new one
	     */
	    if (spool_fn) {
		close_spool();
	    }
	    if (out) {
		/*
		 * if we are interactive or can send back failure messages, we
		 * can abort on timeouts....
		 */
		smtp_input_signals();
		alarm((unsigned int) smtp_receive_message_timeout);
	    }
	    smtp_remove_on_timeout = TRUE;
	    errno = 0;
	    if (queue_message(in, SMTP_DOTS, recipients, &errstr) == FAIL) {
		int oerrno = errno;

		log_spool_errors();
		/*
		 * we need to catch this here since queue_message() may
		 * have failed because the file grew too big and we don't
		 * want to let them get away with a 451 if that happened!
		 */
		if (accepted_msg_size >= 0 && msg_size >= accepted_msg_size) {
		    exitvalue = EX_NOPERM;
		    if (out) {
			fprintf(out, "552-5.3.4 Message input exceeded maximum message size of %ld bytes.\r\n",
				(unsigned long int) accepted_msg_size);
			send_smtp_msg(out, 552, "5.3.4", TRUE, no_files_by_email_msg);
		    }
		    write_log(WRITE_LOG_SYS, "remote %s: message too big from %s%s%s%s%s%s%s%s%s",
			      smtp_cmd_list[smtp_cmd_id].name,
			      ident_sender ? ident_sender : "",
			      ident_sender ? "@" : "",
			      sender_host ? sender_host : "",
			      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
			      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
			      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
			      sender_host_addr ? "[" : "",
			      sender_host_addr ? sender_host_addr : "",
			      sender_host_addr ? "]" : "");
		    swallow_smtp(in);	/* why do we bother? */
		    break;
		} else {
		    exitvalue = EX_CANTCREAT;
		    if (out) {
			/* XXX using errno to pass status is an ugly hack */
			if (oerrno) {
			    dprintf(out, "451 4.3.0 Failed to queue message: %v: %s\r\n",
				    errstr, strerror(oerrno));
			    fflush(out);
			} else {
			    /*
			     * XXX RFC 2476 (submission) suggests 554 or 550,
			     * but 550 is already used and 554 is for
			     * "syntactic problems in the data"
			     */
			    fprintf(out, "553-5.7.1 Your message has been refused due to content filtering rules:\r\n553-5.7.1 \r\n");
			    /* note the extra spaces in the ESC are a hack to get some additional indentation */
			    send_smtp_msg(out, 553, "5.7.1  ", TRUE, errstr);
			}
		    }
		    write_log(WRITE_LOG_SYS, "remote %s: %s to queue message from %s%s%s%s%s%s%s%s%s: %s: %s.",
			      smtp_cmd_list[smtp_cmd_id].name,
			      oerrno ? "failed" : "refused",
			      ident_sender ? ident_sender : "",
			      ident_sender ? "@" : "",
			      sender_host ? sender_host : "",
			      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
			      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
			      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
			      sender_host_addr ? "[" : "",
			      sender_host_addr ? sender_host_addr : "",
			      sender_host_addr ? "]" : "",
			      errstr, oerrno ? strerror(oerrno) : "rejected");
		}
		unlink_spool();
		reset_state();
		break;
	    }
	    smtp_processing_signals();	/* back to handling commands... */
	    if (sender == NULL) {		/* XXX how can this ever happen? */
		unlink_spool();
		reset_state();
		exitvalue = EX_NOINPUT;
		break;
	    }
	    /*
	     * we need to use read_message(), but it clobbers sender (which
	     * would only be reset if process_args() were called, but we don't
	     * need to go that far) so we'll just squirrel away a copy of our
	     * current pointer to sender for restoration before we call
	     * log_incoming()....
	     */
	    saved_sender = COPY_STRING(sender);
	    if ((argv = read_message()) == NULL) {
		log_spool_errors();
		unlink_spool();
		if (out) {
		    sleep(2);
		    fprintf(out, "451 4.3.0 error in spooled message\r\n");
		    fflush(out);
		}
		write_log(WRITE_LOG_SYS, "remote %s: error in queued message from %s%s%s%s%s%s%s%s%s",
			  smtp_cmd_list[smtp_cmd_id].name,
			  ident_sender ? ident_sender : "",
			  ident_sender ? "@" : "",
			  sender_host ? sender_host : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
			  sender_host_addr ? "[" : "",
			  sender_host_addr ? sender_host_addr : "",
			  sender_host_addr ? "]" : "");
		reset_state();
		exitvalue = EX_TEMPFAIL;
		break;
	    }
	    /* XXX free_argv(argv); */
	    /*
	     * This shouldn't ever trigger because we should never write a
	     * spool file larger than accepted_msg_size in the first place, but
	     * we'll keep the check here too after the message has been fully
	     * received, just to be on the safe side.
	     *
	     * in any case don't be fussy about headers, use msg_body_size
	     * instead of msg_size in this test....
	     */
	    if (accepted_msg_size >= 0 && msg_body_size > (unsigned long int) accepted_msg_size) {
		if (out) {
		    sleep(smtp_error_delay > 0 ? smtp_error_delay : 2);
		    fprintf(out, "552-5.3.4 Your message is too big for this system to accept in one transaction.\r\n");
		    send_smtp_msg(out, 552, "5.3.4", TRUE, no_files_by_email_msg);
		}
		write_log(WRITE_LOG_SYS, "remote %s: message too big (body %ld bytes) from %s%s%s%s%s%s%s%s%s",
			  smtp_cmd_list[smtp_cmd_id].name,
			  msg_body_size,
			  ident_sender ? ident_sender : "",
			  ident_sender ? "@" : "",
			  sender_host ? sender_host : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
			  sender_host_addr ? "[" : "",
			  sender_host_addr ? sender_host_addr : "",
			  sender_host_addr ? "]" : "");
		log_spool_errors();
		unlink_spool();
		reset_state();
		exitvalue = EX_NOPERM;
		break;
	    }
	    /*
	     * we will now accept the message for delivery so turn off the
	     * timeout alarm and prevent removal of the queue file on SIGTERM
	     * or SIGINT.
	     */
	    alarm((unsigned int) 0);
	    smtp_remove_on_timeout = FALSE;

	    /* penalize all very large messages which would require multiple I/Os */
	    if (msg_size > (off_t) message_buf_size) {
		queue_only = TRUE;
	    }
	    check_grade();
	    sender = saved_sender;	/* restore what read_message() undid before we log it */
	    log_incoming();
	    log_spool_errors();		/* XXX what if there are some?  shouldn't this abort? */
	    if (out) {
		dprintf(out, "250 2.6.0 Mail accepted, queue ID %s%s on %q.\r\n",
			message_id,
			queue_only ? " (delivery deferred for later processing)" : "",
			primary_name);
		fflush(out);
	    } else {
		DEBUG3(DBG_REMOTE_LO, "Mail accepted, queue ID %s%s on %q.\n",
		       message_id,
		       queue_only ? " (delivery deferred for later processing)" : "",
		       primary_name);
	    }
	    /* always allow an extra element to store the ending NULL */
	    if (file_i >= file_cnt) {
		/* we need to grow the array of spool file names */
		file_cnt += 8;
		files = (char **)xrealloc((char *)files,
					  (file_cnt + 1) * sizeof(*files));
	    }
	    files[file_i++] = xprintf("%s/input/%s", spool_dir, spool_fn);
	    reset_state();
	    break;

	case VRFY_CMD:
	    if (out) {
		if (smtp_vrfy_delay) {
		    sleep(smtp_vrfy_delay);
		}
		strip_rfc822_comments(data);
		strip_rfc822_whitespace(data);
		if (*data == ' ') {
		    data++;
		}
#if 0 /* to get rid of preparse_address_1() call in verify_addr() since all other callers do it already */
		/* XXX or mabye use check_addr_operand() -- can VRFY ever have ESMTP options? */
		ppaddr = preparse_address_1(data, &errstr, restp);
		if (!ppaddr) {
		    /* complain using errstr */
		    break;
		}
		verify_addr(ppaddr, out, VRFY_CMD);
#else
		verify_addr(data, out, VRFY_CMD);
#endif
		reset_hit_table();
		fflush(out);
	    } else {
		DEBUG(DBG_REMOTE_LO, "VRFY not supported in batch mode -- use '-bv'.\n");
	    }
	    break;

	case EXPN_CMD:
	    if (smtp_sess_deny) {
		send_session_denied_reply(out, "EXPN", data);
		break;
	    }
	    if (out) {
#ifdef NO_SMTP_EXPN
		sleep(1);
		fprintf(out, "502-5.5.1 Command not implemented.\r\n");
		fflush(out);
		sleep(smtp_error_delay > 0 ? smtp_error_delay : 2);
		fprintf(out, "502 5.5.1 Try 'help' for assistance.\r\n");
		fflush(out);
#else
		if (smtp_allow_expn) {
		    if (smtp_expn_delay) {
			sleep(smtp_expn_delay);
		    }
		    strip_rfc822_comments(data);
		    strip_rfc822_whitespace(data);
		    if (*data == ' ') {
			data++;
		    }
		    expand_addr(data, out);
		    reset_hit_table();
		    fflush(out);
		} else {
		    sleep(1);
		    fprintf(out, "502-5.7.0 Command disabled.\r\n");
		    fflush(out);
		    sleep(smtp_error_delay > 0 ? smtp_error_delay : 2);
		    fprintf(out, "502 5.7.0 Try 'help' for assistance.\r\n");
		    fflush(out);
		}
#endif
	    } else {
		DEBUG(DBG_REMOTE_LO, "EXPN not supported in batch mode -- use '-bv'!\n");
	    }
	    break;

	case QUIT_CMD:
	    smtp_reset_signals();
	    if (out) {
		dprintf(out, "221 2.2.0 %q closing connection\r\n", primary_name);
		fflush(out);
	    } else {
		DEBUG1(DBG_REMOTE_LO, "%q closing connection now.\n", primary_name);
	    }
	    if (file_i == 0 && !smtp_sess_deny && sender_host && sender && recipients) {
		write_log(WRITE_LOG_SYS, "remote %s: no messages received from %s%s%s%s%s%s%s%s%s for %s%s",
			  smtp_cmd_list[smtp_cmd_id].name,
			  ident_sender ? ident_sender : "",
			  ident_sender ? "@" : "",
			  sender_host ? sender_host : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
			  sender_host_addr ? "[" : "",
			  sender_host_addr ? sender_host_addr : "",
			  sender_host_addr ? "]" : "",
			  recipients ? recipients->in_addr : "",
			  recipients && recipients->succ ? " (and others)" : "");
	    }
	    if (sender_host) {
		xfree(sender_host);
		sender_host = NULL;
	    }
	    reset_state();		/* bfree() can catch latent bugs */
	    files[file_i] = NULL;
	    errfile = save_errfile;
	    debug = save_debug;

	    return files;

	case RSET_CMD:
#ifndef NO_LOG_RSET
	    write_log(WRITE_LOG_SYS, "remote %s: requested by %s%s%s%s%s%s%s%s%s",
		      smtp_cmd_list[smtp_cmd_id].name,
		      ident_sender ? ident_sender : "",
		      ident_sender ? "@" : "",
		      sender_host ? sender_host : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
		      sender_host_addr ? "[" : "",
		      sender_host_addr ? sender_host_addr : "",
		      sender_host_addr ? "]" : "");
#endif
	    /*
	     * WARNING:  DO NOT zap the HELO address here!!!!
	     *
	     * It seems some of the virus vendors assume that a RSET is safe to
	     * use to keep a connection open while they hold back the MUA from
	     * sending so that they can do their scanning of outbound messages.
	     */
	    reset_state();
	    if (out) {
		fprintf(out, "250 2.3.0 Reset state\r\n");
		fflush(out);
	    } else {
		DEBUG(DBG_REMOTE_LO, "Reset state\n");
	    }
	    break;

	case NOOP_CMD:
	    if (out) {
		fprintf(out, "250 2.3.0 Okay\r\n");
		fflush(out);
	    } else {
		DEBUG(DBG_REMOTE_LO, "NOOP Okay\n");
	    }
	    break;

	case VERB_CMD:			/* XXX hack: "yes" just means debug = 1 */
	case DEBUG_CMD: {
	    int temp = 0;		/* new debug level */

	    /*
	     * XXX ideally for "VERB on" we should always and only send back
	     * compliant replies, e.g. sort of as we currently do in
	     * decode_mail_options()...  Doing that universally though,
	     * especially in places where it might be really useful,
	     * e.g. invalid_operand_warning(), would require pushing useful
	     * verbose messages onto a "stack" of strings that can be spit out
	     * prior to the real response, and using the same response and
	     * enhanced status codes.
	     */
	    /* XXX this really should be response-rate limited. */
	    if (smtp_sess_deny) {
		send_session_denied_reply(out, smtp_cmd_list[smtp_cmd_id].name, data);
		break;
	    }
	    if (smtp_allow_debug) {	/* XXX maybe we shouldn't allow VERB to be disabled? */
		strip_rfc822_comments(data);
		strip_rfc822_whitespace(data);
		if (*data == ' ') {
		    data++;
		}
		if (*data) {
		    if (smtp_cmd_id == DEBUG_CMD) {
			char *errbuf = NULL;

			temp = c_atol(data, &errbuf);
			if (errbuf) {
			    if (out) {
				sleep(1);
				dprintf(out, "500-5.5.4 '%q': bad number:\r\n", data);
				fflush(out);
				sleep(smtp_error_delay > 0 ? smtp_error_delay : 2);
				dprintf(out, "500 5.5.4 %v\r\n", errbuf);
				fflush(out);
			    } else {
				DEBUG2(DBG_REMOTE_LO, "'%q': bad number: %s\n", data, errbuf);
			    }
			    break;
			}
		    } else {
			if (EQIC(data, "on")) {
			    temp = 1;
			} else if (EQIC(data, "off")) {
			    temp = 0;
			} else {
			    if (out) {
				sleep(1);
				dprintf(out, "500-5.5.4 '%q': invalid parameter,\r\n", data);
				fflush(out);
				sleep(smtp_error_delay > 0 ? smtp_error_delay : 2);
				fprintf(out, "500 5.5.4 Usage: VERB on|off\r\n");
				fflush(out);
			    } else {
				DEBUG1(DBG_REMOTE_LO, "'%q': invalid parameter, use 'VERB on|off'\n", data);
			    }
			    break;
			}
		    }
		} else {
		    if (smtp_cmd_id == DEBUG_CMD) {
			temp = 1;
		    } else {
			temp = ! debug;
		    }
		}
		if (out) {
		    if (temp == 0) {
			fprintf(out, "250 2.3.0 %s output disabled\r\n", (smtp_cmd_id == DEBUG_CMD) ? "Debugging" : "Verbose");
		    } else {
			DEBUG1(DBG_REMOTE_LO, " output grabbed by SMTP\r\n", (smtp_cmd_id == DEBUG_CMD) ? "Debugging" : "Verbose");
			if (smtp_cmd_id == DEBUG_CMD) {
			    fprintf(out, "250 2.3.0 Debugging level: %d\r\n", temp);
			} else {
			    fprintf(out, "250 2.3.0 Verbose output enabled\r\n");
			}
		    }
		}
		fflush(out);
		debug = temp;
		errfile = out;
		write_log(WRITE_LOG_SYS, "remote %s: '%q' debugging at level %d %s to %s%s%s%s%s%s%s%s%s",
			  smtp_cmd_list[smtp_cmd_id].name,
			  data,
			  temp,
			  (errfile == out) ? "sent" :
			  (debug == temp) ? "refused" : "disabled",
			  ident_sender ? ident_sender : "",
			  ident_sender ? "@" : "",
			  sender_host ? sender_host : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
			  sender_host_addr ? "[" : "",
			  sender_host_addr ? sender_host_addr : "",
			  sender_host_addr ? "]" : "");
		break;
	    } else {
		if (out) {
		    sleep(1);
		    fprintf(out, "500-5.7.0 I hear you knocking,\r\n");
		    fflush(out);
		    sleep(smtp_error_delay > 0 ? smtp_error_delay : 2);
		    fprintf(out, "500 5.7.0 but you can't come in!\r\n");
		    fflush(out);
		} else {
		    DEBUG(DBG_REMOTE_LO, "DEBUG command is not available.\n");
		}
	    }
	    break;
	}
	case HELP_CMD:
	    /* XXX should this be response-rate limited? */
	    write_log(WRITE_LOG_SYS, "remote %s: '%q' requested by %s%s%s%s%s%s%s%s%s",
		      smtp_cmd_list[smtp_cmd_id].name,
		      data,
		      ident_sender ? ident_sender : "",
		      ident_sender ? "@" : "",
		      sender_host ? sender_host : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
		      sender_host_addr ? "[" : "",
		      sender_host_addr ? sender_host_addr : "",
		      sender_host_addr ? "]" : "");
	    for (i = 0; help_msg[i]; i++) {
		fprintf(out ? out : errfile, "%s\r\n", help_msg[i]);
	    }
	    fflush(out ? out : errfile);
	    break;

	case EOF_CMD:
	    smtp_reset_signals();
	    if (out) {			/* XXX can this hang? */
		dprintf(out, "421 4.3.0 %q Lost input channel\r\n", primary_name);
		fflush(out);
	    }
	    write_log(WRITE_LOG_SYS, "%s unexpectedly from %s%s%s%s%s%s%s%s%s%s",
		      out ? "lost connection" : "got EOF",
		      (sender_host || sender_host_addr) ? "" : "stdin on ",
		      ident_sender ? ident_sender : "",
		      ident_sender ? "@" : "",
		      sender_host ? sender_host : (sender_host_addr ? "" : "localhost"),
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
		      sender_host_addr ? "[" : "",
		      sender_host_addr ? sender_host_addr : "",
		      sender_host_addr ? "]" : "");
	    /* drop our end of the connection now too... */
	    files[file_i] = NULL;
	    errfile = save_errfile;
	    debug = save_debug;

	    return files;

	case BOGUS_CMD:
	case TOOLONG_CMD:
	    smtp_reset_signals();
	    if (out) {
		sleep((smtp_error_delay > 2) ? (smtp_error_delay / 2) : 2);
		fprintf(out, "521-5.5.1 SMTP command %s.\r\n",
			smtp_cmd_id == BOGUS_CMD ? "contained invalid characters"
						 : "was too long");
		fflush(out);
		sleep(smtp_error_delay > 0 ? smtp_error_delay : 2);
		fprintf(out, "521 5.5.1 All attempts to abuse this server are logged.\r\n");
		fflush(out);
	    }
	    write_log(WRITE_LOG_SYS, "remote ERROR: %s from %s%s%s%s%s%s%s%s%s%s",
		      smtp_cmd_id == BOGUS_CMD ? "invalid characters" : "command too long",
		      (sender_host || sender_host_addr) ? "" : "stdin on ",
		      ident_sender ? ident_sender : "",
		      ident_sender ? "@" : "",
		      sender_host ? sender_host : (sender_host_addr ? "" : "localhost"),
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
		      sender_host_addr ? "[" : "",
		      sender_host_addr ? sender_host_addr : "",
		      sender_host_addr ? "]" : "");
	    /* now we had better drop the connection now... */
	    files[file_i] = NULL;
	    errfile = save_errfile;
	    debug = save_debug;
	    exitvalue = EX_PROTOCOL;

	    return files;

	case OTHER_CMD: {
	    static int logged_unknown = FALSE;

	    if (out) {
		sleep(1);
		fprintf(out, "500-5.5.1 Command unrecognized.\r\n");
		fflush(out);
		sleep(smtp_error_delay > 0 ? smtp_error_delay : 2);
		fprintf(out, "500 5.5.1 Try 'help' for assistance.\r\n");
		fflush(out);
	    }
	    if (!logged_unknown) {
		write_log(WRITE_LOG_SYS, "remote %q: unknown command from %s%s%s%s%s%s%s%s%s",
			  data,
			  ident_sender ? ident_sender : "",
			  ident_sender ? "@" : "",
			  sender_host ? sender_host : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
			  sender_host_addr ? "[" : "",
			  sender_host_addr ? sender_host_addr : "",
			  sender_host_addr ? "]" : "");
	    }
	    logged_unknown = TRUE;
	    exitvalue = EX_PROTOCOL;
	    break;
	}
	default:
	    non_compliant_reply(out, smtp_cmd_id, TRUE, 521, "5.3.0", "Internal error. Connection closing now...");
	    write_log(WRITE_LOG_SYS, "internal error, unexpected value %d from read_smtp_command(), while connected to %s%s%s%s%s%s%s%s%s",
		      smtp_cmd_id,
		      ident_sender ? ident_sender : "",
		      ident_sender ? "@" : "",
		      sender_host ? sender_host : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
		      sender_host_addr ? "[" : "",
		      sender_host_addr ? sender_host_addr : "",
		      sender_host_addr ? "]" : "");
	    panic(EX_SOFTWARE, "receive_smtp(): read_smtp_command() gave impossible result from parsing: '%q'", data);
	    /* NOTREACHED */
	}
    }
    /* NOTREACHED */
}

static char *
check_addr_operand(out, smtp_cmd_id, restp)
    FILE *out;
    int smtp_cmd_id;
    char **restp;
{
    char *orig_data;
    char *ppaddr;
    char *errstr;

    assert(restp);

    orig_data = COPY_STRING(data);
    strip_rfc822_comments(data);
    if (strcmp(data, orig_data) != 0) {
	invalid_operand_warning(smtp_cmd_id, orig_data, "contained unwanted comments");
    }
    xfree(orig_data);

    if (isspace(*data)) {
	invalid_operand_warning(smtp_cmd_id, data, "unnecessary whitespace before address operand");
	while (isspace(*data)) {
	    data++;
	}
    }
    if (*data != '<') {
	invalid_operand_warning(smtp_cmd_id, data, "syntax error in parameter: opening angle bracket missing");
    }

    /*
     * Check that the address is at least parsable....
     *
     * Note that using preparse_address_1() here will implicitly strip
     * unnecessary whitespace from the address portion, and indeed this will
     * even allow display names to be given provided the whole parameter is not
     * also wrapped in angle brackets (which would create invalid nesting).
     *
     * RFC 2821 says quite clearly in its syntax diagrams that none of the RFC
     * 822 whitespace is allowed in SMTP address parameters, but in the spirit
     * of being liberal in what we accept and all that we won't even complain
     * if there's unnecessary whitespace within the address itself.
     *
     * Note also that preparse_address_1() will likely suck up some or all of
     * any ESMTP options into the domain token if angle brackets are not
     * provided to explicitly wrap the address.  The result will be an eventual
     * failure from parse_address() since one space will still remain at every
     * position where any whitespace was given prior to, and between, options.
     */
    ppaddr = preparse_address_1(data, &errstr, restp);
    if (!ppaddr) {
	/* XXX maybe we should/could just use non_compliant_reply() here? */
	if (out) {
	    dprintf(out, "501-5.5.2 '%q' parameter rejected,\r\n", data);
	    fflush(out);
	    sleep(1);
	    dprintf(out, "501-5.5.2 address parse error:\r\n");
	    dprintf(out, "501-5.5.2\r\n");
	    fflush(out);
	    send_smtp_msg(out, 501, "5.5.2    ", TRUE, errstr);
	}
	write_log(WRITE_LOG_SYS, "remote %s: '%q' address parse error from %s%s%s%s%s%s%s%s%s: %s",
		  smtp_cmd_list[smtp_cmd_id].name,
		  data,
		  ident_sender ? ident_sender : "",
		  ident_sender ? "@" : "",
		  sender_host ? sender_host : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
		  sender_host_addr ? "[" : "",
		  sender_host_addr ? sender_host_addr : "",
		  sender_host_addr ? "]" : "",
		  errstr);
	exitvalue = EX_NOUSER;
	return NULL;
    }

    /*
     * If anything remains after the address operand then check for ESMTP
     * options and warn if not using strict syntax, or if 
     * 
     * note: *restp will always be non-NULL if preparse_address_1() succeeds,
     * but it might point at the final NUL byte.
     */
    if (**restp) {
	if (**restp == ' ') {
	    /* skip the required space so we don't complain about it */
	    (*restp)++;		/* C syntax warning: don't try *restp++ here! */
	    if (*restp) {
		/*
		 * clean up the options and note any unnecessary whitespace
		 *
		 * WARNING:  this isn't strictly the correct way to do this
		 * since ESMTP options are not really addresses....
		 *
		 * This isn't strictly necessary either since the options
		 * parsers will skip over arbitrary amounts of consecutive
		 * whitespace.  We do it here just to be pedantic about
		 * reporting non-compliant clients.
		 */
		orig_data = COPY_STRING(*restp);
		strip_rfc822_whitespace(*restp);
		if (strcmp(*restp, orig_data) != 0) {
		    invalid_operand_warning(smtp_cmd_id, orig_data, "ESMTP options contained unnecessary whitespace");
		}
		xfree(orig_data);
	    }
	    /* see if anything is left after all that */
	    if (!*restp) {
		if (*sender_proto == 'e') {
		    invalid_operand_warning(smtp_cmd_id, orig_data, "ESMTP options expected after a trailing space!");
		} else {
		    invalid_operand_warning(smtp_cmd_id, data, "unnecessary trailing whitespace after address operand");
		}
	    }
	} else if (*sender_proto == 'e') {
	    invalid_operand_warning(smtp_cmd_id, data, "ESMTP options may have been given without the required space separator");
	}
	/* do this last so we don't error out with just trailing whitespace */
	if (*restp && *sender_proto != 'e') {
	    non_compliant_reply(out, smtp_cmd_id, TRUE, 501, "5.5.4", "syntax error in parameters: ESMTP options may have been given without using EHLO");
	    exitvalue = EX_DATAERR;
	    return NULL;
	}
    }

    DEBUG4(DBG_REMOTE_MID, "check_addr_operand(): returning: '%q'%s%q%s\n",
	   ppaddr,
	   **restp ? ", with ESMTP options: '" : "",
	   *restp,
	   **restp ? "'" : "");

    return ppaddr;
}

/*
 * Print a message on the FILE stream 'out' such that
 * it conforms to the SMTP status reply format.
 *
 * Note it may already contain embedded newlines, and may optionally contain a
 * trailing newline.
 *
 * Uses an opportunistic, non-rigid, word-wrap algorithm....
 *
 * XXX should we have an auto-wrap flag to control word-wrap?
 */
static void
send_smtp_msg(out, status, esc, endmsg, str)
    FILE *out;
    int status;			/* SMTP reply status code */
    char *esc;			/* enhanced status code (RFC 3463 via RFC 2034) (and/or white space indent) */
    int endmsg;			/* is this string the end? */
    const char *str;		/* string to print */
{
    size_t cur_len;
    char c;

    /*
     * XXX there should be more debugging code in here to validate that the
     * status code conforms to RFC 821 and maybe even that the ESC is proper
     * too.  For example the first two digits are not allowed to be greater
     * than 5, and so the maximum value is 559.  Even the last digit should
     * probably be restricted from 1-5, though RFC 821 is silent on the proper
     * extent of its range. Eg:
     *
     *    assert(status <= 559);
     *    assert(strlen(esc) <= 74);
     *	  if ((status % 10) > 5) DEBUG();
     */
    while (*str) {
	char line[81];

	(void) sprintf(line, "%d %s%s",
		       status,
		       (esc && *esc) ? esc : "",
		       (esc && *esc) ? " " : "");
	cur_len = strlen(line);
	/*
	 * XXX FIXME!!!! we should probably be eliminating/quoting unwelcome
	 * characters too!  (which ones should we watch for?  How to quote?)
	 */
	while ((c = *str)) {
	    str++;		/* must increment only after successful test! */
	    if (c == '\r') {
		continue;			/* ignore all <CR>s */
	    }
	    if (c == ' ' || c == '\t') {
		const char *n = str - 1;	/* start at c's position */

		while (*n && (*n == ' ' || *n == '\t')) {
		    n++;			/* find start of next word */
		}
		/*
		 * if the total length of the current whitespace and the next
		 * word is past the end of a line then...
		 *
		 * note standards-conforming strcspn() implementations include
		 * the trailing NUL character in the terminator list and thus
		 * also match the end of a string.
		 */
		if ((strcspn(n, " \t\n") + cur_len + (n - str)) >= 77) {
		    str = n;			/* drop the current whitespace */
		    break;			/* and wrap now. */
		}
	    }
	    if (c == '\n') {
		break;				/* always wrap on any newline */
	    }
	    line[cur_len++] = c;
	    if (cur_len >= (sizeof(line) - 2)) {
		line[cur_len] = '\\';
		break;				/* also break if EOB */
	    }
	}
	line[cur_len] = '\0';			/* terminate the line */
	line[3] = (*str || !endmsg) ? '-' : ' '; /* will we continue? */
	/*
	 * rate limit multiple lines of output if status represents an error
	 *
	 * Note we only flush first if sleeping to ensure any previous line is
	 * seen before the delay occurs.
	 */
	if (status > 399) {
	    fflush(out);
	    sleep((line[3] == ' ') ? (smtp_error_delay > 0 ? smtp_error_delay : 2) : 1);
	}
	fprintf(out, "%s\r\n", line); /* XXX use dprintf("%v")? */
    }
    fflush(out);

    return;
}

/*
 *	do_greeting - handle the SMTP greeting command
 *
 * It is generally agreed that "Hello" is an appropriate greeting because
 * if you entered a room and said "Goodbye," it could confuse a lot of
 * people.
 *              -- Dolph Sharp, "I'm O.K., You're Not So Hot"
 */
static void
do_greeting(out, peer, fromaddr)
    FILE *out;			/* output to client, if not batch mode */
    void *peer;			/* result of accept(), or NULL if SMTP-on-stdin */
    void *fromaddr;
{
    char *orig_data;
    int ehlo_p = (*sender_proto == 'e'); /* using ESMTP? */
    char *msg = NULL;			/* temp message string pointer */

    reset_state();		/* any greeting implies a RSET */

    /* and do zap the HELO address of course... */
    if (sender_host) {
	xfree(sender_host);
	sender_host = NULL;
    }
    sender_host_invalid = NULL;
    sender_host_really_invalid = NULL;

    if (*data && *data != ' ') {
	if (out) {
	    non_compliant_reply(out, ehlo_p ? EHLO_CMD : HELO_CMD, TRUE, 501, "", "missing space before hostname");
	    exitvalue = EX_NOHOST;
	    return;
	} else {
	    invalid_operand_warning(ehlo_p ? EHLO_CMD : HELO_CMD, data, "missing space before hostname");
	}
    } else if (*data == ' ') {
	data++;				/* skip the space so we don't complain about it */
    }

    /*
     * don't let them get anywhere at all if we've already decided we don't
     * want them talking to us.
     */
    if (smtp_sess_deny && out) {
	deny_greeting(out, ehlo_p, data);
	exitvalue = EX_UNAVAILABLE;

	return;
    }

    orig_data = COPY_STRING(data);
    strip_rfc822_comments(data);
    if (strcmp(data, orig_data) != 0) {
	invalid_operand_warning(ehlo_p ? EHLO_CMD : HELO_CMD, orig_data, "contained unwanted comments");
    }
    xfree(orig_data);

    orig_data = COPY_STRING(data);
    strip_rfc822_whitespace(data);
    if (strcmp(data, orig_data) != 0) {
	invalid_operand_warning(ehlo_p ? EHLO_CMD : HELO_CMD, orig_data, "contained unnecessary whitespace");
    }
    xfree(orig_data);

    if (out && !*data) {
	non_compliant_reply(out, ehlo_p ? EHLO_CMD : HELO_CMD, TRUE, 501, "", "SMTP greeting requires a hostname (or IP address literal) as operand");
	exitvalue = EX_NOHOST;
	return;
    }

    if (out && peer) {
	int fatal;
	char *errstr = NULL;		/* hold error message */

	fatal = FALSE;
	if (! (sender_host = verify_host(data, fromaddr, &errstr, &fatal))) {
	    char *err1;

	    sleep((fatal && smtp_error_delay > 2) ? (smtp_error_delay / 2) : 2);
	    dprintf(out, "%d-%s error while validating '%s' host name '%q'.\r\n",
		    fatal ? 501 : 401,
		    fatal ? "fatal" : "temporary",
		    ehlo_p ? "EHLO" : "HELO",
		    data);
	    fflush(out);
	    sleep(1);
	    err1 = xprintf("connection %s from %s%s%s%s%s%s%s",
			   fatal ? "rejected" : "deferred",
			   ident_sender ? ident_sender : "",
			   ident_sender ? "@" : "",
			   sender_host_really ? sender_host_really : "(UNKNOWN)",
			   sender_host_addr ? " remote address [" : "",
			   sender_host_addr ? sender_host_addr : "",
			   sender_host_addr ? "]." : ".",
			   errstr ? "\nReason given was:\n\n" : ".\n");
	    /* no ESC -- this is the greeting... */
	    send_smtp_msg(out, (fatal ? 501 : 401), "", (errstr ? FALSE : TRUE), err1);
	    xfree(err1);
	    if (errstr) {
		/* setting the ESC to "   " is a hack to get indentation! */
		send_smtp_msg(out, (fatal ? 501 : 401), "    ", TRUE, (const char *) errstr);
	    }
	}
	/*
	 * don't log identical messages again on HELO after EHLO
	 *
	 * note this is only intended to prevent multiple logging in the normal
	 * case where a client-SMTP (e.g. another Smail) will always try HELO
	 * after a rejected EHLO regardless of the error code from the EHLO.
	 */
	if (ehlo_errstr && errstr && EQ(ehlo_errstr, errstr)) {
	    DEBUG1(DBG_REMOTE_LO, "not logging duplicate bad sender_host: %s\n", errstr);
	    errstr = NULL;
	}
	if (!ehlo_errstr && (!sender_host || errstr)) { /* log "ignored" errors as warnings */
	    write_log(WRITE_LOG_SYS, "remote %s: %s operand: '%q': from %s%s%s%s%s%s%s%s%s%s%v.",
		      ehlo_p ? "EHLO" : "HELO",
		      sender_host ? "warning: questionable" : "rejected: invalid",
		      data,
		      ident_sender ? ident_sender : "",
		      ident_sender ? "@" : "",
		      (sender_host && !EQIC(sender_host, data)) ? sender_host : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
		      sender_host_addr ? "[" : "",
		      sender_host_addr ? sender_host_addr : "",
		      sender_host_addr ? "]" : "",
		      errstr ? ": " : "",
		      errstr ? errstr : "");
	}
	/*
	 * if EHLO then record errstr in case client turns around and says HELO
	 * the same way and gets the same error again....
	 */
	if (ehlo_p && !ehlo_errstr && errstr) {
	    ehlo_errstr = COPY_STRING(errstr);
	}
	if (!sender_host) {
	    exitvalue = EX_NOHOST;
	    return;			/* broken DNS or forger */
	}
    } else if (!out) {
	DEBUG1(DBG_REMOTE_LO, "do_greeting(): doing batch SMTP, so not verifying sender host %q.\n", data);
	sender_host = (data[0] != '\0') ? xprintf("%q", data) : COPY_STRING("(unknown.and.not-verified)");
    } else {
	DEBUG1(DBG_REMOTE_LO, "do_greeting(): doing interactive SMTP on stdin, so not verifying sender host %q.\n", data);
	sender_host = (data[0] != '\0') ? xprintf("%q", data) : COPY_STRING("(stdin)");
    }
#ifdef HAVE_BSD_NETWORKING
    if (sender_host_addr &&		/* implies '&& out' */
	!match_ip(sender_host_addr, smtp_hello_dnsbl_except, ':', ';', FALSE, (char **) NULL) &&
	match_dnsbl(sender_host, smtp_hello_dnsbl_domains, "smtp_hello_dnsbl_domains", &smtp_dnsbl_match, &smtp_dnsbl_addr, &msg)) {
	smtp_sess_deny = TRUE;
	smtp_sess_deny_reason = SMTP_SESS_DENY_DNSBL;
	smtp_sess_deny_msg = msg;
	deny_greeting(out, ehlo_p, sender_host);
	exitvalue = EX_NOHOST;

	return;
    }
#endif /* HAVE_BSD_NETWORKING */
    /*
     * XXX maybe we should be doing rDNS-related checks here, or in some other
     * separate function, not in verify_host().
     */
    if (out) {
	if (accepted_msg_size == 0) {
		/* no ESC -- still in greeting... */
		dprintf(out, "452 %q system resources low.  Please try again much later!\r\n", primary_name);
		fflush(out);
		write_log(WRITE_LOG_SYS, "remote DEFERRED: not enough spool space: from %s%s%s%s%s%s%s%s%s",
		      ident_sender ? ident_sender : "",
		      ident_sender ? "@" : "",
		      sender_host ? sender_host : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
		      sender_host_addr ? "[" : "",
		      sender_host_addr ? sender_host_addr : "",
		      sender_host_addr ? "]" : "");
		unlink_spool();
		exit(EX_TEMPFAIL);
		/* NOTREACHED */
	}
	/*
	 * RFC 821 says "The receiver-SMTP identifies itself to the sender-SMTP
	 * in [...] the response to this command [HELO]."
	 */
	dprintf(out, "250%s%q Hello %v (%s%s%s%s%s%s%s%s%s%s)%s\r\n",
		ehlo_p ? "-" : " ",
		primary_name,
		data,
		ident_sender ? ident_sender : "",
		ident_sender ? "@" : "",
		(sender_host && !EQIC(sender_host, data)) ? sender_host : "",
		(sender_host && !EQIC(sender_host, data)) ? "(" : "",
		(sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
		(sender_host && !EQIC(sender_host, data)) ? ")" : "",
		((sender_host && !EQIC(sender_host, data)) ||
		 (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really))) ? " " : "",
		sender_host_addr ? "from address [" : "",
		sender_host_addr ? sender_host_addr : "",
		sender_host_addr ? "]" : "",
		ehlo_p ? ", here is what we support:" : ".");
	if (ehlo_p) {
	    fprintf(out, "250-ENHANCEDSTATUSCODES\r\n");
	    if (accepted_msg_size >= 0) {
		fprintf(out, "250-SIZE %ld\r\n", (unsigned long int) accepted_msg_size);
	    } else {
		fprintf(out, "250-SIZE\r\n");	/* this is probably pointless... */
	    }
#ifdef CLAIM_BROKEN_8BITMIME_SUPPORT
	    /*
	     * WARNING: don't claim 8BITMIME support unless we're willing to
	     * validate the content as either being properly MIME encoded, or
	     * being just 7-bit ASCII.  Also if we accept an un-encoded
	     * 8BITMIME message for relay and later find the destination
	     * doesn't offer 8BITMIME then we'd either have to bounce the
	     * message or re-encode it as a MIME message.
	     */
	    fprintf(out, "250-8BITMIME\r\n");
#endif
#ifdef HAVE_ESMTP_PIPELINING		/* defined in iobpeek.h if support is possible */
	    fprintf(out, "250-PIPELINING\r\n");
#endif
#ifndef NO_SMTP_EXPN
	    if (smtp_allow_expn) {
		fprintf(out, "250-EXPN\r\n");
	    }
#endif
	    /*
	     * XXX TODO: include "250-DSN\r\n" once we have implemented the
	     * minimum additional MAIL & RCPT options required by RFC 3461.
	     */
	    fprintf(out, "250-VRFY\r\n");
	    fprintf(out, "250 HELP\r\n");
	}
	(void) fflush(out);
    }

    return;
}

/*
 * deny_greeting - common code to reject session at greeting
 *
 * Do not include an ENHANCEDSTATUSCODES value as we are still in the greeting
 * phase and ESC is not allowed there.
 *
 * Note: uses globals smtp_sess_deny_reason and smtp_sess_deny_msg, and
 * possibly also smtp_dnsbl_match and smtp_dnsbl_addr as well.
 */
static void
deny_greeting(out, ehlo_p, host)
    FILE *out;
    int ehlo_p;
    char *host;
{
    char *log_reason = NULL;
    char *tmpmsg = NULL;

    /*
     * NOTE: we do not include enhanced status codes (ESC) (RFC 3463) in the
     * response to HELO/EHLO.
     */
    tmpmsg = xprintf("You are not permitted to send mail from %q%s%s%s%s%s%s.\n",
		     host,
		     (sender_host_really && !EQIC(host ? host : "", sender_host_really)) ? "(" : "",
		     (sender_host_really && !EQIC(host ? host : "", sender_host_really)) ? sender_host_really : "",
		     (sender_host_really && !EQIC(host ? host : "", sender_host_really)) ? ")" : "",
		     sender_host_addr ? "[" : "",
		     sender_host_addr ? sender_host_addr : "",
		     sender_host_addr ? "]" : "");
    send_smtp_msg(out, 550, "", FALSE, tmpmsg);
    xfree(tmpmsg);
    tmpmsg = NULL;
    switch (smtp_sess_deny_reason) {
    case SMTP_SESS_DENY_DNSBL:
	tmpmsg = xprintf("All SMTP connections have been blocked from that address because it matches in the DNS Black List:\n\n\t%s\tA\t%s\n",
			 smtp_dnsbl_match, smtp_dnsbl_addr);
	send_smtp_msg(out, 550, "", FALSE, tmpmsg);
	xfree(tmpmsg);
	tmpmsg = NULL;
	break;
    case SMTP_SESS_DENY_REJECT:
    case SMTP_SESS_DENY_REJECT_PTR:
	tmpmsg = xprintf("All SMTP connections from your system have been explicitly blocked by the postmaster at %q.\n",
			 primary_name);
	send_smtp_msg(out, 550, "", FALSE, tmpmsg);
	xfree(tmpmsg);
	tmpmsg = NULL;
	break;
    }
    if (smtp_sess_deny_msg) {
	send_smtp_msg(out, 550, "", FALSE,
		      "\nPlease note the following additional important information:\n\n");
	/* setting the ESC to " " is a hack to get indentation! */
	send_smtp_msg(out, 550, " ", FALSE, smtp_sess_deny_msg);
    }
    /* this won't be used except in an SMTP reply... */
    tmpmsg = xprintf("\n\
Please forward this message in its entirety to your own LOCAL <postmaster>\
 and ask them to help you to resolve this problem.\n\n\
Postmaster@[%s]:  In order to discover the reason for this reject and to work\
 to resolve the issue, please contact <postmaster@%q> (via an unblocked server\
 of course!).  You must include your IP address, given above, in order to\
 receive any useful help.  The best way do do this would be to forward this\
 entire status response.",
		     sender_host_addr ? sender_host_addr : "UNKNOWN-IP",
		     primary_name);
    send_smtp_msg(out, 550, "", TRUE, tmpmsg);
    xfree(tmpmsg);
    tmpmsg = NULL;

    switch (smtp_sess_deny_reason) {
    case SMTP_SESS_DENY_DNSBL:
	log_reason = xprintf("matched DNSBL: %s [%s]", smtp_dnsbl_match, smtp_dnsbl_addr);
	break;
    case SMTP_SESS_DENY_REJECT:
	log_reason = xprintf("matched in smtp_reject_hosts");
	break;
    case SMTP_SESS_DENY_REJECT_PTR:
	log_reason = xprintf("matched in smtp_host_reject_hostnames%s%v",
			     smtp_sess_deny_msg ? ": " : "",
			     smtp_sess_deny_msg ? smtp_sess_deny_msg : "");
	break;
    }
    /* XXX assert(log_reason)? */
    /*
     * don't log messages identical to the first one again
     *
     * note this is only intended to prevent multiple logging in the normal
     * case where a client-SMTP (e.g. another Smail) will always try HELO after
     * a rejected EHLO regardless of the error code from the EHLO.
     */
    if (first_smtp_sess_deny_reason && EQ(first_smtp_sess_deny_reason, log_reason)) {
	DEBUG1(DBG_REMOTE_LO, "not logging duplicate deny reason: %s\n", log_reason);
	xfree(log_reason);
	return;
    }
    write_log(WRITE_LOG_SYS, "remote %s: refusing SMTP connection from %s%s%q%s%s%s%s%s%s: %s.",
	      ehlo_p ? "EHLO" : "HELO",
	      ident_sender ? ident_sender : "",
	      ident_sender ? "@" : "",
	      host,
	      (sender_host_really && !EQIC(host ? host : "", sender_host_really)) ? "(" : "",
	      (sender_host_really && !EQIC(host ? host : "", sender_host_really)) ? sender_host_really : "",
	      (sender_host_really && !EQIC(host ? host : "", sender_host_really)) ? ")" : "",
	      sender_host_addr ? "[" : "",
	      sender_host_addr ? sender_host_addr : "",
	      sender_host_addr ? "]" : "",
	      log_reason);
    if (!first_smtp_sess_deny_reason) {
	first_smtp_sess_deny_reason = COPY_STRING(log_reason);
    }
    xfree(log_reason);

    return;
}

static void
send_session_denied_reply(out, cmd, param)
    FILE *out;
    char *cmd;
    char *param;
{
    if (out) {
	sleep((smtp_error_delay > 2) ? (smtp_error_delay / 2) : 2);
	fprintf(out, "550-5.5.1 You%s%s%s%s%s are not permitted to issue any more commands%s%s%s.\r\n",
		(sender_host_really || ident_sender) ? " (i.e. " : "",
		ident_sender ? ident_sender : "",
		ident_sender ? "@" : "",
		sender_host_really ? sender_host_really : "",
		(sender_host_really || ident_sender) ? ")" : "",
		sender_host_addr ? " from [" : "",
		sender_host_addr ? sender_host_addr : "",
		sender_host_addr ? "]" : "");
	fflush(out);
	sleep(smtp_error_delay > 0 ? smtp_error_delay : 2);
	/* if have we seen HELO/EHLO then this is a protocol violation */
	if (sender_host) {
	    fprintf(out, "550 5.5.1 If you see this message then your mailer may be violating the SMTP protocol.\r\n");
	} else {
	    fprintf(out, "550 5.5.1 Please disconnect now.\r\n");
	}
	fflush(out);
    } else {
	DEBUG4(DBG_REMOTE_LO, "Refusing command '%v%s%q%s'.\n",
	       cmd,
	       *param ? " '" : "",
	       *param ? param : "",
	       *param ? "'" : "");
    }
    write_log(WRITE_LOG_SYS, "remote %v:%s%q%s refusing unwanted command from %s%s%s%s%s%s%s%s%s",
	      cmd,
	      *param ? " '" : "",
	      *param ? param : "",
	      *param ? "'" : "",
	      ident_sender ? ident_sender : "",
	      ident_sender ? "@" : "",
	      sender_host ? sender_host : "",
	      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
	      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
	      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
	      sender_host_addr ? "[" : "",
	      sender_host_addr ? sender_host_addr : "",
	      sender_host_addr ? "]" : "");
    return;
}

static void
non_compliant_reply(out, smtp_cmd_id, logit, ecode, estatus, errtxt)
    FILE *out;
    int smtp_cmd_id;
    int logit;
    int ecode;
    char *estatus;		/* enhanced status code (RFC 3463) */
    char *errtxt;
{
    char *in_estatus;

#ifdef LOG_SMTP_NON_COMPLIANCE
    if (logit) {
	write_log(WRITE_LOG_SYS, "remote %s: sent %d-%s: '%s' to %s%s%s%s%s%s%s%s%s",
		  smtp_cmd_list[smtp_cmd_id].name,
		  ecode,
		  (estatus && *estatus) ? estatus : "(NO EnhStCode)",
		  errtxt,
		  ident_sender ? ident_sender : "",
		  ident_sender ? "@" : "",
		  sender_host ? sender_host : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
		  sender_host_addr ? "[" : "",
		  sender_host_addr ? sender_host_addr : "",
		  sender_host_addr ? "]" : "");
    }
#else
    if (logit) {
	DEBUG5(DBG_REMOTE_LO,
	       "%d-%s protocol error with '%s%v' command:\n\t%s\n",
	       ecode, estatus, smtp_cmd_list[smtp_cmd_id].name, data, errtxt);
    }
#endif
    if (!out) {
	return;
    }
    dprintf(out, "%d-%s Fatal protocol error with your '%s%v' command:\r\n%d-%s \r\n",
	    ecode, estatus, smtp_cmd_list[smtp_cmd_id].name, data,
	    ecode, estatus);
    in_estatus = xprintf("%s    ", estatus);
    send_smtp_msg(out, ecode, in_estatus, FALSE, errtxt);
    xfree(in_estatus);
    send_smtp_msg(out, ecode, estatus, TRUE, non_compliant_client_msg);
    return;
}

/*
 * invalid_operand_warning - possibly log something about misbehaviours of the
 *			     client
 *
 * XXX it might be nice to also write this out to the client if VERB mode is
 * enabled.
 */
static void
invalid_operand_warning(smtp_cmd_id, operand, comment)
    int smtp_cmd_id;			/* (SMTP command  XXX should maybe add e_smtp_cmds) */
    char *operand;
    char *comment;
{
#ifdef LOG_SMTP_INVALID_OPERAND_WARNING
    write_log(WRITE_LOG_SYS, "remote %v: warning: '%q' operand syntax is not strictly valid (%s), from %s%s%s%s%s%s%s%s%s",
	      smtp_cmd_list[smtp_cmd_id].name,
	      operand,
	      comment,
	      ident_sender ? ident_sender : "",
	      ident_sender ? "@" : "",
	      sender_host ? sender_host : "",
	      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
	      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
	      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
	      sender_host_addr ? "[" : "",
	      sender_host_addr ? sender_host_addr : "",
	      sender_host_addr ? "]" : "");
#else
    DEBUG3(DBG_REMOTE_LO, "invalid_operand_warning(): %v: '%q' operand syntax is not strictly valid: %s",
	   smtp_cmd_list[smtp_cmd_id].name,
	   operand,
	   comment);
#endif
    return;
}

static void
reset_state()
{
    struct addr *cur;
    struct addr *next;

    for (cur = recipients; cur; cur = next) {
	next = cur->succ;
	xfree(cur->in_addr);
	if (cur->work_addr) {
	    /* work_addr is defined only for interactive smtp */
	    xfree(cur->work_addr);
	}
	xfree((char *)cur);
    }
    recipients = NULL;
    num_smtp_recipients = 0;
    sent_refused_data = FALSE;

    /* next DATA command will turn this back on until end-of-DATA (`.') */
    smtp_remove_on_timeout = FALSE;

    /*
     * FYI, do not zap sender_host_addr or any ident stuff, or any other TCP
     * connection related stuff
     */
    if (sender) {
	xfree(sender);
	sender = NULL;
    }

    /*
     * reset the main address hash table so as to not block if we see the same
     * addresses in a new "session"
     */
    reset_hit_table();
}

static enum e_smtp_cmds
read_smtp_command(fp, out)
    register FILE *fp;			/* SMTP command input stream */
    register FILE *out;			/* output, may have to be flushed */
{
    register int c;			/* current input char */
    int flushed_p = !out;		/* pretend already flushed if no 'out' */
    smtp_cmd_list_t *cp;		/* pointer into global smtp_cmd_list */
    static struct str input;		/* buffer storing recent command */
    static int inited = FALSE;		/* TRUE if input initialized */

    DEBUG(DBG_REMOTE_HI, "read_smtp_command() called.\n");
    if (! inited) {
	STR_INIT(&input);
	inited = TRUE;
    } else {
	STR_CHECK(&input);
	STR_CLEAR(&input);
    }
#ifdef DIAGNOSTIC /* #ifndef NDEBUG */
    STR_NEXT(&input, '\0');		/* start this one NUL-terminated for gdb! */
    STR_PREV(&input);
#endif
    /*
     * to support pipelining we don't flush the output buffer until we need to
     * read more input...
     */
    if (!flushed_p && IOB_MAYBE_EMPTY_P(fp)) {
	++flushed_p;
	fflush(out);
    }
    while ((c = getc(fp)) != '\n' && STR_LEN(&input) <= MAX_SMTP_CMD_LINE_LEN) {
	if (c == '\r') {
	    continue;			/* ignore <CR> */
	}
	if (c == EOF) {
	    if (errno == EINTR && got_sighup) {
		DEBUG(DBG_REMOTE_MID, "read_smtp_command() got EOF with EINTR.\n");
		/*
		 * make sure we don't loop -- i.e. getc() must be interrupted
		 * again, with SIGHUP which will turn got_sighup on again, in
		 * order to get us to go around the loop again (clearing errno
		 * might have the same effect, at least on NetBSD where a real
		 * EOF doesn't seem to change errno, but with this separate
		 * state variable we avoid any other weird interactions)
		 *
		 * Note the SIGALRM handler never returns and anything else
		 * that might interrupt getc() is best treated as EOF anyway.
		 */
		got_sighup = FALSE;
		continue;
	    }
	    DEBUG(DBG_REMOTE_MID, "read_smtp_command() returning EOF_CMD.\n");
	    return EOF_CMD;
	}
	if (!isascii(c) || c == '\0') {
	    DEBUG(DBG_REMOTE_MID, "read_smtp_command() returning BOGUS_CMD.\n");
	    return BOGUS_CMD;
	}
	STR_NEXT(&input, c);
#ifdef DIAGNOSTIC /* #ifndef NDEBUG */
	STR_NEXT(&input, '\0');		/* keep it NUL terminated for gdb! */
	STR_PREV(&input);
#endif
    }
    STR_NEXT(&input, '\0');		/* keep it NUL terminated */
    /* NOTE: don't use STR_DONE() since we re-use this buffer */

    if (STR_LEN(&input) >= MAX_SMTP_CMD_LINE_LEN) {
	write_log(WRITE_LOG_SYS, "remote WARNING: command line too long from %s%s%s%s%s%s%s%s%s!",
		  ident_sender ? ident_sender : "",
		  ident_sender ? "@" : "",
		  sender_host ? sender_host : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
		  sender_host_addr ? "[" : "",
		  sender_host_addr ? sender_host_addr : "",
		  sender_host_addr ? "]" : "");
	if (c != '\n') {
	    while ((c = getc(fp)) != '\n') {
		if (c == EOF) {
		    return TOOLONG_CMD;		/* TOOLONG_CMD will drop connection anyway */
		}
		sleep(1);			/* "Just try it buddy!" */
	    }
	}
	return TOOLONG_CMD;			/* we'll drop this connection after error */
    }

    /* WARNING: only set this pointer *after* the string has stopped growing */
    data = STR(&input);

    DEBUG3(DBG_REMOTE_MID, "read_smtp_command() got '%q' (%u bytes, max = %u).\n", data, STR_LEN(&input), input.a);

    for (cp = smtp_cmd_list; cp < ENDTABLE(smtp_cmd_list); cp++) {
	size_t cmdlen = strlen(cp->name);

	if (strncmpic(cp->name, data, cmdlen) == 0) {
	    /* point "data" at any operand(s) (skip past command) */
	    data += cmdlen;
	    DEBUG2(DBG_REMOTE_MID, "read_smtp_command() returning '%s' with operand '%q'.\n", cp->name, data);

	    return cp->cmd;
	}
    }

    DEBUG1(DBG_REMOTE_LO, "read_smtp_command() returning OTHER_CMD: '%q'.\n", data);

    return OTHER_CMD;
}

static int
decode_mail_options(options, out)
    char *options;			/* remainder of "MAIL FROM:" operand */
    FILE *out;			/* SMTP out channel */
{
    long body_size = -1;	/* body size the client claims it will send */

    while (options && *options) {
	size_t optionlen;
	char ch;		/* NUL place-holder */

	/* maybe we have an extended MAIL command */
	while (isspace((int) *options)) {
	    ++options;
	}
	optionlen = 0;
	/* find end of option name */
	while (*(options+optionlen) && !isspace((int) *(options+optionlen)) && *(options+optionlen) != '=') {
	    ++optionlen;
	}
	if (strncmpic(options, "SIZE", optionlen) == 0) {
	    char *errbuf = NULL;	/* pointer to returned error msg */

	    options += optionlen;	/* point to the parameter value */
	    if (*options != '=') {
		non_compliant_reply(out, MAIL_CMD, TRUE, 501, "5.5.2", "ESMTP SIZE= option is missing its value.");
		return -1;
	    }
	    ++options;
	    optionlen = 0;
	    body_size = 0;
	    /* find the end of the SIZE parameter */
	    while (isdigit((int) *(options+optionlen))) {
		++optionlen;
	    }
	    ch = *(options+optionlen);	/* save... */
	    *(options+optionlen) = '\0';	/* NUL terminate # */
	    body_size = c_atol(options, &errbuf);
	    *(options+optionlen) = ch;	/* ...restore */
	    if (errbuf) {
		char *msg = xprintf("Malformed ESMTP SIZE= option: bad number: %s.", errbuf);

		if (out) {
		    non_compliant_reply(out, MAIL_CMD, TRUE, 501, "5.5.4", msg);
		} else {
		    DEBUG1(DBG_REMOTE_LO, "%s\n", msg);
		}
		xfree(msg);
		return -1;
	    }
	    /* there must be a space, tab, or newline (NUL) after the last digit */
	    if (*(options+optionlen) && !isspace((int) *(options+optionlen))) {
		non_compliant_reply(out, MAIL_CMD, TRUE, 501, "5.5.4", "Invalid ESMTP SIZE parameter value.");
		return -1;
	    }
	    /*
	     * NOTE: the order of the next two 'if's is important so that we
	     * don't complain unecessarily about user sending files by e-mail.
	     *
	     * Note that normally if we have lots of free space available
	     * accepted_msg_size will be equal to max_message_size, but if not
	     * then accepted_msg_size will be less and will be equal to the
	     * amount of unreserved free space available for one incoming
	     * message.
	     */
	    if (accepted_msg_size >= 0 &&
		((body_size > accepted_msg_size &&
		  accepted_msg_size < max_message_size) ||
		 (max_message_size <= 0 &&
		  body_size > accepted_msg_size))) {
		write_log(WRITE_LOG_SYS, "remote MAIL FROM: not enough free space for SIZE=%ld %s%s%s%s%s%s%s%s%s",
			  body_size,
			  ident_sender ? ident_sender : "",
			  ident_sender ? "@" : "",
			  sender_host ? sender_host : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
			  sender_host_addr ? "[" : "",
			  sender_host_addr ? sender_host_addr : "",
			  sender_host_addr ? "]" : "");
		if (out) {
		    fprintf(out, "452-4.3.1 There is currently no room for a message of %ld bytes!\r\n", (long) body_size);
		    sleep(smtp_error_delay > 2 ? (smtp_error_delay / 2) : 2);
		    fprintf(out, "452 4.3.1 Please try again much later, thanks!\r\n");
		    fflush(out);
		}
		return -1;
	    }
	    if (accepted_msg_size >= 0 && body_size > accepted_msg_size) {
#ifndef NO_LOG_SMTP_TOO_BIG
		write_log(WRITE_LOG_SYS, "remote MAIL FROM: SIZE=%ld too big from %s%s%s%s%s%s%s%s%s",
			  body_size,
			  ident_sender ? ident_sender : "",
			  ident_sender ? "@" : "",
			  sender_host ? sender_host : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
			  sender_host_addr ? "[" : "",
			  sender_host_addr ? sender_host_addr : "",
			  sender_host_addr ? "]" : "");
#endif
		if (out) {
		    fprintf(out, "552-5.3.4 A message of %ld bytes is far too large!\r\n", (long) body_size);
		    send_smtp_msg(out, 552, "5.3.4", TRUE, no_files_by_email_msg);
		}
		return -1;
	    }
	    if (out && debug) {
		fprintf(out, "250-2.1.0 A message of %ld bytes can be accepted.\r\n", (long) body_size);
	    }
#ifdef CLAIM_BROKEN_8BITMIME_SUPPORT	/* XXX actually either RFC 1652 "8BITMIME" or RFC 3030 "CHUNKING" */
	} else if (strncmpic(options, "BODY", optionlen) == 0) {
	    options += optionlen;
	    if (*options != '=') {
		non_compliant_reply(out, MAIL_CMD, TRUE, 501, "5.5.2", "ESMTP BODY option is missing its value.");
		return -1;
	    }
	    options++;
	    optionlen = 0;
	    /* find the end of the BODY parameter */
	    while (*(options+optionlen) && !isspace((int) *(options+optionlen))) {
		++optionlen;
	    }
	    ch = *(options+optionlen);
	    *(options+optionlen) = '\0';	/* NUL terminate # */
	    if (EQIC(options, "7BIT")) {
		/* What more could you want in America?  ;-) */
	    } else if (EQIC(options, "8BITMIME")) {
		/* We'll be lazy and leave this up to the MUA to decode... */
	    } else {
		*(options+optionlen) = ch;	/* restore */
		non_compliant_reply(out, MAIL_CMD, TRUE, 501, "5.5.2", "Invalid ESMTP BODY parameter value.");
		return -1;
	    }
	    if (out && debug) {
		fprintf(out, "250-2.1.0 A message containing %s can be accepted.\r\n", options);
	    }
	    *(options+optionlen) = ch;	/* restore */
#endif /* CLAIM_BROKEN_8BITMIME_SUPPORT */
	} else {
	    char *msg;

/* XXX TODO: implement RFC 2852 "BY=" (should not be seen unless we include DELIVERBY in EHLO response) */
/* XXX TODO: implement RFC 3461 "RET=" (should not be seen unless we include ESC in EHLO response) */
/* XXX TODO: implement RFC 3461 "ENVID=" (should not be seen unless we include ESC in EHLO response) */

	    msg = xprintf("syntax error in parameters: unknown ESMTP option: %V", (size_t) optionlen, options);
	    non_compliant_reply(out, MAIL_CMD, TRUE, 555, "5.5.2", msg);
	    xfree(msg);
	    return -1;
	}
	options += optionlen;
    }

    return 0;
}

#ifndef NO_SMTP_EXPN
/*
 * expand_addr - expand an address
 *
 * display the list of items that an address expands to.
 */
static void
expand_addr(in_addr, out)
    char *in_addr;			/* input address string */
    FILE *out;				/* write expansion here */
{
    struct addr *addr = alloc_addr();	/* get an addr structure */
    struct addr *okay = NULL;		/* list of deliverable addrs */
    struct addr *defer = NULL;		/* list of currently unknown addrs */
    struct addr *fail = NULL;		/* list of undeliverable addrs */
    register struct addr *cur;		/* current addr to display */
    char *errstr;			/* hold error message */
    int oexitval = exitvalue;		/* resolve_addr_list() can clobber this */

# ifndef NO_LOG_SMTP_EXPN
    write_log(WRITE_LOG_SYS, "remote EXPN: '%q' by %s%s%s%s%s%s%s%s%s",
	      in_addr,
	      ident_sender ? ident_sender : "",
	      ident_sender ? "@" : "",
	      sender_host ? sender_host : "",
	      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
	      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
	      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
	      sender_host_addr ? "[" : "",
	      sender_host_addr ? sender_host_addr : "",
	      sender_host_addr ? "]" : "");
# endif
    addr->in_addr = COPY_STRING(in_addr); /* setup the input addr structure */
    /* build the mungeable addr string */
    addr->work_addr = preparse_address(in_addr, &errstr);
    if (addr->work_addr == NULL) {
	sleep(1);
	dprintf(out, "501-5.1.3 %q address parse error:\r\n", in_addr);
	fflush(out);
	sleep(smtp_error_delay > 0 ? smtp_error_delay : 2);
	dprintf(out, "501 5.1.3 %v\r\n", errstr);
	fflush(out);
	return;
    }

    /* cache directors and routers on the assumption we will need them again */
    if (! queue_only) {
	if (! cached_directors) {
	    cache_directors();
	}
	if (! cached_routers) {
	    cache_routers();
	}
    }

    resolve_addr_list(addr, &okay, &defer, &fail, TRUE);
    exitvalue = oexitval;
    if (okay && out) {
	addr = okay;				/* OK to reuse addr now... */
	okay = NULL;
	check_smtp_remote_allow(addr, &okay, &defer, &fail);
    }
    if (okay) {
	/* display the complete list of resolved addresses */
	for (cur = okay; cur->succ; cur = cur->succ) {
	    dprintf(out, "250-2.1.0 %q\r\n", cur->in_addr);
	}
	/* the last one should not begin with 250- */
	dprintf(out, "250 2.1.0 %q\r\n", cur->in_addr);
    } else if (defer) {
	sleep(1);
	for (cur = defer; cur; cur = cur->succ) {
	    if (cur->error && cur->error->message) {
		dprintf(out, "550-5.1.5 '%q' deferred: %v.\r\n",  cur->in_addr, cur->error->message);
	    } else {
		dprintf(out, "550-5.1.5 '%q' Address processing problem.\r\n", cur->in_addr);
	    }
	    fflush(out);
	    sleep(1);
	}
	sleep(smtp_error_delay > 0 ? smtp_error_delay : 2);
	dprintf(out, "550 5.1.5 Problems expanding %q\r\n", in_addr);
    } else {
	sleep(1);
	for (cur = fail; cur; cur = cur->succ) {
	    if (cur->error && cur->error->message) {
		dprintf(out, "550-5.1.1 '%q' failed: %v.\r\n", cur->in_addr, cur->error->message);
	    } else {
		dprintf(out, "550-5.1.1 '%q' Address processing failure.\r\n",  cur->in_addr);
	    }
	    fflush(out);
	    sleep(1);
	}
	sleep(smtp_error_delay > 0 ? smtp_error_delay : 2);
	dprintf(out, "550 5.1.5 %sFailures expanding %v\r\n", fail ? "" : "internal ", in_addr);
	fflush(out);

	if (!fail) {
	    panic(EX_SOFTWARE, "remote EXPN: %s%s%s%s%s%s: '%q': recipient <%q%s%q>, for sender '%s', caused resolve_addr_list() or check_smtp_remote_allow() to fail abnormally!",
		  ident_sender ? ident_sender : "",
		  ident_sender ? "@" : "",
		  sender_host ? sender_host : "",
		  sender_host_addr ? "[" : "",
		  sender_host_addr ? sender_host_addr : "<no-client-host-addr>",
		  sender_host_addr ? "]" : "",
		  in_addr,
		  addr->work_addr,
		  addr->target ? "@" : "",
		  addr->target ? addr->target : "",
		  sender);
	    /* NOTREACHED */
	}
    }
    /* XXX free_addr_list(okay); */
    /* XXX free_addr_list(defer); */
    /* XXX free_addr_list(fail); */

    return;
}
#endif	/* !NO_SMTP_EXPN */


/*
 * verify_addr_form - verify the form and syntax of an address
 */
static int
verify_addr_form(in_addr, raw_addr, out, smtp_cmd, pvtarget)
    char *in_addr;	/* from preparse_address_1() or preparse_address() */
    char *raw_addr;	/* full data from user */
    FILE *out;		/* file for response codes */
    enum e_smtp_cmds smtp_cmd; /* only VRFY_CMD, RCPT_CMD, and MAIL_CMD are legal */
    char **pvtarget;	/* return the target domain */
{
    struct addr *vaddr;
    int form;
    char *p;

    vaddr = alloc_addr();
    vaddr->in_addr = COPY_STRING(raw_addr);
    vaddr->work_addr = COPY_STRING(in_addr);
    form = parse_address(vaddr->work_addr, &vaddr->target,
			 &vaddr->remainder, &vaddr->parseflags);
    if (vaddr->target) {
	*pvtarget = COPY_STRING(vaddr->target);
    } else {
	*pvtarget = NULL;
    }
    p = vaddr->remainder;
#if HAVE_BSD_NETWORKING
    /*
     * This code is sort of duplicating the check in
     * parse_address():check_target_and_remainder(), but here we use rfc821
     * rules, not the rfc2822 rules used there.
     */
    if (form == MAILBOX || form == LOCAL) {
	/*
	 * we must now do further SMTP-specific validation of the local part
	 * (in theory parse_address() has validated the domain target part)
	 */
	if (*p == '"') {
	    if (!rfc821_is_quoted_string(p)) {
		vaddr->remainder = "invalid quoted mailbox local part";
		form = FAIL;
	    }		
	} else {
	    if (!rfc821_is_dot_string(p)) {
		vaddr->remainder = "invalid character in unquoted mailbox local part";
		form = FAIL;
	    }
	}
    }
#endif
    if (form == FAIL) {
	if (out) {
	    sleep(1);
	    dprintf(out, "501-5.1.7 '%q' %s address syntax error,\r\n",
		    raw_addr,
		    (smtp_cmd == RCPT_CMD || smtp_cmd == VRFY_CMD) ? "recipient" : "sender");
	    dprintf(out, "501-5.1.7 parameter rejected because of:\r\n");
	    dprintf(out, "501-5.1.7\r\n");
	    send_smtp_msg(out, 501, "5.1.7    ", TRUE, (vaddr->remainder) ? vaddr->remainder : "<unknown error>");
	    fflush(out);
	}
	write_log(WRITE_LOG_SYS, "remote %s: rejected: invalid operand: '%q': %v: from %s%s%s%s%s%s%s%s%s",
		  smtp_cmd_list[smtp_cmd].name,
		  raw_addr,
		  (vaddr->remainder) ? vaddr->remainder : "<unknown error>",
		  ident_sender ? ident_sender : "",
		  ident_sender ? "@" : "",
		  sender_host ? sender_host : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
		  sender_host_addr ? "[" : "",
		  sender_host_addr ? sender_host_addr : "",
		  sender_host_addr ? "]" : "");

	/* XXX instead just free_addr(vaddr); */
	xfree(vaddr->in_addr);
	xfree(vaddr->work_addr);
	xfree((char *) vaddr);

	return 0;
    }
    if (form == LOCAL && ((smtp_cmd == RCPT_CMD || smtp_cmd == VRFY_CMD) ? !(EQIC(vaddr->work_addr, "postmaster") || EQIC(vaddr->work_addr, "abuse")) : TRUE)) {
	if (out) {
	    sleep((smtp_error_delay > 2) ? (smtp_error_delay / 2) : 2);
	    dprintf(out, "550-5.1.7 '%q' is invalid,\r\n", in_addr);
	    fflush(out);
	    sleep(1);
	    dprintf(out, "550-5.1.7 %s address cannot be an unqualified mailbox,\r\n",
		    (smtp_cmd == RCPT_CMD || smtp_cmd == VRFY_CMD) ? "recipient" : "sender");
	    fflush(out);
	    sleep(smtp_error_delay > 0 ? smtp_error_delay : 2);
	    fprintf(out, "550 5.1.7 it must have a domain portion.\r\n");
	    fflush(out);
	}
	write_log(WRITE_LOG_SYS, "remote %s '%q' is a local mailbox, missing domain part; by %s%s%s%s%s%s%s%s%s",
		  smtp_cmd_list[smtp_cmd].name,
		  in_addr,
		  ident_sender ? ident_sender : "",
		  ident_sender ? "@" : "",
		  sender_host ? sender_host : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
		  sender_host_addr ? "[" : "",
		  sender_host_addr ? sender_host_addr : "",
		  sender_host_addr ? "]" : "");
	/* XXX instead just free_addr(vaddr); */
	xfree(vaddr->in_addr);
	xfree(vaddr->work_addr);
	xfree((char *) vaddr);
	return 0;
    }
    if (form == BERKENET || form == DECNET) {
	/* XXX should be impossible unless USE_DECNET or USE_BERKENET are defined */
	if (out) {
	    sleep((smtp_error_delay > 2) ? (smtp_error_delay / 2) : 2);
	    dprintf(out, "553-5.1.7 '%q' %s address cannot be a %s form address...\r\n",
		    in_addr,
		    (smtp_cmd == RCPT_CMD || smtp_cmd == VRFY_CMD) ? "recipient" : "sender",
		    (form == BERKENET) ? "BERKENET" :
		    (form == DECNET) ? "DECNET" : "<bad-form!>");
	    fflush(out);
	    sleep(smtp_error_delay > 0 ? smtp_error_delay : 2);
	    fprintf(out, "553 5.1.7 Please try a strict mailbox form address.\r\n");
	    fflush(out);
	}
	write_log(WRITE_LOG_SYS, "remote %s '%q' is a %s(%d) address; by %s%s%s%s%s%s%s%s%s",
		  smtp_cmd_list[smtp_cmd].name,
		  in_addr,
		  (form == BERKENET) ? "BERKENET" :
		  (form == DECNET) ? "DECNET" : "<bad-form!>",
		  form,
		  ident_sender ? ident_sender : "",
		  ident_sender ? "@" : "",
		  sender_host ? sender_host : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
		  sender_host_addr ? "[" : "",
		  sender_host_addr ? sender_host_addr : "",
		  sender_host_addr ? "]" : "");
	/* XXX instead just free_addr(vaddr); */
	xfree(vaddr->in_addr);
	xfree(vaddr->work_addr);
	xfree((char *) vaddr);
	return 0;
    }
    /*
     * Source-route addrs have the following allowable syntax:
     *
     *	<reverse-path> ::= <path>	# MAIL FROM:
     *
     *	<forward-path> ::= <path>	# RCPT TO:
     *
     *	<path> ::= "<" [ <a-d-l> ":" ] <mailbox> ">"
     *
     *	<a-d-l> ::= <at-domain> | <at-domain> "," <a-d-l>
     *
     *	<at-domain> ::= "@" <domain>
     *
     *	<domain> ::=  <element> | <element> "." <domain>
     *
     *	<element> ::= <name> | "#" <number> | "[" <dotnum> "]"
     *
     *	<mailbox> ::= <local-part> "@" <domain>
     *
     *	<local-part> ::= <dot-string> | <quoted-string>
     *
     * I.e. for RFC_ROUTE or RFC_ENDROUTE the remainder will be a full
     * <mailbox>...
     *
     * XXX Do we really want to allow RFC_ROUTE/RFC_ENDROUTE addresses?
     *
     * RFC 2821 first says (regarding recipient addresses):
     *
     *	Receiving systems MUST recognize source route syntax but SHOULD strip
     *	off the source route specification and utilize the domain name
     *	associated with the mailbox as if the source route had not been
     *	provided.
     *
     *	Similarly, relay hosts SHOULD strip or ignore source routes
     *
     * later regarding sender addresses it says source routes:
     *
     *	MUST BE accepted, SHOULD NOT be generated, and SHOULD be ignored.
     *
     * What, exactly, does "ignored" mean?  Likely it should read "SHOULD be
     * stripped off", which would imply ignoring the suggested routing and just
     * routing the destination independently.
     *
     * RFC 2821 also says:
     *
     *	SMTP servers MAY decline to act as mail relays or to accept addresses
     *	that specify source routes.
     *
     * We will hopefully do the right thing to deny all relaying from
     * unauthorised clients, regardless of whether source route addresses are
     * used or not.
     *
     * With respect to sending errors back to the return-path RFC 2821 says:
     *
     *	If the address is an explicit source route, it MUST be stripped down to
     *	its final hop.
     *
     * XXX I'm not sure we meet this last requirement correctly yet....
     *
     * XXX What about other forms (eg. UUCP_ROUTE, PCT_MAILBOX)?
     */
    if (form != MAILBOX) {
	write_log(WRITE_LOG_SYS, "remote %s '%q' (target %s) is a %s(%d) address; by %s%s%s%s%s%s%s%s%s",
		  smtp_cmd_list[smtp_cmd].name,
		  in_addr,
		  vaddr->target ? vaddr->target : "(no-domain)",
		  (form == RFC_ROUTE) ? "RFC_ROUTE" :
		  (form == RFC_ENDROUTE) ? "RFC_ENDROUTE" :
		  (form == MAILBOX) ? "MAILBOX" :
		  (form == UUCP_ROUTE) ? "UUCP_ROUTE" :
		  (form == PCT_MAILBOX) ? "PCT_MAILBOX" :
		  (form == LOCAL) ? "LOCAL" :
		  (form == BERKENET) ? "BERKENET" :
		  (form == DECNET) ? "DECNET" : "<bad-form!>",
		  form,
		  ident_sender ? ident_sender : "",
		  ident_sender ? "@" : "",
		  sender_host ? sender_host : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
		  sender_host_addr ? "[" : "",
		  sender_host_addr ? sender_host_addr : "",
		  sender_host_addr ? "]" : "");
    }

    DEBUG3(DBG_REMOTE_MID, "verify_addr_form(): %s '%q' verified.\n",
	   smtp_cmd_list[smtp_cmd].name,
	   in_addr,
	   vaddr->target ? vaddr->target : "(no-domain)");

    /* XXX instead just free_addr(vaddr); */
    xfree(vaddr->in_addr);
    xfree(vaddr->work_addr);
    xfree((char *) vaddr);

    return 1;
}


/*
 * verify_sender - verify envelope sender address
 */
static int
verify_sender(pp_sender, raw_sender, out)
    char *pp_sender;			/* from preparse_address_1() */
    char *raw_sender;			/* full data from user */
    FILE *out;				/* file for response codes */
{
    char *vtarget;			/* domain from sender address */
    char *reject_reason = NULL;		/* additional SMTP reply from smtp_reject_* setting */
    /* NOTE: the following two must always be set in tandem */
    char *reject_msg = NULL;		/* SMTP message for RHSBL rejection */
    char *log_reason = NULL;		/* log message for RHSBL rejection */

    if (!verify_addr_form(pp_sender, raw_sender, out, MAIL_CMD, &vtarget)) {
	/* errors already logged and printed */
	return 0;
    }
    /* XXX ignores match_re_list() errors for now */
    if (match_re_list(pp_sender, smtp_sender_reject, TRUE, &reject_reason) == MATCH_MATCHED) {
	reject_msg = xprintf("The sender address '%q' has been blocked by the PostMaster at %q\n",
			     pp_sender, primary_name);
	log_reason = xprintf("'%q' matched in smtp_sender_reject", pp_sender);
#if 0
	estatus = "5.1.7";
#endif
    }
    if (!reject_msg && vtarget &&
	match_hostname(vtarget, smtp_sender_reject_hostnames, &reject_reason) == MATCH_MATCHED) {
	reject_msg = xprintf("The domain '%q' has been blocked by the postmaster at %q.\n", vtarget, primary_name);
	log_reason = xprintf("%q matched in smtp_sender_reject_hostnames", vtarget);
#if 0
	estatus = "5.1.8";
#endif
    }
    if (!reject_msg && smtp_sender_reject_db && smtp_sender_reject_db_proto) {
	char *db_name = expand_string(smtp_sender_reject_db, (struct addr *) NULL, (char *) NULL, (char *) NULL);
	char *dbinfo;
	char *error = NULL;

	if (db_name) {
	    int result = open_database(db_name, smtp_sender_reject_db_proto, 1, 2, (struct stat *) NULL, &dbinfo, &error);

	    if (result == DB_SUCCEED) {
		result = lookup_database(dbinfo, pp_sender, &reject_reason, &error);
		close_database(dbinfo);
		if (result == DB_SUCCEED) {
		    reject_msg = xprintf("The sender address '%q' has been rejected by the PostMaster at %q\n",
					 pp_sender, primary_name);
		    log_reason = xprintf("'%q' matched in smtp_sender_reject_db", pp_sender);
#if 0
		    estatus = "5.1.7";
#endif
		} else if (result != DB_NOMATCH) {
		    /*
		     * XXX if the DB is specified but not available, should we
		     * defer? [probably]
		     */
		    write_log(WRITE_LOG_PANIC | WRITE_LOG_TTY,
			      "Warning: lookup of %q in %s:%s failed: %s",
			      pp_sender, smtp_sender_reject_db_proto, db_name, error);
		}
	    } else {
		/*
		 * XXX if the DB is specified but not available, should we
		 * defer? [probably]
		 */
		write_log(WRITE_LOG_PANIC | WRITE_LOG_TTY,
			  "Warning: open of %s:%s failed: %s\n",
			  smtp_sender_reject_db_proto, db_name, error);
	    }
	}
    }
#if defined(HAVE_BIND) && !defined(HAVE_BSD_NETWORKING)
# include "ERROR:  (HAVE_BIND && !HAVE_BSD_NETWORKING) is not a supported configuration!"
#endif
#ifdef HAVE_BSD_NETWORKING
    if (!reject_msg && vtarget && *vtarget == '[') { /* looks like a literal IP address.... */
	in_addr_t inet;		/* internet address */
	char *p;

	p = strchr(vtarget, ']');
	if (!p) {
	    DEBUG1(DBG_REMOTE_LO,
		   "verify_sender(%q): Invalid address literal, missing closing ']'.\n",
		   vtarget);
	    reject_msg = xprintf("Invalid address literal '%q', missing closing ']'\n", vtarget);
	    log_reason = xprintf("Invalid address literal '%q', missing closing ']'", vtarget);
#if 0
	    estatus = "5.?.?";
#endif
	} else {
	    *p = '\0';			/* XXX should only modify a copy... */
	    inet = get_inet_addr(&vtarget[1]);
	    *p = ']';
	    DEBUG4(DBG_REMOTE_HI,
		   "verify_sender(%q): inet from literal '%v': [0x%lx] aka %s\n",
		   raw_sender, vtarget,
		   ntohl(inet), inet_ntoa(inet_makeaddr((in_addr_t) ntohl(inet), (in_addr_t) 0)));
	    if (inet == INADDR_NONE) {
		DEBUG2(DBG_REMOTE_LO,
		       "verify_sender(%q): inet_addr(%v) failed: bad host address form.\n",
		       raw_sender, vtarget);
		reject_msg = xprintf("Invalid address literal '%q', bad host address form\n", vtarget);
		log_reason = xprintf("get_inet_addr(%q): bad host address form", vtarget);
#if 0
		estatus = "5.?.?";
#endif
	    } else if (inet == INADDR_LOOPBACK && !peer_is_localhost) {
		DEBUG2(DBG_REMOTE_LO,
		       "verify_sender(%v): inet_addr(%v) failed: localhost sender address is always invalid!\n",
		       raw_sender, vtarget);
		reject_msg = xprintf("Invalid address literal '%q':  You are not connecting from localhost!\n", vtarget);
		log_reason = xprintf("not a local peer");
#if 0
		estatus = "5.?.?";
#endif
	    } else if (inet == 0 && !peer_is_localhost) {
		DEBUG2(DBG_REMOTE_LO,
		       "verify_sender(%v): inet_addr(%v) failed: localhost (0) sender address is always invalid!\n",
		       raw_sender, vtarget);
		reject_msg = xprintf("Invalid address literal '%q':  You are not connecting from the local host!\n", vtarget);
		log_reason = xprintf("not a local peer");
#if 0
		estatus = "5.?.?";
#endif
	    }
	    *p = '\0';			/* XXX should only modify a copy... */
	    /*
	     * no need for checking against smtp_bad_mx_except -- this is a
	     * domain literal (if you really want to make an exception for an
	     * internal host using a restricted MX address then you'll have to
	     * implement your internal DNS properly and teach your internal
	     * mailers not to use domain literals!)
	     */
	    if (!reject_msg && !peer_is_localhost &&
		match_ip(&vtarget[1], smtp_bad_mx_targets, ':', ';', FALSE, &reject_reason)) {

		*p = ']';
		reject_msg = xprintf("The address literal '%q' is not authorised for use in any e-mail address.\n", vtarget);
		log_reason = xprintf("%q matched in smtp_bad_mx_targets", vtarget);
#if 0
		estatus = "5.?.?";
#endif
	    }
	    *p = ']';
	}
    } /* note the ELSE below -- we don't need to do MX checks for domain literals */
# if defined(HAVE_BIND)
    else if (!reject_msg && sender_host_addr && vtarget) {
	if (! match_ip(sender_host_addr, smtp_sender_no_verify, ':', ';', FALSE, (char **) NULL)) {
	    int mxresult;
	    unsigned long bindflgs = BIND_DOMAIN_REQUIRED | BIND_DEFER_NO_CONN | BIND_LOCAL_MX_OKAY | BIND_DONT_FILTER;
	    struct rt_info rt_info;
	    struct error *binderr = NULL;
	    char *binderrmsg;
	    static struct bindlib_private bindpriv =  BIND_TEMPLATE_ATTRIBUTES;

	    rt_info.next_host = NULL;
	    rt_info.route = NULL;
	    rt_info.transport = NULL;
	    rt_info.tphint_list = NULL;

	    if (smtp_sender_verify_mx_only) {
		bindflgs |= BIND_MX_ONLY; /* i.e. don't look for A RRs (avoid term servers) */
	    }

	    mxresult = bind_addr(vtarget, bindflgs, &bindpriv, "verify_sender", &rt_info, &binderr);
	    binderrmsg = (binderr && binderr->message && *binderr->message) ?
			 binderr->message :
			 (mxresult == DB_SUCCEED) ? "(none)" : "unknown error";

	    DEBUG5(DBG_REMOTE_MID, "verify_sender(%v, %v): bind_addr(%v): %d, %s.\n",
		   pp_sender, raw_sender, vtarget, mxresult, binderrmsg);

	    if (rt_info.next_host) {
		xfree(rt_info.next_host); /* clean up alloc'ed storage we don't need */
	    }
	    if (rt_info.route) {
		xfree(rt_info.route);	/* clean up alloc'ed storage we don't need */
	    }

	    switch (mxresult) {
	    case DB_SUCCEED: {		/* check that the result is valid */
		struct transport_hints *hints;
		struct transport_hints *mx_hints;

		for (hints = rt_info.tphint_list;
		     hints && !EQ(hints->hint_name, "mx"); /* skipt past any non-MX hints */
		     hints = hints->succ) {
		    ;
		}
		if (!hints) {
		    if (smtp_sender_verify_mx_only) {
			/* this should not be possible given we set BIND_MX_ONLY! */
			reject_msg = xprintf("The domain '%q' has no valid DNS MX records!\n", vtarget);
			log_reason = xprintf("impossible DB_SUCCEED from bind_addr(%q) with no MXs", vtarget);
#if 0
			estatus = "5.?.?";
#endif
		    } else {
			int i;
			struct hostent *hp;	/* addr list for remote host */

			/* start over checking any A RRs (aka fake MX RRs) */
			if (!(hp = gethostbyname(vtarget))) {
			    reject_msg = xprintf("The domain '%q' has no valid DNS A records: %s\n",
						 vtarget, hstrerror(h_errno));
			    log_reason = xprintf("gethostbyname(%q) failed: %s",
						 vtarget, hstrerror(h_errno));
#if 0
			    estatus = "5.?.?";
#endif
			} else {
			    for (i = 0; hp->h_addr_list[i]; i++) {
				struct in_addr hname_addr;
				char *a_target;

				memcpy((char *) &hname_addr, hp->h_addr_list[0], sizeof(struct in_addr));
				a_target = inet_ntoa(hname_addr);
				/*
				 * Since there were no valid MX records, we
				 * treat the hostname as if it were the primary
				 * MX....
				 */
				if ((match_hostname(vtarget, smtp_bad_mx_except, (char **) NULL) != MATCH_MATCHED) &&
				    match_ip(a_target, smtp_bad_mx_targets, ':', ';', FALSE, &reject_reason)) {

				    reject_msg = xprintf("The hostname '%q' has an unauthorised address of [%s].\n",
							 vtarget, a_target);
				    log_reason = xprintf("%q[%s] matched in smtp_bad_mx_targets",
							 vtarget, a_target);
#if 0
				    estatus = "5.?.?";
#endif
				}
			    }
			}
		    }
		} else {
		    if (match_hostname(vtarget, smtp_bad_mx_except, (char **) NULL) != MATCH_MATCHED) {
			for (mx_hints = hints; mx_hints; mx_hints = mx_hints->succ) {
			    struct ipaddr_hint *ip_addr;

			    if (!EQ(mx_hints->hint_name, "mx")) {
				continue;		/* ignore all but the MX RRs */
			    }

#  define mx_hint	((struct mx_transport_hint *) (mx_hints->private))

			    for (ip_addr = mx_hint->ipaddrs; ip_addr; ip_addr = ip_addr->succ) {
				char *mx_target;

				mx_target = inet_ntoa(ip_addr->addr);
				if (match_ip(mx_target, smtp_bad_mx_targets, ':', ';', FALSE, &reject_reason)) {
				    reject_msg = xprintf("The DNS MX target host '%q' (for the domain '%q') has an unauthorised address of [%s].\n",
							 mx_hint->exchanger, vtarget, mx_target);
				    log_reason = xprintf("%q[%v] matched in smtp_bad_mx_targets (MX target for %q)",
							 mx_hint->exchanger, mx_target, vtarget);
#if 0
				    estatus = "5.?.?";
#endif
				}
			    }
#  undef mx_hint
			}
		    }
		}
		break;
	    }
	    case DB_AGAIN:		/* DNS lookup must be deferred */
	    case FILE_AGAIN:		/* lost contact with server */
	    case FILE_NOMATCH:		/* There is no server available */
		if (out) {
		    dprintf(out, "450-4.4.3 defer all mail from '%q'.\r\n", pp_sender);
		    dprintf(out, "450-4.4.3 Sender address target domain '%q' cannot be verified at this time.\r\n", vtarget);
		    fprintf(out, "450-4.4.3 \r\n");
		    fprintf(out, "450-4.4.3 Reason given was: %s.\r\n", binderrmsg);
		    fflush(out);
		    sleep(smtp_error_delay > 0 ? smtp_error_delay : 2); /* force them to make it at least this much later! */
		    fprintf(out, "450 4.4.3 Try again later.\r\n");
		    fflush(out);
		}
#  ifndef NO_LOG_DNS_TEMPORARY_ERRORS
		write_log(WRITE_LOG_SYS, "remote MAIL FROM: '%q' target '%q': DNS temporary error: %s; from %s%s%s%s%s%s%s%s%s",
			  raw_sender,
			  vtarget,
			  binderrmsg,
			  ident_sender ? ident_sender : "",
			  ident_sender ? "@" : "",
			  sender_host ? sender_host : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
			  sender_host_addr ? "[" : "",
			  sender_host_addr ? sender_host_addr : "",
			  sender_host_addr ? "]" : "");
#  endif
		xfree(vtarget);

		return 0;

	    case DB_NOMATCH:		/* no such domain */
		reject_msg = xprintf("Sender address target domain '%q' is not valid%s\n",
				     vtarget,
				     smtp_sender_verify_mx_only ? " for e-mail use.  There is no MX record for it." : ".");
		log_reason = xprintf("target '%q' is not a valid domain%s",
				     vtarget,
				     smtp_sender_verify_mx_only ? " (no MX record)" : "");
#if 0
		estatus = "5.?.?";
#endif
		break;

	    case DB_FAIL:			/* bad DNS request? */
	    case FILE_FAIL:			/* major DNS error! */
		    if (out) {
			sleep((smtp_error_delay > 2) ? (smtp_error_delay / 2) : 2);
			dprintf(out, "550-5.4.3 rejecting sender address '%q'.\r\n", pp_sender);
			fflush(out);
			sleep(1);
			dprintf(out, "550-5.4.3 Failed to verify sender address target domain '%q'.\r\n", vtarget);
			fflush(out);
			sleep(1);
			fprintf(out, "550-5.4.3 \r\n550-5.4.3 Reason given was:\r\n550-5.4.3 \r\n");
			send_smtp_msg(out, 550, "5.4.3", TRUE, binderrmsg);
		    }
		    write_log(WRITE_LOG_SYS, "remote MAIL FROM: '%q' target '%q': DNS failed: %s; from %s%s%s%s%s%s%s%s%s",
			      raw_sender,
			      vtarget,	
			      binderrmsg,
			      ident_sender ? ident_sender : "",
			      ident_sender ? "@" : "",
			      sender_host ? sender_host : "",
			      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
			      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
			      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
			      sender_host_addr ? "[" : "",
			      sender_host_addr ? sender_host_addr : "",
			      sender_host_addr ? "]" : "");
		    xfree(vtarget);

		    return 0;

	    default:
		if (out) {
		    dprintf(out, "421-4.4.3 defer all mail from '%q'.\r\n", pp_sender);
		    dprintf(out, "421-4.4.3 Impossible error returned when trying to verify target domain '%q'.\r\n", vtarget);
		    fprintf(out, "421-4.4.3 \r\n421-4.4.3 Reason given was: %s.\r\n421-4.4.3 \r\n", binderrmsg);
		    fflush(out);
		    sleep(smtp_error_delay > 0 ? smtp_error_delay : 2); /* some ignore the reason and try again immediately! */
		    fprintf(out, "421 4.4.3 Try again later.  Connection closing now...\r\n");
		    fflush(out);
		}
		panic(EX_SOFTWARE, "verify_sender(%q, %q): bind_addr(%q) gave impossible result %d: %s.",
		      pp_sender, raw_sender, vtarget, mxresult, binderrmsg);
		/* NOTREACHED */
	    }
	}
    }
# endif	/* HAVE_BIND (note DNSBLs used in the code below are probably DNS
	 * based, but conceptually don't have to be)
	 */
    /*
     * note a match in smtp_sender_reject_hostnames has precedence over
     * smtp_sender_rhsbl_except
     */
    if (!reject_msg) {
	/*
	 * This is a special case where we call expand_string() so that
	 * we can use variable names in a list, since in this case it makes
	 * the most sense and is the least error prone to just always
	 * include "$hostnames:$more_hostnames" in the list of exceptions.
	 *
	 * Note that if the expand_string() fails then the result is as if
	 * smtp_sender_rhsbl_except is not set (though maybe a paniclog
	 * entry will be written as a warning).
	 */
	/* XXX ignore errors from match_hostname() for now.... */
	if (vtarget && *vtarget != '[' &&
	    !islocalhost(vtarget) &&
	    (!smtp_sender_rhsbl_except ||
	     match_hostname(vtarget,
			    expand_string(smtp_sender_rhsbl_except, (struct addr *) NULL, (char *) NULL, (char *) NULL),
			    (char **) NULL) != MATCH_MATCHED)) {
	    char *smtp_rhsbl_match = NULL;	/* full domain name matched by an RHSBL */
	    char *smtp_rhsbl_addr = NULL;	/* ascii formatted address value of RHSBL A RR */
	
	    if (match_dnsbl(vtarget, smtp_sender_rhsbl_domains, "smtp_sender_rhsbl_domains", &smtp_rhsbl_match, &smtp_rhsbl_addr, &reject_reason)) {
		reject_msg =
		    xprintf("The domain '%v' matches the DNS Black List entry:\n\n\t%v\tIN A\t%s",
			    vtarget, smtp_rhsbl_match, smtp_rhsbl_addr);
		/*
		 * note vtarget is now included, in the same '%q' form as the
		 * matched A RR name, so logsumm.awk can more easily find the
		 * original RHSBL domain without having to worry about
		 * pretending to parse the domain part out of the original
		 * address parameter (which will be transformed into '%v' form
		 * and so even more difficult to parse)
		 */
		log_reason = xprintf("%q matched RHSBL: %q [%s]", vtarget, smtp_rhsbl_match, smtp_rhsbl_addr);
#if 0
		estatus = "5.?.?";
#endif
	    }
	} else if (smtp_sender_rhsbl_except) {
	    DEBUG4(DBG_REMOTE_MID, "verify_sender(%q): not checking for %q in smtp_sender_rhsbl_domains%s%s\n",
		   pp_sender,
		   vtarget,
		   islocalhost(vtarget) ? ", local target host will be checked with verify_addr()" : "",
		   (vtarget && *vtarget == '[') ? ", domain literal avoids RHSBL checking" : "");
	}
    }
#endif /* HAVE_BSD_NETWORKING */

    if (reject_msg) {
	if (out) {
	    char *start_msg = xprintf("rejecting sender address '%q'.\n", pp_sender);
	    
	    /* XXX use estatus from above, not hard-coded "5.1.8" */
	    send_smtp_msg(out, 550, "5.1.8", FALSE, start_msg);
	    send_smtp_msg(out, 550, "5.1.8", FALSE, reject_msg);
	    xfree(reject_msg);
	    reject_msg = NULL;
	    if (reject_reason) {
		send_smtp_msg(out, 550, "5.1.8", FALSE,
			      "\nPlease note the following additional important information:\n\n");
		/* adding the " " is a hack to get indentation! */
		send_smtp_msg(out, 550, "5.1.8  ", FALSE, reject_reason);
	    }
	    reject_msg = xprintf("\n\
Please forward this message in its entirety to your own LOCAL <postmaster>\
 and ask them to help you to resolve this problem.\n\n\
Postmaster@[%s]:  If you require assistance in resolving this issue please\
 contact <postmaster@%q>.  You must include your IP number, given above, in\
 order to receive any useful help.  The best way do do this would be to\
 forward this entire status response.",
				 sender_host_addr ? sender_host_addr : "UNKNOWN-IP",
				 primary_name);
	    send_smtp_msg(out, 550, "5.1.8", TRUE, reject_msg);
	    xfree(reject_msg);
	    reject_msg = NULL;
	}
	write_log(WRITE_LOG_SYS, "remote MAIL FROM: '%q' refusing SMTP transaction from %s%s%s%s%s%s%s%s%s: %s",
		  raw_sender,
		  ident_sender ? ident_sender : "",
		  ident_sender ? "@" : "",
		  sender_host ? sender_host : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
		  sender_host_addr ? "[" : "",
		  sender_host_addr ? sender_host_addr : "",
		  sender_host_addr ? "]" : "",
		  log_reason);

	if (vtarget) {
	    xfree(vtarget);
	}
	if (log_reason) {
	    xfree(log_reason);
	}
	
	return 0;
    }

    /*
     * Check to see if the domain name is one that's handled locally, and if so
     * then run it through the verify logic to make sure it's OK and the sender
     * has not made a typo in their own mailer's configuration.
     */
    if (vtarget) {
	int verify_local = FALSE;

	if (islocalhost(vtarget)) {
	    verify_local = TRUE;
	}
#ifdef HAVE_BIND
	else {
	    unsigned long bindflgs = BIND_DOMAIN_REQUIRED | BIND_DEFER_NO_CONN;
	    struct rt_info rt_info;
	    struct error *binderr = NULL;
	    static struct bindlib_private bindpriv =  BIND_TEMPLATE_ATTRIBUTES;

	    rt_info.next_host = NULL;
	    rt_info.route = NULL;
	    rt_info.transport = NULL;
	    rt_info.tphint_list = NULL;

	    if (bind_addr(vtarget, bindflgs, &bindpriv, "verify_sender", &rt_info, &binderr) == DB_FAIL) {
		/*
		 * This is a bit of a cheap hack, but it does what we want.  If
		 * the preferred MX record points at a "local" host,
		 * bind_addr() will return the following error (ERR_169), and
		 * we'll know we must handle the target domain's e-mail, even
		 * though it wasn't matched by islocalhost() [perhaps through
		 * the rewrite router, or some other less desirable hack].
		 *
		 * Note that we don't really care if the DNS lookup fails -- it
		 * just means we WON'T be verifying this sender address this
		 * time, so maybe it'll slip through, but that's OK because
		 * eventually the DNS will likely work and we'll catch their
		 * mistake then.
		 */
		if ((binderr->info & ERR_MASK) == ERR_169) {
		    verify_local = TRUE;
		}
	    }		    
	}
#endif /* HAVE_BIND */
	if (verify_local && !verify_addr(pp_sender, out, MAIL_CMD)) {
	    /* NOTE: error reply is printed and logged by verify_addr() */
	    xfree(vtarget);

	    return 0;
	}
    }
    if (vtarget) {
	xfree(vtarget);
    }

    return 1;
}

/*
 * verify_addr - verify an address
 *
 * for RCPT_CMD and VRFY_CMD callers redisplay the input address, if it is a
 * valid address.
 *
 * XXX Should always be called with a preparsed address (result of
 * preparse_address_1()) so that we don't have to do it again here!
 */
static int
verify_addr(in_addr, out, smtp_cmd)
    char *in_addr;			/* input address string */
    FILE *out;				/* write expansion here */
    enum e_smtp_cmds smtp_cmd;		/* only MAIL_CMD & RCPT_CMD & VRFY_CMD are legal */
{
    struct addr *addr = alloc_addr();	/* get an addr structure */
    struct addr *okay = NULL;		/* verified address */
    struct addr *defer = NULL;		/* temporarily unverifiable addr */
    struct addr *fail = NULL;		/* unverified addr */
    char *errstr;			/* hold error message */
    char *vtarget;			/* target domain from verify_addr_form() [not used] */
    char *reason;			/* optional administrative reason from match_ip() */
    int oexitval;			/* resolve_addr_list() can clobber exitvalue */
    
    DEBUG2(DBG_REMOTE_MID, "verify_addr(%v): checking %s address deliverability.\n",
	   in_addr,
	   (smtp_cmd == MAIL_CMD) ? "sender" : "recipient");

    addr->in_addr = COPY_STRING(in_addr); /* setup the input addr structure */
    /* build the mungeable addr string */
    addr->work_addr = preparse_address(in_addr, &errstr);
    if (addr->work_addr == NULL) {
	DEBUG2(DBG_REMOTE_MID, "verify_addr(%v): failed: %s.\n", in_addr, errstr);

	if (out) {
	    sleep(1);
	    dprintf(out, "501-%s '%q' malformed address:\r\n",
		    (smtp_cmd == VRFY_CMD) ? "5.1.3 VRFY" : 
		    (smtp_cmd == RCPT_CMD) ? "5.1.3 RCPT TO" :
		    (smtp_cmd == MAIL_CMD) ? "5.1.7 MAIL FROM" :
		    "5.1.0 input",	/* XXX 5.1.7? */
		    in_addr);
	    fflush(out);
	    sleep(smtp_error_delay > 0 ? smtp_error_delay : 2);
	    fprintf(out, "501 %s %s.\r\n",
		    (smtp_cmd == VRFY_CMD) ? "5.1.3 VRFY" : 
		    (smtp_cmd == RCPT_CMD) ? "5.1.3 RCPT TO" :
		    (smtp_cmd == MAIL_CMD) ? "5.1.7 MAIL FROM" :
		    "5.1.0 input",      /* XXX 5.1.7? */
		    errstr);
	    fflush(out);
	}
	free_addr(addr);
	return 0;
    }
    if (!verify_addr_form(addr->work_addr, in_addr, out, smtp_cmd, &vtarget)) {
	/* errors already logged and printed */
	free_addr(addr);
	return 0;
    }
    /*
     * Don't accept a recipient address that we also would not accept as a
     * sender address since the only sane reason for rejecting any sender
     * address with a valid form is that it's not deliverable and it won't be
     * any more deliverable as a recipient address and it couldn't be used in a
     * reply to the message we might be accepting here either even if it did
     * turn out to be deliverable.  I.e. we refuse to accept any message to
     * these rejected sender addresses because we wouldn't accept any reply
     * from them.
     *
     * We could probably do the full verify_sender() test (taking care to avoid
     * recursion since verify_sender() will call verify_addr()[us] again for
     * local addresses) to check it as being a valid sender address, but for
     * now we'll just check the really obvious policy reject reasons.
     */
    if (smtp_cmd == RCPT_CMD || smtp_cmd == VRFY_CMD) {
	/* XXX ignores match_re_list() errors for now */
	if (match_re_list(addr->work_addr, smtp_sender_reject, TRUE, &reason) == MATCH_MATCHED) {
	    if (out) {
		errstr = xprintf("the address '%q' is not valid%s%s\n",
				 in_addr,
				 reason ? "\nReason given was: \n\n" : "",
				 reason ? reason : ".");
		send_smtp_msg(out, 550, "5.7.1", TRUE, errstr);
		xfree(errstr);
	    }
	    write_log(WRITE_LOG_SYS, "remote %s: rejected '%q' by %s%s%s%s%s%s%s%s%s: matched in smtp_sender_reject%s%s",
		      smtp_cmd_list[smtp_cmd].name,
		      in_addr,
		      ident_sender ? ident_sender : "",
		      ident_sender ? "@" : "",
		      sender_host ? sender_host : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
		      sender_host_addr ? "[" : "",
		      sender_host_addr ? sender_host_addr : "",
		      sender_host_addr ? "]" : "",
		      reason ? ": " : "",
		      reason ? reason : ".");

	    free_addr(addr);
	    return 0;
	}
	if (vtarget && match_hostname(vtarget, smtp_sender_reject_hostnames, &reason) == MATCH_MATCHED) {
	    if (out) {
		errstr = xprintf("the address '%q' is not valid%s%s\n",
				 in_addr,
				 reason ? "\nReason given was: \n\n" : "",
				 reason ? reason : ".");
		send_smtp_msg(out, 550, "5.7.1", TRUE, errstr);
		xfree(errstr);
	    }
	    write_log(WRITE_LOG_SYS, "remote %s: rejected '%q' by %s%s%s%s%s%s%s%s%s: matched in smtp_sender_reject_hostnames%s%v",
		      smtp_cmd_list[smtp_cmd].name,
		      in_addr,
		      ident_sender ? ident_sender : "",
		      ident_sender ? "@" : "",
		      sender_host ? sender_host : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
		      sender_host_addr ? "[" : "",
		      sender_host_addr ? sender_host_addr : "",
		      sender_host_addr ? "]" : "",
		      reason ? ": " : "",
		      reason ? reason : ".");

	    free_addr(addr);
	    return 0;
	}
	if (smtp_sender_reject_db && smtp_sender_reject_db_proto) {
	    char *db_name = expand_string(smtp_sender_reject_db, (struct addr *) NULL, (char *) NULL, (char *) NULL);
	    char *dbinfo;
	    char *error = NULL;

	    if (db_name) {
		int result = open_database(db_name, smtp_sender_reject_db_proto, 1, 2, (struct stat *) NULL, &dbinfo, &error);

		if (result == DB_SUCCEED) {
		    result = lookup_database(dbinfo, addr->work_addr, &reason, &error);
		    close_database(dbinfo);
		    if (result == DB_SUCCEED) {
			if (out) {
			    errstr = xprintf("the address '%q' is not valid%s%s\n",
					     in_addr,
					     reason ? "\nReason given was: \n\n" : "",
					     reason ? reason : ".");
			    send_smtp_msg(out, 550, "5.7.1", TRUE, errstr);
			    xfree(errstr);
			}
			write_log(WRITE_LOG_SYS, "remote %s: rejected '%q' by %s%s%s%s%s%s%s%s%s: matched in smtp_reject_sender_db%s%s",
				  smtp_cmd_list[smtp_cmd].name,
				  in_addr,
				  ident_sender ? ident_sender : "",
				  ident_sender ? "@" : "",
				  sender_host ? sender_host : "",
				  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
				  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
				  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
				  sender_host_addr ? "[" : "",
				  sender_host_addr ? sender_host_addr : "",
				  sender_host_addr ? "]" : "",
				  reason ? ": " : "",
				  reason ? reason : ".");

			free_addr(addr);
			return 0;
		    } else if (result != DB_NOMATCH) {
			/*
			 * XXX if the DB is specified but not available, should we
			 * defer? [probably]
			 */
			write_log(WRITE_LOG_PANIC | WRITE_LOG_TTY,
				  "Warning: lookup of %q in %s:%s failed: %s",
				  addr->work_addr, smtp_sender_reject_db_proto, db_name, error);
		    }
		} else {
		    /*
		     * XXX if the DB is specified but not available, should we
		     * defer? [probably]
		     */
		    write_log(WRITE_LOG_PANIC | WRITE_LOG_TTY,
			      "Warning: open of %s:%s failed: %s\n",
			      smtp_sender_reject_db_proto, db_name, error);
		}
	    }
	}
    }

#ifdef HAVE_BSD_NETWORKING
    /*
     * If we're verifying a RCPT TO: parameter, but the client is listed in
     * smtp_recipient_no_verify, just accpet the address (and we'll bounce it
     * later if it turns out to be invalid or undeliverable)
     *
     * XXX should we log these?
     */
    if ((smtp_cmd == RCPT_CMD) && sender_host_addr &&
	match_ip(sender_host_addr, smtp_recipient_no_verify, ':', ';', FALSE, &reason)) {

	if (out) {
	    DEBUG1(DBG_REMOTE_MID, "verify_addr(%v): recipient unverified.\n", in_addr);
	    dprintf(out, "250 2.1.0 <%v> Recipient Unverified%s%s%s.\r\n",
		    in_addr,
		    reason ? " (" : "",
		    reason ? reason : "",
		    reason ? ")" : "");
	    fflush(out);
	} else {
	    /* XXX impossible because of sender_host_addr test? */
	    DEBUG4(DBG_REMOTE_LO, "Recipient <%q> unverified%s%s%s\n",
		   in_addr,
		   reason ? " (" : "",
		   reason ? reason : "",
		   reason ? ")" : "");
	}
	free_addr(addr);
	return 1;
    }
#endif /* HAVE_BSD_NETWORKING */

    /* cache directors and routers on the assumption we will need them again */
    if (! queue_only) {
	if (! cached_directors) {
	    cache_directors();
	}
	if (! cached_routers) {
	    cache_routers();
	}
    }
    /*
     * This is really nasty (as in inefficient), but it's the only way to
     * determine the transport driver name.  "okay" may be a list of
     * addresses if an alias was expanded, but the parent will be the
     * original we submit.
     *
     * NOTE: don't set ADDR_VRFY_ONLY -- it sometimes skips setting a transport.
     */
    oexitval = exitvalue;	/* resolve_addr_list() can clobber exitvalue */
    resolve_addr_list(addr, &okay, &defer, &fail, FALSE);
    exitvalue = oexitval;
    /*
     * if the address resolved OK, and if this is not hbSMTP, then check to see
     * if it is headed off-site
     */
    if (okay && out) {
	addr = okay;
	okay = NULL;
	check_smtp_remote_allow(addr, &okay, &defer, &fail);
    }
    /* The result should be on one, and only one, of the output lists.... */
    if (okay) {
	unsigned long int form = okay->flags & ADDR_FORM_MASK;
	struct addr *oaddr = okay;

	if (form == 0 && (okay->parseflags & FOUND_MAILBOX)) {
	    DEBUG1(DBG_REMOTE_MID, "verify_addr(%v): surprise!  Actually was a mailbox form!\n", in_addr);
	    form = MAILBOX;
	}
	if (okay->parent) {
	    DEBUG1(DBG_REMOTE_MID, "verify_addr(%v): address has a local parent so must be local.\n", in_addr);
	    form = LOCAL;
	    do {
		oaddr = oaddr->parent;
	    } while (oaddr->parent);
	}
	switch (form) {
	case LOCAL:
	case MAILBOX:
#ifdef HAVE_BSD_NETWORKING
	    /*
	     * If we're verifying a sender address we might wish to ensure that
	     * locally deliverable addresses are only used by clients permitted
	     * to relay messages to remote destinations.
	     *
	     * XXX Ideally we would be able to tell the difference between an
	     * address routed via $hostnames or $more_hostnames and one routed
	     * via some outside router like one using the 'rewrite' driver.
	     * Normally we don't want to restrict users who are normally not
	     * using our mail server to relay their outgoing messages -- only
	     * to receive (and probably re-route remotely again) their incoming
	     * messages.  Such users will not normally be using clients listed
	     * in smtp_remote_allow.  In the mean time if you host virtual
	     * domains like this then you'd best disable
	     * smtp_local_sender_restrict.
	     *
	     * Note that unfortunately there is no one place where a flag is
	     * set to decide whether all local mailboxes will be treated
	     * without regard to case or not.  As such it is safest here to
	     * simply ignore case when matching against the items in
	     * smtp_local_sender_allow since doing so will err on the side of
	     * safety (i.e. prevent bouncing important messages such as
	     * forwarded mailing list posts, etc., though it will allow
	     * forwarded ~/.forward messages which might result in a loop, but
	     * we'll eventually catch those anyway).
	     *
	     * Don't worry if smtp_remote_allow is unset or emtpy....
	     *
	     * XXX ignores match_re_list() errors for now...
	     */
	    if (smtp_cmd == MAIL_CMD && smtp_local_sender_restrict &&
		(match_re_list(oaddr->remainder, smtp_local_sender_allow, TRUE, (char **) NULL) != MATCH_MATCHED) &&
		sender_host_addr &&
		! match_ip(sender_host_addr, smtp_remote_allow, ':', ';', FALSE, (char **) NULL)) {

		if (out) {
		     send_smtp_msg(out, 550, "5.1.7", TRUE,
				   "A locally deliverable sender address cannot be used from an unauthorised remote client.\n");
		}
		write_log(WRITE_LOG_SYS, "remote %s: rejected '%q', unauthorised remote client using a %s address; by %s%s%s%s%s%s%s%s%s",
			  smtp_cmd_list[smtp_cmd].name,
			  in_addr,
			  (form == LOCAL) ? "LOCAL" : "MAILBOX",
			  ident_sender ? ident_sender : "",
			  ident_sender ? "@" : "",
			  sender_host ? sender_host : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
			  sender_host_addr ? "[" : "",
			  sender_host_addr ? sender_host_addr : "",
			  sender_host_addr ? "]" : "");
		free_addr(addr);

		return 0;			/* do not allow this sender address */
	    } else
#endif
	    {
		DEBUG5(DBG_REMOTE_MID, "verify_addr(%v, out, %s): allowing %s(%d) addressing form%s.\n",
		       in_addr,
		       smtp_cmd_list[smtp_cmd].name,
		       (form == LOCAL) ? "LOCAL" : "MAILBOX",
		       form,
		       (smtp_cmd == MAIL_CMD && smtp_local_sender_restrict && sender_host_addr && smtp_remote_allow) ? " (authorised local sender address)" :
		       (smtp_cmd == MAIL_CMD && !smtp_local_sender_restrict) ? " (unrestricted local sender address)" :
		       (smtp_cmd == MAIL_CMD && smtp_local_sender_restrict) ? " (unverified local sender address)" : "");
	    }
	    break;			/* OK */

	case BERKENET:
	case DECNET:
	    if (out) {
		sleep((smtp_error_delay > 2) ? (smtp_error_delay / 2) : 2);
		dprintf(out, "553-%s '%q'.\r\n",
			(smtp_cmd == VRFY_CMD) ? "5.1.3 rejecting address being verified" :
			(smtp_cmd == RCPT_CMD) ? "5.1.3 rejecting attempts to send to address" :
			(smtp_cmd == MAIL_CMD) ? "5.1.7 rejecting all mail from sender" :
			"5.1.0 rejecting address",
			in_addr);
		fflush(out);
		sleep(smtp_error_delay > 0 ? smtp_error_delay : 2);
		fprintf(out, "553 %s  Address cannot be a %s form address.\r\n",
			(smtp_cmd == VRFY_CMD) ? "5.1.3" :
			(smtp_cmd == RCPT_CMD) ? "5.1.3" :
			(smtp_cmd == MAIL_CMD) ? "5.1.7" :
			"5.1.0",
			(form == BERKENET) ? "BERKENET" : "DECNET");
		fflush(out);
	    }
	    write_log(WRITE_LOG_SYS, "remote %s: '%q' is a %s address; by %s%s%s%s%s%s%s%s%s",
		      smtp_cmd_list[smtp_cmd].name,
		      in_addr,
		      (form == BERKENET) ? "BERKENET" : "DECNET",
		      ident_sender ? ident_sender : "",
		      ident_sender ? "@" : "",
		      sender_host ? sender_host : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
		      sender_host_addr ? "[" : "",
		      sender_host_addr ? sender_host_addr : "",
		      sender_host_addr ? "]" : "");
	    free_addr(addr);

	    return 0;			/* do not allow this address */

	default:
	    /*
	     * Do not permit any "route" form sender address and do not permit
	     * any "route" form recipient addresses unless the client is listed
	     * in smtp_remote_allow.
	     */
#ifdef HAVE_BSD_NETWORKING
	    if (smtp_cmd != MAIL_CMD &&
		sender_host_addr &&
		match_ip(sender_host_addr, smtp_remote_allow, ':', ';', FALSE, (char **) NULL)) {
		DEBUG2(smtp_remote_allow ? DBG_ADDR_HI : DBG_ADDR_LO,
		       "permitting route-form address by [%s] to <%v>.\n",
		       sender_host_addr ? sender_host_addr : "UNKNOWN-IP", in_addr);
	    } else
#endif
	    {
		if (out) {
		    sleep((smtp_error_delay > 2) ? (smtp_error_delay / 2) : 2);
		    dprintf(out, "550-%s '%q'.\r\n",
			    (smtp_cmd == VRFY_CMD) ? "5.1.3 rejecting address being verified" :
			    (smtp_cmd == RCPT_CMD) ? "5.1.3 rejecting attempts to send to address" :
			    (smtp_cmd == MAIL_CMD) ? "5.1.7 rejecting all mail from sender" :
			    "5.1.0 rejecting address",
			    in_addr);
		    fflush(out);
		    sleep(smtp_error_delay > 0 ? smtp_error_delay : 2);
		    fprintf(out, "550 %s That addressing form is not permitted.\r\n",
			    (smtp_cmd == VRFY_CMD) ? "5.1.3" :
			    (smtp_cmd == RCPT_CMD) ? "5.1.3" :
			    (smtp_cmd == MAIL_CMD) ? "5.1.7" :
			    "5.1.0");
		    fflush(out);
		}
		write_log(WRITE_LOG_SYS, "remote %s: %s%s%s%s%s%s%s%s%s: '%q' addressing form %s(%d) not permitted: routed to <%q%s%q>",
			  smtp_cmd_list[smtp_cmd].name,
			  ident_sender ? ident_sender : "",
			  ident_sender ? "@" : "",
			  sender_host ? sender_host : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
			  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
			  sender_host_addr ? "[" : "",
			  sender_host_addr ? sender_host_addr : "",
			  sender_host_addr ? "]" : "",
			  in_addr,
			  (form == RFC_ROUTE) ? "RFC_ROUTE" :
		          (form == RFC_ENDROUTE) ? "RFC_ENDROUTE" :
		          (form == UUCP_ROUTE) ? "UUCP_ROUTE" :
		          (form == PCT_MAILBOX) ? "PCT_MAILBOX" : "<bad-route-form!>",
			  form,
			  okay->work_addr,
			  okay->target ? "@" : "",
			  okay->target ? okay->target : "");
		free_addr(addr);

		return 0;			/* do not allow this recipient */
	    }
	}
	if (smtp_cmd != MAIL_CMD && out) {	/* only if NOT called from verify_sender()! */
	    dprintf(out, "250 2.1.0 '%q' %s Okay.\r\n", in_addr, (smtp_cmd == RCPT_CMD) ? "Recipient" : "Mailbox");
	    fflush(out);
	} else {
	    DEBUG2(DBG_REMOTE_LO, "%s '%q' Okay.\n", in_addr, (smtp_cmd == RCPT_CMD) ? "Recipient" : "Mailbox");
	}
	free_addr(addr);

	return 1;

    } else if (defer) {
	if (out) {
	    dprintf(out, "450-4.3.0 %s the address '%q' is being deferred.\r\n",
		    (smtp_cmd == VRFY_CMD) ? "Your attempt to verify" :
		    	(smtp_cmd == RCPT_CMD) ? "Your attempt to send to" :
				(smtp_cmd == MAIL_CMD) ? "All mail from" : "All use of",
		    in_addr);
	    fprintf(out, "450-4.3.0 This address has not been accepted.\r\n");
	    fprintf(out, "450-4.3.0 \r\n450-4.3.0 Reason given was: (ERR%ld)\r\n",
		    defer->error->info & ERR_MASK);
	    fflush(out);
	    /* note the extra spaces in the ESC are a hack to get some additional indentation */
	    send_smtp_msg(out, 450, "4.3.0  ", FALSE, defer->error->message);
	    sleep((smtp_error_delay > 2) ? (smtp_error_delay / 2) : 1);
	    fprintf(out, "450 4.3.0 You may try again later.\r\n");
	    fflush(out);
	}
	/* XXX this may be very noisy if the DNS gets broken.... */
	write_log(WRITE_LOG_SYS, "remote %s: %s%s%s%s%s%s%s%s%s: '%q' temporary failure returned to '%s' while verifying recipient: (ERR%ld) %s",
		  smtp_cmd_list[smtp_cmd].name,
		  ident_sender ? ident_sender : "",
		  ident_sender ? "@" : "",
		  sender_host ? sender_host : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
		  sender_host_addr ? "[" : "",
		  sender_host_addr ? sender_host_addr : "",
		  sender_host_addr ? "]" : "",
		  in_addr,
		  sender,
		  defer->error->info & ERR_MASK,
		  defer->error->message);
	free_addr(addr);

	return 0;			/* do not allow this address though... */

    } else if (fail) {
        int error_code;		        /* SMTP base reply code for negative result */
	char *estatus;			/* RFC 3463 Enhanced status code (ESC) */

	switch (fail->error->info & ERR_MASK) {

	case ERR_107:			/* director driver not found */
	    error_code = 451;		/* local error in processing */
	    estatus = "4.3.5";		/* mail system config error */
	    break;
	case ERR_109:			/* router driver not found */
	    error_code = 453;		/* local error in processing (XXX 453 not in RFC821#4.3) */
	    estatus = "4.3.5";		/* mail system config error */
	    break;
	case ERR_163:			/* lost connection to BIND server */
	    error_code = 454;		/* local error in processing (XXX 454 not in RFC821#4.3) */
	    estatus = "4.4.3";		/* directory server failure */
	    break;
	case ERR_165:			/* BIND server packet format error */
	    error_code = 455;		/* local error in processing (XXX 455 not in RFC821#4.3) */
	    estatus = "4.4.3";		/* directory server failure */
	    break;
	case ERR_192:			/* address mapped to an 'error' director with config problem */
	    error_code = 451;		/* local error in processing */
	    estatus = "4.2.1";		/* mailbox disabled, not accepting messages */
	    break;

	case ERR_100:			/* unknown user */
	    error_code = 550;		/* mailbox not found */
	    estatus = "5.1.1";		/* bad destination mailbox address */
	    break;
	case ERR_101:			/* unknown host */
	    error_code = 551;		/* mailbox not local */
	    estatus = "5.1.2";		/* bad destination system address */
	    break;
	case ERR_104:			/* security violation */
	    error_code = 550;		/* mailbox not local -- relay not permitted */
	    estatus = "5.7.1";		/* delivery not authorized */
	    break;
	case ERR_111:			/* address parse error */
	    error_code = 501;		/* syntax error in parameter */
	    estatus = "5.1.0";		/* not enough info to choose from 5.1.3 or 5.1.7 */
	    break;
	case ERR_168:			/* no valid MX records for host */
	    error_code = 551;		/* mailbox not local */
	    estatus = "5.1.2";		/* bad destination system address */
	    break;
	case ERR_193:			/* address mapped to an 'error' director */
	    error_code = 551;		/* mailbox not local */
	    estatus = "5.2.1";		/* mailbox disabled, not accepting messages */
	    break;

	default:
	    error_code = 559;		/* flags un-xlated errors (XXX 559 not in RFC821#4.3) */
	    estatus = "5.0.0";		/* unknown permanent error */
	    break;
	}
	if (out) {
	    if (error_code >= 500 && (((fail->error->info & ERR_MASK) != ERR_100) ||
				      smtp_invalid_recipient_error_delay)) {
		sleep(1);
	    }
	    dprintf(out, "%d-%s %s the address '%q' is being %s.\r\n",
		    error_code, estatus,
		    (smtp_cmd == VRFY_CMD) ? "Your attempt to verify" :
		    	(smtp_cmd == RCPT_CMD) ? "Your attempt to send to" :
				(smtp_cmd == MAIL_CMD) ? "All mail from" : "All use of",
		    in_addr,
		    (error_code >= 500) ? "rejected" : "deferred");
	    if (error_code >= 500 && ((fail->error->info & ERR_MASK) != ERR_100)) {
		fflush(out);
		sleep(1);
	    }
	    fprintf(out, "%d-%s This address has not been accepted.\r\n",
		    error_code, estatus);
	    if (error_code >= 500 && (((fail->error->info & ERR_MASK) != ERR_100) ||
				      smtp_invalid_recipient_error_delay)) {
		fflush(out);
		sleep(1);
	    }
	    fprintf(out, "%d-%s \r\n", error_code, estatus);
	    fprintf(out, "%d-%s Reason given was: (ERR%ld)\r\n",
		    error_code, estatus,
		    fail->error->info & ERR_MASK);
	    fprintf(out, "%d-%s \r\n", error_code, estatus);
	    /* note the extra spaces in the ESC are a hack to get some additional indentation */
	    errstr = xprintf("%s  ", estatus);
	    send_smtp_msg(out, error_code, errstr, FALSE, fail->error->message);
	    xfree(errstr);
	    if (error_code < 500) {
		sleep((smtp_error_delay > 2) ? (smtp_error_delay / 2) : 1);
	    } else if ((fail->error->info & ERR_MASK) == ERR_100) {
		sleep(smtp_invalid_recipient_error_delay);
	    } else {
		sleep(smtp_error_delay);
	    }
	    fprintf(out, "%d-%s \r\n", error_code, estatus);
	    fprintf(out, "%d %s %s\r\n",
		    error_code, estatus,
		    (error_code < 500) ? "You may try again later." :
			((smtp_cmd == MAIL_CMD) ? "Your e-mail address may not be correctly configured in your mailer." :
				"A permanent failure has been logged."));
	    fflush(out);
	}
	write_log(WRITE_LOG_SYS, "remote %s: %s%s%s%s%s%s%s%s%s: recipient '%q', for sender '%s', not deliverable: (ERR%ld) %s",
		  smtp_cmd_list[smtp_cmd].name,
		  ident_sender ? ident_sender : "",
		  ident_sender ? "@" : "",
		  sender_host ? sender_host : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
		  (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
		  sender_host_addr ? "[" : "",
		  sender_host_addr ? sender_host_addr : "",
		  sender_host_addr ? "]" : "",
		  in_addr,
		  sender,
		  fail->error->info & ERR_MASK,
		  fail->error->message);
	free_addr(addr);

	return 0;
    } /* else */
    /*
     * hmmm....  We shouldn't have gotten here as the address should have been
     * put on one of the output lists from resolve_addr_list() or
     * check_smtp_remote_allow(), so we die, but not quietly!
     */
    if (out) {
	sleep((smtp_error_delay > 2) ? (smtp_error_delay / 2) : 2);
	dprintf(out, "521-5.1.0 rejecting address '%q'.\r\n", in_addr);
	fflush(out);
	sleep(smtp_error_delay > 0 ? smtp_error_delay : 2);
	dprintf(out, "521 5.1.0 The address <%q%s%q> is not deliverable here!\r\n",
		addr->work_addr,
		addr->target ? "@" : "",
		addr->target ? addr->target : "");
	fflush(out);
    }
    /*
     * note -- there's no real need to free_addr(addr) since we're about to
     * exit, and it would impossible to do anyway as we use it in the panic()
     * parameters
     */
    panic(EX_SOFTWARE, "remote %s: %s%s%s%s%s%s: '%q': recipient <%q%s%q>, for sender '%s', caused resolve_addr_list() or check_smtp_remote_allow() to fail abnormally!",
	  smtp_cmd_list[smtp_cmd].name,
	  ident_sender ? ident_sender : "",
	  ident_sender ? "@" : "",
	  sender_host ? sender_host : "",
	  sender_host_addr ? "[" : "",
	  sender_host_addr ? sender_host_addr : "<no-client-host-addr>",
	  sender_host_addr ? "]" : "",
	  in_addr,
	  addr->work_addr,
	  addr->target ? "@" : "",
	  addr->target ? addr->target : "",
	  sender);
    /* NOTREACHED */
    return 0;
}


/*
 * verify_host - verify the sender hostname syntax and see if it is in the DNS
 *
 * Returns a copy of hostnm if verify succeeds.
 * (possibly with the text that followed a domain literal form appended).
 *
 * WARNING: can currently modify the original hostnm storage too....
 */
static char *
verify_host(hostnm, fromaddr, errorp, fatalp)
    char *hostnm;	/* name to check (may be ASCII-ified) */
    void *fromaddr;	/* possibly a struct sockaddr_in * from getpeername() */
    char **errorp;	/* text describing any error or omission */
    int *fatalp;	/* is this a fatal, or temporary, error? */
{
    char *p;
    int allowed_to_be_broken = FALSE;
    int found_dot;
#ifdef HAVE_BIND
    int found_ptr;
    int aresult_hostnm;
    struct error *binderr = NULL;
    char *binderrmsg;
#endif
#ifdef HAVE_BSD_NETWORKING
    struct hostent *shp = NULL;
    struct sockaddr_in *saddrp = fromaddr; /* result of getpeername() */
#endif

    if (peer_is_localhost
#ifdef HAVE_BSD_NETWORKING
	|| match_ip(inet_ntoa(saddrp->sin_addr), smtp_hello_broken_allow, ':', ';', FALSE, (char **) NULL)
#endif
       ) {
	/*
	 * if the client is running on this host, or is in the broken_allow
	 * list, then set this flag to override any fatal errors, as well as
	 * some of the non-fatal errors that are not likely to get fixed....
	 */
	allowed_to_be_broken = TRUE;
    }

    if (*hostnm == '[') {		/* looks like a literal IP address.... */
#ifdef HAVE_BSD_NETWORKING
	in_addr_t inet;			/* internet address */
	char *comment = NULL;		/* optional message after address literal */
	char *checked_hostnm;		/* return value */
	struct hostent *srhp = NULL;

	p = strchr(hostnm, ']');
	if (!p) {
	    DEBUG1(DBG_REMOTE_LO, "verify_host(%v): Invalid address literal, missing closing ']'.\n", hostnm);
	    *errorp = "Invalid host address literal, missing closing ']'";
	    *fatalp = TRUE;		/* syntax errors are always fatal */
	    return NULL;
	} else if (*(p+1)) {
	    comment = p + 1;		/* mark the start of any additional identifying information */
	}
	if (p) {
	    *p = '\0';			/* XXX should only modify a copy... */
	}
	inet = get_inet_addr(&hostnm[1]);
	if (p) {
	    *p = ']';
	}
	DEBUG5(DBG_REMOTE_HI,
	       "verify_host(%v): inet from helo: [0x%lx] aka %s, actual [0x%lx] aka %s.\n",
	       hostnm,
	       ntohl(inet), COPY_STRING(inet_ntoa(inet_makeaddr((in_addr_t) ntohl(inet), (in_addr_t) 0))), /* HELO */
	       ntohl(saddrp->sin_addr.s_addr), COPY_STRING(inet_ntoa(saddrp->sin_addr))); /* actual */
	if (inet == INADDR_NONE) {
	    DEBUG1(DBG_REMOTE_LO, "verify_host(%v): inet_addr() failed: bad host address form.\n", hostnm);
	    *errorp = "Invalid host address literal";
	    *fatalp = TRUE;		/* syntax error are always fatal */
	    return NULL;
	} else if (inet != saddrp->sin_addr.s_addr) {
	    DEBUG3(DBG_REMOTE_LO, "verify_host(%v): [0x%lx] != [0x%lx].\n", hostnm, ntohl(inet), ntohl(saddrp->sin_addr.s_addr));
	    *errorp = "host address literal does not match remote client address";
	    if (smtp_hello_verify_literal) {
		if (! allowed_to_be_broken) {
		    *fatalp = TRUE;
		    return NULL;		/* broken DNS or mailer config; or forger */
		} else {
		    sender_host_invalid = *errorp;
		}
	    }
	}
	if (sender_host_really) {
	    /*
	     * XXX we should probably be using bind_check_if_canonical_host() here instead
	     */
	    if (!(srhp = gethostbyname(sender_host_really))) {
		DEBUG2(DBG_REMOTE_LO, "verify_host(%v) gethostbyname() for verify_literal failed: %s.\n", sender_host_really, hstrerror(h_errno));
		/*
		 * NOTE: if you get a compiler warning about either hstrerror()
		 * or h_errno not being declared, or something like "illegal
		 * combination of pointer and integer", then you are not
		 * compiling with the headers from a modern enough resolver
		 * library!  (regardless of what you think you're doing ;-)
		 */
		*errorp = COPY_STRING(hstrerror(h_errno));
		if (smtp_hello_verify_literal) {
		    if (! allowed_to_be_broken) {
			switch (h_errno) {
			case TRY_AGAIN:
			    *fatalp = FALSE;
			    break;
			case HOST_NOT_FOUND:
			case NO_DATA:
			case NO_RECOVERY:
#if defined(NO_ADDRESS) && ((NO_ADDRESS - 0) != (NO_DATA))
			case NO_ADDRESS:
#endif
			default:
			    *fatalp = TRUE;
			    break;
			}
			return NULL;	/* broken DNS or mailer config; or forger */
		    } else {
			sender_host_invalid = *errorp;
		    }
		}
	    } else {
		int i;
		int found_a;

		found_a = FALSE;
		for (i = 0; srhp->h_addr_list[i]; i++) {
		    if (memcmp(srhp->h_addr_list[i], (char *) &(saddrp->sin_addr),
			       sizeof(saddrp->sin_addr)) == 0) {
			found_a = TRUE;
			break;		/* name is good, keep it */
		    }
		}
		if (!found_a) {
		    DEBUG3(DBG_REMOTE_LO, "verify_host(%v) gethostbyname() does not find matching A [%s] for PTR %s.\n",
			   hostnm, inet_ntoa(saddrp->sin_addr), sender_host_really);
		    *errorp = "No matching address found at the hostname given by address literal's reverse DNS PTR";
		    if (smtp_hello_verify_literal) {
			if (! allowed_to_be_broken) {
			    *fatalp = TRUE;
			    return NULL;	/* broken DNS or mailer config; or forger */
			} else {
			    sender_host_invalid = *errorp;
			}
		    }
		}
	    }
	} else {
	    *errorp = "Remote address PTR lookup failed.  Host address literals are denied if there is no valid reverse DNS PTR found";
	    if (smtp_hello_verify_literal) {
		if (! allowed_to_be_broken) {
		    *fatalp = TRUE;
		    return NULL;		/* broken DNS or mailer config; or forger */
		} else {
		    sender_host_invalid = *errorp;
		}
	    }
	}
	
	if (comment && !*errorp) {
	    /* this will probably look a little funny in the log */
	    *errorp = xprintf("Client's excuse for using domain literal: '%v'", comment);
	}
	/* re-form this just to avoid having to truncate 'data' at 'comment' */
	checked_hostnm = xprintf("[%s]", COPY_STRING(inet_ntoa(inet_makeaddr((in_addr_t) ntohl(inet), (in_addr_t) 0))));

	return checked_hostnm;
#else
	*errorp = "Host address literals are not supported by this system";
	*fatalp = TRUE;
	return NULL;
#endif /* HAVE_BSD_NETWORKING */
    } /* else not a literal */

    /*
     * now check the syntax of the name itself...
     *
     * don't allow underscores if not allowed_to_be_broken
     */
    if (! rfc1035_is_valid_domainname(hostnm, allowed_to_be_broken, errorp)) {
	*fatalp = TRUE;			/* syntax errors are always fatal */
	return NULL;
    }
    if (*errorp) {			/* underscore error if allowed_to_be_broken */
	sender_host_invalid = *errorp;
    }

    /*
     * This is a special case where we call expand_string() so that we can
     * use variable names in a list, since in this case it makes the most
     * sense and is the least error prone to just always include
     * "${rxquote:hostnames}" in the list.
     *
     * Note that if the expand_string() fails then the result is as if
     * smtp_hello_reject_hostnames is not set (though maybe a paniclog
     * entry will be written as a warning).
     *
     * Note also there is no user-controllable exception list here.  A quick
     * and very dirty hack would be to bypass this check if the client was in
     * the smtp_hello_broken_allow list, but that would greatly overload the
     * meaning of an already far too overloaded control.  Adding yet another
     * bypass list though should also not really be necessary since usually a
     * wee bit of fancy regex logic will allow most offenders to be temporarily
     * bypassed without too much risk of mis-identification (though it doesn't
     * allow for strict identification by source address).  Just be sure you
     * only bypass with completely matched names, and only make the bypass
     * temporary as otherwise abusers will learn it, and they will spoof it.
     */
    if (!peer_is_localhost && smtp_hello_reject_hostnames &&
	match_hostname(hostnm,
		       expand_string(smtp_hello_reject_hostnames, (struct addr *) NULL, (char *) NULL, (char *) NULL),
		       &p) == MATCH_MATCHED) {
	*errorp = xprintf("matched in smtp_hello_reject_hostnames%s%v",
			  p ? ": " : "",
			  p ? p : "");
	*fatalp = TRUE;
	return NULL;
    }

#if defined(HAVE_BSD_NETWORKING) && defined(HAVE_BIND)
    aresult_hostnm = bind_check_if_canonical_host(hostnm, (in_addr_t) saddrp->sin_addr.s_addr, &binderr);

    binderrmsg = (binderr && binderr->message && *binderr->message) ?
	    binderr->message :
	    (aresult_hostnm == DB_SUCCEED) ? "(none)" : "no resolver error messsage given";

    DEBUG4(aresult_hostnm == DB_SUCCEED ? DBG_REMOTE_HI : DBG_REMOTE_LO,
	   "verify_host(): greeting name: bind_check_if_canonical_host(%v, %s): %s, %s.\n",
	   hostnm, inet_ntoa(saddrp->sin_addr), LOOKUP_DBG_NAME(aresult_hostnm), binderrmsg);

    /*
     * Ignore bind errors if the intput was an unqualified hostname, and just
     * complain about that instead.
     */
    found_dot = strchr(hostnm, '.') ? TRUE : FALSE;
    if (aresult_hostnm != DB_SUCCEED && !found_dot) {
	*errorp = "a hostname must be fully qualified with a proper domain name suffix";
	DEBUG2(DBG_REMOTE_LO, "verify_host(): no dots in %v: %s.\n", hostnm, *errorp);
	/*
	 * An unqualified non-existant hostname is always an error even if
	 * we're not verifying the greeting name.
	 */
	if (!allowed_to_be_broken) {
	    *fatalp = TRUE;		/* this is not a temporary error! */
	    return NULL;
	} else {
	    sender_host_invalid = *errorp;
	}
    }

    switch (aresult_hostnm) {
    case DB_SUCCEED:		/* found the host! */
	*fatalp = FALSE;	/* just in case something below returns NULL */
	break;

    case DB_NOMATCH:		/* no such domain (i.e. no _matching_ A RR) */
	if (found_dot) {	/* don't overwrite the "not a FQDN" message above */
	    *errorp = binderrmsg;
	}
	sender_host_invalid = *errorp;
	if (smtp_hello_verify && !allowed_to_be_broken) {
	    *fatalp = TRUE;	/* this is not a temporary error! */
	    return NULL;
	}
 	if (allowed_to_be_broken) {
	    *fatalp = FALSE;	/* not fatal, for now.... */
	}
	break;

    case FILE_NOMATCH:		/* There is no server available */
	if (found_dot) {	/* don't overwrite the "not a FQDN" message set above */
	    *errorp = binderrmsg;
	}
	sender_host_invalid = *errorp;
	if (smtp_hello_verify && !allowed_to_be_broken) {
	    *fatalp = FALSE;	/* ... just a temporary error */
	    return NULL;
	}
	break;

    case DB_AGAIN:		/* DNS lookup must be deferred */
    case FILE_AGAIN:		/* lost contact with server */
	*errorp = xprintf("Temporary DNS problem encountered while trying to verify host '%v': %s", hostnm, binderrmsg);
	sender_host_invalid = *errorp;
	if (smtp_hello_verify && !allowed_to_be_broken) {
	    *fatalp = FALSE;
	    return NULL;
	}
	break;

    case DB_FAIL:		/* bad DNS request? */
    case FILE_FAIL:		/* major DNS error! */
	*errorp = xprintf("DNS failure trying to verify host '%v': %s", hostnm, binderrmsg);
	sender_host_invalid = *errorp;
	if (smtp_hello_verify && !allowed_to_be_broken) {
	    /*
	     * XXX it might be nice to return a local configuration error here,
	     * but that would mean a re-design....
	     */
	    *fatalp = TRUE;
	    return NULL;
	}
	break;

    default:			/* impossible! */
	DEBUG2(DBG_REMOTE_LO, "verify_host(%v): bind_check_if_canonical_host() impossible result %d.\n", hostnm, aresult_hostnm);
	*errorp = xprintf("Fatal internal error encountered while trying to verify host '%v':(%s)", hostnm, binderrmsg);
	sender_host_invalid = *errorp;
	if (smtp_hello_verify && !allowed_to_be_broken) {
	    *fatalp = TRUE;		/* a reject is best at this point... */
	    return NULL;
	}
	break;
    }

    /*
     * note: don't touch sender_host_invalid after this -- it is only intended
     * to relate to the sender_host error if it is allowed_to_be_broken.
     */

    /*
     * Note that errors in the following section are less important than those
     * above, so if there was a problem detected above then it should be
     * presented more prominently....
     *
     * XXX these tests should be moved to a separate function so that they
     * won't get logged with text that says "remote EHLO: rejected: invalid
     * operand: 'blah'", only to be followed by something completely different
     * about the PTR or whatever.
     */
    if (sender_host_addr) {
	if (!(shp = gethostbyaddr((char *) &(saddrp->sin_addr),
				  (socklen_t) sizeof(saddrp->sin_addr), saddrp->sin_family))) {
	    DEBUG3(DBG_REMOTE_LO, "verify_host(%v): gethostbyaddr([%s]) failed: %s.\n",
		   hostnm, sender_host_addr, hstrerror(h_errno));
	}
    }
    if (shp) {
	size_t hlen = strlen(hostnm);
	int aresult_ptrname = DB_SUCCEED;

	if (hostnm[hlen-1] == '.') {
	    /* XXX what if there's more than one? */
	    hostnm[hlen-1] = '\0';	/* the DNS can't return trailing dots */
	}
	/*
	 * just as you need "IP -> name -> IP" to verify a PTR has the right
	 * name, you similarly need "name -> IP -> name" to verify that a name
	 * has the right IP address.
	 */
	found_ptr = FALSE;
	if (EQIC(hostnm, shp->h_name)) {
	    DEBUG1(DBG_REMOTE_HI, "verify_host():  The first PTR matched: %v\n", shp->h_name);
	    found_ptr = TRUE;		/* the first name matched */
	} else {
	    int i;

	    DEBUG2(DBG_REMOTE_LO, "verify_host(%v):  boo-hoo!  The first PTR did not match: %v\n", hostnm, shp->h_name);
	    for (i = 0; (p = (shp->h_aliases)[i]); i++) { /* do any other names match? */
		if (EQIC(hostnm, p)) {
		    DEBUG2(DBG_REMOTE_HI, "verify_host():  YEAH!  The [%d] PTR matched: %v\n", i, p);
		    found_ptr = TRUE;	/* a subsequent name matched */
		    break;		/* yeah! */
		} else {
		    DEBUG3(DBG_REMOTE_LO, "verify_host(%v): Oi!  The [%d] PTR did not match: %v\n", hostnm, i, p);
		}
	    }
	}
	if (found_ptr) {
	    /*
	     * reset sender_host_really so it will be == sender_host & no
	     * mismatch will be reported in the Received: header
	     *
	     * (XXX unless we trimmed a trailing dot above, or did we actually
	     * trim it in the caller's storage?)
	     */
	    if (sender_host_really) {
		xfree(sender_host_really);
	    }
	    /* the use of %q shouldn't be necessary here, but for consistency... */
	    sender_host_really = xprintf("%q", hostnm);
	} else {
	    DEBUG1(DBG_REMOTE_LO, "verify_host(%v):  sigh, no PTR matched\n", hostnm);
	}
	/*
	 * if there is a primary PTR target name to check, and the client is
	 * not in smtp_hello_broken_allow, and...
	 * 
	 * if we're not using smtp_hello_verify, and there was no matching PTR
	 * found....
	 *
	 * or if we're asked to smtp_hello_reject_broken_ptr....
	 */
	if (sender_host_really && !allowed_to_be_broken &&
	    ((!found_ptr && !smtp_hello_verify) ||
	     smtp_hello_reject_broken_ptr)) {
	    char *binderr2;

	    /*
	     * ... then make one last-chance check to see if at least the "IP
	     * -> name -> IP" mapping from the primary PTR is consistent....
	     */
	    aresult_ptrname = bind_check_if_canonical_host(sender_host_really, (in_addr_t) saddrp->sin_addr.s_addr, &binderr);
	    
	    binderr2 = (binderr && binderr->message && *binderr->message) ?
		       binderr->message :
		       (aresult_ptrname == DB_SUCCEED) ? "(none)" : "no resolver error messsage given";

	    DEBUG4(aresult_ptrname == DB_SUCCEED ? DBG_REMOTE_HI : DBG_REMOTE_LO,
		   "verify_host(): PTR name: bind_check_if_canonical_host(%v, %s): %s, %s.\n",
		   sender_host_really, inet_ntoa(saddrp->sin_addr), LOOKUP_DBG_NAME(aresult_ptrname), binderr2);

	    switch (aresult_ptrname) {
	    case DB_SUCCEED:
		/*
		 * if smtp_hello_verify is turned off, and if the greeting name
		 * has not already been found in the PTR list, then we let them
		 * get away with just the "IP -> name -> IP" mapping.
		 */
		found_ptr = TRUE;
		break;

	    case DB_NOMATCH:		/* no such domain (i.e. no _matching_ A RR) */
		if (found_dot) {	/* don't overwrite the "not a FQDN" message set above for hostnm */
		    *errorp = xprintf("Fatal rDNS hostname verification error for '%v': %s", sender_host_really, binderr2);
		    sender_host_really_invalid = *errorp;
		}
		if (smtp_hello_reject_broken_ptr) {
		    *fatalp = TRUE;	/* this is not a temporary error! */
		    return NULL;
		}
		break;

	    case FILE_NOMATCH:		/* There is no server available */
		if (found_dot) {	/* don't overwrite the "not a FQDN" message set above for hostnm */
		    *errorp = xprintf("Temporary rDNS hostname lookup error for '%v': %s", sender_host_really, binderr2);
		}
		*fatalp = FALSE;	/* ... just a temporary error */
		return NULL;

	    case DB_AGAIN:		/* DNS lookup must be deferred */
	    case FILE_AGAIN:		/* lost contact with server */
		*errorp = xprintf("Temporary DNS problem encountered while trying to verify rDNS hostname '%v': %s",
				  sender_host_really, binderr2);
		*fatalp = FALSE;	/* ... just a temporary error */
		return NULL;

	    case DB_FAIL:		/* bad DNS request? */
	    case FILE_FAIL:		/* major DNS error! */
		*errorp = xprintf("DNS failure trying to verify rDNS host '%v': %s",
				  sender_host_really, binderr2);
		*fatalp = TRUE;	/* ... actually probably a local configuration error */
		return NULL;

	    default:			/* impossible! */
		DEBUG2(DBG_REMOTE_LO, "verify_host(%v): bind_check_if_canonical_host() impossible result %d.\n",
		       sender_host_really, aresult_ptrname);
		*errorp = xprintf("Fatal internal error encountered while trying to verify rDNS hostname '%v':(%s)",
				  sender_host_really, binderr2);
		*fatalp = TRUE;	/* a reject is best at this point... */
		return NULL;
	    }
	}
	/* found_ptr could have been reset by the last-ditch check. */
	if (!found_ptr) {
	    /*
	     * If we did not find a "IP -> name" match then the name is bogus
	     * according to the reverse DNS.
	     */
	    DEBUG2(DBG_REMOTE_LO, "verify_host(%v): gethostbyaddr(%s) gave no matching PTR.\n", hostnm, inet_ntoa(saddrp->sin_addr));
	    if (allowed_to_be_broken) {
		sender_host_really_invalid = xprintf("no PTR matching %v%s%s%s",
						     hostnm,
						     *errorp ? " (" : "",
						     *errorp ? *errorp : "",
						     *errorp ? ")" : "");
	    }
	    *errorp = xprintf("%s%sNone of the existing reverse DNS PTRs for the address [%s] has a hostname matching '%v'.  Either your mailer's reverse DNS is misconfigured,%s or a DNS spoofing attempt is underway%s",
			      *errorp ? *errorp : "",
			      *errorp ? " (" : "",
			      inet_ntoa(saddrp->sin_addr),
			      hostnm,
			      smtp_hello_verify ? "" : " or your mailer's hostname is incorrect,",
			      *errorp ? ")" : "");
	    if ((smtp_hello_reject_broken_ptr || smtp_hello_verify_ptr)) {
		if (!allowed_to_be_broken) {
		    *fatalp = TRUE;
		    DEBUG1(DBG_REMOTE_LO, "verify_host(%v): rejecting due to no matching PTR.\n", hostnm);
		    return NULL;	/* broken DNS or mailer config; or forger */
		}
	    }
	}
    } else {
	*errorp = xprintf("%s%sRemote address PTR lookup failed: %s%s",
			  *errorp ? *errorp : "",
			  *errorp ? " (" : "",
			  hstrerror(h_errno),
			  *errorp ? ")" : "");
	sender_host_really_invalid = *errorp;
	if (smtp_hello_verify_ptr && !allowed_to_be_broken) {
	    switch (h_errno) {
	    case TRY_AGAIN:
		*fatalp = FALSE;
		break;
	    case HOST_NOT_FOUND:
	    case NO_DATA:
	    case NO_RECOVERY:
#if defined(NO_ADDRESS) && ((NO_ADDRESS - 0) != (NO_DATA))
	    case NO_ADDRESS:
#endif
	    default:
		*fatalp = TRUE;
		break;
	    }
	    return NULL;		/* broken DNS or mailer config; or forger */
	}
    }
#endif /* HAVE_BSD_NETWORKING && HAVE_BIND */

    *fatalp = FALSE;
    /* the use of %q shouldn't be necessary here, but for consistency... */
    return xprintf("%q", hostnm);
}

/*
 * check to see if the resolved address is local or remote, and if remote
 * whether the sender is permitted to relay via SMTP according to
 * smtp_remote_allow.
 */
static void
check_smtp_remote_allow(in, out, defer, fail)
    struct addr *in;			/* address to test, from resolve_addr_list() */
    struct addr **out;			/* produced addr list w/transports */
    struct addr **defer			/* addrs to defer to a later time */
#ifndef HAVE_BSD_NETWORKING
		__attribute__((unused))
#endif
	;
    struct addr **fail			/* unresolvable addrs */
#ifndef HAVE_BSD_NETWORKING
		__attribute__((unused))
#endif
	;
{
#ifdef HAVE_BIND
    int mxresult;
    int local_precedence;
#endif

    if (in->transport->flags & LOCAL_TPORT) {
	DEBUG2(DBG_ADDR_HI, "check_smtp_remote_allow(%v): OK: address has local transport: %v.\n",
	       in->in_addr, in->transport->name);
	*out = in;

	return;
    }
#ifdef HAVE_BSD_NETWORKING
    if (! EQ(in->transport->driver, TCPSMTP_TRANSPORT_DRV_NM)) {
	DEBUG4(DBG_ADDR_HI, "check_smtp_remote_allow(%v): OK: transport[%v] driver is not %s: %s.\n",
	       in->in_addr, in->transport->name, TCPSMTP_TRANSPORT_DRV_NM, in->transport->driver);
	*out = in;

	return;
    }
#endif
    /*
     * any with a parent *MAY* be local
     *
     * We must allow remote routing via aliases, but we don't want to allow the
     * sender to specify an arbirary value that might resolve to a remote
     * address.  Routers should ensure they don't dive into the local part and
     * try to make too much of it, but watch out as this has been a problem in
     * the past with the likes of the rewrite router.
     */
    if (in->parent) {
	DEBUG2(DBG_ADDR_HI, "check_smtp_remote_allow(%v): OK: address has local parent, from '%s' director.\n",
	       in->parent->in_addr, in->director ? in->director->name : "(INVALID-DIRECTOR)");
	*out = in;

	return;
    } else if (in->succ) {
	/*
	 * "in" must not point to a list unless it was generated by a local
	 * director -- and every director will set "parent"
	 */
	write_log(WRITE_LOG_PANIC, "check_smtp_remote_allow(): internal error: passed a list for a non-local address!");
	return;
    }
    /*
     * Assume if we get this far then the address is remote and will be routed
     * back out via SMTP, so check to see if sender is allowed to relay through
     * us.  We do this first to avoid unnecessary DNS lookups should the sender
     * in fact be listed in smtp_remote_allow.
     *
     * Note here we require smtp_remote_allow to be set and non-empty to ensure
     * that the test is valid.
     */
#ifdef HAVE_BSD_NETWORKING
    if (sender_host_addr) {
	char *reason = NULL;

	if (!smtp_remote_allow || !*smtp_remote_allow) {
	    char *error_text;

	    error_text = xprintf("cannot verify permission to relay to %q:  smtp_remote_allow is not set", in->target);
	    in->error = note_error(ERR_CONFERR | ERR_104, error_text); /* not exactly the right code */
	    *defer = in;

	    return; 
	}
	if (match_ip(sender_host_addr, smtp_remote_allow, ':', ';', FALSE, &reason)) {
	    DEBUG3(smtp_remote_allow ? DBG_ADDR_HI : DBG_ADDR_LO,
		   "permitting remote relay by [%s] to <%v> (%s).\n",
		   sender_host_addr ? sender_host_addr : "UNKNOWN-IP",
		   in->in_addr,
		   reason ? reason : "");
	    *out = in;

	    return;
	}
    } else /* if (!stdout) */
#endif
    {
	/*
	 * XXX this should not ever happen -- callers should always
	 * pre-authorize based on whether they're attached to an output file
	 * handle or not and should not rely on sender_host_addr alone
	 */
	DEBUG2(smtp_remote_allow ? DBG_ADDR_HI : DBG_ADDR_LO,
	       "permitting remote relay by [LOCALHOST] to <%v>.\n",
	       sender_host_addr ? sender_host_addr : "UNKNOWN-IP",
	       in->in_addr);
	*out = in;

	return;
    }

#ifdef HAVE_BIND 
    /*
     * Lastly we can allow relay if the local host MXs for the target
     *
     * However if smtp_permit_mx_backup is set then don't even bother looking
     * for MX records unless the target is matched in some entry in that list.
     *
     * XXX ignore errors for now...
     */
    if (smtp_permit_mx_backup &&
	match_hostname(in->target, smtp_permit_mx_backup, (char **) NULL) != MATCH_MATCHED) {

	DEBUG1(DBG_ADDR_LO, "skipping check to see if local MX'es for '%v' -- not in smtp_permit_mx_backup.\n", in->target);
	invalid_relay_error(in, sender_host_addr);
	*fail = in;

	return;
    }
    switch ((mxresult = bind_check_if_local_mxs(in->target, &local_precedence, &(in->error)))) {
    case DB_SUCCEED:
	if (local_precedence >= 0) {
	    char *dmmytg = NULL;
	    char *dmmyrem = NULL;
	    int dmmyflg = 0;

	    /* it's OK -- we MX for them, UNLESS the MX'ed host isn't the final
	     * target so check the remainder for non-local addressing forms
	     */
	    if (parse_address(in->remainder, &dmmytg, &dmmyrem, &dmmyflg) != LOCAL) {
		invalid_relay_error(in, sender_host_addr);
		*fail = in;
	    } else {
		DEBUG2(smtp_remote_allow ? DBG_ADDR_HI : DBG_ADDR_LO,
		       "permitting relay by [%s] to MX'ed host %v.\n",
		       sender_host_addr ? sender_host_addr : "UNKNOWN-IP", in->target);
		*out = in;
	    }
	} else {
	    invalid_relay_error(in, sender_host_addr);
	    *fail = in;
	}
	break;

    case DB_AGAIN:			/* DNS lookup must be deferred */
    case FILE_AGAIN:			/* lost contact with server, etc. */
    case FILE_NOMATCH:			/* could not connect to server */
	if (!in->error) {
	    char *error_text;

	    error_text = xprintf("defer checking for MXs for %q", in->target);
	    in->error = note_error(ERR_DONTLOG | ERR_163, error_text);
	}
	*defer = in;
	break;

    case DB_NOMATCH:			/* no such domain */
    case DB_FAIL:			/* bad DNS request? */
	if (!in->error) {
	    char *error_text;

	    error_text = xprintf("%q is not a valid domain", in->target);
	    in->error = note_error(ERR_NPOWNER | ERR_168, error_text);
	}	
	*fail = in;
	break;

    case FILE_FAIL:			/* major DNS error! */
	if (!in->error) {
	    char *error_text;

	    error_text = xprintf("error checking for MXs for %q", in->target);
	    in->error = note_error(ERR_NPOWNER | ERR_165, error_text);
	}
	*fail = in;
	break;

    default:				/* panic! */
	if (!in->error) {
	    char *error_text;

	    error_text = xprintf("fatal internal error %d checking for MXs for %q.", mxresult, in->target);
	    in->error = note_error(ERR_NPOWNER | ERR_165, error_text);
	}
	*fail = in;
	break;
    }
    return;

#endif /* HAVE_BIND */
}


/*
 * smtp_input_signals - setup signals for reading the message (DATA phase)
 *
 * Basically, unlink the message except in the case of SIGHUP (reload configs),
 * which will cause the queue_only flag to be set to prevent immediate
 * delivery.
 *
 * Note SIGALRM is always set to call smtp_receive_timeout_sig(), and SIGHUP is
 * always set to call smtp_reload_sig().
 */
static void
smtp_input_signals()
{
    /* leave SIGINT alone if it's already being ignored */
    if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
	(void) signal(SIGINT, smtp_sig_unlink);
    }
    (void) signal(SIGTERM, smtp_sig_unlink);
}

/*
 * smtp_processing_signals - setup signals for getting SMTP commands
 *
 * Note SIGALRM is always set to call smtp_receive_timeout_sig(), and SIGHUP is
 * always set to call smtp_reload_sig().
 */
static void
smtp_processing_signals()
{
    /* leave SIGINT alone if it's already being ignored */
    if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
	(void) signal(SIGINT, smtp_receive_timeout_sig);
    }
    (void) signal(SIGTERM, smtp_receive_timeout_sig);
}

/*
 * smtp_reset_signals - reset signals (and alarms)
 *
 * just before closing the connection, i.e. after QUIT or other major errors,
 * we basically ignore all the interactive signals -- once receive_smtp()
 * returns then signals will be reset as appropriate for the processing and
 * delivery stages (if necessary).
 */
static void
smtp_reset_signals()
{
    (void) alarm((unsigned int) 0);
    (void) signal(SIGALRM, SIG_IGN);
    (void) signal(SIGHUP, SIG_IGN);
    (void) signal(SIGINT, SIG_IGN);
    (void) signal(SIGTERM, SIG_IGN);
}

/*
 * handle_reload_signal - set queue_only flag
 *
 * This is used to prevent immediate delivery of the incoming message --
 * configs should be reloaded first!
 */
static void
smtp_reload_sig(sig)
    int sig;
{
    char signm[SIG2STR_MAX];

    (void) signal(sig, smtp_reload_sig);

    if (sig2str(sig, signm) == -1) {
	sprintf(signm, "#%d", sig);
    }
    DEBUG1(DBG_REMOTE_MID, "smtp_reload_sig(SIG%s) called....\n", signm);

    queue_only = TRUE;
    got_sighup = TRUE;
}

/*
 * smtp_receive_timeout_sig - SMTP timeout (SIGALRM), or SIGINT or SIGTERM
 * received....
 *
 * removes the incomplete message IFF smtp_remove_on_timeout is set
 */
/* ARGSUSED */
static void
smtp_receive_timeout_sig(sig)
    int sig;
{
    char signm[SIG2STR_MAX];

    (void) signal(sig, SIG_IGN);

    if (sig2str(sig, signm) == -1) {
	sprintf(signm, "#%d", sig);
    }
    DEBUG1(DBG_REMOTE_MID, "smtp_receive_timeout_sig(SIG%s) called....\n", signm);

    if (smtp_remove_on_timeout) {
	unlink_spool();
    }

    if (out_file) {
	/* we don't really care about stdio reentrancy -- we're about to exit! */
	dprintf(out_file, "421 4.3.2 %q %s, closing channel\r\n",
		primary_name,
		(sig == SIGALRM) ? "SMTP command timeout" :
			((sig == SIGTERM) ? "Operator shutdown requested" : "Interrupted by user"));
	fflush(out_file);
    }
    write_log(WRITE_LOG_SYS, "SIG%s received while talking with %s%s%s%s%s%s%s%s%s",
	      signm,
	      ident_sender ? ident_sender : "",
	      ident_sender ? "@" : "",
	      sender_host ? sender_host : "",
	      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
	      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
	      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
	      sender_host_addr ? "[" : "",
	      sender_host_addr ? sender_host_addr : "",
	      sender_host_addr ? "]" : "");

    exit(EX_TEMPFAIL);
}

/*
 * smtp_sig_unlink - unlink spool file and fast exit.
 *
 * This is useful for handling signals to abort reading a message in
 * with SMTP.
 */
static void
smtp_sig_unlink(sig)
    int sig;
{
    char signm[SIG2STR_MAX];

    (void) signal(sig, SIG_IGN);

    if (sig2str(sig, signm) == -1) {
	sprintf(signm, "#%d", sig);
    }
    DEBUG1(DBG_REMOTE_MID, "smtp_sig_unlink(SIG%s) called....\n", signm);

    (void) signal(sig, SIG_IGN);
    /*
     * out_file should always be non-NULL because we never set this signal
     * handler unless out was not NULL either, but it's best to check
     * anyway....
     */
    if (out_file) {
	dprintf(out_file, "421 4.3.2 %q Service not available, closing channel\r\n", primary_name);
	fflush(out_file);
    }
    write_log(WRITE_LOG_SYS, "SMTP connection closed by signal SIG%s while talking with %s%s%s%s%s%s%s%s%s",
	      signm,
	      ident_sender ? ident_sender : "",
	      ident_sender ? "@" : "",
	      sender_host ? sender_host : "",
	      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? "(" : "",
	      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
	      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? ")" : "",
	      sender_host_addr ? "[" : "",
	      sender_host_addr ? sender_host_addr : "",
	      sender_host_addr ? "]" : "");
    unlink_spool();

    exit(EX_OSFILE);
}

/*
 *	computed_max_msg_size()
 *
 * return the maximum allowable max_message_size based on the amount of free
 * space remaining in the spool area(s), less any reserved free space.
 */
static off_t
computed_max_msg_size()
{
    long free_kbytes;
    off_t my_max_msg_size;

    /*
     * if max_message_size == 0 then we have to be careful not to overflow a
     * long (which is what off_t may be on non-4.4BSD systems) with the
     * multiplication to bytes....
     */
    my_max_msg_size = (max_message_size <= 0) ? LONG_MAX : max_message_size;
    free_kbytes = spool_max_free_space();
    if (free_kbytes < 0 || free_kbytes > (LONG_MAX / 1024)) {
	/* unknown or overflowed, just hope for the best */
	return ((max_message_size > 0) ? max_message_size : -1);
    }
    if (free_kbytes == 0) {
	return 0;				/* no usable space left */
    }

    return MIN(my_max_msg_size, ((off_t) free_kbytes * 1024));
}

#if defined(HAVE_BSD_NETWORKING) || defined(HAVE_BIND) /* XXX can they ever be "or"? */
static void
invalid_relay_error(in, remote_addr)
    struct addr *in;
    char *remote_addr;
{
    char *error_text;

    /*
     * ERR_104 - security violation
     *
     * DESCRIPTION
     *	The incoming SMTP connection is not from a
     *	local network, and is attempting to send mail
     *	to another remote domain.
     *
     * ACTIONS
     *      The address verification is failed.
     *
     * RESOLUTION
     *      The postmaster should verify that addresses of
     *      all valid local networks are listed properly in
     *      the smtp_remote_allow variable.
     */
    error_text = xprintf("security violation: remote address '%q' is not permitted to relay mail",
			 remote_addr ? remote_addr : "[UNKNOWN]");
    in->error = note_error(ERR_NPOSTMAST | ERR_104, error_text);
}
#endif

/* 
 * Local Variables:
 * c-file-style: "smail"
 * End:
 */
