/*
#ident	"@(#)smail/src:RELEASE-3_2_0_120:smtprecv.c,v 1.238 2004/08/23 06:41:49 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 <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(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"
# include "route.h"
#endif
#include "transport.h"
#include "transports/smtplib.h"		/* for rfc821_is_*_string() */
#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;

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_PARANOID		002	/* smtp_hello_reject_dns_paranoid true and host mismatch */
#define SMTP_SESS_DENY_REJECT		004	/* smtp_reject_hosts matched */
#define SMTP_SESS_DENY_REJECT_HOSTNAME	010	/* smtp_hello_reject_hostnames matched */
#define SMTP_SESS_DENY_REJECT_PTR	020	/* 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) */
    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 void send_smtp_msg __P((FILE *, int, char *, int, const char *));
#ifdef HAVE_BSD_NETWORKING
static void do_greeting __P((FILE *out, int, struct sockaddr_in *, struct hostent **));
#else
static void do_greeting __P((FILE *out, int));
#endif
static void deny_greeting __P((FILE *, int, char *, char *));
static void send_session_denied_reply __P((FILE *, char *, char *));
static void non_compliant_reply __P((FILE *, int, int, char *, char *));
static void invalid_operand_warning __P((char *, char *operand));
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 *));
#ifdef HAVE_BSD_NETWORKING
static int match_dnsbl __P((char *, char *, char **, char **, char **));
#endif
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));
#ifdef HAVE_BSD_NETWORKING
static char *verify_host __P((char *, struct sockaddr_in *, struct hostent*, const int, char const **, int *));
#endif
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 set_term_signal __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));
static void invalid_relay_error __P((struct addr *, char *));

/* variables local to this file */
static char *data;			/* interesting data within input */
static char *orig_data = NULL;		/* saved copy of data before processing */

/*
 * 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 term_signal;
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    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 in sending a message is to state the",
    "250-2.0.0 sender address with a 'MAIL FROM:' command, give the recipients with",
    "250-2.0.0 as many 'RCPT TO:' commands as are required (one address per command)",
    "250-2.0.0 and then to specify the mail message text after the DATA 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 = "Do NOT send 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 file transfer protocol.";

char *sender_host_really = NULL;	/* result of PTR lookup */

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

off_t accepted_msg_size = -1;		/* what we're currently willing 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() */
{
    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 starts at the beginning */
    char *errstr;			/* temp to hold error messages */
    struct addr *cur;			/* address pointer -- current recipient */
    char *rest;				/* ptr used in parsing ESMTP options */
    char *p;				/* just a pointer */
    /* save important state to restore after initialize_state() */
    enum er_proc save_error_proc = error_processing;
    int save_do_aliasing = do_aliasing;
    int save_dont_deliver = dont_deliver;
    FILE *save_errfile = errfile;
    int save_debug = debug;
#ifdef HAVE_BSD_NETWORKING
    struct sockaddr_in from_sa;		/* result of getpeername() */
    struct hostent *shp = NULL;		/* result of gethostbyaddr() */
#endif

    /* initialize state */
    initialize_state();

    /* restore important state */
    error_processing = save_error_proc;
    do_aliasing = save_do_aliasing;
    dont_deliver = save_dont_deliver;

    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_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;

    term_signal = FALSE;
    out_file = out;			/* store ptr 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...
     */
    if (out) {
	char *s;

	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) {
	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];

	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);

	if ((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....
	     */
	    if (peer_sa &&
		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 (errno != ENOTCONN && errno != ECONNRESET && errno != ENOTSOCK) {
		write_log(WRITE_LOG_PANIC, "getpeername(): %s", strerror(errno));
	    } else {
		DEBUG1(DBG_REMOTE_MID, "getpeername(): %s, will try to use peer_sa\n", strerror(errno));
	    }
	    /* XXX if (peer_sa && errno == ENOTSOCK) then weird things are happening! */
	    /*
	     * if possible use the supplied "peer" value (from accept()) if
	     * getpeername() fails, assuming the original value is valid...
	     */
	    if (peer_sa && 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 {
		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);
		DEBUG1(DBG_REMOTE_MID, "peer_sa from accept() was invalid, using loopback: %s\n", inet_ntoa(from_sa.sin_addr));
	    }
	}
	sender_host_addr = COPY_STRING(inet_ntoa(from_sa.sin_addr));
	sender_host_port = xprintf("%u", (unsigned int) ntohs((in_port_t) from_sa.sin_port));

	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 ((smtp_sess_deny = match_ip(sender_host_addr, smtp_reject_hosts, ':', ';', FALSE, &smtp_sess_deny_msg))) {
	    smtp_sess_deny_reason = SMTP_SESS_DENY_REJECT;
	} else if (! match_ip(sender_host_addr, smtp_rbl_except, ':', ';', FALSE, (char **) NULL)) {
	    char *revaddr = flip_inet_addr(sender_host_addr);

	    /*
	     * This code does DNS lookups on the connecting client.  It is down
	     * here because this 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 as otherwise clients
	     * may timeout waiting for the 220 message.
	     */
	    if (match_dnsbl(revaddr, smtp_rbl_domains, &smtp_dnsbl_match, &smtp_dnsbl_addr, &smtp_sess_deny_msg)) {
		smtp_sess_deny = TRUE;
		smtp_sess_deny_reason = SMTP_SESS_DENY_DNSBL;
	    }
	    xfree(revaddr);
	}
    }
# if defined(HAVE_LIBWHOSON)
    if (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, 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 that can set or change
	     * smtp_sess_deny!!!
	     */
	    smtp_sess_deny = FALSE;
	} else {
	    DEBUG1(DBG_REMOTE_MID, "ip '%s' NOT verified by whoson\n", sender_host_addr);
	}
    }
# endif /* HAVE_LIBWHOSON */
    if (sender_host_addr) {
	/*
	 * WARNING!!!!
	 *
	 * We must check shp contents before calling gethost*() again
	 */
	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 {
	    sender_host_really = xprintf("%q", shp->h_name);
	}
    }
    DEBUG2(DBG_REMOTE_MID, "sender addr is [%s], sender host might be '%s'\n",
	   sender_host_addr ? sender_host_addr : "<unset>",
	   sender_host_really ? sender_host_really : "<unknown>");
#else /* !HAVE_BSD_NETWORKING */
    peer_is_localhost = FALSE;
#endif /* HAVE_BSD_NETWORKING */

    /*
     * Note there's no user-controllable exception list here.  If you want
     * exceptions then make them by defining your own list!  ;-)
     *
     * 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).
     */
    if (sender_host_really && !peer_is_localhost && smtp_host_reject_hostnames &&
	match_hostname(sender_host_really,
		       expand_string(smtp_host_reject_hostnames, (struct addr *) NULL, (char *) NULL, (char *) NULL),
		       &smtp_sess_deny_msg) == MATCH_MATCHED) {
	smtp_sess_deny = TRUE;
	smtp_sess_deny_reason = SMTP_SESS_DENY_REJECT_PTR;
    }
#if defined(HAVE_BSD_NETWORKING)
    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_dnsbl_match, &smtp_dnsbl_addr, &smtp_sess_deny_msg)) {
		smtp_sess_deny = TRUE;
		smtp_sess_deny_reason = SMTP_SESS_DENY_DNSBL;
	    }
	} else {
	    DEBUG1(DBG_REMOTE_MID, "do_smtp(): not checking for %s in smtp_host_dnsbl_domains.\n", sender_host_really);
	}
    }
#endif /* HAVE_BSD_NETWORKING */

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

