/*
#ident	"@(#)smail/src:RELEASE-3_2_0_112:smtprecv.c,v 1.125 2001/02/06 00:58:45 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.
 */
#define NEED_SOCKETS			/* Required for IP address lookup */
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <signal.h>
#include "defs.h"
#ifdef SIOCGIFNETMASK
# include <sys/param.h>
# include <net/if.h>
#endif
#ifdef HAVE_STDLIB_H
# include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#if defined(POSIX_OS)
# include <unistd.h>
#else /* not POSIX_OS */
# if defined (HAVE_UNISTD_H)
#  include <unistd.h>
# endif
#endif  /* not POSIX_OS */
#if defined(HAVE_LIBWHOSON)
#include <whoson.h>
#endif
#include "config.h"
#include "smail.h"
#include "main.h"
#include "parse.h"
#include "addr.h"
#include "hash.h"
#include "direct.h"
#include "route.h"
#include "transport.h"
#include "alloc.h"
#include "dys.h"
#include "log.h"
#include "spool.h"
#include "iobpeek.h"
#ifdef HAVE_BIND
# include "lookup.h"
# include "bindlib.h"
# include "route.h"
#endif
#include "extern.h"
#include "debug.h"
#include "exitcodes.h"
#include "error.h"

#ifdef HAVE_RFC1413
# include <ident.h>			/* declarations for ident protocol lookups */
#endif

#ifndef INADDR_NONE
# define INADDR_NONE		((unsigned long) (-1)) /* XXX 64bit too??? */
#endif
#ifndef INADDR_LOOPBACK
# define INADDR_LOOPBACK	((unsigned long) 0x7f000001) /* XXX not 64-bit clean */
#endif

#ifndef INET_ADDRSTRLEN
# define INET_ADDRSTRLEN	((4*3)+3+1)	/* "255.255.255.255" */
#endif

#ifdef HAVE_BSD_NETWORKING
# ifndef OBSOLETE_RESOLVER
extern int h_errno;			/* import h_errno; many systems don't define it in <netdb.h> */
# endif
#endif

/* 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 getsockname() */

/*
 * 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 int smtp_sess_deny = 0;		/* reject the SMTP session outright */

#define SMTP_SESS_DENY_RBL	001	/* smtp_rbl_domains matched */
#define SMTP_SESS_DENY_PARANOID	002	/* smtp_hello_reject_dns_paranoid true and host mismatch */
#define SMTP_SESS_DENY_REJECT	004	/* smtp_hello_reject_hosts matched */

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

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

static char *smtp_rbl_match = NULL;	/* full domain name matched by an RBL */
static char *smtp_rbl_addr = NULL;	/* ascii formatted address value of RBL A RR */

/* types local to this file */
enum e_smtp_commands {
    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] */
    HELP_CMD,				/* HELP */
    EOF_CMD,				/* end of file encountered */
    OTHER_CMD				/* unknown command */
};

/* 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 send_session_denied_reply __P((FILE *, char *, char *));
static void non_compliant_reply __P((FILE *, int, char *, char *));
static void illegal_operand_warning __P((char *, char *operand));
static void reset_state __P((void));
static enum e_smtp_commands read_smtp_command __P((FILE *, FILE *));
static int decode_mail_options __P((char *, FILE *));
static void expand_addr __P((char *, FILE *));
static int match_rbl __P((char *, char **));
static int verify_sender __P((char *, char *, FILE *));
static int verify_addr __P((char *, FILE *, int));
static char *verify_host __P((char *, struct sockaddr_in *, struct hostent*, const int, char const **, int *));
static void check_smtp_remote_allow __P((struct addr *, struct addr **, struct addr **, struct addr **));
static void smtp_input_signals __P((void));
static void smtp_processing_signals __P((void));
static void set_term_signal __P((int));
static void smtp_receive_timeout_sig __P((int));
static void smtp_sig_unlink __P((int));
# ifdef HAVE_DF_SPOOL
static long compute_max_message_size_from_df_spool __P((void));
# endif
static void illegal_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 */

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",
#ifdef NO_SMTP_VRFY
    "250-2.0.0    VRFY <address>                  - verify deliverability of address",
#endif
#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    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."
};

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

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

long accepted_msg_size = -1;		/* what we're currently willing to take... */


/*
 * 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)
    FILE *in;				/* stream of SMTP commands */
    FILE *out;				/* channel for responses */
{
    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;
    int from_sa_len = sizeof(from_sa);
    struct sockaddr_in my_sa;
    int my_sa_len = sizeof(my_sa);
    struct hostent *shp = NULL;		/* result of gethostbyaddr() */
#endif /* HAVE_BSD_NETWORKING */

    /* initialize state */
    initialize_state();

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

    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 */
    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 = 0;

	    if (!done_it) {
		write_log(WRITE_LOG_PANIC, "expand_string(): failed for smtp_banner='%s'.",
			  smtp_banner);
		done_it++;
	    }
	    s = "invalid smtp_banner definition -- please notify my postmaster!\nSmail ready";
	}
#ifdef HAVE_EHLO
	p = s;
	/* XXX this leaks, but it's too hard to fix elegantly!  ;-) */
	s = xprintf("%s\nESMTP supported", p);
#endif
	send_smtp_msg(out, 220, "", TRUE, s);
	fflush(out);
    }

#ifdef HAVE_BSD_NETWORKING
    if (out) {
	if ((getpeername(fileno(in), (struct sockaddr *) &from_sa, &from_sa_len) == 0) &&
	    (from_sa_len > 0) &&
	    (from_sa.sin_family == AF_INET)) {

	    p = inet_ntoa(from_sa.sin_addr);
	    sender_host_addr = COPY_STRING(p);

	    if (getsockname(fileno(in), (struct sockaddr *) &my_sa, &my_sa_len) == 0 &&
		(my_sa_len > 0) &&
		(my_sa.sin_family == AF_INET)) {

		struct in_addr netaddr;
		int netbits;
		char mynet[INET_ADDRSTRLEN+3];

		p = inet_ntoa(my_sa.sin_addr);
		smtp_local_addr = COPY_STRING(p);

		/*
		 * NOTE: there is absolutely no way to get the actual interface
		 * netmask on your average 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 netowrk!  ;-)
		 */
		netaddr = inet_makeaddr(inet_netof(my_sa.sin_addr), 0L);
		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_LO, "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_LO, "inet_net_ntop(netaddr=0x%x, netbits=%d): %s\n",
			    ntohl(netaddr.s_addr), netbits, strerror(errno));
		     p = inet_ntoa(inet_makeaddr(ntohl(netaddr.s_addr), 0L));
		     sprintf(mynet, "%s/%d", p, netbits);
		}
		smtp_local_net = COPY_STRING(mynet);
	    } else {
		DEBUG1(DBG_REMOTE_LO, "getsockname(): %s.\n", strerror(errno));
		smtp_local_net = NULL;	/* just to be sure! (inited to NULL) */
		my_sa.sin_family = AF_UNSPEC;
		my_sa.sin_port = 25;
		my_sa.sin_addr.s_addr = htonl((unsigned long) INADDR_LOOPBACK);
	    }
	} else {
	   /* XXX maybe this should be a panic?, or does -bS/-bs rely on this? */
	    DEBUG1(DBG_REMOTE_LO, "getpeername(): %s.\n", out ? strerror(errno) : "<no response channel>");
	    sender_host_addr = NULL;	/* just to be sure! (inited to NULL) */
	    smtp_local_net = NULL;	/* just to be sure! (inited to NULL) */
	    my_sa.sin_family = AF_UNSPEC;
	    my_sa.sin_port = 25;	/* as good a "guess" as any!  ;-) */
	    my_sa.sin_addr.s_addr = htonl((unsigned long) INADDR_LOOPBACK);
	    from_sa.sin_family = AF_UNSPEC;
	    from_sa.sin_port = (unsigned short) -1;
	    from_sa.sin_addr.s_addr = htonl((unsigned long) INADDR_LOOPBACK);
	}
	DEBUG2(DBG_REMOTE_MID, "local addr is [%s], local net might be [%s].\n",
	       inet_ntoa(my_sa.sin_addr),
	       smtp_local_net ? smtp_local_net : "<unset>");
    }
    if (smtp_hello_reject_hosts && sender_host_addr) {
	smtp_sess_deny = match_ip(sender_host_addr, smtp_hello_reject_hosts, ':', ';', &smtp_sess_deny_msg);
	smtp_sess_deny_reason = SMTP_SESS_DENY_REJECT;
    }
    if (smtp_rbl_domains && sender_host_addr) {
	if (! match_ip(sender_host_addr, smtp_rbl_except, ':', ';', (char **) NULL)) {
	    if (match_rbl(sender_host_addr, &smtp_sess_deny_msg)) {
		smtp_sess_deny = 1;
		smtp_sess_deny_reason = SMTP_SESS_DENY_RBL;
	    }
	}
    }
# if defined(HAVE_LIBWHOSON)
    if (out && 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);
	wso = wso_query(sender_host_addr, retbuf, sizeof(retbuf));
	retbuf[sizeof(retbuf)-1] = '\0'; /* just in case... */
	if (wso < 0) {
	    /* XXX hope this doesn't flood the log! */
	    write_log(WRITE_LOG_PANIC, "wso_query(%s): whoson query failed: %s", sender_host_addr, retbuf);
	} else if (wso == 0) {
	    DEBUG2(DBG_REMOTE_MID, "ip '%s' verified by whoson as '%s'\n", sender_host_addr, retbuf);
	    smtp_sess_deny = 0;
	} else {
	    DEBUG1(DBG_REMOTE_MID, "ip '%s' NOT verified by whoson\n", sender_host_addr);
	}
    }
