/*
#ident	"@(#)smail/src:RELEASE-3_2_0_108:smtprecv.c,v 1.95 1999/09/19 20:51:48 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 <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <signal.h>
#include "defs.h"
#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 */
#include "config.h"
#include "main.h"
#include "smail.h"
#include "addr.h"
#include "transport.h"
#include "dys.h"
#include "log.h"
#include "hash.h"
#include "alloc.h"
#include "iobpeek.h"
#ifdef HAVE_BIND
# include "lookup.h"
# include "bindlib.h"
# include "route.h"
#endif
#ifndef DEPEND
# include "extern.h"
# include "debug.h"
# include "exitcodes.h"
# include "error.h"
#endif

#ifdef HAVE_LIBWRAP
# include <tcpd.h>			/* TCP Wrappers declarations */
#endif

#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

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

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

char *smtp_local_addr = NULL;		/* ascii representation of getsockname() */

/* a guess at the classic IP of the local network (used by match_ip())*/
char *smtp_local_net = NULL;

#ifdef HAVE_LIBWRAP
static int smtp_session_denied = 0;	/* result of host_access() */
#endif

/* types local to this file */
enum e_smtp_commands {
    HELO_CMD,				/* HELO domain */
    EHLO_CMD,				/* EHLO domain */
    MAIL_CMD,				/* MAIL FROM:<sender> */
    RCPT_CMD,				/* RCPT TO:<recipient> */
    DATA_CMD,				/* DATA */
    VRFY_CMD,				/* VRFY */
    EXPN_CMD,				/* EXPN */
    QUIT_CMD,				/* QUIT */
    RSET_CMD,				/* RSET */
    NOOP_CMD,				/* NOOP */
    DEBUG_CMD,				/* DEBUG [level] */
    HELP_CMD,				/* HELP */
    EOF_CMD,				/* end of file encountered */
    OTHER_CMD				/* unknown command */
};

/* functions local to this file */
#ifdef __STDC__
# ifdef HAVE_LIBWRAP
static void send_session_denied_reply(FILE *, char *, char *);
# endif
static void non_compliant_reply(FILE *, int, char *);
static void illegal_operand_warning(char *, char *operand);
static void reset_state(void);
static enum e_smtp_commands read_smtp_command(FILE *, FILE *);
static int decode_mail_options(char *, FILE *);
static void expand_addr(char *, FILE *);
static int verify_sender(char *, char *, FILE *);
static int verify_addr(char *, FILE *, int);
static char *verify_host(char *, struct sockaddr_in *, struct hostent*, char const *, const int, char const **, int *);
static void check_smtp_remote_allow(struct addr *, struct addr **, struct addr **, struct addr **);
static void smtp_input_signals(void);
static void smtp_processing_signals(void);
static void set_term_signal(int);
static void smtp_receive_timeout_sig(int);
static void smtp_sig_unlink(int);
# ifdef HAVE_DF_SPOOL
static long compute_max_message_size_from_df_spool(void);
# endif
static void illegal_relay_error(struct addr *, char *);
#else /* not __STDC__ */
# ifdef HAVE_LIBWRAP
static void send_session_denied_reply();
# endif
static void non_compliant_reply();
static void illegal_operand_warning();
static void reset_state();
static enum e_smtp_commands read_smtp_command();
static int decode_mail_options();
static void expand_addr();
static int verify_sender();
static int verify_addr();
static char *verify_host();
static void check_smtp_remote_allow();
static void smtp_input_signals();
static void smtp_processing_signals();
static void set_term_signal();
static void smtp_receive_timeout_sig();
static void smtp_sig_unlink();
# ifdef HAVE_DF_SPOOL
static long compute_max_message_size_from_df_spool();
# endif
static void illegal_relay_error();
#endif /* not __STDC__ */

/* variables local to this file */
static char *data;			/* interesting data within input */

static int term_signal;
static int smtp_remove_on_timeout;
static FILE *out_file;
static char *help_msg[] = {
    "250-The following SMTP commands are recognized:",
    "250-",
    "250-   HELO hostname                   - startup and give your hostname",
    "250-   EHLO hostname                   - startup with extension info",
    "250-   MAIL FROM:<sender-address>      - start transaction from sender",
    "250-   RCPT TO:<recipient-address>     - name recipient for message",
#ifdef NO_SMTP_VRFY
    "250-   VRFY <address>                  - verify deliverability of address",
#endif
#ifndef NO_SMTP_EXPN
    "250-   EXPN <address>                  - expand mailing list address",
#endif
    "250-   DATA                            - start text of mail message",
    "250-   RSET                            - reset state, drop transaction",
    "250-   NOOP                            - do nothing",
#ifndef NODEBUG
    "250-   DEBUG [level]                   - set debugging level, default 1",
#endif
    "250-   HELP                            - produce this help message",
    "250-   QUIT                            - close SMTP connection",
    "250-",
    "250-The normal sequence of events in sending a message is to state the",
    "250-sender address with a 'MAIL FROM:' command, give the recipients with",
    "250-as many 'RCPT TO:' commands as are required (one address per command)",
    "250-and then to specify the mail message text after the DATA command.",
    "250 Multiple messages may be specified.  End the last one with a QUIT."
};

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

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

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


/*
 * receive_smtp - receive mail over SMTP.
 *
 * Take SMTP commands on the `in' file.  Send reply messages
 * to the `out' file.  If `out' is NULL, then don't send reply
 * messages (i.e., read batch SMTP commands).
 *
 * return an array of spool files which were created in this SMTP
 * conversation.
 *
 * The last spooled message is left open as an efficiency move, so the
 * caller must arrange to close it or process it to completion.  As
 * well, it is the callers responsibility to close the input and
 * output channels.
 */
char **
receive_smtp(in, out)
    FILE *in;				/* stream of SMTP commands */
    FILE *out;				/* channel for responses */
{
    static char **files = NULL;		/* array of names of spool files to return */
    static int file_cnt = 7;		/* initially put 7 parts in array */
    int file_i = 0;			/* index starts at the beginning */
    const char *errstr;			/* temp to hold error messages */
    struct addr *cur;			/* address pointer -- current recipient */
    char *rest;				/* ptr used in parsing ESMTP options */
    int ehlo_p = 0;			/* using ESMTP? */
    int c;
    char *p;				/* just a pointer */
    /* save important state to restore after initialize_state() */
    enum er_proc save_error_proc = error_processing;
    int save_do_aliasing = do_aliasing;
    int save_dont_deliver = dont_deliver;
    FILE *save_errfile = errfile;
    int save_debug = debug;
#ifdef HAVE_BSD_NETWORKING
    struct sockaddr_in from_sa;
    int from_sa_len = sizeof(from_sa);
    struct sockaddr_in my_sa;
    int my_sa_len = sizeof(my_sa);
    struct hostent *shp = NULL;		/* result of gethostbyaddr() */
#endif /* HAVE_BSD_NETWORKING */

    /* initialize state */
    initialize_state();

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

    term_signal = FALSE;
    out_file = out;			/* store ptr in a static global for signal handlers */
    smtp_processing_signals();

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

    DEBUG(DBG_REMOTE_HI, "receive_smtp() called.\n");
    /* output the startup banner line */
    if (out) {
	char *s;

	DEBUG(DBG_REMOTE_HI, "receive_smtp() sending smtp_banner.\n");
	s = expand_string(smtp_banner, (struct addr *)NULL,
			  (char *)NULL, (char *)NULL);
	if (!s) {
	    static 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";
	}
	while (*s) {
	    fprintf(out, "220%c", strchr(s, '\n') == NULL ? ' ' : '-');
	    while ((c = *s)) {
		s++;
		if (c == '\n') {
		    break;
		}
		putc(c, out);
	    }
	    putc('\r', out);
	    putc('\n', out);
	}
	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)) {

		p = inet_ntoa(my_sa.sin_addr);
		smtp_local_addr = COPY_STRING(p);
		p = inet_ntoa(inet_makeaddr(inet_netof(my_sa.sin_addr), 0L));
		smtp_local_net = COPY_STRING(p);

		/* now work backwards and replace ".0[.]" with ".*[.]" for match_ip() */
		for (p = smtp_local_net + strlen(smtp_local_net); p > smtp_local_net+2; p--) {
		    if ((*p == '.' || *p == '\0') && *(p-1) == '0' && *(p-2) == '.') {
			*(p-1) = '*';
			p -= 1;		/* skip back over the new '*' */
		    } else if (*p && *p != '.' && *p != '0') {
			break;		/* don't s~204.0.102.0~204.*.102.*~! */
		    }
		}
		if ((p = index(smtp_local_net,'*'))) {
		    *(p+1) = '\0';	/* trunc. after first wildcard */
		}
	    } 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 {
	    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);
	}
    }