#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 */

    accepted_msg_size = computed_max_msg_size();
    if (out) {
	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));
    }
    if (out) {
	(void) signal(SIGALRM, smtp_receive_timeout_sig);
    }
    while (! term_signal || out == NULL) {
	int smtp_cmd_id;

	if (out) {
	    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, TRUE, &from_sa, &shp);
# else
	    do_greeting(out, TRUE);
# 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:
#ifdef HAVE_BSD_NETWORKING
	    do_greeting(out, FALSE, &from_sa, &shp);
#else
	    do_greeting(out, FALSE);
#endif
	    break;

	case MAIL_CMD: {
	    int is_bounce = FALSE;

	    if (smtp_sess_deny) {
		if (out) {
		    send_session_denied_reply(out, "MAIL FROM", data);
		}
		exitvalue = EX_UNAVAILABLE;
		break;
	    }
	    if (*data != '<' || !(p = strchr(data, '>'))) {
		invalid_operand_warning("MAIL FROM", data);
	    } else if (p && *(p+1) && !isspace((int) *(p+1))) {
		invalid_operand_warning("MAIL FROM", data);
	    }
	    orig_data = COPY_STRING(data);
	    strip_rfc822_comments(data);
	    if (strcmp(data, orig_data) != 0) {
		invalid_operand_warning("MAIL FROM", orig_data);
	    }
	    strip_rfc822_whitespace(data);
	    if (data[0] == '\0') {
		non_compliant_reply(out, TRUE, 501, "5.5.4", "'MAIL FROM:' requires return return-path address as operand.");
		exitvalue = EX_DATAERR;
		break;
	    }
	    if (!sender_host) {
	        /* DO NOT send enhanced status as we are apparently not past HELO/EHLO. */
		non_compliant_reply(out, TRUE, 503, "", "'MAIL FROM:' must be preceded by HELO/EHLO command.");
		break;
	    }
	    if (sender) {
		non_compliant_reply(out, TRUE, 503, "5.5.1", "Sender already specified");
		exitvalue = EX_USAGE;
		break;
	    }
	    /*
	     * use preparse_address_1() because we need "rest" here....
	     */
	    sender = preparse_address_1(data, &errstr, &rest);
	    if (!sender) {
		if (out) {
		    sleep(1);
		    dprintf(out, "501-5.5.2 '%q' Address parse error:\r\n", data);
		    fflush(out);
		    sleep(smtp_error_delay > 0 ? smtp_error_delay : 2);
		    dprintf(out, "501 5.5.2 %v\r\n", errstr);
		    fflush(out);
		}
		exitvalue = EX_NOUSER;
		break;
	    }
	    is_bounce = FALSE;
	    if (sender && !*sender) {
		/* special error sender form <> given */
		sender = COPY_STRING("<>");
		if (smtp_max_bounce_recipients > 0) {
		    smtp_max_recipients = smtp_max_bounce_recipients;
		}
		is_bounce = TRUE;
	    } else if (sender && EQ(sender, "+")) {
		/* special smail-internal <+> was given */
		sender = COPY_STRING("<+>");
		smtp_max_recipients = 1;	/* XXX multiples impossible? (see notify.c?) */
		is_bounce = TRUE;
	    } else if (sender && *sender && !verify_sender(sender, data, out)) {
		/* NOTE: error reply was printed and logged by verify_sender() */
		xfree(sender);
		sender = NULL;
		exitvalue = EX_NOUSER;
		break;
	    }
	    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 site when using message the 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 MAIL FROM: '%q' refusing SMTP transaction from %s%s%s%s%s%s[%s]: client sending bounce %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 : "",
			      (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;
		}
	    }
	    /* NOTE: we allow MAIL options even without EHLO.... */
	    if (decode_mail_options(rest, out) < 0) {
		xfree(sender);
		sender = NULL;
		exitvalue = EX_DATAERR;
		break;
	    }
	    if (out) {
		fprintf(out, "250 2.1.0 %s Sender Okay.\r\n", sender);
		fflush(out);
	    }
	    break;
	}
	case RCPT_CMD: {
	    int rcpt_to_had_comments = FALSE;

	    if (smtp_sess_deny) {
		if (out) {
		    send_session_denied_reply(out, "RCPT TO", data);
		}
		exitvalue = EX_UNAVAILABLE;
		break;
	    }
	    if (*data != '<' || !(p = strchr(data, '>'))) {
		invalid_operand_warning("RCPT TO", data);
	    } else if (p && *(p+1) && !isspace((int) *(p+1))) {
		invalid_operand_warning("RCPT TO", data);
	    }
	    orig_data = COPY_STRING(data);
	    strip_rfc822_comments(data);
	    if (strcmp(data, orig_data) != 0) {
		rcpt_to_had_comments = TRUE;
	    }
	    strip_rfc822_whitespace(data);
	    if (data[0] == '\0') {
		non_compliant_reply(out, TRUE, 501, "5.5.4", "'RCPT TO:' requires forward-path address as operand.");
		exitvalue = EX_NOUSER;
		break;
	    }
	    /* do this after the above checks just to catch gross errors earlier */
	    if (!sender) {
		non_compliant_reply(out, ((*sender_proto == 'e') ? FALSE : TRUE),
				    503, "5.5.1", "'RCPT TO:' 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);
		}
		exitvalue = EX_TEMPFAIL;
		break;
	    }
	    /*
	     * NOTE: error/OK reply is printed to client by verify_addr()
	     */
	    if (! verify_addr(data, out, RCPT_CMD)) {
		exitvalue = EX_NOUSER;
		break;
	    }
	    if (rcpt_to_had_comments) {
/* XXX TODO implement RFC 3461 "NOTIFY=" (should not see unless we include DSN in EHLO response) */
/* XXX TODO implement RFC 3461 "ORCPT=" (should not see unless we include DSN in EHLO response) */
		invalid_operand_warning("RCPT TO", orig_data);
	    }
	    /*
	     * create a new address for this recipient and add to the recipients list
	     */
	    cur = alloc_addr();
	    /*
	     * surround in angle brackets, if the addr begins with `-'.
	     * This will avoid ambiguities in the data dumped to the spool
	     * file.
	     */
	    if (data[0] == '-') {
		cur->in_addr = xprintf("<%s>", data);
	    } else {
		cur->in_addr = COPY_STRING(data);
	    }
	    cur->succ = recipients;
	    recipients = cur;
	    break;
	}
	case DATA_CMD:
	    if (smtp_sess_deny) {
		if (out) {
		    send_session_denied_reply(out, "DATA", data);
		}
		exitvalue = EX_UNAVAILABLE;
		break;
	    }
	    if (sender == NULL) {
		if (out) {
		    non_compliant_reply(out, ((*sender_proto == 'e') ? FALSE : TRUE),
					503, "5.5.1", "'DATA' 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) {
		    non_compliant_reply(out, ((*sender_proto == 'e') ? FALSE : TRUE),
					503, "5.5.1", "'DATA' command must be preceded by 'RCPT TO:' 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 (out) {
		fprintf(out, "354 Enter mail, end with \".\" on a line by itself...\r\n");
		fflush(out);
		alarm((unsigned int) 0);
	    }

	    /*
	     * 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 not interactive and cannot send back failure
		 * messages, always try to accept the complete message.
		 */
		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 DATA: message too big 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 ? "]" : "");
		    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 DATA: %s to queue message from %s%s%s%s%s%s%s%s%s: %s: %s.",
			      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();
		smtp_remove_on_timeout = FALSE;
		break;
	    }
	    smtp_processing_signals();
	    if (sender == NULL) {		/* XXX how can this ever happen? */
		unlink_spool();
		reset_state();
		smtp_remove_on_timeout = FALSE;
		exitvalue = EX_NOINPUT;
		break;
	    }
	    if (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 DATA: error in queued message 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 ? "]" : "");
		}
		reset_state();
		smtp_remove_on_timeout = FALSE;
		exitvalue = EX_TEMPFAIL;
		break;
	    }
	    /*
	     * 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) {
		write_log(WRITE_LOG_SYS, "remote DATA: message too big (body %ld bytes) from %s%s%s%s%s%s%s%s%s",
			  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();
		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.\r\n");
		    send_smtp_msg(out, 552, "5.3.4", TRUE, no_files_by_email_msg);
		}
		reset_state();
		smtp_remove_on_timeout = FALSE;
		exitvalue = EX_NOPERM;
		break;
	    }
	    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_bufsiz) {
		queue_only = TRUE;
	    }
	    check_grade();
	    log_incoming();
	    log_spool_errors();
	    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);
	    }
	    /* 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);
		verify_addr(data, out, VRFY_CMD);
		reset_hit_table();
		fflush(out);
	    }
	    break;

	case EXPN_CMD:
	    if (smtp_sess_deny) {
		if (out) {
		    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);
		    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
	    }
	    break;

	case QUIT_CMD:
	    smtp_reset_signals();
	    if (out) {
		dprintf(out, "221 2.2.0 %q closing connection\r\n", primary_name);
		fflush(out);
	    }
	    /*
	     * don't use the "remote QUIT:" form so that matching errors and
	     * other less common commands is easier
	     */
	    /* XXX FIXME we should really only log this if no message was received.... */
	    write_log(WRITE_LOG_SYS, "remote got QUIT from %s@%s%s%s%s%s%s%s",
		      ident_sender ? ident_sender : "<no-ident>",
		      sender_host ? sender_host : "<no-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();		/* 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 RSET: requested by %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 ? "]" : "");