# endif /* HAVE_LIBWHOSON */
    if (sender_host_addr) {
	/* WARNING: must check shp contents before calling gethost*() again */
	if (!(shp = gethostbyaddr((char *) &(from_sa.sin_addr),
				  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 = COPY_STRING(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>");
#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! ;-) */
    /* 
     * This code does a Ident/RFC1413 lookup on the connecting user (if possible)
     * Its here because this may cause a delay and it seems better to have the
     * delay once the connection is established rather than at the start
     *
     * 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 (out && rfc1413_query_timeout > 0) { /* switch off RFC1413 by setting timeout <= 0 */
	DEBUG(DBG_REMOTE_HI, "receive_smtp() getting remote identity.\n");
	if ((ident_sender = ident_id(fileno(in), (int) rfc1413_query_timeout))) {
	    ident_method = "rfc1413";
	}
    }
#endif /* HAVE_RFC1413 */

    if (out) {
	write_log(WRITE_LOG_SYS, "remote connection from %s%s%s[%s]",
		  ident_sender ? ident_sender : "",
		  ident_sender ? "@" : "",
		  sender_host_really ? sender_host_really : "(unknown)",
		  sender_host_addr ? sender_host_addr : "UNKNOWN-IP");
#if 0 /* XXX should we log bsmtp starts too? */
    } else {
	write_log(WRITE_LOG_SYS, "bsmtp session started.");
#endif
    }
    if (out) {
	(void) signal(SIGALRM, smtp_receive_timeout_sig);
    }
    while (! term_signal || out == NULL) {
	int smtp_cmd_id;

	if (out) {
	    alarm(smtp_receive_command_timeout);
	}
	switch ((smtp_cmd_id = read_smtp_command(in, out))) {
	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) {
		fprintf(out, "501 ESMTP support not enabled.  Where did you get the idea it was?!?!?\r\n");
	    }
# ifdef NO_LOG_EHLO /* XXX overloaded from meaning in transports/smtplib.c */
	    DEBUG4(DBG_REMOTE_LO, "received unsupported EHLO from %s%s%s[%s].\n",
		   ident_sender ? ident_sender : "", ident_sender ? "@" : "",
		   sender_host_really ? sender_host_really : "(unknown)",
		   sender_host_addr ? sender_host_addr : "UNKNOWN-IP");
# else /* !NO_LOG_EHLO */
	    write_log(WRITE_LOG_SYS, "remote EHLO: '%s' unsupported, from %s%s%s[%s].",
		      ident_sender ? ident_sender : "", ident_sender ? "@" : "",
		      sender_host_really ? sender_host_really : "(unknown)",
		      sender_host_addr ? sender_host_addr : "UNKNOWN-IP");
# endif /* NO_LOG_EHLO */
#endif /* HAVE_EHLO */
	    break;

	case HELO_CMD:
#ifdef HAVE_BSD_NETWORKING
	    do_greeting(out, FALSE, &from_sa, &shp);
#else
	    do_greeting(out, TRUE);
#endif
	    break;

	case MAIL_CMD:
	    if (smtp_sess_deny) {
		if (out) {
		    send_session_denied_reply(out, "MAIL FROM", data);
		}
		break;
	    }
	    if (*data != '<' || !(p = strchr(data, '>'))) {
		illegal_operand_warning("MAIL FROM", data);
	    } else if (p && *(p+1) && !isspace(*(p+1))) {
		illegal_operand_warning("MAIL FROM", data);
	    }
	    orig_data = COPY_STRING(data);
	    strip_rfc822_comments(data);
	    if (strcmp(data, orig_data) != 0) {
		illegal_operand_warning("MAIL FROM", orig_data);
	    }
	    strip_rfc822_whitespace(data);
	    if (data[0] == '\0') {
		non_compliant_reply(out, 501, "5.5.4", "'MAIL FROM:' requires return return-path address as operand.");
		break;
	    }
	    if (!sender_host) {
	        /* DO NOT send enhanced status as we are apparently not past HELO/EHLO. */
		non_compliant_reply(out, 503, "", "'MAIL FROM:' must be preceded by HELO/EHLO command.");
		break;
	    }
	    if (sender) {
		non_compliant_reply(out, 503, "5.5.1", "Sender already specified");
		break;
	    }
	    sender = preparse_address_1(data, &errstr, &rest);
	    if (!sender) {
		if (out) {
		    fprintf(out, "501 5.5.2 '%s' Address parse error: %s\r\n", data, errstr);
		    fflush(out);
		}
		break;
 	    }
	    if (!verify_sender(sender, data, out)) {
		sender = NULL;
		break;
	    }
	    if (sender && sender[0] == '\0') {
		/* special error sender form <> given */
		sender = COPY_STRING("<>");
	    }
	    if (sender && EQ(sender, "+")) {
		/* special smail-internal <+> was given */
		sender = COPY_STRING("<+>");
	    }
	    if (decode_mail_options(rest, out) < 0) {
		xfree(sender);
		sender = NULL;
		break;
	    }
	    if (out) {
		fprintf(out, "250 2.1.0 %s Sender Okay.\r\n", sender);
		fflush(out);
	    }
	    break;

	case RCPT_CMD:
	    if (smtp_sess_deny) {
		if (out) {
		    send_session_denied_reply(out, "RCPT TO", data);
		}
		break;
	    }
	    if (*data != '<' || !(p = strchr(data, '>'))) {
		illegal_operand_warning("RCPT TO", data);
	    } else if (p && *(p+1) && !isspace(*(p+1))) {
		illegal_operand_warning("RCPT TO", data);
	    }
	    orig_data = COPY_STRING(data);
	    strip_rfc822_comments(data);
	    if (strcmp(data, orig_data) != 0) {
		illegal_operand_warning("RCPT TO", orig_data);
	    }
	    strip_rfc822_whitespace(data);
	    if (data[0] == '\0') {
		non_compliant_reply(out, 501, "5.5.4", "'RCPT TO:' requires forward-path address as operand.");
		break;
	    }
	    /* do this after the above checks just to catch gross errors earlier */
	    if (!sender) {
#ifdef HAVE_ESMTP_PIPELINING		/* defined in iobpeek.h if support is possible */
		non_compliant_reply(out, 503, "5.5.1", "'RCPT TO:' must be preceded by MAIL FROM: command (if not using ESMTP PIPELINING).");
#else
		non_compliant_reply(out, 503, "5.5.1", "'RCPT TO:' must be preceded by MAIL FROM: command.");
#endif
		break;
	    }
	    if (out) {
		char *reason;

		if (sender_host_addr && smtp_recipient_no_verify &&
		    match_ip(sender_host_addr, smtp_recipient_no_verify, ':', ';', &reason)) {
		    fprintf(out, "250 2.1.0 '%s' %s%s%s Recipient Unverified%s%s%s.\r\n", orig_data, /* XXX */
			    (*data == '<') ? "" : "<",
			    data,
			    (*data == '<') ? "" : "<",
			    reason ? " (" : "",
			    reason ? reason : "",
			    reason ? ")" : "");
		    fflush(out);
		    /* fall through to add address to recipient list */
		} else if (! verify_addr(data, out, 1)) { /* check relay rules */
		    /* NOTE: error reply is printed by verify_addr() */
		    break;
		}
		if (smtp_max_recipients && (num_smtp_recipients++ >= smtp_max_recipients)) {
		    fprintf(out, "452 4.5.3 Too many recipients.  Administrative limit exceeded.\r\n");
		    fflush(out);
		    break;
		}
	    }
	    /*
	     * 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);
		}
		break;
	    }
	    if (sender == NULL) {
		if (out) {
#ifdef HAVE_ESMTP_PIPELINING		/* defined in iobpeek.h if support is possible */
		    non_compliant_reply(out, 503, "5.5.1", "'DATA' command must be preceded by 'MAIL FROM:' command (if not using ESMTP PIPELINING).");
#else
		    non_compliant_reply(out, 503, "5.5.1", "'DATA' command must be preceded by 'MAIL FROM:' command.");
#endif
		} else {
		    /* sink the message for the sake of further batched cmds */
		    if (spool_fn) {
			close_spool();
		    }
		    swallow_smtp(in);
		}
		break;
	    }
	    if (recipients == NULL) {
		if (out) {
#ifdef HAVE_ESMTP_PIPELINING		/* defined in iobpeek.h if support is possible */
		    non_compliant_reply(out, 503, "5.5.1", "'DATA' command must be preceded by 'RCPT TO:' command (if not using ESMTP PIPELINING).");
#else
		    non_compliant_reply(out, 503, "5.5.1", "'DATA' command must be preceded by 'RCPT TO:' command.");
#endif
		} else {
		    /* sink 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(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(smtp_receive_message_timeout);
	    }
	    smtp_remove_on_timeout = 1;
	    if (queue_message(in, SMTP_DOTS, recipients, &errstr) == FAIL) {
		exitvalue = EX_IOERR;
		log_spool_errors();
		if (out) {
		    fprintf(out, "451 4.3.0 Failed to queue message: %s: %s\r\n",
			    errstr, strerror(errno));
		    fflush(out);
		} else if (errfile) {
		    /* NOTE: This can happen if the parent goes away without sending anything */
		    /* XXX why not write_log()???? */
		    fprintf(errfile, "Failed to queue message: %s: %s\r\n",
			    errstr, errno == 0 ? "(no input?)" : strerror(errno));
		}
		unlink_spool();
		reset_state();
		smtp_remove_on_timeout = 0;
		break;
	    }
	    smtp_processing_signals();
	    if (sender == NULL) {
		unlink_spool();
		reset_state();
		smtp_remove_on_timeout = 0;
		break;
	    }
	    if (read_message() == NULL) {
		log_spool_errors();
		unlink_spool();
		if (out) {
		    fprintf(out, "451 4.3.0 error in spooled message\r\n");
		    fflush(out);
		}
		reset_state();
		smtp_remove_on_timeout = 0;
		break;
	    }
	    /* don't be fussy about headers, use msg_body_size instead of msg_size */
	    if (max_message_size > 0 && msg_body_size > max_message_size) {
		/* XXX should this be response-rate limited? */
#ifdef NO_LOG_SMTP_TOO_BIG		/* only log when ESMTP SIZE was ignored */
		if (accepted_msg_size > 0 && accepted_msg_size != -1) {
#else
		write_log(WRITE_LOG_SYS, "remote DATA: message too big%s (body %ld bytes) from %s%s%s%s%s%s%s%s%s.",
			  (accepted_msg_size > 0 && accepted_msg_size != -1) ? ", client ignored ESMTP SIZE" : "",
			  msg_body_size,
			  ident_sender ? ident_sender : "",
			  ident_sender ? "@" : "",
			  sender_host ? sender_host : "",
			  sender_host_really ? "(" : "",
			  sender_host_really ? sender_host_really : "",
			  sender_host_really ? ")" : "",
			  sender_host_addr ? " [" : "",
			  sender_host_addr ? sender_host_addr : "",
			  sender_host_addr ? "]" : "");
#endif
#ifdef NO_LOG_SMTP_TOO_BIG
		}
#endif
		log_spool_errors();
		unlink_spool();
		if (out) {
		    fprintf(out, "552-5.3.4 Message too big for system.\r\n");
		    /* NOTE: the remainder of this reply is identical in two places */
		    fprintf(out, "552-5.3.4 Do NOT send files by e-mail!\r\n");
		    fprintf(out, "552-5.3.4 If you are sending a MIME message please set your mailer so that it\r\n");
		    fprintf(out, "552-5.3.4 splits messages larger than 64KB into muliple parts.\r\n");
		    fprintf(out, "552 5.3.4 Otherwise please learn to transfer files with a file transfer protocol.\r\n");
		    fflush(out);
		}
		reset_state();
		smtp_remove_on_timeout = 0;
		break;
	    }
	    alarm(0);
	    smtp_remove_on_timeout = 0;

	    /* penalize all very large messages */
	    if (msg_body_size > 100000) {
		queue_only = TRUE;
	    }
	    check_grade();
	    log_incoming();
	    log_spool_errors();
	    if (out) {
		fprintf(out, "250 2.6.0 Mail accepted, queue ID %s%s on %s.\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) {
		strip_rfc822_comments(data);
		strip_rfc822_whitespace(data);
		verify_addr(data, out, 0);
		reset_hit_table();
		fflush(out);
		if (smtp_vrfy_delay) {
		    sleep(smtp_vrfy_delay);
		}
	    }
	    break;

	case EXPN_CMD:
	    if (smtp_sess_deny) {
		if (out) {
		    send_session_denied_reply(out, "EXPN", data);
		}
		break;
	    }
	    if (out) {
#ifdef NO_SMTP_EXPN
		fprintf(out, "502 5.5.1 Command not implemented\r\n");
#else
		if (smtp_allow_expn) {
		    strip_rfc822_comments(data);
		    strip_rfc822_whitespace(data);
		    expand_addr(data, out);
		    reset_hit_table();
		    fflush(out);
		    if (smtp_expn_delay) {
			sleep(smtp_expn_delay);
		    }
		} else {
		    fprintf(out, "502 5.7.0 Command disabled\r\n");
		}
#endif
		fflush(out);
	    }
	    break;

	case QUIT_CMD:
	    if (out) {
		fprintf(out, "221 2.2.0 %s 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 we should 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%s.",
		      ident_sender ? ident_sender : "",
		      ident_sender ? "@" : "",
		      sender_host ? sender_host : "",
		      sender_host_really ? "(" : "",
		      sender_host_really ? sender_host_really : "",
		      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 ? "(" : "",
		      sender_host_really ? sender_host_really : "",
		      sender_host_really ? ")" : "",
		      sender_host_addr ? " [" : "",
		      sender_host_addr ? sender_host_addr : "",
		      sender_host_addr ? "]" : "");
#endif
	    reset_state();
	    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 DEBUG_CMD: {
	    int temp = 0;		/* new debug level */

	    /* XXX should this 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) {
			char *errbuf = NULL;

			temp = c_atol(data, &errbuf);
			if (errbuf) {
			    fprintf(out, "500 5.5.4 bad number: %s\r\n", errbuf);
			    fflush(out);
			    break;
			}
		    } else {
			temp = 1;
		    }
		    if (temp == 0) {
			fprintf(out, "250 2.3.0 Debugging disabled\r\n");
		    } else {
			DEBUG(DBG_REMOTE_LO, "debugging output grabbed by SMTP\r\n");
			fprintf(out, "250 2.3.0 Debugging level: %d\r\n", temp);
		    }
		    fflush(out);
		    debug = temp;
		    errfile = out;
		    write_log(WRITE_LOG_SYS, "remote DEBUG: '%s' 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 ? "(" : "",
			      sender_host_really ? sender_host_really : "",
			      sender_host_really ? ")" : "",
			      sender_host_addr ? " [" : "",
			      sender_host_addr ? sender_host_addr : "",
			      sender_host_addr ? "]" : "");
		    break;
		}
		fprintf(out, "500 5.7.0 I hear you knocking, 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: '%s' 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 ? "(" : "",
		      sender_host_really ? sender_host_really : "",
		      sender_host_really ? ")" : "",
		      sender_host_addr ? " [" : "",
		      sender_host_addr ? sender_host_addr : "",
		      sender_host_addr ? "]" : "");
	    if (out) {
		int i;

		for (i = 0; i < TABLESIZE(help_msg); i++) {
		    fprintf(out, "%s\r\n", help_msg[i]);
		}
		fflush(out);
	    }
	    break;

	case EOF_CMD:
	    if (out) {			/* XXX can this hang? */
		fprintf(out, "421 4.3.0 %s 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.",
		      ident_sender ? ident_sender : "",
		      ident_sender ? "@" : "",
		      sender_host ? sender_host : "",
		      sender_host_really ? "(" : "",
		      sender_host_really ? sender_host_really : "",
		      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 OTHER_CMD: {
	    static int logged_unknown = 0;

	    /* XXX should this be response-rate limited? */
	    if (out) {
		fprintf(out, "500 5.5.1 Command unrecognized\r\n");
		fflush(out);
	    }
	    if (!logged_unknown) {
		write_log(WRITE_LOG_SYS, "remote %s: 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 ? "(" : "",
			  sender_host_really ? sender_host_really : "",
			  sender_host_really ? ")" : "",
			  sender_host_addr ? " [" : "",
			  sender_host_addr ? sender_host_addr : "",
			  sender_host_addr ? "]" : "");
	    }
	    logged_unknown++;
	    break;
	}
	default:
	    non_compliant_reply(out, 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 ? "(" : "",
		      sender_host_really ? sender_host_really : "",
		      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: %s.", 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.
     */
    fprintf(out, "421 4.3.2 %s 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 ? "(" : "",
	      sender_host_really ? sender_host_really : "",
	      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;
}

/*
 * Print a message on the FILE stream 'out' that may contain newlines such that
 * it conforms to the SMTP status reply format.
 *
 * XXX should we have an auto-wrap flag to word-wrap at < 80 chars?
 */
static void
send_smtp_msg(out, status, dsn, endmsg, str)
    FILE *out;
    int status;
    char *dsn;
    int endmsg;
    const char *str;
{
    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 DSN is proper
     * too.  For example the first two digits are not allowed to be greater
     * than 5, and of course 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);
     *	  if ((status % 10) > 5) DEBUG();
     *
     */
    while (*str) {
	/*
	 * XXX FIXME!!!! we should probably be eliminating/quoting unwelcome
	 * characters too!
	 */	    
	fprintf(out, "%d%c%s%s%s",
		status,
		(!endmsg || strchr(str, '\n')) ? '-' : ' ',
		(dsn && *dsn) ? " " : "",
		(dsn && *dsn) ? dsn : "",
		(dsn && *dsn) ? " " : "");
	while ((c = *str)) {
	    str++;
	    if (c == '\n') {
		break;
	    }
	    putc(c, out);
	}
	putc('\r', out);
	putc('\n', out);
    }

    return;
}

static void
do_greeting(out, ehlo_p,
#ifdef HAVE_BSD_NETWORKING
	    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;
    struct hostent **shpp;	/* result of gethostbyaddr() */
#endif
{
    struct hostent *shp = *shpp; /* keep a local copy */

    *shpp = NULL;		/* make sure this is reset if it is needed again */

    if (smtp_sess_deny) {
	char *log_reason = NULL;

	/* XXX should this be response-rate limited? */
	if (out) {
	    /*
	     * NOTE: do not include enhanced status codes (RFC1893) in the
	     *       response to HELO/EHLO.
	     */
	    fprintf(out, "550-You are not permitted to send mail from %s[%s].\r\n",
		    sender_host_really ? sender_host_really : "(unknown)",
		    sender_host_addr ? sender_host_addr : "UNKNOWN-IP");
	    fprintf(out, "550-All SMTP connections have been blocked from that address\r\n");
	    switch (smtp_sess_deny_reason) {
	    case SMTP_SESS_DENY_RBL:
		fprintf(out, "550-because it matches the RBL (Realtime/Reverse Blocking List):\r\n");
		fprintf(out, "550-\r\n");
		fprintf(out, "550-\t%s\tA\t%s\r\n", smtp_rbl_match, smtp_rbl_addr);
		break;
	    case SMTP_SESS_DENY_REJECT:
		fprintf(out, "550-by the postmaster at %s.\r\n", primary_name);
		break;
	    case SMTP_SESS_DENY_PARANOID: /* XXX this probably can't get here.... */
		fprintf(out, "550-because either a DNS spoofing attack is underway at the moment, or your\r\n");
		fprintf(out, "550-zone's DNS is severely mis-configured!\r\n");
		break;
	    }
	    fprintf(out, "550-\r\n");
	    if (smtp_sess_deny_msg) {
		fprintf(out, "550-Please note the following important additional information:\r\n");
		fprintf(out, "550-\r\n");
		/* setting the DSN to " " is a hack to get indentation! */
		send_smtp_msg(out, 550, " ", FALSE, smtp_sess_deny_msg);
		fprintf(out, "550-\r\n");
	    }
	    fprintf(out, "550-Please forward this message to your own LOCAL postmaster and ask\r\n");
	    fprintf(out, "550-them to contact us regarding this issue.\r\n");
	    fprintf(out, "550-\r\n");
	    fprintf(out, "550-Postmaster@[%s]: Please contact <postmaster@%s>\r\n",
		    sender_host_addr ? sender_host_addr : "UNKNOWN-IP",
		    primary_name);
	    fprintf(out, "550-(via an unblocked server of course!) for assistance in resolving this\r\n");
	    fprintf(out, "550-issue.  You must include your IP number, given above, in order to\r\n");
	    fprintf(out, "550 receive any help, and including this entire status reply is ideal.\r\n");
	    fflush(out);
	}
	switch (smtp_sess_deny_reason) {
	case SMTP_SESS_DENY_RBL:
	    log_reason = xprintf("matched RBL: %s [%s]", smtp_rbl_match, smtp_rbl_addr);
	    break;
	case SMTP_SESS_DENY_REJECT:
	    log_reason = xprintf("matched in smtp_hello_reject_hosts");
	    break;
	case SMTP_SESS_DENY_PARANOID: /* XXX this probably can't get here.... */
	    log_reason = xprintf("triggered by smtp_hello_reject_dns_paranoid");
	    break;
	}
	write_log(WRITE_LOG_SYS, "remote %s: refusing SMTP connection from %s%s%s[%s]: %s.",
		  ehlo_p ? "EHLO" : "HELO",
		  ident_sender ? ident_sender : "",
		  ident_sender ? "@" : "",
		  sender_host_really ? sender_host_really : "(unknown)",
		  sender_host_addr ? sender_host_addr : "UNKNOWN-IP",
		  log_reason);
	xfree(log_reason);
	return;
    }
    orig_data = COPY_STRING(data);
    strip_rfc822_comments(data);
    if (strcmp(data, orig_data) != 0) {
	illegal_operand_warning(ehlo_p ? "EHLO" : "HELO", orig_data);
    }
    strip_rfc822_whitespace(data);
    if (!data[0]) {
	non_compliant_reply(out, 501, "", "SMTP greeting requires a hostname (or IP address literal) as operand");
	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),
				      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 = COPY_STRING(shp->h_name);
	    }
	}
	errstr = NULL;
	fatal = 0;
	if (! (sender_host = verify_host(data, saddrp, shp, h_errno, &errstr, &fatal))) {
	    if (fatal) {
		/* XXX should this be response-rate limited? */

		fprintf(out, "501-%s requires a valid host name as operand: '%s'\r\n",
			ehlo_p ? "EHLO" : "HELO",
			data);
	    } else {
		fprintf(out, "401-%s temporary error while validating host name '%s'.\r\n",
			ehlo_p ? "EHLO" : "HELO",
			data);
	    }
	    fprintf(out, "%d%sconnection %s from %s%s%s%s%s%s%s%s",
		    fatal ? 501 : 401,
		    errstr ? "-" : " ",
		    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 ? (fatal ? ".\r\n501" : ".\r\n401") : "" ,
		    errstr ? "-Reason given was:\r\n" : ".\r\n");
	    if (errstr) {
		/* setting the DSN to " " is a hack to get indentation! */
		send_smtp_msg(out, fatal ? 501 : 401, " ", TRUE, errstr);
	    }
	    fflush(out);
	}
	if (!sender_host || errstr) { /* log even "hidden" errors as warnings */
	    write_log(WRITE_LOG_SYS, "remote %s: %s operand: '%s': from %s%s%s%s%s%s%s%s%s%s%s.",
		      ehlo_p ? "EHLO" : "HELO",
		      sender_host ? "warning: questionable" : "rejected: invalid",
		      orig_data,
		      ident_sender ? ident_sender : "",
		      ident_sender ? "@" : "",
		      sender_host ? sender_host : "",
		      sender_host_really ? "(" : "",
		      sender_host_really ? sender_host_really : "",
		      sender_host_really ? ")" : "",
		      sender_host_addr ? " source [" : "",
		      sender_host_addr ? sender_host_addr : "",
		      sender_host_addr ? "]" : "",
		      errstr ? ": " : "",
		      errstr ? errstr : "");
	}
	if (!sender_host) {
	    return;			/* broken DNS or forger */
	}
#else /* !HAVE_BSD_NETWORKING */
	/* XXX shouldn't be lazy -- should verify some forms the hard way! */
	DEBUG1(DBG_REMOTE_LO, "receive_smtp(): no BSD networking, so not verifying sender host %s.\n", data);
#endif /* HAVE_BSD_NETWORKING */
    } else if (!sender_host && data[0] != '\0') {
	sender_host = COPY_STRING(data);
    }
    if (out) {
	/* XXX RFC 0821 says "The receiver-SMTP identifies itself to
	 * the sender-SMTP in [...] the response to this command
	 * [HELO]."
	 */
	fprintf(out, "250%s%s Hello %s (%s%s%s%s%s%s)%s\r\n",
		ehlo_p ? "-" : " ",
		primary_name, data,
		ident_sender ? ident_sender : "",
		ident_sender ? "@" : "",
		sender_host_really ? sender_host_really : (sender_host ? sender_host : "(UNKNOWN)"),
		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");
#ifdef HAVE_DF_SPOOL
	    accepted_msg_size = compute_max_message_size_from_df_spool();
#endif
	    if (accepted_msg_size == -1 || (max_message_size > 0 && max_message_size < accepted_msg_size)) {
		accepted_msg_size = max_message_size;
	    }
	    if (accepted_msg_size > 0 && accepted_msg_size != -1) {
		fprintf(out, "250-SIZE %ld\r\n", (long) accepted_msg_size);
	    } else {
		fprintf(out, "250-SIZE\r\n");
	    }
	    fprintf(out, "250-8BITMIME\r\n");
#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
	    fprintf(out, "250 HELP\r\n");
	}
	(void) fflush(out);
    }
    reset_state();		/* greeting implies RSET */

    return;
}

static void
send_session_denied_reply(out, cmd, param)
    FILE *out;
    char *cmd;
    char *param;
{
    /* XXX should this be response-rate limited? */

    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");
    /* 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 %s:%s%s%s refusing SMTP connection 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 ? "(" : "",
	      sender_host_really ? sender_host_really : "",
	      sender_host_really ? ")" : "",
	      sender_host_addr ? " [" : "",
	      sender_host_addr ? sender_host_addr : "",
	      sender_host_addr ? "]" : "");
    return;
}

static void
non_compliant_reply(out, ecode, estatus, errtxt)
    FILE *out;
    int ecode;
    char *estatus;		/* enhanced status code (RFC1893) */
    char *errtxt;
{
    /* XXX should this be response-rate limited? */

#ifdef LOG_SMTP_NON_COMPLIANCE
    write_log(WRITE_LOG_SYS, "sent %d-%s: '%s' to %s%s%s%s%s%s%s%s%s.",
	      ecode,
	      estatus,
	      errtxt,
	      ident_sender ? ident_sender : "",
	      ident_sender ? "@" : "",
	      sender_host ? sender_host : "",
	      sender_host_really ? "(" : "",
	      sender_host_really ? sender_host_really : "",
	      sender_host_really ? ")" : "",
	      sender_host_addr ? " source [" : "",
	      sender_host_addr ? sender_host_addr : "",
	      sender_host_addr ? "]" : "");
#endif
    if (!out) {
	return;
    }
    fprintf(out, "%d-%s %s\r\n%d-%s\r\n", ecode, estatus, errtxt, ecode, estatus);
    fprintf(out, "%d-%s If you are seeing this message in a bounce, or in an alert box\r\n", ecode, estatus);
    fprintf(out, "%d-%s from your mailer client, etc., then your mailer software\r\n", ecode, estatus);
#ifdef HAVE_ESMTP_PIPELINING		/* defined in iobpeek.h if support is possible */
    fprintf(out, "%d-%s is not showing you the correct and meaningful SMTP 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
    fprintf(out, "%d %s Please report this error to those responsible for your mailer software.\r\n", ecode, estatus);
    fflush(out);
    return;
}

static void
illegal_operand_warning(operator, operand)
    char *operator;
    char *operand;
{
#ifdef LOG_SMTP_ILLEGAL_OPERAND_WARNING
    write_log(WRITE_LOG_SYS, "remote %s: '%s' operand not strictly legal 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 ? "(" : "",
	      sender_host_really ? sender_host_really : "",
	      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;

    /* 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_commands
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' */
    static struct str input;		/* buffer storing recent command */
    static int inited = FALSE;		/* TRUE if input initialized */
    static struct smtp_cmd_list {
	char *name;
	enum e_smtp_commands cmd;
    } 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 },
	{ "HELP",	HELP_CMD },
    };
    struct smtp_cmd_list *cp;

    DEBUG(DBG_REMOTE_HI, "read_smtp_command() called.\n");
    if (! inited) {
	STR_INIT(&input);
	inited = TRUE;
    } else {
	STR_CHECK(&input);
	STR_CLEAR(&input);
    }
#if 0 /* ndef 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);
#if 0 /* ndef 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 */

#ifndef NO_LOG_SMTP_CMD_TOO_LONG
    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 ? "(" : "",
		  sender_host_really ? sender_host_really : "",
		  sender_host_really ? ")" : "",
		  sender_host_addr ? " [" : "",
		  sender_host_addr ? sender_host_addr : "",
		  sender_host_addr ? "]" : "");
	if (c != '\n') {
	    while ((c = getc(fp)) != '\n') {
		sleep(1);			/* "Just try it buddy!" */
	    }
	}
    }
#endif

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

    DEBUG4(DBG_REMOTE_MID, "read_smtp_command() got '%s' (%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(*data); data++) {
		;
	    }
	    DEBUG2(DBG_REMOTE_MID, "read_smtp_command() returning '%s' with operand '%s'.\n", cp->name, data);
	    return cp->cmd;
	}
    }

    DEBUG1(DBG_REMOTE_LO, "read_smtp_command() returning OTHER_CMD: '%s'.\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;

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

	/* maybe we have an extended MAIL command */
	while (isspace(*rest)) {
	    ++rest;
	}
	restlen = 0;
	/* find end of option name */
	while (*(rest+restlen) && !isspace(*(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, 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(*(rest+restlen))) {
		++restlen;
	    }
	    ch = *(rest+restlen);
	    *(rest+restlen) = '\0';	/* NUL terminate # */
	    body_size = c_atol(rest, &errbuf);
	    *(rest+restlen) = ch;	/* restore */
	    if (errbuf) {
		if (out) {
		    fprintf(out, "501-5.5.4 bad number: %s\r\n", errbuf);
		    non_compliant_reply(out, 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(*(rest+restlen))) {
		non_compliant_reply(out, 501, "5.5.4", "'MAIL FROM:' malformed SIZE parameter.");
		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 ? "(" : "",
			  sender_host_really ? sender_host_really : "",
			  sender_host_really ? ")" : "",
			  sender_host_addr ? " [" : "",
			  sender_host_addr ? sender_host_addr : "",
			  sender_host_addr ? "]" : "");
#endif
		if (out) {
#ifdef HAVE_DF_SPOOL
		    /* XXX this might not be 100% right if short of spool space! */
		    fprintf(out, "552-5.3.4 A message of %ld bytes is far too large!\r\n", (long) body_size);
#else
		    fprintf(out, "552-5.3.4 A message of %ld bytes is far too large!\r\n", (long) body_size);
#endif
		    /* NOTE: the remainder of this reply is identical in two places */
		    fprintf(out, "552-5.3.4 Do NOT send files by e-mail!\r\n");
		    fprintf(out, "552-5.3.4 If you are sending a MIME message please set your mailer so that it\r\n");
		    fprintf(out, "552-5.3.4 splits messages larger than 64KB into muliple parts.\r\n");
		    fprintf(out, "552 5.3.4 Otherwise please learn to transfer files with a file transfer protocol.\r\n");
		    fflush(out);
		}
		return -1;
	    }
	} else if (strncmpic(rest, "BODY", restlen) == 0) {
	    rest += restlen;
	    if (*rest != '=') {
		non_compliant_reply(out, 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(*(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=%s parameter value!", rest);
	    }
	    *(rest+restlen) = ch;	/* restore */
	} else {
	    non_compliant_reply(out, 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 */

# ifndef NO_LOG_SMTP_EXPN
    write_log(WRITE_LOG_SYS, "remote EXPN: '%s' 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 ? "(" : "",
	      sender_host_really ? sender_host_really : "",
	      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) {
	fprintf(out, "501 5.1.3 %s address parse error: %s\r\n", in_addr, 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);
    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) {
	    fprintf(out, "250-2.1.0 %s\r\n", cur->in_addr);
	}
	/* the last one should not begin with 250- */
	fprintf(out, "250 2.1.0 %s\r\n", cur->in_addr);
    }
    if (defer) {
	for (cur = defer; cur->succ; cur = cur->succ) {
	    fprintf(out, "550-5.1.5 '%s' Address processing error.\r\n", cur->in_addr);
	    if (cur->error && cur->error->message) {
		fprintf(out, "550-5.1.5 %s.\r\n",  cur->error->message);
	    }
	}
	if (cur->error && cur->error->message) {
	    fprintf(out, "550-5.1.5 '%s' Address processing error.\r\n", cur->in_addr);
	    fprintf(out, "550 5.1.5 %s.\r\n",  cur->error->message);
	} else {
	    fprintf(out, "550 5.1.5 '%s' Address processing error.\r\n", cur->in_addr);
	}
    }
    if (fail) {
	for (cur = fail; cur->succ; cur = cur->succ) {
	    fprintf(out, "550-5.1.1 '%s' Address is not deliverable.\r\n", cur->in_addr);
	    if (cur->error && cur->error->message) {
		fprintf(out, "550-5.1.1 %s.\r\n",  cur->error->message);
	    }
	}
	if (cur->error && cur->error->message) {
	    fprintf(out, "550-5.1.1 '%s' Address is not deliverable.\r\n", cur->in_addr);
	    fprintf(out, "550 5.1.1 %s.\r\n",  cur->error->message);
	} else {
	    fprintf(out, "550 5.1.1 '%s' Address is not deliverable.\r\n", cur->in_addr);
	}
    }
    fflush(out);
}
#endif	/* !NO_SMTP_EXPN */

/*
 * match_rbl - look up addr in list of addrs given in smtp_rbl_domains
 *
 * - smtp_rbl_domains is a colon-separated list of domains in which an A RR for
 * the ascii-octet-inverted connecting address (ala in-addr.arpa) 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:
 *
 *	smtp_rbl_domains="rbl1.domain;127.0.0.1,10/8\
 *			 :rbl2.domain;127.0.0/24"
 *
 * WARNING: don't diddle with smtp_rbl_domains -- it may be read-only string.
 *
 * Returns 1 if any matched, else 0.
 *
 *	if it matches smtp_rbl_match will be set to the fully expanded domain
 *	that matched and smtp_rbl_addr will be set to the A RR's value, both as
 *	an ASCII string, and as well if a TXT RR is found at the same domain
 *	then smtp_sess_deny_msg will be set to point to its value too.
 */
static int
match_rbl(addr, msgp)
    char *addr;				/* ascii form of remote address */
    char **msgp;			/* pointer to hold TXT message pointer */
{
    char *pat_p, *end_p;
    char *revaddr;
    unsigned int len;

    revaddr = flip_inet_addr(addr);

    /* run through the pattern list */
    for (pat_p = smtp_rbl_domains; /* 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(*pat_p)) {
	       pat_p++;
	}
	/* find the end of the next pattern */
	end_p = pat_p;
	while (*end_p && *end_p != ':' && !isspace(*pat_p)) {
	       end_p++;
	}
	/* skip empty patterns */
	len = end_p - pat_p;
	if (len > 0) {
	    char *rbl;
	    unsigned int rbl_len = strlen(revaddr) + len + 1; /* one more for the '.' */
	    char *p;
	    struct hostent *hp;

	    rbl = xmalloc(rbl_len + 1);
	    strcpy(rbl, revaddr);
	    strcat(rbl, ".");
	    strncat(rbl, pat_p, len);
	    rbl[rbl_len] = '\0';
	    DEBUG1(DBG_ADDR_HI, "match_rbl(): working on RBL spec '%s'\n", rbl);

	    if ((p = strchr(rbl, ';'))) {
		a_rr_pats = p;
		*a_rr_pats = '\0';
		a_rr_pats++;
		DEBUG1(DBG_ADDR_HI, "match_rbl(): found explicit IP pattern list '%s'\n", a_rr_pats);
	    }
	    DEBUG2(DBG_ADDR_MID,
		   "match_rbl(): checking if A RR matching [%s] is found at RBL '%s'\n",
		   a_rr_pats, rbl);

	    /*
	     * Do we (really?) care if there's a temporary server failure when
	     * trying to look up an RBL?  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.
	     */
	    if ((hp = gethostbyname(rbl))) {
		struct in_addr rbl_addr;
		memcpy(&rbl_addr, hp->h_addr_list[0], sizeof(struct in_addr));
		smtp_rbl_addr = COPY_STRING(inet_ntoa(rbl_addr));
		if (match_ip(smtp_rbl_addr, a_rr_pats, ',', '\0', (char **) NULL)) {
		    struct error *junkerr;

		    smtp_rbl_match = rbl;
		    smtp_sess_deny_msg = bind_lookup_txt_rr(rbl, &junkerr);
		    DEBUG5(DBG_ADDR_LO,
			   "match_rbl(): found an A RR matching one of [%s] at RBL %s%s%s%s\n",
			   a_rr_pats, rbl,
			   smtp_sess_deny_msg ? "\n\twith associated TXT RR\n\t``" : "",
			   smtp_sess_deny_msg ? smtp_sess_deny_msg : "",
			   smtp_sess_deny_msg ? "''" : "");
		    /* NOTE: don't free rbl here! */
		    xfree(revaddr);
		    return 1;
		}
	    }

	    xfree(rbl);
	}
	if (*end_p == '\0') {
	    break;
	}
    }
    xfree(revaddr);

    return 0;
}


/*
 * 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 */
{
    struct addr *vaddr;
    int form;

    vaddr = alloc_addr();
    vaddr->in_addr = COPY_STRING(raw_sender);
    vaddr->work_addr = COPY_STRING(pp_sender);
    form = parse_address(vaddr->work_addr, &vaddr->target,
			 &vaddr->remainder, &vaddr->parseflags);
    if (*vaddr->work_addr && (form == FAIL || form == PARSE_ERROR)) {
        fprintf(out, "501 5.1.7 '%s' sender address parse error: %s.\r\n",
		raw_sender,
		(vaddr->remainder) ? vaddr->remainder : "<unknown error>");
	fflush(out);
	xfree(vaddr->in_addr);
	xfree(vaddr->work_addr);
	xfree((char *) vaddr);
	return 0;
    }
    if (*vaddr->work_addr && !EQ(vaddr->work_addr, "+") && form == LOCAL) {
	if (out) {
	    fprintf(out, "550-5.1.7 '%s' sender address cannot be an unqualified address\r\n", pp_sender);
	    fprintf(out, "550 5.1.7 (i.e. it must have a domain portion).\r\n");
	    fflush(out);
	}
	write_log(WRITE_LOG_SYS, "remote MAIL FROM: '%s' is a local address; by %s%s%s%s%s%s%s%s%s.",
		  pp_sender,
		  ident_sender ? ident_sender : "",
		  ident_sender ? "@" : "",
		  sender_host ? sender_host : "",
		  sender_host_really ? "(" : "",
		  sender_host_really ? sender_host_really : "",
		  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 (*vaddr->work_addr && !EQ(vaddr->work_addr, "+") && (form == BERKENET || form == DECNET)) {
	/* XXX should be impossible unless USE_DECNET or USE_BERKENET are defined */
	if (out) {
	    fprintf(out, "553 5.1.7 '%s' Sender address cannot be a %s form address.\r\n",
		    pp_sender,
		    (form == BERKENET) ? "BERKENET" :
		    (form == DECNET) ? "DECNET" : "<bad-form!>");
	    fflush(out);
	}
	write_log(WRITE_LOG_SYS, "remote MAIL FROM: '%s' is a %s(%d) address; by %s%s%s%s%s%s%s%s%s.",
		  pp_sender,
		  (form == BERKENET) ? "BERKENET" :
		  (form == DECNET) ? "DECNET" : "<bad-form!>",
		  form,
		  ident_sender ? ident_sender : "",
		  ident_sender ? "@" : "",
		  sender_host ? sender_host : "",
		  sender_host_really ? "(" : "",
		  sender_host_really ? sender_host_really : "",
		  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 (*vaddr->work_addr && !EQ(vaddr->work_addr, "+") && form != MAILBOX) {
	/*
	 * XXX Do we really want to allow RFC_ROUTE/RFC_ENDROUTE addresses?
	 * XXX What about other forms (eg. UUCP_ROUTE, PCT_MAILBOX)?
	 */
	write_log(WRITE_LOG_SYS, "remote MAIL FROM: '%s' (target %s) is a %s(%d) address; by %s%s%s%s%s%s%s%s%s.",
		  pp_sender,
		  vaddr->target,
		  (form == RFC_ROUTE) ? "RFC_ROUTE" :
		  (form == RFC_ENDROUTE) ? "RFC_ENDROUTE" :
		  (form == UUCP_ROUTE) ? "UUCP_ROUTE" :
		  (form == PCT_MAILBOX) ? "PCT_MAILBOX" : "<bad-form!>",
		  form,
		  ident_sender ? ident_sender : "",
		  ident_sender ? "@" : "",
		  sender_host ? sender_host : "",
		  sender_host_really ? "(" : "",
		  sender_host_really ? sender_host_really : "",
		  sender_host_really ? ")" : "",
		  sender_host_addr ? " [" : "",
		  sender_host_addr ? sender_host_addr : "",
		  sender_host_addr ? "]" : "");
    }
#ifdef HAVE_BIND
    if (*vaddr->work_addr && !EQ(vaddr->work_addr, "+") &&
	!(sender_host_addr && smtp_sender_no_verify &&
	  match_ip(sender_host_addr, smtp_sender_no_verify, ':', ';', (char **) NULL))) {
	int mxresult;
	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(vaddr->target, 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(%s, %s): bind_addr(%s): %d, %s.\n",
	       pp_sender, raw_sender, vaddr->target, 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:
	    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) {
		fprintf(out, "450-4.4.3 defer all mail from '%s'.\r\n", pp_sender);
		fprintf(out, "450-4.4.3 Sender address target domain '%s' cannot be verified at this time.\r\n", vaddr->target);
		fprintf(out, "450-4.4.3 Reason given was: %s.\r\n", binderrmsg);
		fprintf(out, "450 4.4.3 Try again later.\r\n");
		fflush(out);
	    }
	    xfree(vaddr->in_addr);
	    xfree(vaddr->work_addr);
	    xfree((char *) vaddr);
	    return 0;

	case DB_NOMATCH:		/* no such domain */
	    if (out) {
		fprintf(out, "550-5.1.8 reject sender '%s'.\r\n", pp_sender);
		fprintf(out, "550%s5.1.8 Sender address target domain '%s' is not valid%s\r\n",
			smtp_sender_verify_mx_only ? "-" : " ",
			vaddr->target,
			smtp_sender_verify_mx_only ? " for e-mail use.\r\n550 5.1.8 There is no MX record for it." : ".");
		fflush(out);
	    }
	    write_log(WRITE_LOG_SYS, "remote MAIL FROM: '%s' target '%s' is not a valid domain%s; by %s%s%s%s%s%s%s%s%s.",
		      pp_sender,
		      vaddr->target,
		      smtp_sender_verify_mx_only ? " (no MX record)" : "",
		      ident_sender ? ident_sender : "",
		      ident_sender ? "@" : "",
		      sender_host ? sender_host : "",
		      sender_host_really ? "(" : "",
		      sender_host_really ? sender_host_really : "",
		      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;

	case DB_FAIL:			/* bad DNS request? */
	case FILE_FAIL:			/* major DNS error! */
	    if (out) {
		fprintf(out, "550-5.4.3 reject sender '%s'.\r\n", pp_sender);
		fprintf(out, "550-5.4.3 Failure trying to verify address target domain '%s'.\r\n", vaddr->target);
		fprintf(out, "550 5.4.3 Reason given was: %s.\r\n", binderrmsg);
		fflush(out);
	    }
	    write_log(WRITE_LOG_SYS, "remote MAIL FROM: '%s' target '%s': DNS failed: %s; by %s%s%s%s%s%s%s%s%s.",
		      vaddr->in_addr,
		      vaddr->target,
		      binderrmsg,
		      ident_sender ? ident_sender : "",
		      ident_sender ? "@" : "",
		      sender_host ? sender_host : "",
		      sender_host_really ? "(" : "",
		      sender_host_really ? sender_host_really : "",
		      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;

	default:
	    if (out) {
		fprintf(out, "421-4.4.3 defer all mail from '%s'.\r\n", pp_sender);
		fprintf(out, "421-4.4.3 Impossible error returned when trying to verify target domain '%s'.\r\n", vaddr->target);
		fprintf(out, "421-4.4.3 Reason given was: %s.\r\n", binderrmsg);
		fprintf(out, "421 4.4.3 Try again later.  Connection closing now...\r\n");
		fflush(out);
	    }
	    panic(EX_SOFTWARE, "verify_sender(%s, %s): bind_addr(%s) gave impossible result %d: %s.",
		  pp_sender, raw_sender, vaddr->target, mxresult, binderrmsg);
	    /* NOTREACHED */
	}
    }
#endif

    /*
     * 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 mailer's configuration.
     */
    if (*vaddr->work_addr && !EQ(vaddr->work_addr, "+")) {
	int verify_local = 0;

	if (islocalhost(vaddr->target)) {
	    verify_local = 1;
	}
#ifdef HAVE_BIND
	else {
	    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(vaddr->target, 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, 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 = 1;
		}
	    }		    
	}
#endif
	if (verify_local && !verify_addr(pp_sender, out, 2)) {
	    /* NOTE: error reply is printed by verify_addr() */
	    xfree(vaddr->in_addr);
	    xfree(vaddr->work_addr);
	    xfree((char *) vaddr);

	    return 0;
	}
    }

    xfree(vaddr->in_addr);
    xfree(vaddr->work_addr);
    xfree((char *) vaddr);

    return 1;
}

/*
 * verify_addr - verify an address
 *
 * redisplay the input address if it is a valid address.
 */
static int
verify_addr(in_addr, out, rcpt_p)
    char *in_addr;			/* input address string */
    FILE *out;				/* write expansion here */
    int rcpt_p;				/* non-zero if called from RCPT (1) or MAIL (2) */
{
    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 */
    
    DEBUG1(DBG_REMOTE_MID, "verify_addr(%s): 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) {
	if (out) {
	    fprintf(out, "501 '%s:%s' malformed address: %s.\r\n", in_addr,
		    (rcpt_p == 0) ? "5.1.3 VRFY" : 
		    (rcpt_p == 1) ? "5.1.3 RCPT TO" :
		    (rcpt_p == 2) ? "5.1.7 MAIL FROM" :
		    "5.1.0 input",      /* XXX 5.1.7? */
		    errstr);
	    fflush(out);
	}
	xfree((char *) addr);
	return 0;
    }

    /* 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.
     */
    resolve_addr_list(addr, &okay, &defer, &fail, FALSE);
    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) {
	int form = okay->flags & ADDR_FORM_MASK;

	if (form == 0 && (okay->parseflags & FOUND_MAILBOX)) {
	    DEBUG1(DBG_REMOTE_MID, "verify_addr(%s): surprise!  Actually was a mailbox form!\n", in_addr);
	    form = MAILBOX;
	}
	if (okay->parent) {
	    DEBUG1(DBG_REMOTE_MID, "verify_addr(%s): address has a local parent so must be local.\n", in_addr);
	    form = LOCAL;
	}
	switch (form) {
	case LOCAL:
	case MAILBOX:
	    DEBUG3(DBG_REMOTE_MID, "verify_addr(%s): allowing %s(%d) addressing form.\n",
		   in_addr,
		   (form == LOCAL) ? "LOCAL" : (form == MAILBOX) ? "MAILBOX" : "<impossible-form!>",
		   form);
	    break;			/* OK */

	case BERKENET:
	case DECNET:
	    if (out) {
		fprintf(out, "553 %s '%s'.  Address cannot be a %s form address.\r\n",
			(rcpt_p == 0) ? "5.1.3 reject verifying address" :
			(rcpt_p == 1) ? "5.1.3 reject sending to address" :
			(rcpt_p == 2) ? "5.1.7 reject all mail from sender" :
			"5.1.0 reject address",
			in_addr,
			(form == BERKENET) ? "BERKENET" :
			(form == DECNET) ? "DECNET" : "<bad-form!>");
		fflush(out);
	    }
	    write_log(WRITE_LOG_SYS, "remote %s: '%s' is a %s(%d) address; by %s%s%s%s%s%s%s%s%s.",
		      (rcpt_p == 0) ? "VRFY" : (rcpt_p == 1) ? "RCPT TO" : (rcpt_p == 2) ? "MAIL FROM" : "command",
		      in_addr,
		      (form == BERKENET) ? "BERKENET" :
		      (form == DECNET) ? "DECNET" : "<bad-form!>",
		      form,
		      ident_sender ? ident_sender : "",
		      ident_sender ? "@" : "",
		      sender_host ? sender_host : "",
		      sender_host_really ? "(" : "",
		      sender_host_really ? sender_host_really : "",
		      sender_host_really ? ")" : "",
		      sender_host_addr ? " [" : "",
		      sender_host_addr ? sender_host_addr : "",
		      sender_host_addr ? "]" : "");
	    xfree((char *) addr);

	    return 0;			/* do not keep this recipient */

	default:
	    /*
	     * We must not permit route forms unless the sender is in
	     * smtp_remote_allow.
	     */
	    if (sender_host_addr && smtp_remote_allow && match_ip(sender_host_addr, smtp_remote_allow, ':', ';', (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) {
		    fprintf(out, "552 %s '%s'.  That addressing form is not permitted.\r\n",
			    (rcpt_p == 0) ? "5.1.3 reject verifying address" :
			    (rcpt_p == 1) ? "5.1.3 reject sending to address" :
			    (rcpt_p == 2) ? "5.1.7 reject all mail from sender" :
			    "5.1.0 reject address",
			    in_addr);
		    fflush(out);
		}
		write_log(WRITE_LOG_SYS, "remote %s: %s%s%s%s%s%s%s%s%s: '%s' addressing form %s(%d) not permitted: routed to <%s%s%s>",
			  (rcpt_p == 0) ? "VRFY" : (rcpt_p == 1) ? "RCPT TO" : (rcpt_p == 2) ? "MAIL FROM" : "command",
			  ident_sender ? ident_sender : "",
			  ident_sender ? "@" : "",
			  sender_host ? sender_host : "",
			  sender_host_really ? "(" : "",
			  sender_host_really ? sender_host_really : "",
			  sender_host_really ? ")" : "",
			  sender_host_addr ? "[" : "",
			  sender_host_addr ? sender_host_addr : "<no-sender-addr>",
			  sender_host_addr ? "]" : "",
			  in_addr,
			  (form == PARSE_ERROR) ? "PARSE_ERROR" :
			  (form == RFC_ROUTE) ? "RFC_ROUTE" :
		          (form == RFC_ENDROUTE) ? "RFC_ENDROUTE" :
		          (form == UUCP_ROUTE) ? "UUCP_ROUTE" :
		          (form == PCT_MAILBOX) ? "PCT_MAILBOX" : "<bad-form!>",
			  form,
			  okay->work_addr,
			  okay->target ? "@" : "",
			  okay->target ? okay->target : "");
		xfree((char *) addr);

		return 0;			/* do not keep this recipient */
	    }
	}
	if (rcpt_p != 2 && out) {		/* only if not called from verify_sender()! */
	    fprintf(out, "250 2.1.0 '%s' %s Okay.\r\n", in_addr, rcpt_p ? "Recipient" : "Mailbox");
	    fflush(out);
	}
	xfree((char *) addr);

 	return 1;

    } else if (defer) {
	if (out) {
	    fprintf(out, "450-4.3.0 defer %s '%s'.\r\n",
		    in_addr,
		    (rcpt_p == 0) ? "defer verifying address" :
		    (rcpt_p == 1) ? "defer sending to address" :
		    (rcpt_p == 2) ? "defer all mail from sender" :
		    "defer address");
	    fprintf(out, "450-4.3.0 Cannot verify '<%s%s%s>' at this time.\r\n",
		    defer->work_addr,
		    (defer->target) ? "@" : "",
		    (defer->target) ? defer->target : "");
	    fprintf(out, "450-4.3.0 Reason given was: (ERR_%03ld) %s.\r\n",
		    defer->error->info & ERR_MASK,
		    defer->error->message);
	    fprintf(out, "450 4.3.0 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: '%s' <%s%s%s> temporary failure returned to '%s' while verifying recipient: (ERR_%03ld) %s",
		  (rcpt_p == 0) ? "VRFY" : (rcpt_p == 1) ? "RCPT TO" : (rcpt_p == 2) ? "MAIL FROM" : "command",
		  ident_sender ? ident_sender : "",
		  ident_sender ? "@" : "",
		  sender_host ? sender_host : "",
		  sender_host_really ? "(" : "",
		  sender_host_really ? sender_host_really : "",
		  sender_host_really ? ")" : "",
		  sender_host_addr ? "[" : "",
		  sender_host_addr ? sender_host_addr : "<no-sender-addr>",
		  sender_host_addr ? "]" : "",
		  in_addr,
		  defer->work_addr,
		  defer->target ? "@" : "",
		  defer->target ? defer->target : "",
		  sender,
		  defer->error->info & ERR_MASK,
		  defer->error->message);
	xfree((char *) addr);

 	return 0;			/* do not keep this recipient though... */

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

	switch (fail->error->info & ERR_MASK) {
	case ERR_111:			/* address parse error */
	    error_code = 501;		/* syntax error in parameter */
	    enhanced = "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 */
	    enhanced = "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) */
	    enhanced = "4.3.5";         /* mail system config error */
	    break;
	case ERR_163:			/* lost connetion to BIND server */
	    error_code = 454;		/* local error in processing (XXX 454 not in RFC821#4.3) */
	    enhanced = "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) */
	    enhanced = "4.4.3";         /* directory server failure */
	    break;
	case ERR_100:			/* unknown user */
	    error_code = 550;		/* mailbox not found */
	    enhanced = "5.1.1";         /* bad destination mailbox address */
	    break;
	case ERR_101:			/* unknown host */
	    error_code = 551;		/* mailbox not local */
	    enhanced = "5.1.2";         /* bad destination system address */
	    break;
	case ERR_104:			/* security violation */
	    error_code = 550;		/* mailbox not local -- relay not permitted */
	    enhanced = "5.7.1";         /* delivery not authorized */
	    break;
	case ERR_168:			/* no valid MX records for host */
	    error_code = 551;		/* mailbox not local */
	    enhanced = "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) */
	    enhanced = "5.0.0";         /* unknown permanent error */
	    break;
	}
	if (out) {
	    fprintf(out, "%d-%s %s %s '%s'.\r\n",
		    error_code, enhanced,
		    (error_code >= 500) ? "reject" : "defer",
		    (rcpt_p == 0) ? "verifying address" :
		    (rcpt_p == 1) ? "sending to address" :
		    (rcpt_p == 2) ? "all mail from sender" : "address",
		    in_addr);
	    fprintf(out, "%d-%s The address <%s%s%s> was not accepted.\r\n",
		    error_code, enhanced,
		    fail->work_addr,
		    fail->target ? "@" : "",
		    fail->target ? fail->target : "");
	    fprintf(out, "%d-%s Reason given was: (ERR_%03ld) %s.\r\n",
		    error_code, enhanced,
		    fail->error->info & ERR_MASK,
		    fail->error->message);
	    fprintf(out, "%d %s %s\r\n",
		    error_code, enhanced,
		    (error_code < 500) ? "Try again later." :
		    (rcpt_p == 2) ? "Your e-mail address may not be correctly configured in your mailer." :
		    "Permanent failure logged.");
	    fflush(out);
	}
	write_log(WRITE_LOG_SYS, "remote %s: %s%s%s%s%s%s%s%s%s: '%s' <%s%s%s> recipient for sender '%s' not deliverable: (ERR_%03ld) %s",
		  (rcpt_p == 0) ? "VRFY" : (rcpt_p == 1) ? "RCPT TO" : (rcpt_p == 2) ? "MAIL FROM" : "command",
		  ident_sender ? ident_sender : "",
		  ident_sender ? "@" : "",
		  sender_host ? sender_host : "",
		  sender_host_really ? "(" : "",
		  sender_host_really ? sender_host_really : "",
		  sender_host_really ? ")" : "",
		  sender_host_addr ? "[" : "",
		  sender_host_addr ? sender_host_addr : "<no-sender-addr>",
		  sender_host_addr ? "]" : "",
		  in_addr,
		  fail->work_addr,
		  fail->target ? "@" : "",
		  fail->target ? fail->target : "",
		  sender,
		  fail->error->info & ERR_MASK,
		  fail->error->message);
	xfree((char *) addr);

 	return 0;
    }
    /* hmmm, it should have been in one of the lists from resolve_addr_list()! */
    if (out) {
	fprintf(out, "521-5.1.0 reject '%s'.\r\n", in_addr);
	fprintf(out, "521 5.1.0 The address <%s%s%s> 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: '%s' <%s%s%s> recipient for sender '%s' caused resolve_addr_list() to fail!",
	  (rcpt_p == 0) ? "VRFY" : (rcpt_p == 1) ? "RCPT TO" : (rcpt_p == 2) ? "MAIL FROM" : "command",
	  ident_sender ? ident_sender : "",
	  ident_sender ? "@" : "",
	  sender_host ? sender_host : "",
	  sender_host_addr ? "[" : "",
	  sender_host_addr ? sender_host_addr : "<no-sender-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 hostnm if verify succeeds.
 * (possibly with the text that followed a domain literal form appended).
 */
static char *
verify_host(hostnm, saddrp, shp, shpherr, errorp, fatalp)
    char *hostnm;
    struct sockaddr_in *saddrp;
    struct hostent *shp;		/* result of gethostbyaddr() in caller */
    const int shpherr;			/* h_errno from gethostbyaddr() */
    char const **errorp;
    int *fatalp;
{
    struct hostent *hp;
    char *p;
    int found;
    int has_alphas;
    int i;
    int allowed_to_be_broken = FALSE;
    int aresult;
    struct error *binderr = NULL;
    char *binderrmsg;

    if (smtp_hello_broken_allow) {
	allowed_to_be_broken = match_ip(inet_ntoa(saddrp->sin_addr), smtp_hello_broken_allow, ':', ';', (char **)NULL);
    }
    if (*hostnm == '[') {		/* looks like a literal IP address.... */
# if defined(INET_ADDR_USE_STRUCT)
	struct in_addr s_inet;		/* internet address */
# endif
	unsigned long inet;		/* internet address */
	struct str hn;			/* returned host name */

	p = strchr(hostnm, ']');
	if (!p) {
	    DEBUG1(DBG_REMOTE_LO, "verify_host(%s): Invalid literal IP address, missing closing ']'.\n", hostnm);
	    *errorp = "Invalid literal IP address, missing closing ']'";
	    *fatalp = 1;
	    return NULL;
	}
	*p = '\0';
# ifdef INET_ADDR_USE_STRUCT
	s_inet = inet_addr(&hostnm[1]);
	inet = s_inet.s_addr;
# else
	inet = inet_addr(&hostnm[1]);
# endif
	*p = ']';
	DEBUG5(DBG_REMOTE_HI,
	       "verify_host(%s): inet from helo: [0x%lx] aka %s, actual [0x%lx] aka %s.\n",
	       hostnm,
	       ntohl(inet), COPY_STRING(inet_ntoa(*((struct in_addr *) &inet))), /* HELO */
	       ntohl(saddrp->sin_addr.s_addr), COPY_STRING(inet_ntoa(saddrp->sin_addr))); /* actual */
	if (inet == (unsigned long)(-1)) { /* XXX should use INADDR_NONE */
	    DEBUG1(DBG_REMOTE_LO, "verify_host(%s): inet_addr() failed: bad host address form.\n", hostnm);
	    *errorp = "Invalid host address form";
	    *fatalp = 1;
	    return NULL;
	}
	if (inet != saddrp->sin_addr.s_addr) {
	    DEBUG3(DBG_REMOTE_LO, "verify_host(%s): [0x%lx] != [0x%lx].\n", hostnm, ntohl(inet), ntohl(saddrp->sin_addr.s_addr));
	    *errorp = "Host address does not match remote address";
	    if (smtp_hello_verify && !allowed_to_be_broken) {
		*fatalp = 1;
		return NULL;		/* broken DNS or mailer config; or forger */
	    }
	}
	STR_INIT(&hn);
	*p = '\0';
	STR_CAT(&hn, hostnm);		/* always return the literal given */
	STR_CAT(&hn, "]");		/* the bracket we clobbered just before */
	*p = ']';
	if (shp && sender_host_really) { /* both from gethostbyaddr() in caller */
	    if (!(hp = gethostbyname(sender_host_really))) {
		DEBUG2(DBG_REMOTE_LO, "verify_host(%s) gethostbyname() for verify_literal failed: %s.\n", sender_host_really, hstrerror(h_errno));
		*errorp = hstrerror(h_errno);
		if (smtp_hello_verify_literal && !allowed_to_be_broken) {
		    switch (h_errno) {
		    case TRY_AGAIN:
			*fatalp = 0;
			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 = 1;
			break;
		    }
		    return NULL;	/* broken DNS or mailer config; or forger */
		}
	    } else {
		found = 0;
		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 = 1;
			break;		/* name is good, keep it */
		    }
		}
		if (!found) {
		    DEBUG3(DBG_REMOTE_LO, "verify_host(%s) gethostbyname() doesn't find matching A [%s] for PTR %s.\n",
			   hostnm, inet_ntoa(saddrp->sin_addr), sender_host_really);
		    *errorp = "Host domain literal A name does not match any reverse DNS PTR";
		    if (smtp_hello_verify_literal && !allowed_to_be_broken) {
			*fatalp = 1;
			return NULL;	/* broken DNS or mailer config; or forger */
		    }
		}
	    }
	} else {
	    *errorp = "Remote address PTR lookup failed.\\\n\tLiteral IP addresses are denied if no valid reverse DNS PTR is present.";
	    if (smtp_hello_verify_literal && !allowed_to_be_broken) {
	        *fatalp = 1;
		return NULL;		/* broken DNS or mailer config; or forger */
	    }
	}
	if (*++p) {
	    STR_CAT(&hn, " (");
	    STR_CAT(&hn, p);		/* patch on in a comment any remainder */
	    STR_CAT(&hn, ")");
	}
	
	return STR(&hn);
    } /* else not a literal */

    /* first verify syntax of hostnm */
    if (*hostnm == '.') {
	*errorp = "hostname must NOT start with a '.'";
	DEBUG1(DBG_REMOTE_LO, "hostname must not start with '.': %s", hostnm);
	if (!allowed_to_be_broken) {
	    *fatalp = 1;
	    return NULL;
	}
    }
    found = 0;
    has_alphas = 0;
    for (p = hostnm; *p; p++) {
	if (isalpha(*p) || *p == '-')
	    has_alphas = 1;
	if (*p == '.') {
	    found = 1;
	} else if (!(isalnum(*p) || *p == '-')) {
            *errorp = "illegal character in hostname";
            DEBUG2(DBG_REMOTE_LO, "illegal char in hostname: %s, %c.\n", hostnm, *p);
	    if (!allowed_to_be_broken) {
		*fatalp = 1;
		return NULL;
	    }
        }
    }
    if (!found) {
	*errorp = "hostname must contain a '.'";
	DEBUG1(DBG_REMOTE_LO, "hostname must contain a '.': %s.\n", hostnm);
	if (!allowed_to_be_broken) {
	    *fatalp = 1;
	    return NULL;
	}
	/*
	 * there's no point to trying to do any more DNS lookups here (idiots
	 * trying to use top-level domains as hostnames aside)...
	 */
	return xprintf("(locally authorised broken client using invalid hostname!) %s", hostnm);
    }

    /* NOTE:  shp is safe -- this doesn't call any gethostby*() */
    aresult = bind_check_if_canonical_host(hostnm, 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(%s, %s): %d, %s.\n",
	   hostnm, inet_ntoa(saddrp->sin_addr), aresult, binderrmsg);

    switch (aresult) {
    case DB_SUCCEED:			/* found the host! */
	break;
    case DB_NOMATCH:			/* no such domain */
	*errorp = binderrmsg;
	if (smtp_hello_verify && !allowed_to_be_broken) {
	    *fatalp = 1;
	    return NULL;
	}
	break;
    case FILE_NOMATCH:			/* There is no server available */
	*errorp = binderrmsg;
	if (smtp_hello_verify && !allowed_to_be_broken) {
	    *fatalp = 0;
	    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\\\n\tverify host '%s':\\\n\t%s", hostnm, binderrmsg);
	if (smtp_hello_verify && !allowed_to_be_broken) {
	    *fatalp = 0;
	    return NULL;
	}
	break;
    case DB_FAIL:			/* bad DNS request? */
    case FILE_FAIL:			/* major DNS error! */
	*errorp = xprintf("DNS failure trying to verify host '%s':\\\n\t%s", hostnm, binderrmsg);
	/* XXX it might be nice to return a temporary error here, but that would mean a re-design */
	if (smtp_hello_verify && !allowed_to_be_broken) {
	    *fatalp = 1;
	    return NULL;
	}
	break;
    default:				/* impossible! */
	DEBUG2(DBG_REMOTE_LO, "verify_host(%s): bind_check_if_canonical_host() impossible result %d.", hostnm, aresult);
	*errorp = xprintf("Fatal internal error encountered while trying to\\\n\tverify host '%s':(%s)", hostnm, binderrmsg);
	if (smtp_hello_verify && !allowed_to_be_broken) {
	    *fatalp = 1;
	    return NULL;
	}
	break;
    }

    if (shp) {				/* from gethostbyaddr() in caller */
	unsigned int hlen = strlen(hostnm);

	if (strncmpic(hostnm, shp->h_name, hlen) != 0) { /* does the first name match? */
	    found = 0;
	    for (i = 0; (p = (shp->h_aliases)[i]); i++) { /* do any other names match? */
		if (strncmpic(hostnm, p, hlen) == 0) {
		    found = 1;
		    /*
		     * reset sender_host_really so it'll be == sender_host & no
		     * mismatch will be reported in the Received: header
		     */
		    if (sender_host_really) {
			xfree(sender_host_really);
		    }
		    sender_host_really = COPY_STRING(p);
		    break;		/* yeah! */
		}
	    }
	    if (!found) {
		DEBUG2(DBG_REMOTE_LO, "verify_host(%s): gethostbyaddr(%s) no PTR has a matching name.\n", hostnm, inet_ntoa(saddrp->sin_addr));
		*errorp = xprintf("No reverse DNS PTR for the remote address [%s] has a\\\n\thostname matching '%s'", inet_ntoa(saddrp->sin_addr), hostnm);
		if (smtp_hello_verify_ptr && !allowed_to_be_broken) {
		    *fatalp = 1;
		    return NULL;	/* broken DNS or mailer config; or forger */
		}
	    }
	}
	if (smtp_hello_reject_dns_paranoid) {	/* re-invent TCP Wrappers PARANOID check */
	   int notallok = 0;
	   /*
	    * 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, 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, saddrp->sin_addr.s_addr, &binderr)) != DB_SUCCEED) {
			notallok = 1;
			break;
		    }
		}
	    } else {
		notallok = 1;
		p = shp->h_name;
	    }
	    if (notallok) {
		smtp_sess_deny_reason = SMTP_SESS_DENY_PARANOID; /* XXX just in case? */

		DEBUG3(DBG_REMOTE_LO,
		       "verify_host(%s): hostname %s from PTRs of [%s] doesn't 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(%s): 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("Hostname '%s' (from reverse DNS PTR record)\\\n\tdoes not have an address matching [%s]!\\\n\tIs this a DNS spoofing attack, or just badly mis-configured DNS?",
				      p, inet_ntoa(saddrp->sin_addr));
		    if (!allowed_to_be_broken) {
			*fatalp = 1;
			return NULL;
		    }
		    break;
		case FILE_NOMATCH:	/* There is no server available */
		    *errorp = xprintf("DNS server failure encountered while trying to verify matching address for\\\n\thostname '%s':\\\n\t%s",
				      p, binderrmsg);
		    if (!allowed_to_be_broken) {
			*fatalp = 0;
			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 matching address for\\\n\thostname '%s':\\\n\t%s",
				      p, binderrmsg);
		    if (!allowed_to_be_broken) {
			*fatalp = 0;
			return NULL;
		    }
		    break;
		case DB_FAIL:		/* bad DNS request? */
		case FILE_FAIL:		/* major DNS error! */
		    *errorp = xprintf("DNS failure encountered while trying to verify matching address for\\\n\thostname '%s':\\\n\t%s",
				      p, binderrmsg);
		    if (!allowed_to_be_broken) {
			*fatalp = 1;
			return NULL;
		    }
		    break;
		default:		/* impossible! */
		    DEBUG2(DBG_REMOTE_LO, "verify_host(%s): bind_check_if_canonical_host() impossible result %d.", hostnm, aresult);
		    *errorp = xprintf("Fatal internal error encountered while verifying matching address for\\\n\thostname '%s':\\\n\t%s", p, binderrmsg);
		    if (smtp_hello_verify && !allowed_to_be_broken) {
			*fatalp = 1;
			return NULL;
		    }
		    break;
		}
	    }
	}
    } else {
	*errorp = xprintf("Remote address PTR lookup failed:\\\n\t%s", hstrerror(shpherr));
	if (smtp_hello_verify_ptr && !allowed_to_be_broken) {
	    switch (shpherr) {
	    case TRY_AGAIN:
		*fatalp = 0;
		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 = 1;
		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(%s): OK: address has local transport: %s.\n",
	       in->in_addr, in->transport->name);
	*out = in;

	return;
    }
    if (! EQ(in->transport->driver, "tcpsmtp")) {
	DEBUG3(DBG_ADDR_HI, "check_smtp_remote_allow(%s): OK: transport[%s] 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(%s): OK: address has local parent.\n",
	       in->parent->in_addr);
	*out = in;

	return;
    }
    if (in->succ) {
	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 && smtp_remote_allow && match_ip(sender_host_addr, smtp_remote_allow, ':', ';', &reason)) {
	DEBUG3(smtp_remote_allow ? DBG_ADDR_HI : DBG_ADDR_LO,
	       "permitting remote relay by [%s] to %s (%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
     */
    illegal_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
     */
    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) {
		illegal_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 %s.\n",
		       sender_host_addr ? sender_host_addr : "UNKNOWN-IP", in->target);
		*out = in;
	    }
	} else {
	    illegal_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 %s", 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("%s 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 %s", 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 %s.", 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);
}

/*
 * 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_LO, "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_LO, "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! */
	fprintf(out_file, "421 4.3.2 %s SMTP command timeout, closing channel\r\n", primary_name);
    }
    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 ? "(" : "",
	      sender_host_really ? sender_host_really : "",
	      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_LO, "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) {
	fprintf(out_file, "421 4.3.2 %s Service not available, closing channel\r\n", primary_name);
    }
    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 ? "(" : "",
	      sender_host_really ? sender_host_really : "",
	      sender_host_really ? ")" : "",
	      sender_host_addr ? " [" : "",
	      sender_host_addr ? sender_host_addr : "",
	      sender_host_addr ? "]" : "");
    unlink_spool();
    exit(EX_OSFILE);
    /* NOTREACHED */
}

#ifdef HAVE_DF_SPOOL
static long
compute_max_message_size_from_df_spool (void)
{
    long free_bytes = spool_max_free_space ();
    const long reserved = 2*1024*1024;
    const long min_max_message_size = 20*1024;

    if (free_bytes == -1)
	return free_bytes;
    return free_bytes < 2*reserved ? -1 : free_bytes - reserved;
}
#endif

static void
illegal_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 '%s' is not permitted to relay mail",
			 remote_addr ? remote_addr : "[UNKNOWN]");
    in->error = note_error(ERR_NPOSTMAST | ERR_104, error_text);
}