# ifdef HAVE_LIBWRAP
    /*
     * If we're talking SMTP we're likely on a TCP/IP connection, so let's try
     * the TCP Wrappers hosts_access() check.
     *
     * Note that this must come before our call to gethostbyaddr() below.
     *
     * WARNING: the rfc931() routine that can be invoked by hosts_access() if a
     * matching entry uses the "rfc931" option will trash signal(SIGALRM)
     * leaving a broken handler that calls longjmp() to a broken stack frame,
     * so any handler must be set *after* this code!!!!
     */
    if (out) {
	struct request_info libwrap_req;

	/*
	 * We must do this to get the PID logged.
	 *
	 * Older versions of openlog() require only two arguments.
	 */
#  ifdef LOG_MAIL			/* XXX suggests newer interface... */
	(void) openlog(program, LOG_PID, LIBWRAP_DEFAULT_FACILITY);
#  else
	(void) openlog(program, LOG_PID);
#  endif
	/*
	 * Do not pass in the client hostname as that would void the
	 * consistency checks that host_access() does when it first tries to
	 * match the name.  Instead we must let librwap rediscover everything.
	 * If the interface were better documented we could do all this first
	 * and then eliminate the need for most of the gethost*() calls above.
	 */
	request_init(&libwrap_req,
		     RQ_DAEMON, program,
		     RQ_FILE, fileno(out), /* all that's needed for fromhost() */
		     0);
	/*
	 * This is an undocumented call in libwrap but is the only way to get
	 * libwrap to setup its internal socket handling code so that it does
	 * all the hostname consistency checks.  Usage matches tcpd.c itself.
	 */
	fromhost(&libwrap_req);		/* get info from RQ_FILE parameter */
	/*
	 * This is also an undocumented flag in libwrap.  By default we set it
	 * to >1 (i.e. 2) in order to get hosts_access() to syslog matches with
	 * the filename and line number of the matching rule.  This is about as
	 * close as we can get to recording the real reason for the denied
	 * session.  Note that these messages are logged using the default
	 * facility provided above, and at the LOG_DEBUG level.  Unfortunately
	 * there's no way to extract a copy of the actual rule which matched
	 * from this hokey interface, so you have to be careful to version
	 * control your /etc/hosts.* files and make sure you look at the right
	 * one for the timestamp of a given log message.
	 */
	hosts_access_verbose = LIBWRAP_MATCH_VERBOSE;
	/* XXX I wonder if it would be good to set RQ_SERVER_NAME from listen_name???? */
	smtp_session_denied = (! hosts_access(&libwrap_req));
    } else {
	smtp_session_denied = 0;	/* just to be sure! (inited to 0) */
    }
# endif /* HAVE_LIBWRAP */
    if (sender_host_addr) {
	/* WARNING: must check shp contents before calling gethost*() again */
	if (!(shp = gethostbyaddr((char *) &(from_sa.sin_addr),
				  sizeof(from_sa.sin_addr), from_sa.sin_family))) {
	    DEBUG2(DBG_REMOTE_LO, "gethostbyaddr(%s) failed: %s.\n", sender_host_addr, hstrerror(h_errno));
	    sender_host_really = NULL;
	} else {
	    sender_host_really = COPY_STRING(shp->h_name);
	}
    }
    DEBUG3(DBG_REMOTE_MID, "sender addr is [%s], sender host might be '%s', local net might be [%s].\n",
	   sender_host_addr ? sender_host_addr : "<unset>",
	   sender_host_really ? sender_host_really : "<unknown>",
	   smtp_local_net ? smtp_local_net : "<unset>");