#endif
	    reset_state();
	    smtp_sess_deny = FALSE;	/* give them another chance... */
	    if (out) {
		fprintf(out, "250 2.3.0 Reset state\r\n");
		fflush(out);
	    }
	    break;

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

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

	    /* XXX this really should be response-rate limited. */
	    if (smtp_sess_deny) {
		if (out) {
		    send_session_denied_reply(out, "DEBUG", data);
		}
		break;
	    }
	    if (out) {
		if (smtp_allow_debug) {
		    strip_rfc822_comments(data);
		    strip_rfc822_whitespace(data);
		    if (*data) {
			if (smtp_cmd_id == DEBUG_CMD) {
			    char *errbuf = NULL;

			    temp = c_atol(data, &errbuf);
			    if (errbuf) {
				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);
				break;
			    }
			} else {
			    if (EQIC(data, "on")) {
				temp = 1;
			    } else if (EQIC(data, "off")) {
				temp = 0;
			    } else {
				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);
				break;
			    }
			}
		    } else {
			if (smtp_cmd_id == DEBUG_CMD) {
			    temp = 1;
			} else {
			    temp = ! debug;
			}
		    }
		    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 DEBUG: '%q' debugging at level %d %s to %s%s%s%s%s%s%s%s%s",
			      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 {
		    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);
		}
	    }
	    break;
	}
	case HELP_CMD:
	    /* XXX should this be response-rate limited? */
	    write_log(WRITE_LOG_SYS, "remote HELP: '%q' requested by %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 ? "]" : "");
	    if (out) {
		int i;

		for (i = 0; help_msg[i]; i++) {
		    fprintf(out, "%s\r\n", help_msg[i]);
		}
		fflush(out);
	    }
	    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, "lost connection unexpectedly 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;

	    return files;

	case TOOLONG_CMD:
	    smtp_reset_signals();
	    if (out) {
		sleep((smtp_error_delay > 2) ? (smtp_error_delay / 2) : 2);
		fprintf(out, "500-5.5.1 Command too long.\r\n"); /* XXX should this be 521? */
		fflush(out);
		sleep(smtp_error_delay > 0 ? smtp_error_delay : 2);
		fprintf(out, "500 5.5.1 All attempts to abuse this server are logged.\r\n");
		fflush(out);
	    }
	    write_log(WRITE_LOG_SYS, "remote ERROR:  command too long 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 ? "]" : "");
	    /* now better drop the connection... */
	    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, TRUE, 521, "5.3.0", "Internal error. Connection closing now...");
	    write_log(WRITE_LOG_SYS, "internal error while connected to %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 ? " source [" : "",
		      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 */
	}
	if (orig_data) {
	    xfree(orig_data);
	    orig_data = NULL;
	}
    }

    /*
     * we appear to have received a SIGTERM, so shutdown and tell the
     * remote host.
     */
    smtp_reset_signals();
    dprintf(out, "421 4.3.2 %q Service not available, closing channel\r\n", primary_name);
    fflush(out);
    write_log(WRITE_LOG_SYS, "SMTP connection closed by SIGTERM while talking with %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 ? "]" : "");

    files[file_i] = NULL;
    errfile = save_errfile;
    debug = save_debug;
    exitvalue = EX_UNAVAILABLE;

    return files;
}

/*
 * 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, ehlo_p
#ifdef HAVE_BSD_NETWORKING	/* XXX sorry, this is gross, but... */
	    , saddrp, shpp
#endif
	    )
    FILE *out;			/* to remote client, if not batch mode */
    int ehlo_p;			/* using ESMTP? */
#ifdef HAVE_BSD_NETWORKING
    struct sockaddr_in *saddrp;	/* result of getpeername() */
    struct hostent **shpp;	/* result of gethostbyaddr(*saddrp) */
