/*
#ident	"@(#)smail/src:RELEASE-3_2_0_114:smtprecv.c,v 1.136 2001/08/03 21:11:13 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 "transports/smtplib.h"		/* for is_*_string() */
#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

#ifndef MIN				/* in <sys/param.h> on some systems */
# define MIN(a,b)	(((a) < (b)) ? (a) : (b))
#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 */
    TOOLONG_CMD,			/* maximum command length exceeded */
    OTHER_CMD				/* unknown command */
};

/* functions local to this file */
static void send_smtp_msg __P((FILE *, int, char *, int, 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_dnsbl __P((char *, char *, char **, char **, char **));
static int verify_addr_form __P((char *, char *, FILE *, 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));
static long computed_max_msg_size __P((void));
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 char *formatted_get_help_msg = NULL;	/* postmaster nonsense */

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...  the lesser of either free
					 * space (minus reserved space), or
					 * max_message_size (if set)
					 */


/*
 * receive_smtp - receive mail over SMTP.
 *
 * Take SMTP commands on the `in' file.  Send reply messages
 * to the `out' file.  If `out' is NULL, then don't send reply
 * messages (i.e., read batch SMTP commands).
 *
 * return an array of spool files which were created in this SMTP
 * conversation.
 *
 * The last spooled message is left open as an efficiency move, so the
 * caller must arrange to close it or process it to completion.  As
 * well, it is the callers responsibility to close the input and
 * output channels.
 */
char **
receive_smtp(in, out)
    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
     *
     * NOTE:  we do this as soon as possible to avoid having the client
     * timeout...
     */
    if (out) {
	char *s;

	DEBUG(DBG_REMOTE_HI, "receive_smtp() sending smtp_banner.\n");
	s = expand_string(smtp_banner, (struct addr *)NULL,
			  (char *)NULL, (char *)NULL);
	if (!s) {
	    static int done_it = 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;
	/* This leaks, but only once and 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 (out) {
	/* this won't be used except in an SMTP reply... */
	formatted_get_help_msg = xprintf("\n\
Please forward this message to your own LOCAL postmaster and ask them to contact us regarding this issue.\n\n\
Postmaster@[%s]: Please contact <postmaster@%s> (via an unblocked server of course!) for assistance in resolving this issue.  You must include your IP number, given above, in order to receive any help, and including this entire status reply is ideal.",
					 sender_host_addr ? sender_host_addr : "UNKNOWN-IP",
					 primary_name);
    }
    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)) {
	    char *revaddr = flip_inet_addr(sender_host_addr);

	    /*
	     * This code does DNS lookups on the connecting client.  It is down
	     * here because this may cause a delay and it is necessary to have
	     * the delay after the connection is established and the 220
	     * message is sent rather than at the start as otherwise clients
	     * may timeout.
	     */
	    if (match_dnsbl(revaddr, smtp_rbl_domains, &smtp_rbl_match, &smtp_rbl_addr, &smtp_sess_deny_msg)) {
		smtp_sess_deny = 1;
		smtp_sess_deny_reason = SMTP_SESS_DENY_RBL;
	    }
	    xfree(revaddr);
	}
    }
# 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);
	    /*
	     * WARNING!!!!
	     *
	     * We must have already done all checks that can set or change
	     * smtp_sess_deny!!!
	     */
	    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!!!!
	 *
	 * We 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! ;-) */
    if (out && rfc1413_query_timeout > 0) { /* switch off RFC1413 by setting timeout <= 0 */
	DEBUG(DBG_REMOTE_HI, "receive_smtp() getting remote identity.\n");
	/* 
	 * This code does an Ident/RFC1413 lookup on the connecting client (if
	 * possible).
	 *
	 * It is down here because it may cause a delay and it is necessary to
	 * have the delay after the connection is established and the 220
	 * message is sent, rather than at the start, otherwise clients may
	 * timeout.
	 *
	 * A call to ident_lookup() could be used to obtain additional info
	 * available from an RFC1413 ident call, but I don't know what we would
	 * do with the info, so don't bother to obtain it!
	 */
	if ((ident_sender = ident_id(fileno(in), (int) rfc1413_query_timeout))) {
	    ident_method = "rfc1413";
	}
    }