#endif /* HAVE_BSD_NETWORKING */

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

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

    if (out) {
	write_log(WRITE_LOG_SYS, "[%d] remote connection from %s%s%s[%s]",
		  getpid(),
		  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, "[%d] bsmtp session started.", getpid());
#endif
    }
    if (out) {
	(void) signal(SIGALRM, smtp_receive_timeout_sig);
    }
    while (! term_signal || out == NULL) {
	char *orig_data = NULL;

	if (out) {
	    alarm(smtp_receive_command_timeout);
	}
	switch (read_smtp_command(in, out)) {
#ifdef HAVE_EHLO
	case EHLO_CMD:
	    ehlo_p = 1;
	    sender_proto = (out ? "esmtp" : "ebsmtp"); /* reset as appropriate */
	    /* FALLTHRU */
#endif
	case HELO_CMD:
#ifdef HAVE_LIBWRAP
	    if (smtp_session_denied) {
		if (out) {
		    fprintf(out, "550-You are not permitted to send mail from %s[%s].\r\n",
			    sender_host_really ? sender_host_really : "(unknown)",
			    sender_host_addr ? sender_host_addr : "UNKNOWN-IP");
		    fprintf(out, "550-All SMTP connections have been blocked from that address,\r\n");
		    fprintf(out, "550-possibly because it has been listed in one of the many RBLs,\r\n");
		    fprintf(out, "550-or possibly because your DNS is broken or your hostname is wrong.\r\n");
		    fprintf(out, "550-\r\n");
		    fprintf(out, "550-Please forward this message to your own LOCAL postmaster and ask\r\n");
		    fprintf(out, "550-them to contact us regarding this issue.\r\n");
		    fprintf(out, "550-\r\n");
		    fprintf(out, "550-Postmaster@[%s]: Please contact <postmaster@%s>\r\n",
			    sender_host_addr ? sender_host_addr : "UNKNOWN-IP",
			    primary_name);
		    fprintf(out, "550-(via an unblocked server of course!) for exact details on why your\r\n");
		    fprintf(out, "550-mailer's connection was rejected.  You must include your IP number\r\n");
		    fprintf(out, "550 given above in order to receive an accurate answer.\r\n");
		    fflush(out);
		}
		write_log(WRITE_LOG_SYS, "[%d] remote %s: refusing SMTP connection from %s%s%s[%s].",
			  getpid(),
			  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");
		break;
	    }
#endif
	    orig_data = COPY_STRING(data);
	    strip_rfc822_comments(data); /* XXX not required by RFC 821 */
	    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");
		break;
	    }
	    if (sender_host) {
		xfree(sender_host);
		sender_host = NULL;
	    }
	    if (out) {
#ifdef HAVE_BSD_NETWORKING
		int fatal;

		if (sender_host_addr && !shp) {
		    /* we've been around once before and need to reset shp & sender_host_really */
		    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);
		    }
		}
		errstr = NULL;
		fatal = 0;
		if (! (sender_host = verify_host(data, &from_sa, shp, shp ? NULL : hstrerror(h_errno), h_errno, &errstr, &fatal))) {
		    if (fatal) {
			fprintf(out, "501-%s requires a valid host name as operand: '%s'\r\n",
				ehlo_p ? "EHLO" : "HELO",
				data);
			fprintf(out, "501%sconnection rejected from %s%s%s%s%s%s%s%s.\r\n",
				errstr ? "-" : " ",
				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 ? ".\r\n501-Reason given was:\r\n501 " : "",
				errstr ? errstr : "");
			fflush(out);
		    } else {
			fprintf(out, "401-%s temporary error while validating host name '%s'.\r\n",
				ehlo_p ? "EHLO" : "HELO",
				data);
			fprintf(out, "401%sconnection deferred from %s%s%s%s%s%s%s%s\r\n",
				errstr ? "-" : " ",
				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 ? ".\r\n401-Reason given was:\r\n401 " : "",
				errstr ? errstr : "");
			fflush(out);
		    }
		}
		if (!sender_host || errstr) { /* log even "hidden" errors as warnings */
		    write_log(WRITE_LOG_SYS, "[%d] remote %s: %s operand: '%s': from %s%s%s%s%s%s%s%s%s%s%s.",
			      getpid(),
			      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 : "");
		}
		shp = NULL;		/* make sure are reset if needed again */
		if (!sender_host) {
		    break;		/* broken DNS or forger */
		}
#else /* !HAVE_BSD_NETWORKING */
		/* XXX shouldn't be lazy -- should verify some forms the hard way! */
		DEBUG1(DBG_REMOTE_LO, "receive_smtp(): no BSD networking, so not verifying sender host %s.\n", data);
#endif /* HAVE_BSD_NETWORKING */
	    } else if (!sender_host && data[0] != '\0') {
		sender_host = COPY_STRING(data);
	    }
	    if (out) {
		/* XXX RFC 0821 says "The receiver-SMTP identifies itself to
		 * the sender-SMTP in [...] the response to this command
		 * [HELO]."
		 */
		fprintf(out, "250%s%s Hello %s (%s%s%s%s%s%s)%s\r\n",
			ehlo_p ? "-" : " ",
			primary_name, data,
			ident_sender ? ident_sender : "",
			ident_sender ? "@" : "",
			sender_host_really ? sender_host_really : (sender_host ? sender_host : "(UNKNOWN)"),
			sender_host_addr ? " from address [" : "",
			sender_host_addr ? sender_host_addr : "",
			sender_host_addr ? "]" : "",
			ehlo_p ? ", here is what we support:" : ".");
		if (ehlo_p) {
#ifdef HAVE_DF_SPOOL
		    accepted_msg_size = compute_max_message_size_from_df_spool();
#endif
		    if (accepted_msg_size == -1 || (max_message_size > 0 && max_message_size < accepted_msg_size)) {
			accepted_msg_size = max_message_size;
		    }
		    if (accepted_msg_size > 0 && accepted_msg_size != -1) {
			fprintf(out, "250-SIZE %ld\r\n", (long) accepted_msg_size);
		    } else {
			fprintf(out, "250-SIZE\r\n");
		    }
		    fprintf(out, "250-8BITMIME\r\n");
#ifdef HAVE_ESMTP_PIPELINING		/* defined in iobpeek.h if support is possible */
		    fprintf(out, "250-PIPELINING\r\n");
#endif
#ifndef NO_SMTP_EXPN
		    fprintf(out, "250-EXPN\r\n");
#endif
		    fprintf(out, "250 HELP\r\n");
		}
		(void) fflush(out);
	    }
	    reset_state();		/* HELO implies RSET */
	    break;

	case MAIL_CMD:
#ifdef HAVE_LIBWRAP
	    if (smtp_session_denied) {
		if (out) {
		    send_session_denied_reply(out, "DEBUG", data);
		}
		break;
	    }
#endif
	    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, "'MAIL FROM:' requires return return-path address as operand.");
		break;
	    }
	    if (!sender_host) {
		non_compliant_reply(out, 503, "'MAIL FROM:' must be preceded by HELO/EHLO command.");
		break;
	    }
	    if (sender) {
		non_compliant_reply(out, 503, "Sender already specified");
		break;
	    }
	    sender = preparse_address_1(data, &errstr, &rest);
	    if (!sender) {
		if (out) {
		    fprintf(out, "501 '%s' Address parse error: %s\r\n", data, errstr);
		    fflush(out);
		}
		break;
 	    }
	    if (!verify_sender(sender, data, out)) {
		sender = NULL;
		break;
	    }
	    if (sender && sender[0] == '\0') {
		/* special error sender form <> given */
		sender = COPY_STRING("<>");
	    }
	    if (sender && EQ(sender, "+")) {
		/* special smail-internal <+> was given */
		sender = COPY_STRING("<+>");
	    }
	    if (decode_mail_options(rest, out) < 0) {
		xfree(sender);
		sender = NULL;
		break;
	    }
	    if (out) {
		fprintf(out, "250 %s Sender Okay.\r\n", sender);
		fflush(out);
	    }
	    break;

	case RCPT_CMD:
#ifdef HAVE_LIBWRAP
	    if (smtp_session_denied) {
		if (out) {
		    send_session_denied_reply(out, "DEBUG", data);
		}
		break;
	    }
#endif
	    if (*data != '<' || !(p = strchr(data, '>'))) {
		illegal_operand_warning("RCPT TO", data);
	    } else if (p && *(p+1) && !isspace(*(p+1))) {
		illegal_operand_warning("RCPT TO", data);
	    }
	    orig_data = COPY_STRING(data);
	    strip_rfc822_comments(data);
	    if (strcmp(data, orig_data) != 0) {
		illegal_operand_warning("RCPT TO", orig_data);
	    }
	    strip_rfc822_whitespace(data);
	    if (data[0] == '\0') {
		non_compliant_reply(out, 501, "'RCPT TO:' requires forward-path address as operand.");
		break;
	    }
	    /* do this after the above checks just to catch gross errors earlier */
	    if (!sender) {
		non_compliant_reply(out, 503, "'RCPT TO:' must be preceded by MAIL FROM: command.");
		break;
	    }
	    if (out) {
		if (sender_host_addr && smtp_recipient_no_verify &&
		    match_ip(sender_host_addr, smtp_recipient_no_verify)) {
		    fprintf(out, "250 '%s' %s%s%s Recipient Unverified.\r\n", orig_data,
			    (*data == '<') ? "" : "<", data, (*data == '<') ? "" : "<");
		    fflush(out);
		    /* fall through to add address to recipient list */
		} else if (! verify_addr(data, out, 1)) { /* check relay rules */
		    /* NOTE: error reply is printed by verify_addr() */
		    break;
		}
		if (smtp_max_recipients && (num_smtp_recipients++ >= smtp_max_recipients)) {
		    fprintf(out, "452 Too many recipients.  Administrative limit exceeded.\r\n");
		    fflush(out);
		    break;
		}
	    }
	    /*
	     * create a new address for this recipient and add to the recipients list
	     */
	    cur = alloc_addr();
	    /*
	     * surround in angle brackets, if the addr begins with `-'.
	     * This will avoid ambiguities in the data dumped to the spool
	     * file.
	     */
	    if (data[0] == '-') {
		cur->in_addr = xprintf("<%s>", data);
	    } else {
		cur->in_addr = COPY_STRING(data);
	    }
	    cur->succ = recipients;
	    recipients = cur;
	    break;

	case DATA_CMD:
#ifdef HAVE_LIBWRAP
	    if (smtp_session_denied) {
		if (out) {
		    send_session_denied_reply(out, "DEBUG", data);
		}
		break;
	    }
#endif
	    if (sender == NULL) {
		if (out) {
		    non_compliant_reply(out, 503, "Need MAIL FROM: command first.");
		} 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) {
		    non_compliant_reply(out, 503, "DATA command must be preceded by 'RCPT TO:' command.");
		} else {
		    /* sink the message for the sake of further batched cmds */
		    if (spool_fn) {
			close_spool();
		    }
		    swallow_smtp(in);
		}
		break;
	    }
	    if (out) {
		fprintf(out, "354 Enter mail, end with \".\" on a line by itself...\r\n");
		fflush(out);
		alarm(0);
	    }

	    /*
	     * if we had the previous spool file opened, close it
	     * before creating a new one
	     */
	    if (spool_fn) {
		close_spool();
	    }
	    if (out) {
		/*
		 * if we are not interactive and cannot send back failure
		 * messages, always try to accept the complete message.
		 */
		smtp_input_signals();
		alarm(smtp_receive_message_timeout);
	    }
	    smtp_remove_on_timeout = 1;
	    if (queue_message(in, SMTP_DOTS, recipients, &errstr) == FAIL) {
		exitvalue = EX_IOERR;
		log_spool_errors();
		if (out) {
		    fprintf(out, "451 Failed to queue message: %s: %s\r\n",
			    errstr, strerror(errno));
		    fflush(out);
		} else if (errfile) {
		    /* NOTE: This can happen if the parent goes away without sending anything */
		    /* XXX why not write_log()???? */
		    fprintf(errfile, "Failed to queue message: %s: %s\r\n",
			    errstr, errno == 0 ? "(no input?)" : strerror(errno));
		}
		unlink_spool();
		reset_state();
		smtp_remove_on_timeout = 0;
		break;
	    }
	    smtp_processing_signals();
	    if (sender == NULL) {
		unlink_spool();
		reset_state();
		smtp_remove_on_timeout = 0;
		break;
	    }
	    if (read_message() == NULL) {
		log_spool_errors();
		unlink_spool();
		if (out) {
		    fprintf(out, "451 error in spooled message\r\n");
		    fflush(out);
		}
		reset_state();
		smtp_remove_on_timeout = 0;
		break;
	    }
	    alarm(0);
	    smtp_remove_on_timeout = 0;

	    check_grade();
	    log_incoming();
	    log_spool_errors();
	    if (out) {
		fprintf(out, "250 Mail accepted, queue ID %s.\r\n", message_id);
		fflush(out);
	    }
	    /* always allow an extra element to store the ending NULL */
	    if (file_i >= file_cnt) {
		/* we need to grow the array of spool file names */
		file_cnt += 8;
		files = (char **)xrealloc((char *)files,
					  (file_cnt + 1) * sizeof(*files));
	    }
	    files[file_i++] = xprintf("%s/input/%s", spool_dir, spool_fn);
	    reset_state();
	    break;

	case VRFY_CMD:
	    if (out) {
		strip_rfc822_comments(data);
		strip_rfc822_whitespace(data);
		verify_addr(data, out, 0);
		fflush(out);
	    }
	    break;

	case EXPN_CMD:
#ifdef HAVE_LIBWRAP
	    if (smtp_session_denied) {
		if (out) {
		    send_session_denied_reply(out, "DEBUG", data);
		}
		break;
	    }