#endif
{
#ifdef HAVE_BSD_NETWORKING
    struct hostent *shp = *shpp; /* keep a local copy */

    /*
     * make sure the caller's copy is reset in case the caller calls us again
     */
    *shpp = NULL;
#endif

    orig_data = COPY_STRING(data);
    strip_rfc822_comments(data);
    if (smtp_sess_deny && out) {
	deny_greeting(out, ehlo_p,
		      data,
		      sender_host_addr ? sender_host_addr : "UNKNOWN-IP");
	exitvalue = EX_UNAVAILABLE;

	return;
    }
    if (strcmp(data, orig_data) != 0) {
	invalid_operand_warning(ehlo_p ? "EHLO" : "HELO", orig_data);
    }
    strip_rfc822_whitespace(data);
    if (!data[0]) {
	non_compliant_reply(out, TRUE, 501, "", "SMTP greeting requires a hostname (or IP address literal) as operand");
	exitvalue = EX_NOHOST;
	return;
    }
    if (sender_host) {
	xfree(sender_host);
	sender_host = NULL;
    }
    if (out) {
#ifdef HAVE_BSD_NETWORKING
	int fatal;
	const char *errstr;		/* hold error message */
	    
	if (sender_host_addr && !shp) {
	    /* we've been around once before and need to reset shp & sender_host_really */
	    if (!(shp = gethostbyaddr((char *) &(saddrp->sin_addr),
				      (socklen_t) sizeof(saddrp->sin_addr), saddrp->sin_family))) {
		DEBUG2(DBG_REMOTE_LO, "gethostbyaddr(%s) failed: %s.\n", sender_host_addr, hstrerror(h_errno));
		sender_host_really = NULL;
	    } else {
		sender_host_really = xprintf("%q", shp->h_name);
	    }
	}
	errstr = NULL;
	fatal = FALSE;
	if (! (sender_host = verify_host(data, saddrp, shp, h_errno, &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 ? "-Reason given was:\n\n" : ".\n");
	    /* no ESC -- this is the greeting... */
	    send_smtp_msg(out, (fatal ? 501 : 401), "", (errstr ? TRUE : FALSE), 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%s%v.",
		      ehlo_p ? "EHLO" : "HELO",
		      sender_host ? "warning: questionable" : "rejected: invalid",
		      orig_data,
		      ident_sender ? ident_sender : "",
		      ident_sender ? "@" : "",
		      (sender_host && !EQIC(sender_host, orig_data)) ? sender_host : "",
		      (sender_host && !EQIC(sender_host, orig_data)) ? "(" : "",
		      (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really)) ? sender_host_really : "",
		      (sender_host && !EQIC(sender_host, orig_data)) ? ")" : "",
		      ((sender_host && !EQIC(sender_host, orig_data)) ||
		       (sender_host_really && !EQIC(sender_host ? sender_host : "", sender_host_really))) ? " " : "",
		      sender_host_addr ? "source [" : "",
		      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 /* !HAVE_BSD_NETWORKING */
	/* XXX we shouldn't be lazy -- should verify some forms the hard way! */
	DEBUG1(DBG_REMOTE_LO, "do_greeting(): no BSD networking, so not verifying sender host %q.\n", data);
	sender_host = (data[0] != '\0') ? COPY_STRING(data) : COPY_STRING("(unknown.and.unverified)");
#endif /* HAVE_BSD_NETWORKING */
    } else {
	DEBUG1(DBG_REMOTE_LO, "do_greeting(): no output stream, so not verifying sender host %q.\n", data);
	sender_host = (data[0] != '\0') ? COPY_STRING(data) : COPY_STRING("(unknown.and.not-verified)");
    }
    /*
     * 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).
     */
    if (out && !peer_is_localhost && smtp_hello_reject_hostnames &&
	match_hostname(sender_host,
		       expand_string(smtp_hello_reject_hostnames, (struct addr *) NULL, (char *) NULL, (char *) NULL),
		       &smtp_sess_deny_msg) == MATCH_MATCHED) {
	smtp_sess_deny = TRUE;
	smtp_sess_deny_reason = SMTP_SESS_DENY_REJECT_HOSTNAME;
	deny_greeting(out, ehlo_p, sender_host, (char *) NULL);
	exitvalue = EX_NOHOST;
	
	return;
    }
#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_dnsbl_match, &smtp_dnsbl_addr, &smtp_sess_deny_msg)) {
	smtp_sess_deny = TRUE;
	smtp_sess_deny_reason = SMTP_SESS_DENY_DNSBL;
	deny_greeting(out, ehlo_p, sender_host, (char *) NULL);
	exitvalue = EX_NOHOST;

	return;
    }
#endif /* HAVE_BSD_NETWORKING */
    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_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 ? " source [" : "",
		      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 %q (%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);
    }
    reset_state();		/* greeting implies RSET */

    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, hostaddr)
    FILE *out;
    int ehlo_p;
    char *host;
    char *hostaddr;
{
    char *log_reason = NULL;
    char *tmpmsg = NULL;

    /*
     * NOTE: we do not include enhanced status codes (RFC 3463) in the
     *       response to HELO/EHLO.
     */
    tmpmsg = xprintf("You are not permitted to send mail from %q(%s[%s]).\n",
		     host,
		     sender_host_really ? sender_host_really : "",
		     hostaddr ? hostaddr : (sender_host_addr ? sender_host_addr : "UNKNOWN-IP"));
    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);
	break;
    case SMTP_SESS_DENY_REJECT:
    case SMTP_SESS_DENY_REJECT_HOSTNAME:
    case SMTP_SESS_DENY_REJECT_PTR:
	tmpmsg = xprintf("All SMTP connections from your server have been explicitly blocked by the postmaster at %q.\n",
			 primary_name);
	send_smtp_msg(out, 550, "", FALSE, tmpmsg);
	xfree(tmpmsg);
	tmpmsg = NULL;
	break;
    case SMTP_SESS_DENY_PARANOID: /* XXX this probably can't get here.... */
	send_smtp_msg(out, 550, "", FALSE,
		      "All SMTP connections have been blocked from your domain\
 either because a DNS spoofing attack is underway at the moment, or the DNS for\
 your zone is so severely mis-configured that what we see of it is\
 indistinguishable from such an attack!\n");
	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_HOSTNAME:
	log_reason = xprintf("matched in smtp_hello_reject_hostnames");
	break;
    case SMTP_SESS_DENY_REJECT_PTR:
	log_reason = xprintf("matched in smtp_host_reject_hostnames");
	break;
    case SMTP_SESS_DENY_PARANOID: /* XXX this probably can't get here.... */
	log_reason = xprintf("triggered by smtp_hello_reject_dns_paranoid");
	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.",
	      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)) ? ")" : "",
	      hostaddr ? hostaddr : (sender_host_addr ? sender_host_addr : "UNKNOWN-IP"),
	      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;
{
    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 from [%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 ? sender_host_addr : "UNKNOWN-IP");
    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);
    write_log(WRITE_LOG_SYS, "remote %v:%s%q%s refusing SMTP transaction 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, logit, ecode, estatus, errtxt)
    FILE *out;
    int logit;
    int ecode;
    char *estatus;		/* enhanced status code (RFC 3463) */
    char *errtxt;
{
    /* XXX should this be response-rate limited? */

#ifdef LOG_SMTP_NON_COMPLIANCE
    if (logit) {
	write_log(WRITE_LOG_SYS, "sent %d-%s: '%s' to %s%s%s%s%s%s%s%s%s",
		  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 ? " source [" : "",
		  sender_host_addr ? sender_host_addr : "",
		  sender_host_addr ? "]" : "");
    }
#endif
    if (!out) {
	return;
    }
    fflush(out);		/* just in case not first */
    sleep((smtp_error_delay > 2) ? (smtp_error_delay / 2) : 2);
    dprintf(out, "%d-%s %v\r\n%d-%s\r\n", ecode, estatus, errtxt, ecode, estatus);
    fflush(out);
    sleep(1);
    fprintf(out, "%d-%s If you are seeing this message in a bounce, or in an alert box\r\n", ecode, estatus);
    fflush(out);
    sleep(1);
    fprintf(out, "%d-%s from your mailer client, etc., then your mailer software\r\n", ecode, estatus);
    fflush(out);
    sleep(1);
#ifdef HAVE_ESMTP_PIPELINING		/* defined in iobpeek.h if support is possible */
    fprintf(out, "%d-%s is not showing you the correct and most meaningful error message.\r\n", ecode, estatus);
#else
    fprintf(out, "%d-%s may not be compliant with the SMTP protocol (RFC 821 et al).\r\n", ecode, estatus);
#endif
    fflush(out);
    sleep(smtp_error_delay > 0 ? smtp_error_delay : 2);
    fprintf(out, "%d %s Please report this error to those responsible for your mailer software.\r\n", ecode, estatus);
    fflush(out);
    return;
}

/* ARGSUSED */
static void
invalid_operand_warning(operator, operand)
    char *operator;			/* (SMTP command  XXX should maybe be e_smtp_cmds) */
    char *operand
#ifndef LOG_SMTP_INVALID_OPERAND_WARNING
		GCC_UNUSED_HACK
#endif
	;
{
#ifdef LOG_SMTP_INVALID_OPERAND_WARNING
    write_log(WRITE_LOG_SYS, "remote %v: '%q' operand not strictly valid from %s%s%s%s%s%s%s%s%s",
	      operator,
	      operand,
	      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 ? " source [" : "",
	      sender_host_addr ? sender_host_addr : "",
	      sender_host_addr ? "]" : "");
#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;

    /* 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
    DEBUG3(DBG_REMOTE_MID, "read_smtp_command() starting with string at 0x%lx, len = %u, max = %u.\n", (long) STR(&input), STR_LEN(&input), input.a);
    /*
     * 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) {
	    DEBUG(DBG_REMOTE_MID, "read_smtp_command() returning EOF_CMD.\n");
	    return EOF_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);

    DEBUG4(DBG_REMOTE_MID, "read_smtp_command() got '%q' (%u bytes @ 0x%lx, max = %u).\n", data, STR_LEN(&input), (long) data, input.a);

    for (cp = smtp_cmd_list; cp < ENDTABLE(smtp_cmd_list); cp++) {
	if (strncmpic(cp->name, data, strlen(cp->name)) == 0) {
	    char *p;

	    /* point "data" at any operand (skip past command & whitespace) */
	    for (p = cp->name; *data && *p; data++, p++) {
		;
	    }
	    for (; *data && isspace((int) *data); data++) {
		;
	    }
	    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(rest, out)
    char *rest;			/* remainder of "MAIL FROM:" operand */
    FILE *out;			/* SMTP out channel */
{
    long body_size = -1;	/* body size the client claims it will send */

    while (rest && *rest) {
	size_t restlen;
	char ch;		/* NUL place-holder */

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

	    rest += restlen;
	    if (*rest != '=') {
		non_compliant_reply(out, TRUE, 501, "5.5.2", "'MAIL FROM:<...> SIZE=?' missing parameter.");
		return -1;
	    }
	    ++rest;
	    restlen = 0;
	    body_size = 0;
	    /* find the end of the SIZE parameter */
	    while (isdigit((int) *(rest+restlen))) {
		++restlen;
	    }
	    ch = *(rest+restlen);	/* save... */
	    *(rest+restlen) = '\0';	/* NUL terminate # */
	    body_size = c_atol(rest, &errbuf);
	    *(rest+restlen) = ch;	/* ...restore */
	    if (errbuf) {
		if (out) {
		    sleep(1);
		    fprintf(out, "501-5.5.4 bad number: %s\r\n", errbuf);
		    non_compliant_reply(out, TRUE, 501, "5.5.4", "Malformed 'MAIL FROM:' SIZE=number clause.");
		}
		return -1;
	    }
	    /* is there a space, tab, or newline after the last digit? */
	    if (*(rest+restlen) && !isspace((int) *(rest+restlen))) {
		non_compliant_reply(out, TRUE, 501, "5.5.4", "'MAIL FROM:' malformed SIZE parameter.");
		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) {
		    sleep(smtp_error_delay > 2 ? (smtp_error_delay / 2) : 2);
		    fprintf(out, "452 4.3.1 There is no room for a message of %ld bytes!  Please try again much later!\r\n", (long) body_size);
		    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;
	    }
#ifdef CLAIM_BROKEN_8BITMIME_SUPPORT	/* XXX actually either RFC 1652 "8BITMIME" or RFC 3030 "CHUNKING" */
	} else if (strncmpic(rest, "BODY", restlen) == 0) {
	    rest += restlen;
	    if (*rest != '=') {
		non_compliant_reply(out, TRUE, 501, "5.5.2", "'MAIL FROM:<...> BODY=?' missing parameter.");
		return -1;
	    }
	    rest++;
	    restlen = 0;
	    /* find the end of the BODY parameter */
	    while (*(rest+restlen) && !isspace((int) *(rest+restlen))) {
		++restlen;
	    }
	    ch = *(rest+restlen);
	    *(rest+restlen) = '\0';	/* NUL terminate # */
	    if (strcmpic(rest, "7BIT") == 0) {
		/* What more could you want in America?  ;-) */
	    } else if (strcmpic(rest, "8BITMIME") == 0) {
		/* We'll be lazy and leave this up to the MUA to decode... */
	    } else {
		DEBUG1(DBG_REMOTE_LO, "Unknown ESMTP BODY=%q parameter value!", rest);
	    }
	    *(rest+restlen) = ch;	/* restore */
#endif /* CLAIM_BROKEN_8BITMIME_SUPPORT */
	} else {
/* XXX TODO: implement RFC 3461 "RET=" (should not see unless we include ESC in EHLO response) */
/* XXX TODO: implement RFC 3461 "ENVID=" (should not see unless we include ESC in EHLO response) */
	    non_compliant_reply(out, TRUE, 555, "5.5.2", "Unknown MAIL FROM: ESMTP option.");
	    return -1;
	}
	rest += restlen;
    }

    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 = 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) {
	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);
    }
    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);
    }
    if (fail) {
	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 Failures expanding %v\r\n", in_addr);
    }
    fflush(out);
}
#endif	/* !NO_SMTP_EXPN */