#endif /* HAVE_RFC1413 */

    accepted_msg_size = computed_max_msg_size();
    if (out) {
	write_log(WRITE_LOG_SYS, "remote connection from %s%s%s[%s]",
		  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) {
		sleep(smtp_error_delay);
		fprintf(out, "501 ESMTP support not enabled.  Where did you get the idea it was?!?!?\r\n");
		fflush(out);
	    }
# ifdef NO_LOG_EHLO /* XXX overloaded from meaning in transports/smtplib.c */
	    DEBUG4(DBG_REMOTE_LO, "received unsupported EHLO from %s%s%s[%s].\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 */
	    exitvalue = EX_USAGE;
#endif /* HAVE_EHLO */
	    break;

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

	case MAIL_CMD:
	    if (smtp_sess_deny) {
		if (out) {
		    send_session_denied_reply(out, "MAIL FROM", data);
		}
		exitvalue = EX_UNAVAILABLE;
		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.");
		exitvalue = EX_DATAERR;
		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");
		exitvalue = EX_USAGE;
		break;
	    }
	    /*
	     * use preparse_address_1() because we need "rest" here....
	     */
	    sender = preparse_address_1(data, &errstr, &rest);
	    if (!sender) {
		if (out) {
		    sleep(smtp_error_delay);
		    fprintf(out, "501 5.5.2 '%s' Address parse error: %s\r\n", data, errstr);
		    fflush(out);
		}
		exitvalue = EX_NOUSER;
		break;
 	    }
	    if (sender && *sender && !verify_sender(sender, data, out)) {
		/* NOTE: error reply is printed and logged by verify_sender() */
		sender = NULL;
		exitvalue = EX_NOUSER;
		break;
	    }
	    if (sender && !*sender) {
		/* 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;
		exitvalue = EX_DATAERR;
		break;
	    }
	    if (out) {
		fprintf(out, "250 2.1.0 %s Sender Okay.\r\n", sender);
		fflush(out);
	    }
	    break;

	case RCPT_CMD: {
	    int rcpt_to_had_comments = 0;

	    if (smtp_sess_deny) {
		if (out) {
		    send_session_denied_reply(out, "RCPT TO", data);
		}
		exitvalue = EX_UNAVAILABLE;
		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) {
		rcpt_to_had_comments = 1;
	    }
	    strip_rfc822_whitespace(data);
	    if (data[0] == '\0') {
		non_compliant_reply(out, 501, "5.5.4", "'RCPT TO:' requires forward-path address as operand.");
		exitvalue = EX_NOUSER;
		break;
	    }
	    /* do this after the above checks just to catch gross errors earlier */
	    if (!sender) {
#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 (! verify_addr(data, out, 1)) {
		/* NOTE: error reply is printed by verify_addr() */
		exitvalue = EX_NOUSER;
		break;
	    }
	    if (rcpt_to_had_comments) {
		illegal_operand_warning("RCPT TO", orig_data);
	    }
	    if (smtp_max_recipients && (num_smtp_recipients++ >= smtp_max_recipients)) {
		if (out) {
		    sleep(smtp_error_delay);
		    fprintf(out, "452 4.5.3 Too many recipients.  Administrative limit exceeded.\r\n");
		    fflush(out);
		}
		exitvalue = EX_TEMPFAIL;
		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);
		}
		exitvalue = EX_UNAVAILABLE;
		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) {
		log_spool_errors();
		/*
		 * we need to catch this here since queue_message() may
		 * have failed because the file grew too big and we don't
		 * want to let them get away with a 451 if that happened!
		 */
		if (accepted_msg_size >= 0 && msg_size >= accepted_msg_size) {
		    exitvalue = EX_NOPERM;
		    if (out) {
			fprintf(out, "552-5.3.4 Message input exceeded maximum message size of %ld bytes.\r\n",
				accepted_msg_size);
			/*
			 * NOTE: the remainder of this reply is identical to
			 * one below and in decode_mail_options().  Keep it
			 * that way!
			 */
			send_smtp_msg(out, 552, "5.3.4", TRUE, "Do NOT send files by e-mail!\n\
If you are sending a MIME message please set your mailer so that it splits messages larger than 64KB into multiple parts.  Otherwise please learn to transfer files with a file transfer protocol.");
			fflush(out);
		    }
		    write_log(WRITE_LOG_SYS, "remote DATA: message too big from %s%s%s%s%s%s%s%s%s.",
			      ident_sender ? ident_sender : "",
			      ident_sender ? "@" : "",
			      sender_host ? sender_host : "",
			      sender_host_really ? "(" : "",
			      sender_host_really ? sender_host_really : "",
			      sender_host_really ? ")" : "",
			      sender_host_addr ? " [" : "",
			      sender_host_addr ? sender_host_addr : "",
			      sender_host_addr ? "]" : "");
		    swallow_smtp(in);	/* why do we bother? */
		    break;
		} else {
		    exitvalue = EX_CANTCREAT;
		    if (out) {
			fprintf(out, "451 4.3.0 Failed to queue message: %s: %s\r\n",
				errstr, strerror(errno));
			fflush(out);
		    }
		    write_log(WRITE_LOG_SYS, "remote DATA: failed to queue message from %s%s%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 ? "]" : "",
			      errstr, strerror(errno));
		}
		unlink_spool();
		reset_state();
		smtp_remove_on_timeout = 0;
		break;
	    }
	    smtp_processing_signals();
	    if (sender == NULL) {		/* XXX how can this ever happen? */
		unlink_spool();
		reset_state();
		smtp_remove_on_timeout = 0;
		exitvalue = EX_NOINPUT;
		break;
	    }
	    if (read_message() == NULL) {
		log_spool_errors();
		unlink_spool();
		if (out) {
		    sleep(smtp_error_delay);
		    fprintf(out, "451 4.3.0 error in spooled message\r\n");
		    fflush(out);
		    write_log(WRITE_LOG_SYS, "remote DATA: error in queued message from %s%s%s%s%s%s%s%s%s.",
			      ident_sender ? ident_sender : "",
			      ident_sender ? "@" : "",
			      sender_host ? sender_host : "",
			      sender_host_really ? "(" : "",
			      sender_host_really ? sender_host_really : "",
			      sender_host_really ? ")" : "",
			      sender_host_addr ? " [" : "",
			      sender_host_addr ? sender_host_addr : "",
			      sender_host_addr ? "]" : "");
		}
		reset_state();
		smtp_remove_on_timeout = 0;
		exitvalue = EX_TEMPFAIL;
		break;
	    }
	    /*
	     * This shouldn't ever trigger because we should never write a
	     * spool file larger than accepted_msg_size in the first place, but
	     * we'll keep the check here too after the message has been fully
	     * received, just to be on the safe side.
	     *
	     * in any case don't be fussy about headers, use msg_body_size
	     * instead of msg_size in this test....
	     */
	    if (accepted_msg_size >= 0 && msg_body_size > accepted_msg_size) {
		write_log(WRITE_LOG_SYS, "remote DATA: message too big (body %ld bytes) from %s%s%s%s%s%s%s%s%s.",
			  msg_body_size,
			  ident_sender ? ident_sender : "",
			  ident_sender ? "@" : "",
			  sender_host ? sender_host : "",
			  sender_host_really ? "(" : "",
			  sender_host_really ? sender_host_really : "",
			  sender_host_really ? ")" : "",
			  sender_host_addr ? " [" : "",
			  sender_host_addr ? sender_host_addr : "",
			  sender_host_addr ? "]" : "");
		log_spool_errors();
		unlink_spool();
		if (out) {
		    sleep(smtp_error_delay);
		    fprintf(out, "552-5.3.4 Message is too big for this system.\r\n");
		    fflush(out);
		    /*
		     * NOTE: the remainder of this reply is identical to one
		     * above and one in decode_mail_options().  Keep it that
		     * way!
		     */
		    send_smtp_msg(out, 552, "5.3.4", TRUE, "Do NOT send files by e-mail!\n\
If you are sending a MIME message please set your mailer so that it splits messages larger than 64KB into multiple parts.  Otherwise please learn to transfer files with a file transfer protocol.");
		    fflush(out);
		}
		reset_state();
		smtp_remove_on_timeout = 0;
		exitvalue = EX_NOPERM;
		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) {
		if (smtp_vrfy_delay) {
		    sleep(smtp_vrfy_delay);
		}
		strip_rfc822_comments(data);
		strip_rfc822_whitespace(data);
		verify_addr(data, out, 0);
		reset_hit_table();
		fflush(out);
	    }
	    break;

	case EXPN_CMD:
	    if (smtp_sess_deny) {
		if (out) {
		    send_session_denied_reply(out, "EXPN", data);
		}
		break;
	    }
	    if (out) {
#ifdef NO_SMTP_EXPN
		sleep(smtp_error_delay);
		fprintf(out, "502 5.5.1 Command not implemented\r\n");
		fflush(out);
#else
		if (smtp_allow_expn) {
		    if (smtp_expn_delay) {
			sleep(smtp_expn_delay);
		    }
		    strip_rfc822_comments(data);
		    strip_rfc822_whitespace(data);
		    expand_addr(data, out);
		    reset_hit_table();
		    fflush(out);
		} else {
		    sleep(smtp_error_delay);
		    fprintf(out, "502 5.7.0 Command disabled\r\n");
		    fflush(out);
		}
#endif
	    }
	    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 FIXME we should really only log this if no message was received.... */
	    write_log(WRITE_LOG_SYS, "remote got QUIT from %s@%s(%s)[%s].",
		      ident_sender ? ident_sender : "<no-ident>",
		      sender_host ? sender_host : "<no-host>",
		      sender_host_really ? sender_host_really : "<unknown>",
		      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) {
			    sleep(smtp_error_delay);
			    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;
		}
		sleep(smtp_error_delay);
		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 TOOLONG_CMD:
	    if (out) {
		sleep(smtp_error_delay);
		fprintf(out, "500 5.5.1 Command too long\r\n");	/* XXX should this be 521? */
		fflush(out);
	    }
	    /* now better drop the connection... */
	    files[file_i++] = NULL;
	    errfile = save_errfile;
	    debug = save_debug;
	    exitvalue = EX_PROTOCOL;
	    return files;

	case OTHER_CMD: {
	    static int logged_unknown = 0;

	    /* XXX should this be response-rate limited? */
	    if (out) {
		sleep(smtp_error_delay);
		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++;
	    exitvalue = EX_PROTOCOL;
	    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.
     */
    sleep(smtp_error_delay);
    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;
    exitvalue = EX_UNAVAILABLE;
    return files;
}

/*
 * Print a message on the FILE stream 'out' such that
 * it conforms to the SMTP status reply format.
 *
 * Note it may already contain newlines.
 *
 * Uses an opportunistic, non-rigid, word-wrap algorithm....
 *
 * XXX should we have an auto-wrap flag to control word-wrap?
 */
static void
send_smtp_msg(out, status, dsn, endmsg, str)
    FILE *out;
    int status;			/* SMTP reply status code */
    char *dsn;			/* DSN (RFC 1893) prefix string (and/or white space indent) */
    int endmsg;			/* is this string the end? */
    char *str;			/* string to print */
{
    int cur_len;
    int will_wrap;
    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 so the maximum value is 559.  Even the last digit should
     * probably be restricted from 1-5, though RFC 821 is silent on the proper
     * extent of its range. Eg:
     *
     *    assert(status <= 559);
     *	  if ((status % 10) > 5) DEBUG();
     */
    while (*str) {
	int rem_len = strlen(str);

	if (endmsg && rem_len > 80 && (strcspn((str + 80), " \t\n") < (rem_len - 80))) {
	    will_wrap = TRUE;
	} else {
	    will_wrap = FALSE;
	}
	fprintf(out, "%d%c%s%s",
		status,
		(!endmsg || will_wrap || strchr(str, '\n')) ? '-' : ' ',
		(dsn && *dsn) ? dsn : "",
		(dsn && *dsn) ? " " : "");
	cur_len = 5 + strlen(dsn);
	/*
	 * XXX FIXME!!!! we should probably be eliminating/quoting unwelcome
	 * characters too!  (which ones should we watch for?  How to quote?)
	 */
	while ((c = *str)) {
	    str++;		/* must increment only after successful test! */
	    if (c == '\r') {
		continue;			/* ignore all <CR>s */
	    }
	    if (c == ' ' || c == '\t') {
		char *n = str - 1;		/* str already inc'ed above */

		while (*n && (*n == ' ' || *n == '\t')) {
		    n++;			/* find start of next word */
		}
		if ((strcspn(n, " \t\n") + cur_len + (n - str - 1)) >= 80) {
		    str = n;			/* drop the whitespace */
		    break;			/* and wrap... */
		}
	    }
	    if (c == '\n') {
		break;
	    }
	    putc(c, out);
	    cur_len++;
	}
	putc('\r', out);
	putc('\n', out);
	/*
	 * rate limit multiple lines of output if status represents an error
	 * Note we only flush if sleeping -- the caller will normally flush too.
	 */
	if (status > 399) {
	    fflush(out);
	    sleep(1);
	}
    }

    return;
}

static void
do_greeting(out, ehlo_p,
#ifdef HAVE_BSD_NETWORKING	/* XXX sorry, this is gross, but... */
	    saddrp, shpp
#endif
	    )
    FILE *out;			/* to remote client, if not batch mode */
    int ehlo_p;			/* using ESMTP? */
#ifdef HAVE_BSD_NETWORKING
    struct sockaddr_in *saddrp;
    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;

	exitvalue = EX_UNAVAILABLE;
	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");
	    fflush(out);
	    sleep(1);
	    fprintf(out, "550-All SMTP connections have been blocked from that address\r\n");
	    fflush(out);
	    sleep(1);
	    switch (smtp_sess_deny_reason) {
	    case SMTP_SESS_DENY_RBL:
		send_smtp_msg(out, 550, "", FALSE,
			      "because it matches the RBL (Realtime/Reverse Blocking/Black List):\n\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.... */
		send_smtp_msg(out, 550, "", FALSE,
			      "because either a DNS spoofing attack is underway at the moment, or your zone's DNS is severely mis-configured in a way indistinguishable from such an attack!\n");
		break;
	    }
	    if (smtp_sess_deny_msg) {
		send_smtp_msg(out, 550, "", FALSE,
			      "\nPlease note the following additional important information:\n\n");
		/* setting the DSN to " " is a hack to get indentation! */
		send_smtp_msg(out, 550, " ", FALSE, smtp_sess_deny_msg);
	    }
	    fflush(out);
	    sleep(smtp_error_delay);		/* give the longer pause now.... */
	    send_smtp_msg(out, 550, "", TRUE, formatted_get_help_msg);
	}
	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;
	}
	/* assert(log_reason)? */
	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");
	exitvalue = EX_NOHOST;
	return;
    }
    if (sender_host) {
	xfree(sender_host);
	sender_host = NULL;
    }
    if (out) {
#ifdef HAVE_BSD_NETWORKING
	int fatal;
	const char *errstr;		/* hold error message */
	    
	if (sender_host_addr && !shp) {
	    /* we've been around once before and need to reset shp & sender_host_really */
	    if (!(shp = gethostbyaddr((char *) &(saddrp->sin_addr),
				      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))) {
	    sleep(smtp_error_delay);
	    if (fatal) {
		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);
	    }
	    fflush(out);
	    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, (char *) 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) {
	    exitvalue = EX_NOHOST;
	    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]."
	 */
	if (accepted_msg_size == 0) {
		/* no DSN -- still in greeting... */
		fprintf(out, "452 %s system resources low.  Please try again much later!\r\n", primary_name);
		fflush(out);
		write_log(WRITE_LOG_SYS, "SMTP connection aborted, not enough spool space: from %s%s%s%s%s%s%s%s%s.",
		      ident_sender ? ident_sender : "",
		      ident_sender ? "@" : "",
		      sender_host ? sender_host : "",
		      sender_host_really ? "(" : "",
		      sender_host_really ? sender_host_really : "",
		      sender_host_really ? ")" : "",
		      sender_host_addr ? " source [" : "",
		      sender_host_addr ? sender_host_addr : "",
		      sender_host_addr ? "]" : "");
		unlink_spool();
		exit(EX_TEMPFAIL);
		/* NOTREACHED */
	}
	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");
	    if (accepted_msg_size >= 0) {
		fprintf(out, "250-SIZE %ld\r\n", (long) accepted_msg_size);
	    } else {
		fprintf(out, "250-SIZE\r\n");	/* this is probably pointless... */
	    }
	    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;
{
    sleep(smtp_error_delay);
    fprintf(out, "550-5.5.1 You%s%s%s%s%s are not permitted to issue any more commands from [%s].\r\n",
	    (sender_host_really || ident_sender) ? " (i.e. " : "",
	    ident_sender ? ident_sender : "",
	    ident_sender ? "@" : "",
	    sender_host_really ? sender_host_really : "",
	    (sender_host_really || ident_sender) ? ")" : "",
	    sender_host_addr ? sender_host_addr : "UNKNOWN-IP");
    fflush(out);
    /* 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);
    fflush(out);
    sleep(1);
    fprintf(out, "%d-%s If you are seeing this message in a bounce, or in an alert box\r\n", ecode, estatus);
    fflush(out);
    sleep(1);
    fprintf(out, "%d-%s from your mailer client, etc., then your mailer software\r\n", ecode, estatus);
    fflush(out);
    sleep(1);
#ifdef HAVE_ESMTP_PIPELINING		/* defined in iobpeek.h if support is possible */
    fprintf(out, "%d-%s is not showing you the correct and 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
    fflush(out);
    sleep(smtp_error_delay);		/* make them wait for the end of the message! */
    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 */

    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') {
		if (c == EOF) {
		    return TOOLONG_CMD;		/* TOOLONG_CMD will drop connection anyway */
		}
		sleep(1);			/* "Just try it buddy!" */
	    }
	}
	return TOOLONG_CMD;			/* we'll drop this connection after error */
    }

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

    DEBUG4(DBG_REMOTE_MID, "read_smtp_command() got '%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; 	/* body size the client claims it will send */

    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);	/* save... */
	    *(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);
		    fflush(out);
		    sleep(1);
		    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;
	    }
	    /*
	     * NOTE: the order of the next two 'if's is important so that we
	     * don't complain unecessarily about user sending files by e-mail.
	     */
	    if (accepted_msg_size >= 0 &&
		((body_size > accepted_msg_size &&
		  accepted_msg_size < max_message_size) ||
		 (max_message_size <= 0 &&
		  body_size > accepted_msg_size))) {
		write_log(WRITE_LOG_SYS, "remote MAIL FROM: not enough free space for SIZE=%ld %s%s%s%s%s%s%s%s%s.",
			  body_size,
			  ident_sender ? ident_sender : "",
			  ident_sender ? "@" : "",
			  sender_host ? sender_host : "",
			  sender_host_really ? "(" : "",
			  sender_host_really ? sender_host_really : "",
			  sender_host_really ? ")" : "",
			  sender_host_addr ? " [" : "",
			  sender_host_addr ? sender_host_addr : "",
			  sender_host_addr ? "]" : "");
		if (out) {
		    sleep(smtp_error_delay);
		    fprintf(out, "452 4.3.1 There is no room for a message of %ld bytes!  Please try again much later!\r\n", (long) body_size);
		    fflush(out);
		}
		return -1;
	    }
	    if (accepted_msg_size >= 0 && body_size > accepted_msg_size) {
#ifndef NO_LOG_SMTP_TOO_BIG
		write_log(WRITE_LOG_SYS, "remote MAIL FROM: SIZE=%ld too big from %s%s%s%s%s%s%s%s%s.",
			  body_size,
			  ident_sender ? ident_sender : "",
			  ident_sender ? "@" : "",
			  sender_host ? sender_host : "",
			  sender_host_really ? "(" : "",
			  sender_host_really ? sender_host_really : "",
			  sender_host_really ? ")" : "",
			  sender_host_addr ? " [" : "",
			  sender_host_addr ? sender_host_addr : "",
			  sender_host_addr ? "]" : "");
#endif
		if (out) {
		    sleep(smtp_error_delay);
		    fprintf(out, "552-5.3.4 A message of %ld bytes is far too large!\r\n", (long) body_size);
		    fflush(out);
		    /*
		     * NOTE: the remainder of this reply is identical to one in
		     * DATA_CMD handling in receive_smtp().  Keep it that way!
		     */
		    send_smtp_msg(out, 552, "5.3.4", TRUE, "Do NOT send files by e-mail!\n\
If you are sending a MIME message please set your mailer so that it splits messages larger than 64KB into multiple parts.  Otherwise please learn to transfer files with a file transfer protocol.");
		    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 */
    int oexitval = exitvalue;		/* resolve_addr_list() can clobber this */

# 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) {
	sleep(smtp_error_delay);
	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);
    exitvalue = oexitval;
    if (okay) {
	addr = okay;				/* OK to reuse addr now... */
	okay = NULL;
	check_smtp_remote_allow(addr, &okay, &defer, &fail);
    }
    if (okay) {
	/* display the complete list of resolved addresses */
	for (cur = okay; cur->succ; cur = cur->succ) {
	    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);
	    fflush(out);
	    sleep(1);
	    if (cur->error && cur->error->message) {
		fprintf(out, "550-5.1.5 %s.\r\n",  cur->error->message);
		fflush(out);
		sleep(1);
	    }
	}
	fflush(out);
	sleep(smtp_error_delay);		/* give the long delay now.... */
	if (cur->error && cur->error->message) {
	    fprintf(out, "550-5.1.5 '%s' Address processing error.\r\n", cur->in_addr);
	    fflush(out);
	    sleep(1);
	    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);
	    fflush(out);
	    sleep(1);
	    if (cur->error && cur->error->message) {
		fprintf(out, "550-5.1.1 %s.\r\n",  cur->error->message);
		fflush(out);
		sleep(1);
	    }
	}
	fflush(out);
	sleep(smtp_error_delay);		/* give the long delay now... */
	if (cur->error && cur->error->message) {
	    fprintf(out, "550-5.1.1 '%s' Address is not deliverable.\r\n", cur->in_addr);
	    fflush(out);
	    sleep(1);
	    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_dnsbl - look up subdomian in list of domains given in dnsbl
 *
 * - dnsbl is a colon-separated list of domains in which an A RR for
 * the specified name is looked up.
 *
 * - Optionally a comma-separated list of valid A RR values, either as explicit
 * 4-octet ascii-form host addresses, or in a network/mask form, follows a
 * semicolon after any domain (given in form suitable for a config file entry):
 *
 *	"\
 *	:dns.bl.domain;127.0.0.1,10/8\
 *	:dns.bl.domain;127.0.0/24\
 *	"
 *
 * WARNING: don't diddle with dnsbl -- it may be read-only string.
 *
 * Returns 1 if any matched, else 0.
 *
 *	if it matches *matchp will be set to the fully expanded domain that
 *	matched and *addrp will be set to the A RR's value, both as ASCII
 *	strings; and as well if a TXT RR is found at the same domain then the
 *	*msgp will be set to point to its value too.
 */
static int
match_dnsbl(target, dnsbl, matchp, addrp, msgp)
    char *target;			/* subdomain to match */
    char *dnsbl;			/* DNS BlackList domain list */
    char **matchp;			/* pointer to hold formatted match address */
    char **addrp;			/* pointer to hold ASCII A RR value address */
    char **msgp;			/* pointer to hold TXT message pointer */
{
    char *pat_p, *end_p;
    unsigned int len;

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

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

	/* skip any spaces at front */
	while (*pat_p && isspace(*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 *bl_hname;
	    char *txtaddr;
	    unsigned int bl_hname_len = strlen(target) + len + 1; /* one more for the '.' */
	    char *p;
	    struct hostent *hp;

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

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

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

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

    return 0;
}


/*
 * verify_addr_form - verify the form and syntax of an address
 */
static int
verify_addr_form(in_addr, raw_addr, out, smtpcmd, pvtarget)
    char *in_addr;	/* from preparse_address_1() or preparse_address() */
    char *raw_addr;	/* full data from user */
    FILE *out;		/* file for response codes */
    char *smtpcmd;	/* which SMTP are we doing this for? */
    char **pvtarget;	/* return the target domain */
{
    struct addr *vaddr;
    int form;
    int is_rcpt_to = EQIC(smtpcmd, "rcpt to:");
    char *p;

    vaddr = alloc_addr();
    vaddr->in_addr = COPY_STRING(raw_addr);
    vaddr->work_addr = COPY_STRING(in_addr);
    form = parse_address(vaddr->work_addr, &vaddr->target,
			 &vaddr->remainder, &vaddr->parseflags);
    if (vaddr->target) {
	*pvtarget = COPY_STRING(vaddr->target);
    } else {
	*pvtarget = NULL;
    }
    p = vaddr->remainder;
    if (form != FAIL && form != PARSE_ERROR) {
	/*
	 * we must now do further SMTP-specific validation of the local part
	 */
	if (*p == '"') {
	    if (!is_quoted_string(p)) {
		vaddr->remainder = "illegal quoted mailbox local part";
		form = FAIL;
	    }		
	} else {
	    if (!is_dot_string(p)) {
		vaddr->remainder = "illegal character in unquoted mailbox local part";
		form = FAIL;
	    }
	}
    }
    if (form == FAIL || form == PARSE_ERROR) {
	sleep(smtp_error_delay);
        fprintf(out, "501 5.1.7 '%s' address parse error: %s.\r\n",
		raw_addr,
		(vaddr->remainder) ? vaddr->remainder : "<unknown error>");
	fflush(out);
	write_log(WRITE_LOG_SYS, "remote %s '%s' is invalid [%s]; by %s%s%s%s%s%s%s%s%s.",
		  smtpcmd,
		  raw_addr,
		  (vaddr->remainder) ? vaddr->remainder : "<unknown error>",
		  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 (out &&
	form == LOCAL &&
	!((is_rcpt_to ? FALSE : *vaddr->work_addr) ||
	  EQIC(vaddr->work_addr, is_rcpt_to ? "Postmaster" : "+"))) {
	fprintf(out, "550-5.1.7 '%s' address cannot be an unqualified address\r\n", in_addr);
	fflush(out);
	sleep(smtp_error_delay);
	fprintf(out, "550 5.1.7 (i.e. it must have a domain portion).\r\n");
	fflush(out);
	write_log(WRITE_LOG_SYS, "remote %s '%s' is a local address; by %s%s%s%s%s%s%s%s%s.",
		  smtpcmd,
		  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 ? "]" : "");
	xfree(vaddr->in_addr);
	xfree(vaddr->work_addr);
	xfree((char *) vaddr);
	return 0;
    }
    if (out && (form == BERKENET || form == DECNET)) {
	/* XXX should be impossible unless USE_DECNET or USE_BERKENET are defined */
	sleep(smtp_error_delay);
	fprintf(out, "553 5.1.7 '%s' address cannot be a %s form address.\r\n",
		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.",
		  smtpcmd,
		  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(vaddr->in_addr);
	xfree(vaddr->work_addr);
	xfree((char *) vaddr);
	return 0;
    }
    if (out &&
	form != MAILBOX &&
	!((is_rcpt_to ? FALSE : *vaddr->work_addr) ||
	  EQIC(vaddr->work_addr, is_rcpt_to ? "Postmaster" : "+"))) {
	/*
	 * 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 %s '%s' (target %s) is a %s(%d) address; by %s%s%s%s%s%s%s%s%s.",
		  smtpcmd,
		  in_addr,
		  vaddr->target ? vaddr->target : "(no-domain)",
		  (form == RFC_ROUTE) ? "RFC_ROUTE" :
		  (form == RFC_ENDROUTE) ? "RFC_ENDROUTE" :
		  (form == MAILBOX) ? "MAILBOX" :
		  (form == UUCP_ROUTE) ? "UUCP_ROUTE" :
		  (form == PCT_MAILBOX) ? "PCT_MAILBOX" :
		  (form == LOCAL) ? "LOCAL" :
		  (form == BERKENET) ? "BERKENET" :
		  (form == DECNET) ? "DECNET" : "<bad-form!>",
		  form,
		  ident_sender ? ident_sender : "",
		  ident_sender ? "@" : "",
		  sender_host ? sender_host : "",
		  sender_host_really ? "(" : "",
		  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 1;
}


/*
 * verify_sender - verify envelope sender address
 */
static int
verify_sender(pp_sender, raw_sender, out)
    char *pp_sender;			/* from preparse_address_1() */
    char *raw_sender;			/* full data from user */
    FILE *out;				/* file for response codes */
{
    char *vtarget;

    if (!verify_addr_form(pp_sender, raw_sender, out, "MAIL FROM:", &vtarget)) {
	/* errors already logged and printed */
	return 0;
    }
#ifdef HAVE_BIND
    if (!EQ(pp_sender, "+") &&
	!(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(vtarget, bindflgs, &bindpriv, "verify_sender()", &rt_info, &binderr);
	binderrmsg = (binderr && binderr->message && *binderr->message) ?
		binderr->message :
		(mxresult == DB_SUCCEED) ? "(none)" : "unknown error";

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

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

	switch (mxresult) {
	case DB_SUCCEED:
	    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);
		fflush(out);
		sleep(1);
		fprintf(out, "450-4.4.3 Sender address target domain '%s' cannot be verified at this time.\r\n", vtarget);
		fflush(out);
		sleep(1);
		fprintf(out, "450-4.4.3 Reason given was: %s.\r\n", binderrmsg);
		fflush(out);
		sleep(smtp_error_delay); /* force them to make it at least this much later! */
		fprintf(out, "450 4.4.3 Try again later.\r\n");
		fflush(out);
	    }
#ifndef NO_LOG_DNS_TEMPORARY_ERRORS
	    write_log(WRITE_LOG_SYS, "remote MAIL FROM: '%s' target '%s': DNS temporary error: %s; by %s%s%s%s%s%s%s%s%s.",
		      raw_sender,
		      vtarget,
		      binderrmsg,
		      ident_sender ? ident_sender : "",
		      ident_sender ? "@" : "",
		      sender_host ? sender_host : "",
		      sender_host_really ? "(" : "",
		      sender_host_really ? sender_host_really : "",
		      sender_host_really ? ")" : "",
		      sender_host_addr ? " [" : "",
		      sender_host_addr ? sender_host_addr : "",
		      sender_host_addr ? "]" : "");
#endif
	    xfree(vtarget);

	    return 0;

	case DB_NOMATCH:		/* no such domain */
	    if (out) {
		fprintf(out, "550-5.1.8 reject sender '%s'.\r\n", pp_sender);
		fflush(out);
		sleep(smtp_error_delay);
		fprintf(out, "550%s5.1.8 Sender address target domain '%s' is not valid%s\r\n",
			smtp_sender_verify_mx_only ? "-" : " ",
			vtarget,
			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,
		      vtarget,
		      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(vtarget);

	    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);
		fflush(out);
		sleep(1);
		fprintf(out, "550-5.4.3 Failure trying to verify address target domain '%s'.\r\n", vtarget);
		fflush(out);
		sleep(smtp_error_delay);
		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.",
		      raw_sender,
		      vtarget,
		      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(vtarget);

	    return 0;

	default:
	    if (out) {
		fprintf(out, "421-4.4.3 defer all mail from '%s'.\r\n", pp_sender);
		fflush(out);
		sleep(1);
		fprintf(out, "421-4.4.3 Impossible error returned when trying to verify target domain '%s'.\r\n", vtarget);
		fflush(out);
		sleep(1);
		fprintf(out, "421-4.4.3 Reason given was: %s.\r\n", binderrmsg);
		fflush(out);
		sleep(smtp_error_delay); /* some ignore the reason and try again immediately! */
		fprintf(out, "421 4.4.3 Try again later.  Connection closing now...\r\n");
		fflush(out);
	    }
	    panic(EX_SOFTWARE, "verify_sender(%s, %s): bind_addr(%s) gave impossible result %d: %s.",
		  pp_sender, raw_sender, vtarget, mxresult, binderrmsg);
	    /* NOTREACHED */
	}
	if (smtp_sender_rhsbl_domains &&
	    (! smtp_sender_rhsbl_except || ! is_string_in_list(vtarget, smtp_sender_rhsbl_except))) {
	    char *smtp_rhsbl_match = NULL;	/* full domain name matched by an RHSBL */
	    char *smtp_rhsbl_addr = NULL;	/* ascii formatted address value of RHSBL A RR */
	    char *smtp_rhsbl_msg = NULL;	/* value of any RHSBL TXT RR */

	    if (match_dnsbl(vtarget, smtp_sender_rhsbl_domains, &smtp_rhsbl_match, &smtp_rhsbl_addr, &smtp_rhsbl_msg)) {
		if (out) {
		    fprintf(out, "550-5.1.8 reject sender '%s'.\r\n", pp_sender);
		    fflush(out);
		    sleep(1);
		    fprintf(out, "550-5.1.8 The domain '%s' matches the blacklist:\r\n550-5.1.8\r\n", vtarget);
		    fflush(out);
		    sleep(1);
		    fprintf(out, "550-5.1.8\t%s\tA\t%s\r\n", smtp_rhsbl_match, smtp_rhsbl_addr);
		    fflush(out);
		    sleep(smtp_error_delay);		/* give the longer pause now.... */
		    if (smtp_rhsbl_msg) {
			send_smtp_msg(out, 550, "5.1.8", FALSE,
				      "\nPlease note the following additional important information:\n\n");
			/* adding the " " is a hack to get indentation! */
			send_smtp_msg(out, 550, "5.1.8  ", FALSE, smtp_rhsbl_msg);
		    }
		    send_smtp_msg(out, 550, "5.1.8", TRUE, formatted_get_help_msg);
		}
		write_log(WRITE_LOG_SYS, "remote MAIL FROM: refusing SMTP connection from %s%s%s[%s]: matched RHSBL %s [%s].",
			  pp_sender,
			  ident_sender ? ident_sender : "",
			  ident_sender ? "@" : "",
			  sender_host_really ? sender_host_really : "(unknown)",
			  sender_host_addr ? sender_host_addr : "UNKNOWN-IP",
			  smtp_rhsbl_match,
			  smtp_rhsbl_addr);
		xfree(vtarget);

		return 0;
	    }
	}
    }
#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 own mailer's configuration.
     */
    if (!EQ(pp_sender, "+")) {
	int verify_local = 0;

	if (islocalhost(vtarget)) {
	    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(vtarget, bindflgs, &bindpriv, "verify_sender()", &rt_info, &binderr) == DB_FAIL) {
		/*
		 * This is a bit of a cheap hack, but it does what we want.  If
		 * the preferred MX record points at a "local" host,
		 * bind_addr() will return the following error, 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 and logged by verify_addr() */
	    xfree(vtarget);

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

    return 1;
}

/*
 * verify_addr - verify an address
 *
 * redisplay the input address if it is a valid address.
 */
static int
verify_addr(in_addr, out, 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 */
    char *vtarget;			/* target domain from verify_addr_form() [not used] */
    char *reason;			/* optional administrative reason from match_ip() */
    
    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) {
	    sleep(smtp_error_delay);
	    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;
    }
    if (!verify_addr_form(addr->work_addr, in_addr, out, "RCPT TO:", &vtarget)) {
	/* errors already logged and printed */
	return 0;
    }
    if (!out) {
	return 1;			/* Batched-SMTP, must be OK, right? */
    }

    /* 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();
	}
    }
    if (sender_host_addr && smtp_recipient_no_verify &&
	match_ip(sender_host_addr, smtp_recipient_no_verify, ':', ';', &reason)) {

	if (out) {
	    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);
	}
	return 1;
    } else {
	int oexitval = exitvalue;		/* resolve_addr_list() can clobber this */

	/*
	 * 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);
	exitvalue = oexitval;
	if (okay) {
	    addr = okay;
	    okay = NULL;
	    check_smtp_remote_allow(addr, &okay, &defer, &fail);
	}
    }
    /* The result should be on one, and only one, of the output lists.... */
    if (okay) {
	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) {
		sleep(smtp_error_delay);
		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) {
		    sleep(smtp_error_delay);
		    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");
	    fflush(out);
	    sleep(1);
	    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 : "");
	    fflush(out);
	    sleep(1);
	    fprintf(out, "450-4.3.0 Reason given was: (ERR_%03ld) %s.\r\n",
		    defer->error->info & ERR_MASK,
		    defer->error->message);
	    fflush(out);
	    sleep(smtp_error_delay);
	    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);
	    fflush(out);
	    sleep(1);
	    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 : "");
	    fflush(out);
	    sleep(1);
	    fprintf(out, "%d-%s Reason given was: (ERR_%03ld) %s.\r\n",
		    error_code, enhanced,
		    fail->error->info & ERR_MASK,
		    fail->error->message);
	    fflush(out);
	    sleep(smtp_error_delay);
	    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);
	fflush(out);
	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.  Literal 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 verify host '%s': %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': %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 verify 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 hostname 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) does not have an address matching [%s]!  Is 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 hostname '%s': %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 hostname '%s': %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 hostname '%s': %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 hostname '%s': %s", p, binderrmsg);
		    if (smtp_hello_verify && !allowed_to_be_broken) {
			*fatalp = 1;
			return NULL;
		    }
		    break;
		}
	    }
	}
    } else {
	*errorp = xprintf("Remote address PTR lookup failed: %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! */
	sleep(smtp_error_delay);
	fprintf(out_file, "421 4.3.2 %s SMTP command timeout, closing channel\r\n", primary_name);
	fflush(out_file);
    }
    write_log(WRITE_LOG_SYS, "SMTP connection timeout while talking with %s%s%s%s%s%s%s%s%s.",
	      ident_sender ? ident_sender : "",
	      ident_sender ? "@" : "",
	      sender_host ? sender_host : "",
	      sender_host_really ? "(" : "",
	      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) {
	sleep(smtp_error_delay);
	fprintf(out_file, "421 4.3.2 %s Service not available, closing channel\r\n", primary_name);
	fflush(out_file);
    }
    write_log(WRITE_LOG_SYS, "SMTP connection closed by signal %d while talking with %s%s%s%s%s%s%s%s%s.",
	      sig,
	      ident_sender ? ident_sender : "",
	      ident_sender ? "@" : "",
	      sender_host ? sender_host : "",
	      sender_host_really ? "(" : "",
	      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 */
}

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

    /*
     * if max_message_size == 0 then we have to be careful not to overflow a
     * long with the multiplication to bytes....
     */
    my_max_msg_size = (max_message_size <= 0) ? (LONG_MAX / 1024) : (max_message_size / 1024);
    free_kbytes = spool_max_free_space();
    if (free_kbytes < 0) {
	/* unknown, just hope for the best */
	return (max_message_size ? max_message_size : -1);
    }
    if (free_kbytes == 0) {
	return 0;				/* no usable space left */
    }

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

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