#endif
	    if (out) {
#ifdef NO_SMTP_EXPN
		fprintf(out, "502 Command not implemented\r\n");
#else
		if (smtp_allow_expn) {
		    strip_rfc822_comments(data);
		    strip_rfc822_whitespace(data);
		    expand_addr(data, out);
		} else {
		    fprintf(out, "502 Command disabled\r\n");
		}
#endif
		fflush(out);
	    }
	    break;

	case QUIT_CMD:
	    if (out) {
		fprintf(out, "221 %s closing connection\r\n", primary_name);
		fflush(out);
	    }
	    write_log(WRITE_LOG_SYS, "[%d] remote got QUIT from %s%s%s%s%s%s%s%s%s.",
		      getpid(),
		      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();
	    files[file_i++] = NULL;
	    errfile = save_errfile;
	    debug = save_debug;
	    return files;

	case RSET_CMD:
	    reset_state();
	    if (out) {
		fprintf(out, "250 Reset state\r\n");
		fflush(out);
	    }
	    break;

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

	case DEBUG_CMD: {
	    int temp = 0;		/* new debug level */

#ifdef HAVE_LIBWRAP
	    if (smtp_session_denied) {
		if (out) {
		    send_session_denied_reply(out, "DEBUG", data);
		}
		break;
	    }
#endif
	    if (out) {
		if (smtp_allow_debug) {
		    strip_rfc822_comments(data);
		    strip_rfc822_whitespace(data);
		    if (*data) {
			char *errbuf = NULL;

			temp = c_atol(data, &errbuf);
			if (errbuf) {
			    fprintf(out, "500 bad number: %s\r\n", errbuf);
			    fflush(out);
			    break;
			}
		    } else {
			temp = 1;
		    }
		    if (temp == 0) {
			fprintf(out, "250 Debugging disabled\r\n");
		    } else {
			DEBUG(DBG_REMOTE_LO, "debugging output grabbed by SMTP\r\n");
			fprintf(out, "250 Debugging level: %d\r\n", temp);
		    }
		    fflush(out);
		    debug = temp;
		    errfile = out;
		    break;
		}
		fprintf(out, "500 I hear you knocking, but you can't come in\r\n");
		fflush(out);
	    }
	    write_log(WRITE_LOG_SYS, "[%d] remote DEBUG: '%s' debugging at level %d %s to %s%s%s%s%s%s%s%s%s",
		      getpid(),
		      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;
	}
	case HELP_CMD:
	    write_log(WRITE_LOG_SYS, "[%d] remote HELP: '%s' requested by %s%s%s%s%s%s%s%s%s",
		      getpid(),
		      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 %s Lost input channel\r\n", primary_name);
		fflush(out);
	    }
	    write_log(WRITE_LOG_SYS, "[%d] lost connection unexpectedly from %s%s%s%s%s%s%s%s%s.",
		      getpid(),
		      ident_sender ? ident_sender : "",
		      ident_sender ? "@" : "",
		      sender_host ? sender_host : "",
		      sender_host_really ? "(" : "",
		      sender_host_really ? sender_host_really : "",
		      sender_host_really ? ")" : "",
		      sender_host_addr ? " [" : "",
		      sender_host_addr ? sender_host_addr : "",
		      sender_host_addr ? "]" : "");
	    files[file_i++] = NULL;
	    errfile = save_errfile;
	    debug = save_debug;
	    return files;

	case OTHER_CMD: {
	    static int logged_unknown = 0;

	    if (out) {
		fprintf(out, "500 Command unrecognized\r\n");
		fflush(out);
	    }
	    if (!logged_unknown) {
		write_log(WRITE_LOG_SYS, "[%d] remote %s: uknown command from %s%s%s%s%s%s%s%s%s.",
			  getpid(),
			  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++;		/* keep out here so debugger can see how many */
	    break;
	}
	default:
	    non_compliant_reply(out, 521, "Internal error. Connection closing now...");
	    panic(EX_SOFTWARE, "receive_smtp(): read_smtp_command() gave impossible result from parsing: %s.", data);
	    /* NOTREACHED */
	}
	if (orig_data) {
	    xfree(orig_data);
	    orig_data = NULL;
	}
    }

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

#ifdef HAVE_LIBWRAP
static void
send_session_denied_reply(out, cmd, param)
    FILE *out;
    char *cmd;
    char *param;
{
    fprintf(out, "550-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");
    fprintf(out, "550 If you see this message then your mailer is violating the SMTP protocol.\r\n");
    fflush(out);
    write_log(WRITE_LOG_SYS, "[%d] remote %s:%s%s%s refusing SMTP connection from %s%s%s%s%s%s%s%s%s",
	      getpid(), 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;
}
#endif /* HAVE_LIBWRAP */

static void
non_compliant_reply(out, ecode, errtxt)
    FILE *out;
    int ecode;
    char *errtxt;
{
#ifdef LOG_SMTP_NON_COMPLIANCE
    write_log(WRITE_LOG_SYS, "[%d] sent %d: '%s' to %s%s%s%s%s%s%s%s%s.",
	      getpid(),
	      ecode,
	      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\r\n%d-\r\n", ecode, errtxt, ecode);
    fprintf(out, "%d-If you are seeing this message in a bounce, or in an alert box\r\n", ecode);
    fprintf(out, "%d-from your mailer client, etc., then your mailer software is\r\n", ecode);
    fprintf(out, "%d-hopelessly non-compliant and incompatible with SMTP (RFC 821 et al).\r\n", ecode);
    fprintf(out, "%d Please report this bug to those responsible for your mailer software.\r\n", ecode);
    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, "[%d] remote %s: '%s' operand not strictly legal from %s%s%s%s%s%s%s%s%s.",
	      getpid(),
	      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;
    }
}

static enum e_smtp_commands
read_smtp_command(fp, out)
    register FILE *fp;			/* SMTP command 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_CLEAR(&input);
	STR_NEXT(&input, '\0');
	STR_PREV(&input);
    }
    for (;;) {
	if (!flushed_p && IOB_MAYBE_EMPTY_P(fp)) {
	    ++flushed_p;
	    fflush(out);
	}
	c = getc(fp);
	if (c == '\r') {
	    continue;
	}
	if (c == EOF || c == '\n') {
	    break;
	}
	STR_NEXT(&input, c);
	STR_NEXT(&input, '\0');		/* keep it NUL terminated */
	STR_PREV(&input);
    }
    /* return end of file pseudo command if end of file encountered */
    if (c == EOF) {
	DEBUG(DBG_REMOTE_MID, "read_smtp_command() returning EOF_CMD.\n");
	return EOF_CMD;
    }
    /* NOTE: don't use STR_DONE() since we re-use this buffer */
    DEBUG1(DBG_REMOTE_MID, "read_smtp_command() got '%s'.\n", STR(&input));

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

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

	    /* point "data" at any operand (skip past command & whitespace) */
	    for (p = cp->name; *data && *p; data++, p++) {
		;
	    }
	    for (; *data && isspace(*data); data++) {
		;
	    }
	    DEBUG2(DBG_REMOTE_MID, "read_smtp_command() returning '%s' with operand '%s'.\n", cp->name, data);
	    return cp->cmd;
	}
    }

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

    return OTHER_CMD;
}