#if defined(HAVE_BSD_NETWORKING)
/*
 * match_dnsbl - look up subdomain in list of domains given in dnsbl
 *
 * - dnsbl is a colon-separated list of domains in which an A RR for
 * the specified name is looked up.
 *
 * - Optionally a comma-separated list of valid A RR values, either as explicit
 * 4-octet ascii-form host addresses, or in a network/mask form, follows a
 * semicolon after any domain (given in form suitable for a config file entry):
 *
 *	"\
 *	:dns.bl.domain;127.0.0.1,10/8\
 *	:dns.bl.domain;127.0.0/24\
 *	"
 *
 * WARNING: don't diddle with dnsbl -- it may be read-only string.
 *
 * Returns 1 if any matched, else 0.
 *
 *	if a match is found then *matchp will be set to the fully expanded
 *	domain that matched and *addrp will be set to the A RR's value, both as
 *	ASCII strings; and as well if a TXT RR is found at the same domain then
 *	the *msgp will be set to point to its value too.
 */
static int
match_dnsbl(target, dnsbl, matchp, addrp, msgp)
    char *target;			/* subdomain to match */
    char *dnsbl;			/* DNS BlackList domain list */
    char **matchp;			/* pointer to hold formatted match address */
    char **addrp;			/* pointer to hold ASCII A RR value address */
    char **msgp;			/* pointer to hold TXT message pointer */
{
    char *pat_p, *end_p;
    size_t len;

    if (msgp) {
	*msgp = NULL;
    }
    if (!dnsbl) {
	return 0;
    }

    /* run through the pattern list */
    for (pat_p = dnsbl; /* NOTEST */ ; pat_p = end_p + 1) {
	char *a_rr_pats;

	/* Matches any de facto standard RBL value.  Don't use 0/0 here!
	 * Remember the fiasco with relayips.shub-inter.net!
	 */
	a_rr_pats = "127/8";

	/* skip any spaces at front */
	while (*pat_p && isspace((int) *pat_p)) {
	       pat_p++;
	}
	/* find the end of the next pattern */
	end_p = pat_p;
	while (*end_p && *end_p != ':' && !isspace((int) *pat_p)) {
	       end_p++;
	}
	/* skip empty patterns */
	len = end_p - pat_p;
	if (len > 0) {
	    char *bl_hname;
	    char *txtaddr;
	    size_t bl_hname_len = strlen(target) + len + 1; /* one more for the '.' */
	    char *p;
	    struct hostent *hp;

	    bl_hname = xmalloc(bl_hname_len + 1);
	    strcpy(bl_hname, target);
	    strcat(bl_hname, ".");
	    strncat(bl_hname, pat_p, len);
	    bl_hname[bl_hname_len] = '\0';
	    DEBUG1(DBG_ADDR_HI, "match_dnsbl(): working on DNSBL spec '%v'\n", bl_hname);

	    if ((p = strchr(bl_hname, ';'))) {
		a_rr_pats = p;
		*a_rr_pats = '\0';
		a_rr_pats++;
		DEBUG1(DBG_ADDR_HI, "match_dnsbl(): found explicit IP pattern list '%v'\n", a_rr_pats);
	    }
	    DEBUG2(DBG_ADDR_MID,
		   "match_dnsbl(): checking if A RR matching [%v] is found at DNSBL '%v'\n",
		   a_rr_pats, bl_hname);
	    /*
	     * Do we (really?) care if there's a temporary server failure when
	     * trying to look up an DNSBL?  If we were very serious about
	     * blocking every possible piece of unwanted e-mail then we would
	     * be, I guess.  That would really complicate things though because
	     * then we'd have to return either a fatal or temporary error,
	     * probably as either yet another global variable, or as a value
	     * set in a pointed at int or string passed as a parameter by
	     * reference.
	     *
	     * XXX this might be one place where we could make a second call
	     * after first calling res_init() in case smail is running on a
	     * DHCP client that just changed networks...
	     */
	    if ((hp = gethostbyname(bl_hname))) {
		struct in_addr bl_hname_addr;

		memcpy((char *) &bl_hname_addr, hp->h_addr_list[0], sizeof(struct in_addr));
		txtaddr = COPY_STRING(inet_ntoa(bl_hname_addr));
		if (match_ip(txtaddr, a_rr_pats, ',', '\0', FALSE, (char **) NULL)) {
		    struct error *junkerr;

		    *matchp = bl_hname;
		    *addrp = txtaddr;
		    *msgp = bind_lookup_txt_rr(bl_hname, &junkerr);
		    DEBUG5(DBG_ADDR_LO,
			   "match_dnsbl(): found an A RR matching one of [%v] at DNSBL %v%s%v%s\n",
			   a_rr_pats,
			   bl_hname,
			   *msgp ? "\n\twith associated TXT RR\n\t``" : "error getting TXT RR: ",
			   *msgp ? *msgp : junkerr->message,
			   *msgp ? "''" : "");
		    /* NOTE: don't free bl_hname or txtaddr here! */
		    return 1;
		}
		xfree(txtaddr);
	    }
	    xfree(bl_hname);
	}
	if (*end_p == '\0') {
	    break;
	}
    }

    return 0;
}
#endif /* defined(HAVE_BSD_NETWORKING) */


/*
 * 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 1	/*
	 * XXX this code may only be duplicating the check in
	 * parse_address():check_target_and_remainder()
	 */
    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 parse error:\r\n",
		    raw_addr,
		    (smtp_cmd == RCPT_CMD || smtp_cmd == VRFY_CMD) ? "recipient" : "sender");
	    fflush(out);
	    sleep(smtp_error_delay > 0 ? smtp_error_delay : 2);
	    dprintf(out, "501 5.1.7 because of: %v\r\n",
		    (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 ? "]" : "");

	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 ? "]" : "");
	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 ? "]" : "");
	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:
     *
     *	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 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".
     *
     * 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 ? "]" : "");
    }

    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_reject_sender_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
	    }
	    if (!reject_msg && 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
	    }
	    *p = '\0';			/* XXX should only modify a copy... */
	    /*
	     * no worries about smtp_bad_mx_except -- this is a domain literal
	     * (if you want to make an exception for this host then you'll have
	     * to implement your internal DNS properly!)
	     */
	    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 = ']';
	}
    }
# 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 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; by %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 : "");
#  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 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; by %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 : "");
		    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 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)
	 */
    /*
     * 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).
     */
    /* note a match in smtp_sender_reject_hostnames has precedence */
    /* XXX should we use && !islocalhost() too? */
    /* XXX ignore errors from match_hostname() for now.... */
    if (!reject_msg && 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_rhsbl_match, &smtp_rhsbl_addr, &reject_reason)) {
	    reject_msg =
		xprintf("The domain '%q' matches the DNS Black List entry:\n\n\t%s\tA\t%s",
			vtarget, smtp_rhsbl_match, smtp_rhsbl_addr);
	    log_reason = xprintf("matched RHSBL: %s [%s]", smtp_rhsbl_match, smtp_rhsbl_addr);
#if 0
	    estatus = "5.?.?";
#endif
	}
    } else {
	DEBUG2(DBG_REMOTE_MID, "verify_sender(%q): not checking for %q in smtp_sender_rhsbl_domains\n", pp_sender, vtarget);
    }
#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",
		  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 : "",
		  log_reason);

	if (vtarget) {
	    xfree(vtarget);
	}
	if (reject_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
 *
 * redisplay the input address if it is a valid address.
 */
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 */
    
    DEBUG1(DBG_REMOTE_MID, "verify_addr(%v): checking deliverability.\n", in_addr);

    addr->in_addr = 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);
	}
	xfree((char *) addr);
	return 0;
    }
    if (!verify_addr_form(addr->work_addr, in_addr, out, smtp_cmd, &vtarget)) {
	/* errors already logged and printed */
	return 0;
    }
    if (!out) {
	return 1;			/* Batched-SMTP, must be OK, right? */
    }
    /*
     * 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) {
	    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);
	    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 : ".");

	    return 0;
	}
	if (vtarget && match_hostname(vtarget, smtp_sender_reject_hostnames, &reason) == MATCH_MATCHED) {
	    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);
	    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%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 : ".");

	    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) {
			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);
			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 : ".");

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

    /*
     * 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)) {

	DEBUG1(DBG_REMOTE_MID, "verify_addr(%v): recipient unverified.\n", in_addr);

	if (out) {
	    dprintf(out, "250 2.1.0 '%q' %s%q%s Recipient Unverified%s%s%s.\r\n",
		    orig_data, /* XXX */
		    (*data == '<') ? "" : "<",
		    data,
		    (*data == '<') ? "" : ">",	/* assume the address has a proper closing angle-bracket */
		    reason ? " (" : "",
		    reason ? reason : "",
		    reason ? ")" : "");
	    fflush(out);
	}
	return 1;
    }

    /* 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 (okay) {
	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:
	    /*
	     * 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).
	     *
	     * 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 ? "]" : "");
		xfree((char *) addr);

		return 0;			/* do not allow this sender address */
	    } else {
		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 ? "]" : "");
	    xfree((char *) 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.
	     */
	    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 %s.\n",
		       sender_host_addr ? sender_host_addr : "UNKNOWN-IP", in_addr);
	    } else {
		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 : "<no-client-host-address>",
			  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 : "");
		xfree((char *) 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);
	}
	xfree((char *) 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) ? "Attempts to verify" :
		    	(smtp_cmd == RCPT_CMD) ? "Attempts 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 Reason given: (ERR%ld) %s\r\n",
		    defer->error->info & ERR_MASK,
		    defer->error->message);
	    fflush(out);
	    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 : "<no-client-host-addr>",
		  sender_host_addr ? "]" : "",
		  in_addr,
		  sender,
		  defer->error->info & ERR_MASK,
		  defer->error->message);
	xfree((char *) 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 */

	switch (fail->error->info & ERR_MASK) {
	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_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_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_168:			/* no valid MX records for host */
	    error_code = 551;		/* mailbox not local */
	    estatus = "5.1.2";		/* bad destination system address */
	    break;
					/* 558 used below */
	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) ? "Attempts to verify" :
		    	(smtp_cmd == RCPT_CMD) ? "Attempts 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 Reason given: (ERR%ld) %s\r\n",
		    error_code, estatus,
		    fail->error->info & ERR_MASK,
		    fail->error->message);
	    fflush(out);
	    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 %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 : "<no-client-host-addr>",
		  sender_host_addr ? "]" : "",
		  in_addr,
		  sender,
		  fail->error->info & ERR_MASK,
		  fail->error->message);
	xfree((char *) addr);

	return 0;
    }
    /*
     * hmmm....  We shouldn't have gotten here as the address should have been
     * put on one of the output lists from resolve_addr_list()!
     */
    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);
    }
    panic(EX_SOFTWARE, "remote %s: %s%s%s%s%s%s: '%q': recipient <%q%s%q>, for sender '%s', caused resolve_addr_list() to fail!",
	  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;
}