static int
decode_mail_options(rest, out)
    char *rest;				/* remainder of "MAIL FROM:" operand */
    FILE *out;				/* SMTP out channel */
{
    long body_size = -1;

    while (rest && *rest) {
	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, "'MAIL FROM:<...> SIZE=?' missing parameter.");
		return -1;
	    }
	    ++rest;
	    restlen = 0;
	    body_size = 0;
	    /* find the end of the SIZE parameter */
	    while (isdigit(*(rest+restlen))) {
		++restlen;
	    }
	    ch = *(rest+restlen);
	    *(rest+restlen) = '\0';	/* NUL terminate # */
	    body_size = c_atol(rest, &errbuf);
	    *(rest+restlen) = ch;	/* restore */
	    if (errbuf) {
		if (out) {
		    fprintf(out, "501-bad number: %s\r\n", errbuf);
		    non_compliant_reply(out, 501, "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, "'MAIL FROM:' malformed SIZE parameter.");
		return -1;
	    }
	    if (accepted_msg_size > 0 && body_size > accepted_msg_size) {
		if (out) {
#ifdef HAVE_DF_SPOOL
		    /* XXX this might not be 100% right if short of spool space! */
		    fprintf(out, "552-A message of %ld bytes is far too large!\r\n", (long) body_size);
#else
		    fprintf(out, "552-A message of %ld bytes is far too large!\r\n", (long) body_size);
#endif
		    fprintf(out, "552-If you are sending a MIME message please set your mailer so that it\r\n");
		    fprintf(out, "552-splits messages larger than 64KB into muliple parts.\r\n");
		    fprintf(out, "552 Otherwise please learn to transfer files with a file transfer protocol.\r\n");
		    fflush(out);
		}
		return -1;
	    }
	} else if (strncmpic(rest, "BODY", restlen) == 0) {
	    rest += restlen;
	    if (*rest != '=') {
		non_compliant_reply(out, 501, "'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, "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 */
    char *errstr;			/* hold error message */

# ifndef NO_LOG_SMTP_EXPN
    write_log(WRITE_LOG_SYS, "[%d] remote EXPN: '%s' by %s%s%s%s%s%s%s%s%s.",
	      getpid(),
	      in_addr,
	      ident_sender ? ident_sender : "",
	      ident_sender ? "@" : "",
	      sender_host ? sender_host : "",
	      sender_host_really ? "(" : "",
	      sender_host_really ? sender_host_really : "",
	      sender_host_really ? ")" : "",
	      sender_host_addr ? " [" : "",
	      sender_host_addr ? sender_host_addr : "",
	      sender_host_addr ? "]" : "");
# endif
    addr->in_addr = in_addr;		/* setup the input addr structure */
    /* build the mungeable addr string */
    addr->work_addr = preparse_address(in_addr, &errstr);
    if (addr->work_addr == NULL) {
	fprintf(out, "501 %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, FALSE);
    if (okay) {
	addr = okay;
	okay = NULL;
	check_smtp_remote_allow(addr, &okay, &defer, &fail);
    }
    if (okay) {
	register struct addr *cur;	/* current addr to display */

	/* display the complete list of resolved addresses */
	for (cur = okay; cur->succ; cur = cur->succ) {
	    fprintf(out, "250-%s\r\n", cur->in_addr);
	}
	/* the last one should not begin with 250- */
	fprintf(out, "250 %s\r\n", cur->in_addr);
    } else {
	/* just say we couldn't find it */
	fprintf(out, "550 %s Address is not deliverable.\r\n", in_addr);
    }
    fflush(out);
}
#endif	/* !NO_SMTP_EXPN */

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

    vaddr = alloc_addr();
    vaddr->in_addr = COPY_STRING(raw_sender);
    vaddr->work_addr = COPY_STRING(pp_sender);
    form = parse_address(vaddr->work_addr, &vaddr->target,
			 &vaddr->remainder, &vaddr->parseflags);
    if (*vaddr->work_addr && (form == FAIL || form == PARSE_ERROR)) {
	fprintf(out, "501 '%s' sender address parse error: %s.\r\n",
		raw_sender,
		(vaddr->remainder) ? vaddr->remainder : "<unknown error>");
	fflush(out);
	xfree(vaddr->in_addr);
	xfree(vaddr->work_addr);
	xfree(vaddr);
	return 0;
    }
    if (*vaddr->work_addr && !EQ(vaddr->work_addr, "+") && form == LOCAL) {
	if (out) {
	    fprintf(out, "550-'%s' sender address cannot be an unqualified address\r\n", pp_sender);
	    fprintf(out, "550 (i.e. it must have a domain portion).\r\n");
	    fflush(out);
	}
	write_log(WRITE_LOG_SYS, "[%d] remote MAIL FROM: '%s' is a local address; by %s%s%s%s%s%s%s%s%s.",
		  getpid(),
		  pp_sender,
		  ident_sender ? ident_sender : "",
		  ident_sender ? "@" : "",
		  sender_host ? sender_host : "",
		  sender_host_really ? "(" : "",
		  sender_host_really ? sender_host_really : "",
		  sender_host_really ? ")" : "",
		  sender_host_addr ? " [" : "",
		  sender_host_addr ? sender_host_addr : "",
		  sender_host_addr ? "]" : "");
	xfree(vaddr->in_addr);
	xfree(vaddr->work_addr);
	xfree(vaddr);
	return 0;
    }
    if (*vaddr->work_addr && !EQ(vaddr->work_addr, "+") && (form == BERKENET || form == DECNET)) {
	/* XXX should be impossible unless USE_DECNET or USE_BERKENET are defined */
	if (out) {
	    fprintf(out, "553 '%s' Sender address cannot be a %s form address.\r\n",
		    pp_sender,
		    (form == BERKENET) ? "BERKENET" :
		    (form == DECNET) ? "DECNET" : "<bad-form!>");
	    fflush(out);
	}
	write_log(WRITE_LOG_SYS, "[%d] remote MAIL FROM: '%s' is a %s(%d) address; by %s%s%s%s%s%s%s%s%s.",
		  getpid(),
		  pp_sender,
		  (form == BERKENET) ? "BERKENET" :
		  (form == DECNET) ? "DECNET" : "<bad-form!>",
		  form,
		  ident_sender ? ident_sender : "",
		  ident_sender ? "@" : "",
		  sender_host ? sender_host : "",
		  sender_host_really ? "(" : "",
		  sender_host_really ? sender_host_really : "",
		  sender_host_really ? ")" : "",
		  sender_host_addr ? " [" : "",
		  sender_host_addr ? sender_host_addr : "",
		  sender_host_addr ? "]" : "");
	xfree(vaddr->in_addr);
	xfree(vaddr->work_addr);
	xfree(vaddr);
	return 0;
    }
    if (*vaddr->work_addr && !EQ(vaddr->work_addr, "+") && form != MAILBOX) {
	/*
	 * XXX Do we really want to allow RFC_ROUTE/RFC_ENDROUTE addresses?
	 * XXX What about other forms (eg. UUCP_ROUTE, PCT_MAILBOX)?
	 */
	write_log(WRITE_LOG_SYS, "[%d] remote MAIL FROM: '%s' (target %s) is a %s(%d) address; by %s%s%s%s%s%s%s%s%s.",
		  getpid(),
		  pp_sender,
		  vaddr->target,
		  (form == RFC_ROUTE) ? "RFC_ROUTE" :
		  (form == RFC_ENDROUTE) ? "RFC_ENDROUTE" :
		  (form == UUCP_ROUTE) ? "UUCP_ROUTE" :
		  (form == PCT_MAILBOX) ? "PCT_MAILBOX" : "<bad-form!>",
		  form,
		  ident_sender ? ident_sender : "",
		  ident_sender ? "@" : "",
		  sender_host ? sender_host : "",
		  sender_host_really ? "(" : "",
		  sender_host_really ? sender_host_really : "",
		  sender_host_really ? ")" : "",
		  sender_host_addr ? " [" : "",
		  sender_host_addr ? sender_host_addr : "",
		  sender_host_addr ? "]" : "");
    }
#ifdef HAVE_BIND
    if (*vaddr->work_addr && !EQ(vaddr->work_addr, "+") &&
	!(sender_host_addr && smtp_sender_no_verify &&
	  match_ip(sender_host_addr, smtp_sender_no_verify))) {
	int mxresult;
	long bindflgs = BIND_DOMAIN_REQUIRED | BIND_DEFER_NO_CONN | BIND_LOCAL_MX_OKAY | BIND_DONT_FILTER;
	struct rt_info rt_info;
	struct error *binderr = NULL;
	char *binderrmsg;
	static struct bindlib_private bindpriv =  BIND_TEMPLATE_ATTRIBUTES;

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

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

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

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

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

	switch (mxresult) {
	case DB_SUCCEED:
	    break;

	case DB_AGAIN:			/* DNS lookup must be deferred */
	case FILE_AGAIN:		/* lost contact with server */
	case FILE_NOMATCH:		/* There is no server available */
	    if (out) {
		fprintf(out, "450-defer all mail from '%s'.\r\n", pp_sender);
		fprintf(out, "450-Sender address target domain '%s' cannot be verified at this time.\r\n", vaddr->target);
		fprintf(out, "450-Reason given was: %s.\r\n", binderrmsg);
		fprintf(out, "450 Try again later.\r\n");
		fflush(out);
	    }
	    xfree(vaddr->in_addr);
	    xfree(vaddr->work_addr);
	    xfree(vaddr);
	    return 0;

	case DB_NOMATCH:		/* no such domain */
	    if (out) {
		fprintf(out, "550-reject sender '%s'.\r\n", pp_sender);
		fprintf(out, "550%sSender address target domain '%s' is not valid%s\r\n",
			smtp_sender_verify_mx_only ? "-" : " ",
			vaddr->target,
			smtp_sender_verify_mx_only ? " for e-mail use.\r\n550 There is no MX record for it." : ".");
		fflush(out);
	    }
	    write_log(WRITE_LOG_SYS, "[%d] remote MAIL FROM: '%s' target '%s' is not a valid domain%s; by %s%s%s%s%s%s%s%s%s.",
		      getpid(),
		      pp_sender,
		      vaddr->target,
		      smtp_sender_verify_mx_only ? " (no MX record)" : "",
		      ident_sender ? ident_sender : "",
		      ident_sender ? "@" : "",
		      sender_host ? sender_host : "",
		      sender_host_really ? "(" : "",
		      sender_host_really ? sender_host_really : "",
		      sender_host_really ? ")" : "",
		      sender_host_addr ? " [" : "",
		      sender_host_addr ? sender_host_addr : "",
		      sender_host_addr ? "]" : "");
	    xfree(vaddr->in_addr);
	    xfree(vaddr->work_addr);
	    xfree(vaddr);
	    return 0;

	case DB_FAIL:			/* bad DNS request? */
	case FILE_FAIL:			/* major DNS error! */
	    if (out) {
		fprintf(out, "550-reject sender '%s'.\r\n", pp_sender);
		fprintf(out, "550-Failure trying to verify address target domain '%s'.\r\n", vaddr->target);
		fprintf(out, "550 Reason given was: %s.\r\n", binderrmsg);
		fflush(out);
	    }
	    write_log(WRITE_LOG_SYS, "[%d] remote MAIL FROM: '%s' target '%s': DNS failed: %s; by %s%s%s%s%s%s%s%s%s.",
		      getpid(),
		      vaddr->in_addr,
		      vaddr->target,
		      binderrmsg,
		      ident_sender ? ident_sender : "",
		      ident_sender ? "@" : "",
		      sender_host ? sender_host : "",
		      sender_host_really ? "(" : "",
		      sender_host_really ? sender_host_really : "",
		      sender_host_really ? ")" : "",
		      sender_host_addr ? " [" : "",
		      sender_host_addr ? sender_host_addr : "",
		      sender_host_addr ? "]" : "");
	    xfree(vaddr->in_addr);
	    xfree(vaddr->work_addr);
	    xfree(vaddr);
	    return 0;

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

    /*
     * Check to see if the domain name is one that's handled locally, and if so
     * then run it through the verify logic to make sure it's OK and the sender
     * has not made a typo in their mailer's configuration.
     */
    if (*vaddr->work_addr && !EQ(vaddr->work_addr, "+")) {
	int verify_local = 0;

	if (islocalhost(vaddr->target)) {
	    verify_local = 1;
	}
#ifdef HAVE_BIND
	else {
	    long bindflgs = BIND_DOMAIN_REQUIRED | BIND_DEFER_NO_CONN;
	    struct rt_info rt_info;
	    struct error *binderr = NULL;
	    static struct bindlib_private bindpriv =  BIND_TEMPLATE_ATTRIBUTES;

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

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

	    return 0;
	}
    }

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

    return 1;
}

/*
 * verify_addr - verify an address
 *
 * redisplay the input address if it is a valid address.
 */
static int
verify_addr(in_addr, out, rcpt_p)
    char *in_addr;			/* input address string */
    FILE *out;				/* write expansion here */
    int rcpt_p;				/* non-zero if called from RCPT (1) or MAIL (2) */
{
    struct addr *addr = alloc_addr();	/* get an addr structure */
    struct addr *okay = NULL;		/* verified address */
    struct addr *defer = NULL;		/* temporarily unverifiable addr */
    struct addr *fail = NULL;		/* unverified addr */
    char *errstr;			/* hold error message */
    
    DEBUG1(DBG_REMOTE_MID, "verify_addr(%s): checking deliverability.\n", in_addr);

    addr->in_addr = in_addr;		/* setup the input addr structure */
    /* build the mungeable addr string */
    addr->work_addr = preparse_address(in_addr, &errstr);
    if (addr->work_addr == NULL) {
	if (out) {
	    fprintf(out, "501 '%s:%s' malformed address: %s.\r\n", in_addr,
		    (rcpt_p == 0) ? "VRFY" : (rcpt_p == 1) ? "RCPT TO" : (rcpt_p == 2) ? "MAIL FROM" : "input",
		    errstr);
	    fflush(out);
	}
	xfree(addr);
	return 0;
    }

    /* cache directors and routers on the assumption we will need them again */
    if (! queue_only) {
	if (! cached_directors) {
	    cache_directors();
	}
	if (! cached_routers) {
	    cache_routers();
	}
    }
    /*
     * This is really nasty (as in inefficient), but it's the only way to
     * determine the transport driver name.  "okay" may be a list of addresses
     * if an alias was expanded, but the parent will be the original we submit.
     */
    resolve_addr_list(addr, &okay, &defer, &fail, FALSE);
    if (okay) {
	addr = okay;
	okay = NULL;
	check_smtp_remote_allow(addr, &okay, &defer, &fail);
    }
    /* The result should be on one, and only one, of the output lists.... */
    if (okay) {
	int form = okay->flags & ADDR_FORM_MASK;

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

	case BERKENET:
	case DECNET:
	    if (out) {
		fprintf(out, "553 reject %s '%s'.  Address cannot be a %s form address.\r\n",
			(rcpt_p == 0) ? "verifying address" :
			(rcpt_p == 1) ? "sending to address" :
			(rcpt_p == 2) ? "all mail from sender" : "address",
			in_addr,
			(form == BERKENET) ? "BERKENET" :
			(form == DECNET) ? "DECNET" : "<bad-form!>");
		fflush(out);
	    }
	    write_log(WRITE_LOG_SYS, "[%d] remote %s: '%s' is a %s(%d) address; by %s%s%s%s%s%s%s%s%s.",
		      getpid(),
		      (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(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)) {
		DEBUG2(smtp_remote_allow ? DBG_ADDR_HI : DBG_ADDR_LO,
		       "permitting route-form address by [%s] to %s.\n",
		       sender_host_addr ? sender_host_addr : "UNKNOWN-IP", in_addr);
	    } else {
		if (out) {
		    fprintf(out, "552 reject %s '%s'.  That addressing form is not permitted.\r\n",
			    (rcpt_p == 0) ? "verifying address" :
			    (rcpt_p == 1) ? "sending to address" :
			    (rcpt_p == 2) ? "all mail from sender" : "address",
			    in_addr);
		    fflush(out);
		}
		write_log(WRITE_LOG_SYS, "[%d] remote %s: %s%s%s%s%s%s%s%s%s: '%s' addressing form %s(%d) not permitted: routed to <%s%s%s>",
			  getpid(),
			  (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(addr);

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

 	return 1;

    } else if (defer) {
	if (out) {
	    fprintf(out, "450-defer %s '%s'.\r\n",
		    in_addr,
		    (rcpt_p == 0) ? "verifying address" :
		    (rcpt_p == 1) ? "sending to address" :
		    (rcpt_p == 2) ? "all mail from sender" : "address");
	    fprintf(out, "450-Cannot verify '<%s%s%s>' at this time.\r\n",
		    defer->work_addr,
		    (defer->target) ? "@" : "",
		    (defer->target) ? defer->target : "");
	    fprintf(out, "450-Reason given was: (ERR_%03ld) %s.\r\n",
		    defer->error->info & ERR_MASK,
		    defer->error->message);
	    fprintf(out, "450 Try again later.\r\n");
	    fflush(out);
	}
	/* XXX this may be very noisy if the DNS gets broken.... */
	write_log(WRITE_LOG_SYS, "[%d] 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",
		  getpid(),
		  (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(addr);

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

    } else if (fail) {
	int error_code;			/* reply code for negative result */

	switch (fail->error->info & ERR_MASK) {
	case ERR_111:			/* address parse error */
	    error_code = 501;		/* syntax error in parameter */
	    break;
	case ERR_107:			/* director driver not found */
	    error_code = 451;		/* local error in processing */
	    break;
	case ERR_109:			/* router driver not found */
	    error_code = 453;		/* local error in processing (XXX 453 not in RFC821#4.3) */
	    break;
	case ERR_163:			/* lost connetion to BIND server */
	    error_code = 454;		/* local error in processing (XXX 454 not in RFC821#4.3) */
	    break;
	case ERR_165:			/* BIND server packet format error */
	    error_code = 455;		/* local error in processing (XXX 455 not in RFC821#4.3) */
	    break;
	case ERR_100:			/* unknown user */
	    error_code = 550;		/* mailbox not found */
	    break;
	case ERR_101:			/* unknown host */
	    error_code = 551;		/* mailbox not local */
	    break;
	case ERR_104:			/* security violation */
	    error_code = 550;		/* mailbox not local -- relay not permitted */
	    break;
	case ERR_168:			/* no valid MX records for host */
	    error_code = 551;		/* mailbox not local */
	    break;
					/* 558 used below */
	default:
	    error_code = 559;		/* flags un-xlated errors (XXX 559 not in RFC821#4.3) */
	    break;
	}
	if (out) {
	    fprintf(out, "%d-%s %s '%s'.\r\n",
		    error_code,
		    (error_code >= 500) ? "reject" : "defer",
		    (rcpt_p == 0) ? "verifying address" :
		    (rcpt_p == 1) ? "sending to address" :
		    (rcpt_p == 2) ? "all mail from sender" : "address",
		    in_addr);
	    fprintf(out, "%d-The address <%s%s%s> was not accepted.\r\n",
		    error_code,
		    fail->work_addr,
		    fail->target ? "@" : "",
		    fail->target ? fail->target : "");
	    fprintf(out, "%d-Reason given was: (ERR_%03ld) %s.\r\n",
		    error_code,
		    fail->error->info & ERR_MASK,
		    fail->error->message);
	    fprintf(out, "%d %s\r\n",
		    error_code,
		    (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, "[%d] remote %s: %s%s%s%s%s%s%s%s%s: '%s' <%s%s%s> recipient for sender '%s' not deliverable: (ERR_%03ld) %s",
		  getpid(),
		  (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(addr);

 	return 0;
    }
    /* hmmm, it should have been in one of the lists from resolve_addr_list()! */
    if (out) {
	fprintf(out, "521-reject '%s'.\r\n", in_addr);
	fprintf(out, "521 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, "[%d] remote %s: %s%s%s%s%s%s: '%s' <%s%s%s> recipient for sender '%s' caused resolve_addr_list() to fail!",
	  getpid(),
	  (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, shpmsg, shpherr, errorp, fatalp)
    char *hostnm;
    struct sockaddr_in *saddrp;
    struct hostent *shp;		/* result of gethostbyaddr() in caller */
    char const *shpmsg;			/* from 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);
    }
    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 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 without PTRs";
	    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 '.'";
	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;
	}
    }

    /* 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 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("DNS failure trying to verify host '%s' (%s)", hostnm, binderrmsg);
	/* XXX it would be nice to return a temporary error here, but that would mean a re-design */
	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;
	}
    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 when verifying host '%s' (%s)", hostnm, binderrmsg);
	if (smtp_hello_verify && !allowed_to_be_broken) {
	    *fatalp = 1;
	    return NULL;
	}
	break;
    }

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

	if (strncmpic(hostnm, shp->h_name, hlen) != 0) {
	    found = 0;
	    for (i = 0; (p = (shp->h_aliases)[i]); i++) {
		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) PTR does not have matching name.\n", hostnm, inet_ntoa(saddrp->sin_addr));
		*errorp = "Remote address PTR does not have a matching name";
		if (smtp_hello_verify_ptr && !allowed_to_be_broken) {
		    *fatalp = 1;
		    return NULL;	/* broken DNS or mailer config; or forger */
		}
	    }
	}
    } else {
	*errorp = xprintf("Remote address PTR lookup failed (%s)", shpmsg);
	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.
 */
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 */
{
#ifdef HAVE_BIND
    int mxresult;
    int local_precedence;
#endif

    if (in->parent) {			/* any with a parent must be local */
	DEBUG1(DBG_ADDR_HI, "check_smtp_remote_allow(%s): OK: address has local parent.\n",
	       in->parent->in_addr);
	in->succ = *out;
	*out = in;

	return;
    }
    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);
	in->succ = *out;
	*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);
	in->succ = *out;
	*out = in;

	return;
    }
    /*
     * Assume if we get this far then the address is remote and will be routed
     * back out via SMTP, so check to see if sender is allowed to relay through
     * us.  We do this first to avoid unnecessary DNS lookups should the sender
     * in fact be listed in smtp_remote_allow.
     */
    if (sender_host_addr && smtp_remote_allow && match_ip(sender_host_addr, smtp_remote_allow)) {
	DEBUG2(smtp_remote_allow ? DBG_ADDR_HI : DBG_ADDR_LO,
	       "permitting remote relay by [%s] to %s.\n",
	       sender_host_addr ? sender_host_addr : "UNKNOWN-IP", in->in_addr);
	in->succ = *out;
	*out = in;

	return;
    }

#ifndef HAVE_BIND
    /* XXX for now just don't allow any remote relay */
    illegal_relay_error(in, sender_host_addr);
    in->succ = *fail;
    *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);
		in->succ = *fail;
		*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);
		in->succ = *out;
		*out = in;
	    }
	} else {
	    illegal_relay_error(in, sender_host_addr);
	    in->succ = *fail;
	    *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);
	}
	in->succ = *defer;
	*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);
	}	
	in->succ = *fail;
	*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);
	}
	in->succ = *fail;
	*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);
	}
	in->succ = *fail;
	*fail = in;
	break;
    }
#endif /* HAVE_BIND */

    return;
}

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

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

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

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

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

    (void) signal(sig, SIG_IGN);
    /*
     * out_file should always be non-NULL because we never set this signal
     * handler unless out was not NULL either, but it's best to check
     * anyway....
     */
    if (out_file) {
	/* we don't really care about stdio reentrancy -- we're about to exit! */
	fprintf(out_file, "421 %s SMTP command timeout, closing channel\r\n", primary_name);
    }
    write_log(WRITE_LOG_SYS, "[%d] SMTP connection timeout while talking with %s%s%s%s%s%s%s%s%s.",
	      getpid(),
	      ident_sender ? ident_sender : "",
	      ident_sender ? "@" : "",
	      sender_host ? sender_host : "",
	      sender_host_really ? "(" : "",
	      sender_host_really ? sender_host_really : "",
	      sender_host_really ? ")" : "",
	      sender_host_addr ? " [" : "",
	      sender_host_addr ? sender_host_addr : "",
	      sender_host_addr ? "]" : "");
    if (smtp_remove_on_timeout) {
	unlink_spool();
    }
    exit(EX_TEMPFAIL);
    /* NOTREACHED */
}

/*
 * smtp_sig_unlink - unlink spool file and fast exit.
 *
 * This is useful for handling signals to abort reading a message in
 * with SMTP.
 */
static void
smtp_sig_unlink(sig)
    int sig;
{
    DEBUG1(DBG_REMOTE_LO, "smtp_sig_unlink(%d) called....\n", sig);

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

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

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

static void
illegal_relay_error(in, remote_addr)
    struct addr *in;
    char *remote_addr;
{
    char *error_text;

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