#ifdef HAVE_BSD_NETWORKING
/*
 * verify_host - verify the sender hostname 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, saddrp, shp, shpherr, errorp, fatalp)
    char *hostnm;			/* name to check (may be ASCII-ified) */
    struct sockaddr_in *saddrp;
    struct hostent *shp;		/* result of gethostbyaddr() in caller */
    const int shpherr;			/* h_errno from gethostbyaddr() */
    char const **errorp;		/* text describing any error or omission */
    int *fatalp;			/* is this a fatal, or temporary, error? */
{
    struct hostent *hp;
    char *p;
    int found;
    int found_badchar;
    int found_underscore;
    int found_dot;
    int has_alphas;
    int i;
    int allowed_to_be_broken = FALSE;
    int aresult;
    struct error *binderr = NULL;
    char *binderrmsg;

    if (peer_is_localhost ||
	match_ip(inet_ntoa(saddrp->sin_addr), smtp_hello_broken_allow, ':', ';', FALSE, (char **) NULL)) {
	allowed_to_be_broken = TRUE;
    }

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

	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 && !allowed_to_be_broken) {
		*fatalp = TRUE;
		return NULL;		/* broken DNS or mailer config; or forger */
	    }
	}
	if (shp && sender_host_really) { /* both from gethostbyaddr() in caller */
	    if (!(hp = 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 hstrerror() not
		 * being declared, or one warning about "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 = hstrerror(h_errno);
		if (smtp_hello_verify_literal && !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 {
		found = FALSE;
		for (i = 0; hp->h_addr_list[i]; i++) {
		    if (memcmp(hp->h_addr_list[i], (char *) &(saddrp->sin_addr),
			       sizeof(saddrp->sin_addr)) == 0) {
			found = TRUE;
			break;		/* name is good, keep it */
		    }
		}
		if (!found) {
		    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 && !allowed_to_be_broken) {
			*fatalp = TRUE;
			return NULL;	/* broken DNS or mailer config; or forger */
		    }
		}
	    }
	} 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 && !allowed_to_be_broken) {
	        *fatalp = TRUE;
		return NULL;		/* broken DNS or mailer config; or forger */
	    }
	}
	
	if (comment) {
	    /* prepend the optional identifying information in a comment.... */
	    checked_hostnm = xprintf("(%v) %v", comment, hostnm);
	} else {
	    checked_hostnm = xprintf("%v", hostnm); /* use %v just to be paranoid */
	}

	return checked_hostnm;
    } /* else not a literal */

    /*
     * first verify syntax of hostnm (as per RFC 1035)
     *
     * WARNING: we should probably also ensure the total length, the length of
     * each label, the start char of each label, etc., are all valid.
     */
    if (*hostnm == '.') {
	*errorp = "hostname must NOT start with a '.'";
	DEBUG1(DBG_REMOTE_LO, "hostname must not start with '.': %v", hostnm);
	*fatalp = TRUE;			/* syntax errors are always fatal */
	return NULL;
    }
    if (*hostnm == '-') {
	*errorp = "hostname must NOT start with a '-'";
	DEBUG1(DBG_REMOTE_LO, "hostname must not start with '-': %v", hostnm);
	*fatalp = TRUE;			/* syntax errors are always fatal */
	return NULL;
    }
    found_badchar = FALSE;
    found_underscore = FALSE;
    found_dot = FALSE;
    has_alphas = FALSE;
    for (p = hostnm; *p; p++) {
	if (isascii((int) *p) && (isalpha((int) *p) || *p == '-')) {
	    has_alphas = TRUE;
	}
	if (*p == '.') {
	    found_dot = TRUE;
	} else if (*p == '_') {
	    found_underscore = TRUE;
	} else if (*p != '-' && !(isascii((int) *p) && isalnum((int) *p))) {
	    found_badchar = TRUE;
            DEBUG1(DBG_REMOTE_LO, "invalid character in hostname: 0x%x.\n", *p);
        }
    }
    --p;			/* backup to last char... */
    /*
     * note: we handle !found_dot only after doing a DNS lookup for A RRs
     */
    if (*p == '.') {
	*errorp = "hostname must NOT end with a '.'";
	DEBUG1(DBG_REMOTE_LO, "hostname must not end with '.': %v", hostnm);
	*fatalp = TRUE;		/* syntax errors are always fatal */
	return NULL;
    }
    if (*p == '-') {
	*errorp = "hostname must NOT end with a '-'";
	DEBUG1(DBG_REMOTE_LO, "hostname must not end with '-': %v", hostnm);
	*fatalp = TRUE;		/* syntax errors are always fatal */
	return NULL;
    }
    if (found_badchar) {
	*errorp = "invalid character in hostname (must be all ASCII alpha-numeric or '-' or '.')";
	*fatalp = TRUE;		/* syntax errors this bad are always fatal... */
	return NULL;
    }
    /*
     * allow specific grace to be given to the plainly ignorant though since
     * this one causes no real harm....
     */
    if (found_underscore && !allowed_to_be_broken) {
	*errorp = "invalid character in hostname (must be all ASCII alpha-numeric or '-' or '.')";
	*fatalp = TRUE;
	return NULL;
    }
    if (!has_alphas) {		/* there are no valid all-numeric TLDs... */
#if 0 /* this code _might_ give a more meaningful error message.... */
	in_addr_t inet;		/* internet address */

	/* let's see if it's an IP address just for fun... */
	inet = get_inet_addr(&hostnm[1]);
	DEBUG5(DBG_REMOTE_HI,
	       "verify_host(%v): invalid inet form 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 = "possible host address literal given without square brackets, but is not even a valid IP address";
	    *fatalp = TRUE;
	    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 given without square brackets and does not even match remote client address";
	    *fatalp = TRUE;
	    return NULL;		/* broken DNS or mailer config; or forger */
	} else {
	    *errorp = "invalid host address literal syntax (address literals must be enclosed in square brackets!)";
	}
#else
	*errorp = "possible host address literal given with invalid syntax (address literals must be enclosed in square brackets!)";
#endif
	/* even if it matches this is still a syntax error... */
	*fatalp = TRUE;
	return NULL;
    }

    /* NOTE:  shp is safe -- this doesn't call any gethostby*() subroutine */
    aresult = bind_check_if_canonical_host(hostnm, (in_addr_t) saddrp->sin_addr.s_addr, &binderr);

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

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

    /*
     * Treat all bind errors equally if the intput was an unqualified hostname.
     */
    if (aresult != DB_SUCCEED && !found_dot) {
	*errorp = "a hostname must be fully qualified and should contain at least one '.'";
	DEBUG2(DBG_REMOTE_LO, "%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;
	}
    }

    switch (aresult) {
    case DB_SUCCEED:		/* found the host! */
	*fatalp = FALSE;	/* just in case something below returns non-NULL */
	break;
    case DB_NOMATCH:		/* no such domain (i.e. no _matching_ A RR) */
	if (found_dot) {		/* don't overwrite FQDN message above */
	    *errorp = binderrmsg;
	}
	if (smtp_hello_verify && !allowed_to_be_broken) {
	    *fatalp = TRUE;		/* this is not a temporary error! */
	    return NULL;
	}
 	if (allowed_to_be_broken) {
	    /* no point going on to check the PTRs if the forward DNS is busted */
	    *fatalp = FALSE;
	    return xprintf("(client is using the wrong hostname!) %v", hostnm);
	}
	break;
    case FILE_NOMATCH:		/* There is no server available */
	if (found_dot) {		/* don't overwrite FQDN message above */
	    *errorp = binderrmsg;
	}
	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);
	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);
	if (smtp_hello_verify && !allowed_to_be_broken) {
	    /*
	     * XXX it might be nice to return a temporary 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.", hostnm, aresult);
	*errorp = xprintf("Fatal internal error encountered while trying to verify host '%v':(%s)", hostnm, binderrmsg);
	if (smtp_hello_verify && !allowed_to_be_broken) {
	    *fatalp = TRUE;		/* a bounce is best at this point... */
	    return NULL;
	}
	break;
    }

    /*
     * Note that errors in the following section are less important than those
     * above, so if there was one from above it will be presented more
     * prominently....
     */
    if (shp) {				/* from gethostbyaddr() in caller */
	size_t hlen = strlen(hostnm);

	if (hostnm[hlen-1] == '.') {
	    /* XXX what if there's more than one? */
	    hostnm[hlen-1] = '\0';	/* the DNS can't return trailing dots */
	}
	if (strcmpic(hostnm, shp->h_name) != 0) { /* does the first name match? */
	    found = FALSE;
	    for (i = 0; (p = (shp->h_aliases)[i]); i++) { /* do any other names match? */
		if (strcmpic(hostnm, p) == 0) {
		    found = TRUE;
		    break;		/* yeah! */
		}
	    }
	    if (!found) {
		DEBUG2(DBG_REMOTE_LO, "verify_host(%v): gethostbyaddr(%s) no PTR has a matching name.\n", hostnm, inet_ntoa(saddrp->sin_addr));
		*errorp = xprintf("%s%sNone of the existing reverse DNS PTRs for your remote address [%s] has a hostname matching '%v'.  %s%s",
				  *errorp ? *errorp : "",
				  *errorp ? " (" : "",
				  inet_ntoa(saddrp->sin_addr),
				  hostnm,
				  smtp_hello_verify ? "Your reverse DNS is likely misconfigured" : "Either your reverse DNS is mis-configured, or your hostname was spoofed",
				  *errorp ? ")" : "");
		if ((smtp_hello_verify || smtp_hello_verify_ptr) && !allowed_to_be_broken) {
		    *fatalp = TRUE;
		    return NULL;	/* broken DNS or mailer config; or forger */
		}
	    }
	} else {
	    found = TRUE;
	}
	if (found) {
	    /*
	     * reset sender_host_really so it'll 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);
	    }
	    sender_host_really = COPY_STRING(hostnm);
	}
	if (smtp_hello_reject_dns_paranoid) {	/* re-invent TCP Wrappers PARANOID check */
	   int notallok = FALSE;

	   /*
	    * do all the PTR names have at least one address that matches?
	    * it's OK if there is no PTR -- that's just lame....  ;-)
	    * this bloody h_name crap is messy....
	    */
	    if ((aresult = bind_check_if_canonical_host(shp->h_name, (in_addr_t) saddrp->sin_addr.s_addr, &binderr)) == DB_SUCCEED) { /* does first name match */
		for (i = 0; (p = (shp->h_aliases)[i]); i++) { /* do all other names match? */
		    if ((aresult = bind_check_if_canonical_host(p, (in_addr_t) saddrp->sin_addr.s_addr, &binderr)) != DB_SUCCEED) {
			notallok = TRUE;
			break;
		    }
		}
	    } else {
		notallok = TRUE;
		p = shp->h_name;
	    }
	    if (notallok) {
		smtp_sess_deny_reason = SMTP_SESS_DENY_PARANOID; /* XXX just in case? */

		DEBUG3(DBG_REMOTE_LO,
		       "verify_host(%v): hostname %s (from PTRs of [%s]) does not have any matching addresses!\n",
		       hostnm, p, inet_ntoa(saddrp->sin_addr));

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

		DEBUG5(DBG_REMOTE_LO,
		       "verify_host(%v): bind_check_if_canonical_host(%s, %s): %d, %s.\n",
		       hostnm, p, inet_ntoa(saddrp->sin_addr), aresult, binderrmsg);

		switch (aresult) {
		case DB_NOMATCH:	/* no such domain */
		    *errorp = xprintf("%s%sHostname '%s' (from your PTR record) does not have any address matching [%s]!  Possibly a DNS spoofing attack, or possibly just badly mis-configured DNS.%s",
				      *errorp ? *errorp : "",
				      *errorp ? " (" : "",
				      p,
				      inet_ntoa(saddrp->sin_addr),
				      *errorp ? ")" : "");
		    if (!allowed_to_be_broken) {
			*fatalp = TRUE;
			return NULL;
		    }
		    break;
		case FILE_NOMATCH:	/* There is no server available */
		    *errorp = xprintf("%s%sDNS server failure encountered while trying to verify matching address for hostname '%s': %s%s",
				      *errorp ? *errorp : "",
				      *errorp ? " (" : "",
				      p,
				      binderrmsg,
				      *errorp ? ")" : "");
		    if (!allowed_to_be_broken) {
			*fatalp = FALSE;
			return NULL;
		    }
		    break;
		case DB_AGAIN:		/* DNS lookup must be deferred */
		case FILE_AGAIN:	/* lost contact with server */
		    *errorp = xprintf("%s%sTemporary DNS problem encountered while trying to verify matching address for hostname '%s': %s%s",
				      *errorp ? *errorp : "",
				      *errorp ? " (" : "",
				      p,
				      binderrmsg,
				      *errorp ? ")" : "");
		    if (!allowed_to_be_broken) {
			*fatalp = FALSE;
			return NULL;
		    }
		    break;
		case DB_FAIL:		/* bad DNS request? */
		case FILE_FAIL:		/* major DNS error! */
		    *errorp = xprintf("%s%sDNS failure encountered while trying to verify matching address for hostname '%s': %s%s",
				      *errorp ? *errorp : "",
				      *errorp ? " (" : "",
				      p,
				      binderrmsg,
				      *errorp ? ")" : "");
		    if (!allowed_to_be_broken) {
			*fatalp = TRUE;
			return NULL;
		    }
		    break;
		default:		/* impossible! */
		    DEBUG2(DBG_REMOTE_LO, "verify_host(%v): bind_check_if_canonical_host() impossible result %d.", hostnm, aresult);
		    *errorp = xprintf("%s%sFatal internal error encountered while verifying matching address for hostname '%s': %s%s",
				      *errorp ? *errorp : "",
				      *errorp ? " (" : "",
				      p,
				      binderrmsg,
				      *errorp ? ")" : "");
		    if (smtp_hello_verify && !allowed_to_be_broken) {
			*fatalp = TRUE;
			return NULL;
		    }
		    break;
		}
	    }
	}
    } else {
	*errorp = xprintf("%s%sRemote address PTR lookup failed: %s%s",
			  *errorp ? *errorp : "",
			  *errorp ? " (" : "",
			  hstrerror(shpherr),
			  *errorp ? ")" : "");
	if (smtp_hello_verify_ptr && !allowed_to_be_broken) {
	    switch (shpherr) {
	    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 */
	}
    }

    return COPY_STRING(hostnm);
}
#endif

/*
 * check to see if the resolved address is local or remote, and if remote
 * whether sender is permitted to relay via SMTP according to
 * smtp_remote_allow.
 *
 * If passed a list from a local address expansion the entire list is "moved"
 * to the appropriate output pointer.
 */
static void
check_smtp_remote_allow(in, out, defer, fail)
    struct addr *in;			/* address to test (XXX may be a list!?!?!?) */
    struct addr **out;			/* produced addr list w/transports */
    struct addr **defer;		/* addrs to defer to a later time */
    struct addr **fail;			/* unresolvable addrs */
{
    char *reason = NULL;
#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;
    }
    if (! EQ(in->transport->driver, "tcpsmtp")) {
	DEBUG3(DBG_ADDR_HI, "check_smtp_remote_allow(%v): OK: transport[%v] driver is not tcpsmtp: %s.\n",
	       in->in_addr, in->transport->name, in->transport->driver);
	*out = in;

	return;
    }
    /*
     * 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) {
	DEBUG1(DBG_ADDR_HI, "check_smtp_remote_allow(%v): OK: address has local parent.\n",
	       in->parent->in_addr);
	*out = in;

	return;
    }
    if (in->succ) {
	/* XXX should this be a full panic()?  or don't we care at this point? */
	write_log(WRITE_LOG_PANIC, "check_smtp_remote_allow(): internal error: passed a list for a non-local address!");
    }
    /*
     * 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.
     */
    if (sender_host_addr && 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;
    }

#ifndef HAVE_BIND
    /*
     * XXX for now just don't allow *any* remote relay in a non-DNS environment
     */
    invalid_relay_error(in, sender_host_addr ? sender_host_addr : "UNKNOWN-IP");
    *fail = in;
#else /* 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 (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 ? sender_host_addr : "UNKNOWN-IP");
	*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 ? sender_host_addr : "UNKNOWN-IP");
		*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 ? sender_host_addr : "UNKNOWN-IP");
	    *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;
    }
#endif /* HAVE_BIND */

    return;
}


/*
 * smtp_input_signals - setup signals for reading in message with smtp
 *
 * Basically, unlink the message except in the case of SIGTERM, which
 * will cause sig_term and queue_only to be set.
 */
static void
smtp_input_signals()
{
    if (signal(SIGHUP, SIG_IGN) != SIG_IGN) {
	(void) signal(SIGHUP, smtp_sig_unlink);
    }
    if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
	(void) signal(SIGINT, smtp_sig_unlink);
    }
    (void) signal(SIGTERM, set_term_signal);
}

/*
 * smtp_processing_signals - setup signals for getting smtp commands
 *
 * basically, everything interesting should cause a call to
 * set_term_signal.
 */
static void
smtp_processing_signals()
{
    if (signal(SIGHUP, SIG_IGN) != SIG_IGN) {
	(void) signal(SIGHUP, set_term_signal);
    }
    if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
	(void) signal(SIGINT, set_term_signal);
    }
    (void) signal(SIGTERM, set_term_signal);
}

/*
 * smtp_reset_signals - reset signals (and alarms)
 *
 * we basically ignore all the interactive signals -- once receive_smtp()
 * returns then signals will be reset as appropriate for the processing and
 * delivery stages.
 */
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);
}

/*
 * set_term_signal - set term_signal and queue_only
 *
 * This is used by signals to abort SMTP command processing and to
 * prevent attempting delivery.
 *
 * NOTE:  This doesn't work correctly for systems that lack restartable
 *	  system calls, as read will return EINTR for such systems,
 *	  rather than continuing.  This situation could be improved,
 *	  though it doesn't really seem worth the rather large amount
 *	  of bother required.
 */
static void
set_term_signal(sig)
    int sig;
{
    DEBUG1(DBG_REMOTE_MID, "set_term_signal(%d) called....\n", sig);

    (void) signal(sig, set_term_signal);
    term_signal = TRUE;
    queue_only = TRUE;
}

/*
 * smtp_receive_timeout_sig - timeout SMTP
 */
/* ARGSUSED */
static void
smtp_receive_timeout_sig(sig)
    int sig;
{
    DEBUG1(DBG_REMOTE_MID, "smtp_receive_timeout_sig(%d) called....\n", sig);

    (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) {
	/* we don't really care about stdio reentrancy -- we're about to exit! */
	dprintf(out_file, "421 4.3.2 %q SMTP command timeout, closing channel\r\n", primary_name);
	fflush(out_file);
    }
    write_log(WRITE_LOG_SYS, "SMTP connection timeout while talking with %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 (smtp_remove_on_timeout) {
	unlink_spool();
    }
    exit(EX_TEMPFAIL);
    /* NOTREACHED */
}

/*
 * 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;
{
    DEBUG1(DBG_REMOTE_MID, "smtp_sig_unlink(%d) called....\n", sig);

    (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 %d while talking with %s%s%s%s%s%s%s%s%s",
	      sig,
	      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);
    /* NOTREACHED */
}

/*
 *	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));
}

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

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