/*
#ident	"@(#)smail/src:RELEASE-3_2_0_103:smtprecv.c,v 1.77 1998/10/08 06:13:33 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 "main.h"
#include "smail.h"
#include "addr.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_RFC1413
/* Include the declarations for ident protocol lookups */
#include <ident.h>
#endif /* HAVE_RFC1413 */

#ifdef HAVE_LIBWRAP
extern int smtp_session_denied;		/* from modes.c */
#endif

/* Declare the ident variables - even if HAVE_RFC1413 is not defined, so that configs
 * can be kept consistant
 */
char *ident_sender = NULL;		/* The calculated identity of the sender */
char *ident_method = NULL;		/* Method used to get identity */

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

/* 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__
static void reset_state(void);
static enum e_smtp_commands read_smtp_command(FILE *, 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 *, char const **);
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
#else /* not __STDC__ */
static void reset_state();
static enum e_smtp_commands read_smtp_command();
static void expand_addr();
static int verify_sender();
static int verify_addr();
static char *verify_host();
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
#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",
    "250-   VRFY <address>                  - verify deliverability of address",
#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",
    "250-   DEBUG [level]                   - set debugging level, default 1",
    "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."
};

typedef enum {
    BF_unspecified,
    BF_7bit,
    BF_8bitmime
}
BodyFormat;

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

#define BS_UNSPECIFIED -1L
typedef long BodySize;

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


/*
 * 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 */
{
    char *error;			/* temp to hold error messages */
    struct addr *cur;
    static char **files = NULL;
    static int file_cnt = 7;		/* initially put 7 parts in array */
    int file_i = 0;			/* index starts at the beginning */
    /* 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;
    int temp, i, c;
    char *rest;
    int ehlo_p = 0;
    long accepted_msg_size = max_message_size;
#ifdef HAVE_BSD_NETWORKING
    struct sockaddr_in from_sa;
    int from_sa_len = sizeof(from_sa);
    struct hostent *shp = NULL;		/* result of gethostbyaddr() */

# ifndef OBSOLETE_RESOLVER
    extern int h_errno;			/* import h_errno; many systems don't define it in <netdb.h> */
# endif
#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;
    smtp_processing_signals();
    if (out) {
	(void) signal(SIGALRM, smtp_receive_timeout_sig);
    }

    /* 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", index(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 &&
	(getpeername(fileno(in), (struct sockaddr *) &from_sa, &from_sa_len) == 0) &&
	(from_sa.sin_family == AF_INET)) {
	char *p;
	struct sockaddr_in my_sa;
	int my_sa_len = sizeof(my_sa);

	/* INET_NTOA_USE_STRUCT not needed here, I think */

	/* XXX if this ever got called twice for the same process it would leak */
	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.sin_family == AF_INET)) {
	    char *p;

	    /* XXX if this ever got called twice for the same process it would leak */
	    p = inet_ntoa(inet_makeaddr(inet_netof(my_sa.sin_addr), 0));
	    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;
	}
    } else {
	DEBUG1(DBG_REMOTE_LO, "getpeername(): %s.\n", out ? strerror(errno) : "<no response chan>");
	bzero((char *) &from_sa, sizeof(from_sa));
	sender_host_addr = NULL;
	smtp_local_net = NULL;
    }
    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), AF_INET))) {
	    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], 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 protocol used - this used to be in the HELO code, but that gives
     * problems if the sender never says HELO 
     */
    if (sender_proto == NULL) {
        sender_proto = (out? "smtp": "bsmtp");
    }

#ifdef HAVE_RFC1413
    /* 
     * 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, "remote connection 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 ? "]" : "");
    }
    while (! term_signal || out == NULL) {
	char *orig_data = NULL;

	if (out) {
	    alarm(smtp_receive_command_timeout);
	}
	switch (read_smtp_command(in, out)) {
	case EHLO_CMD:
	    ehlo_p = 1;
	    sender_proto = (out? "esmtp": "ebsmtp");
	    /* FALLTHRU */
	case HELO_CMD:
#ifdef HAVE_LIBWRAP
	    if (out && smtp_session_denied) {
		fprintf(out, "550 You are not permitted to send mail\r\n");
		fflush(out);
		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_addr ? "[" : "",
			  sender_host_addr ? sender_host_addr : "",
			  sender_host_addr ? "]" : "");
		break;
	    }
#endif
	    orig_data = COPY_STRING(data);
	    strip_rfc822_comments(data); /* XXX not required by RFC 821 */
	    if (out) {
		if (!data[0]) {
		    fprintf(out, "501 %s requires a host name or domain literal as operand\r\n",
			    ehlo_p ? "EHLO" : "HELO");
		    fflush(out);
		    break;
		}
	    }
	    if (sender_host) {
		xfree(sender_host);
		sender_host = NULL;
	    }
	    if (out) {
#ifdef HAVE_BSD_NETWORKING
		const char *error = NULL;

		if (sender_host_addr && !shp) {
		    /* we've been around once before and need to reset... */
		    if (!(shp = gethostbyaddr((char *) &(from_sa.sin_addr),
					      sizeof(from_sa.sin_addr), AF_INET))) {
			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);
		    }
		}
		if (! (sender_host = verify_host(data, &from_sa, shp, shp ? NULL : hstrerror(h_errno), &error))) {
		    /* XXX how many MTAs will break if this is multi-lined??? */
		    fprintf(out, "501 %s requires a valid host name as operand: '%s' rejected from %s%s%s%s%s%s%s%s.\r\n",
			    ehlo_p ? "EHLO" : "HELO", data,
			    ident_sender ? ident_sender : "",
			    ident_sender ? "@" : "",
			    sender_host_really ? sender_host_really : "",
			    sender_host_addr ? " remote address [" : "",
			    sender_host_addr ? sender_host_addr : "",
			    sender_host_addr ? "]" : "",
			    error ? ": " : "", error ? error : "");
		    fflush(out);
		}
		if (!sender_host || error) { /* 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 ? "questionable" : "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 ? "]" : "",
			      error ? ": " : "",
			      error ? error : "");
		}
		shp = NULL;		/* make sure are reset if needed again */
		if (!sender_host) {
		    break;		/* broken DNS or forger */
		}
#else
		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's what we support:" : ".");
		if (ehlo_p) {
#ifndef NO_SMTP_EXPN
		    fprintf(out, "250-EXPN\r\n");
#endif
#ifdef HAVE_DF_SPOOL
		    accepted_msg_size = compute_max_message_size_from_df_spool();
		    if (accepted_msg_size == -1 
			|| (max_message_size && max_message_size < accepted_msg_size))
			accepted_msg_size = max_message_size;
#endif
		    if (accepted_msg_size && accepted_msg_size != -1) {
			fprintf(out, "250-SIZE %ld\r\n", (long) accepted_msg_size);
		    } else {
			fprintf(out, "250-SIZE\r\n");
		    }
#ifdef HAVE_ESMTP_8BITMIME
		    fprintf(out, "250-8BITMIME\r\n");
#endif
#ifdef HAVE_ESMTP_PIPELINING
		    fprintf(out, "250-PIPELINING\r\n");
#endif
		    fprintf(out, "250 HELP\r\n");
		}
		(void) fflush(out);
	    }
	    reset_state();
	    break;

	case MAIL_CMD:
#ifdef HAVE_LIBWRAP
	    if (out && smtp_session_denied) {
		fprintf(out, "550 You%s%s%s are not permitted to send mail%s%s%s.\r\n",
			sender_host_really ? " (" : "",
			sender_host_really ? sender_host_really : "",
			sender_host_really ? ")" : "",
			sender_host_addr ? "from [" : "",
			sender_host_addr ? sender_host_addr : "",
			sender_host_addr ? "]" : "");
		fflush(out);
		write_log(WRITE_LOG_SYS, "remote MAIL FROM: '%s' refusing SMTP connection 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 ? "]" : "");
		break;
	    }
#endif
	    orig_data = COPY_STRING(data);
	    strip_rfc822_comments(data);
	    if (out && data[0] == '\0') {
		fprintf(out, "501 MAIL FROM requires return address as operand.\r\n");
		fflush(out);
		break;
	    }
	    if (out && !sender_host) {
		fprintf(out, "503 MAIL FROM must be preceded by HELO/EHLO command.\r\n");
		fflush(out);
		break;
	    }
	    if (sender) {
		if (out) {
		    fprintf(out, "503 Sender already specified\r\n");
		    fflush(out);
		}
		break;
	    }
	    sender = preparse_address_1(data, &error, &rest);
	    if (out && !sender) {
		fprintf(out, "501 <%s> ... %s\r\n", data, error);
		fflush(out);
		break;
 	    }
	    if (out && !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("<+>");
	    }
	    {				/* XXX this should be a sub-function too! */
		char * format = 0;
		int format_length = 0;
#ifdef HAVE_ESMTP_8BITMIME
		BodyFormat body_format = BF_unspecified;
#endif
		BodySize body_size = BS_UNSPECIFIED;

		while (rest && *rest != '\0') {
		    /* maybe we have an extended MAIL command */
		    while (*rest != '\0' && isspace (*rest)) {
			++rest;
		    }
		    {
			int restlen = 0;
			while (*(rest+restlen) != 0
			       && !isspace (*(rest+restlen))
			       && *(rest+restlen) != '=') {
			    ++restlen;
			}
			if (strncmpic(rest, "SIZE", restlen) == 0) {
			    rest += restlen;
			    if (*rest != '=') {
				if (out) {
				    *(rest+restlen) = '\0';
				    fprintf(out, "555 missing SIZE parameter (saw %s)\r\n",
					    rest);
				}
				goto fail_mail_cmd;
			    }
			    ++rest;
			    restlen = 0;
			    body_size = 0;
			    while (*(rest+restlen) != 0
				   && isdigit (*(rest+restlen))
				   && (!accepted_msg_size || body_size <= accepted_msg_size)) {
				body_size = 10*body_size+*(rest+restlen)-'0';
				++restlen;
			    }
			    if (accepted_msg_size && body_size > accepted_msg_size) {
				if (out) {
				    fprintf(out, "552 message too large\r\n");
				}
				goto fail_mail_cmd;
			    } else if (*(rest+restlen) != 0
				       && !isspace (*(rest+restlen))) {
				if (out) {
				    while (*(rest+restlen) != 0
					   && !isspace (*(rest+restlen))) {
					++restlen;
				    }
				    *(rest+restlen) = '\0';
				    fprintf(out, "555 malformed SIZE clause %s\r\n",
					    rest);
				}
				goto fail_mail_cmd;
			    }
			} else
#ifdef HAVE_ESMTP_8BITMIME
			if (strncmpic(rest, "BODY", restlen) == 0) {
			    rest += restlen;
			    if (*rest != '=') {
				if (out) {
				    *(rest+restlen) = '\0';
				    fprintf(out, "555 missing BODY parameter\r\n",
					    rest);
				}
				goto fail_mail_cmd;
			    }
			    ++rest;
			    restlen = 0;
			    while (*(rest+restlen) != 0
				   && !isspace (*(rest+restlen))) {
				++restlen;
			    }
			    if (strncmpic(rest, "7BIT", restlen) == 0) {
				body_format = BF_7bit;
			    } else if (strncmpic(rest, "8BITMIME", restlen) == 0) {
				body_format = BF_8bitmime;
			    } else {
				if (out) {
				    *(rest+restlen) = '\0';
				    fprintf(out, "555 unknown BODY type %s\r\n",
					    rest);
				}
				goto fail_mail_cmd;
			    }
			} else 
#endif
			{
			    if (out) {
				*(rest+restlen) = '\0';
				fprintf(out, "555 Unknown MAIL FROM: option %s\r\n", rest);
			    }
			    goto fail_mail_cmd;
			}
			rest += restlen;
		    }
		}
		if (out) {
		    if (format && *format != '\0') {
			*(format+format_length) = '\0';
			fprintf(out, "250 %s ... Sender Okay, using format %s\r\n",
				sender, format);
		    } else {
			fprintf(out, "250 %s ... Sender Okay\r\n",
				sender);
		    }
		}
	    }
	    if (out) {
		fflush(out);
	    }
	    break;

	  fail_mail_cmd:
	    if (sender) {
		xfree(sender);
		sender = NULL;
	    }
	    if (out) {
		fflush(out);
	    }
	    break;

	case RCPT_CMD:
#ifdef HAVE_LIBWRAP
	    if (out && smtp_session_denied) {
		fprintf(out, "550 You%s%s%s are not permitted to send mail%s%s%s.\r\n",
			sender_host_really ? " (" : "",
			sender_host_really ? sender_host_really : "",
			sender_host_really ? ")" : "",
			sender_host_addr ? "from [" : "",
			sender_host_addr ? sender_host_addr : "",
			sender_host_addr ? "]" : "");
		fflush(out);
		write_log(WRITE_LOG_SYS, "remote RCPT TO: '%s' refusing SMTP connection 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 ? "]" : "");
		break;
	    }
#endif
	    orig_data = COPY_STRING(data);
	    strip_rfc822_comments(data);
	    if (out && data[0] == '\0') {
		fprintf(out, "501 RCPT TO requires address as operand.\r\n");
		fflush(out);
		break;
	    }
	    if (!sender) {
		fprintf(out, "501 RCPT TO must be preceded by MAIL FROM command.\r\n");
		fflush(out);
		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:
	    if (sender == NULL) {
		if (out) {
		    fprintf(out, "503 Need MAIL FROM command first.\r\n");
		    fflush(out);
		} 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) {
		    fprintf(out, "503 DATA command must be preceded by RCPT TO command.\r\n");
		    fflush(out);
		} 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, &error) == FAIL) {
		exitvalue = EX_IOERR;
		log_spool_errors();
		if (out) {
		    fprintf(out, "451 Failed to queue message: %s: %s\r\n",
			    error, strerror(errno));
		    fflush(out);
		} else if (errfile) {
		    fprintf(errfile, "Failed to queue message: %s: %s\r\n",
			    error, 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\r\n");
		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:
#define SMTP_RPLY_252	"252 Cannot VRFY user, but will take message for this user and attempt delivery.\r\n"
	    if (out) {
#ifdef NO_SMTP_VRFY			/* XXX should be a variable ala smtp_debug */
		fprintf(out, SMTP_RPLY_252);
#else
		if (smtp_info) {
		    orig_data = COPY_STRING(data);
		    strip_rfc822_comments(data);
		    verify_addr(data, out, 0);
		} else {
		    fprintf(out, SMTP_RPLY_252);
		}
#endif
		fflush(out);
	    }
	    break;

	case EXPN_CMD:
#ifdef HAVE_LIBWRAP
	    if (out && smtp_session_denied) {
		fprintf(out, "550 You%s%s%s are not permitted to view lists%s%s%s.\r\n",
			sender_host_really ? " (" : "",
			sender_host_really ? sender_host_really : "",
			sender_host_really ? ")" : "",
			sender_host_addr ? "from [" : "",
			sender_host_addr ? sender_host_addr : "",
			sender_host_addr ? "]" : "");
		fflush(out);
		write_log(WRITE_LOG_SYS, "remote EXPN: '%s' refusing SMTP connection 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 ? "]" : "");
		break;
	    }
#endif
	    if (out) {
#ifdef NO_SMTP_EXPN			/* XXX should be a variable ala smtp_debug */
		fprintf(out, "502 Command not implemented\r\n");
#else
		if (smtp_info) {
		    orig_data = COPY_STRING(data);
		    strip_rfc822_comments(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);
	    }
	    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:
#ifdef HAVE_LIBWRAP
	    if (out && smtp_session_denied) {
		fprintf(out, "550 You%s%s%s are not permitted to use this command%s%s%s.\r\n",
			sender_host_really ? " (" : "",
			sender_host_really ? sender_host_really : "",
			sender_host_really ? ")" : "",
			sender_host_addr ? "from [" : "",
			sender_host_addr ? sender_host_addr : "",
			sender_host_addr ? "]" : "");
		fflush(out);
		write_log(WRITE_LOG_SYS, "remote DEBUG: '%s' refusing SMTP connection 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 ? "]" : "");
		break;
	    }
#endif
	    if (out) {
#ifndef NODEBUG
		if (smtp_debug) {
		    orig_data = COPY_STRING(data);
		    strip_rfc822_comments(data);
		    if (data[0]) {
			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");
			write_log(WRITE_LOG_SYS, "remote DEBUG: '%s' debugging output at level %d grabbed by %s%s%s%s%s%s%s%s%s",
				  data, temp, 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 ? "]" : "");
			fprintf(out, "250 Debugging level: %d\r\n", temp);
		    }
		    fflush(out);
		    debug = temp;
		    errfile = out;
		    break;
		}
#endif	/* !NODEBUG */
		write_log(WRITE_LOG_SYS, "remote DEBUG: '%s' debugging at level %d refused to %s%s%s%s%s%s%s%s%s",
			  data, temp, 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 ? "]" : "");
		fprintf(out, "500 I hear you knocking, but you can't come in\r\n");
		fflush(out);
	    }
	    break;

	case HELP_CMD:
	    if (out) {
		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 ? "]" : "");
		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);
	    }
	    files[file_i++] = NULL;
	    errfile = save_errfile;
	    debug = save_debug;
	    return files;

	default:
	    if (out) {
		fprintf(out, "500 Command unrecognized\r\n");
		fflush(out);
	    }
	    break;
	}
	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);

    files[file_i] = NULL;
    errfile = save_errfile;
    debug = save_debug;
    return files;
}

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(f, out)
    register FILE *f;			/* SMTP command stream */
    register FILE *out;			/* output, may have to be flushed */
{
    static struct str input;		/* buffer storing recent command */
    static int inited = FALSE;		/* TRUE if input initialized */
    register int c;			/* input char */
    int flushed_p = !out;

    static struct smtp_cmd_list {
	char *name;
	int len;
	enum e_smtp_commands cmd;
    } smtp_cmd_list[] = {
	{ "HELO", 	sizeof("HELO")-1,	HELO_CMD },
	{ "EHLO", 	sizeof("EHLO")-1,	EHLO_CMD },
	{ "MAIL FROM:",	sizeof("MAIL FROM:")-1,	MAIL_CMD },
	{ "RCPT TO:",	sizeof("RCPT TO:")-1,	RCPT_CMD },
	{ "DATA",	sizeof("DATA")-1,	DATA_CMD },
	{ "VRFY",	sizeof("VRFY")-1,	VRFY_CMD },
	{ "EXPN",	sizeof("EXPN")-1,	EXPN_CMD },
	{ "QUIT",	sizeof("QUIT")-1,	QUIT_CMD },
	{ "RSET",	sizeof("RSET")-1,	RSET_CMD },
	{ "NOOP",	sizeof("NOOP")-1,	NOOP_CMD },
	{ "DEBUG",	sizeof("DEBUG")-1,	DEBUG_CMD },
	{ "HELP",	sizeof("HELP")-1,	HELP_CMD },
    };
    struct smtp_cmd_list *cp;

    DEBUG(DBG_REMOTE_HI, "read_smtp_command() called.\n");
    if (! inited) {
	STR_INIT(&input);
	inited = TRUE;
    } else {
	input.i = 0;
    }
    for (;;) {
	if (!flushed_p && IOB_MAYBE_EMPTY_P (f)) {
	    ++flushed_p;
	    fflush (out);
	}
	c = getc(f);
	if (c == EOF || c == '\n') {
	    break;
	}
	STR_NEXT(&input, c);
    }
    if (input.p[input.i - 1] == '\r') {
	input.p[input.i - 1] = '\0';
    } else {
	STR_NEXT(&input, '\0');
    }

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

    DEBUG1(DBG_REMOTE_MID, "read_smtp_command() got '%s'.\n", input.p);
    for (cp = smtp_cmd_list; cp < ENDTABLE(smtp_cmd_list); cp++) {
	if (strncmpic(cp->name, input.p, cp->len) == 0) {
	    for (data = input.p + cp->len; isspace(*data); data++) ;
	    return cp->cmd;
	}
    }

    return OTHER_CMD;
}

#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 *error;			/* hold error message */

# ifndef NO_LOG_SMTP_EXPN
    write_log(WRITE_LOG_SYS, "remote EXPN: '%s' by %s%s%s%s%s%s%s%s%s.", in_addr,
	      ident_sender ? ident_sender : "", ident_sender ? "@" : "",
	      sender_host ? sender_host : "",
	      sender_host_really ? "(" : "",
	      sender_host_really ? sender_host_really : "",
	      sender_host_really ? ")" : "",
	      sender_host_addr ? " [" : "",
	      sender_host_addr ? sender_host_addr : "",
	      sender_host_addr ? "]" : "");
# endif
    addr->in_addr = in_addr;		/* setup the input addr structure */
    /* build the mungeable addr string */
    addr->work_addr = preparse_address(in_addr, &error);
    if (addr->work_addr == NULL) {
	fprintf(out, "501 %s ... %s\r\n", in_addr, error);
	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();
	}
    }

    hit_table = new_hash_table(hit_table_len,
			       (struct block *) NULL,
			       HASH_DEFAULT);
    resolve_addr_list(addr, &okay, &defer, &fail, TRUE);
    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);
	    fflush(out);
	}
	/* 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 ... not matched\r\n", in_addr);
    }
}
#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' address parse error: %s.\r\n", vaddr->work_addr,
		(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) {
	fprintf(out, "501 '%s' sender address cannot be a local or unqualified address (i.e. it must have a domain portion).\r\n", vaddr->work_addr);
	fflush(out);
	write_log(WRITE_LOG_SYS, "remote MAIL FROM: '%s' is a local address; by %s%s%s%s%s%s.",
		  vaddr->in_addr,
		  ident_sender ? ident_sender : "", ident_sender ? "@" : "",
		  sender_host,
		  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 */
	fprintf(out, "501 '%s' sender address cannot be a %s address.\r\n",
		vaddr->work_addr,
		(form == BERKENET) ? "BERKENET" :
			(form == DECNET) ? "DECNET" : "<bad-form!>");
	fflush(out);
	write_log(WRITE_LOG_SYS, "remote MAIL FROM: '%s' is a %s address; by %s%s%s%s%s%s.",
		  vaddr->in_addr,
		  (form == BERKENET) ? "BERKENET" :
			(form == DECNET) ? "DECNET" : "<bad-form!>",
		  ident_sender ? ident_sender : "", ident_sender ? "@" : "",
		  sender_host,
		  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, "remote MAIL FROM: '%s' (target %s) is a %s address; by %s%s%s%s%s%s.",
		  vaddr->in_addr, vaddr->target,
		  (form == RFC_ROUTE) ? "RFC_ROUTE" :
			(form == RFC_ENDROUTE) ? "RFC_ENDROUTE" :
			(form == UUCP_ROUTE) ? "UUCP_ROUTE" :
			(form == PCT_MAILBOX) ? "PCT_MAILBOX" : "<bad-form!>",
		  ident_sender ? ident_sender : "", ident_sender ? "@" : "",
		  sender_host,
		  sender_host_addr ? " [" : "",
		  sender_host_addr ? sender_host_addr : "",
		  sender_host_addr ? "]" : "");
    }
#ifdef HAVE_BIND
    if (*vaddr->work_addr && !EQ(vaddr->work_addr, "+")) {
	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";

	DEBUG4(DBG_REMOTE_MID, "verify_sender(%s): bind_addr(%s): %d, %s.\n",
	       vaddr->in_addr, 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:
	    return 1;

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

	case DB_NOMATCH:		/* no such domain */
	    fprintf(out, "550 '%s' sender address target '%s' is not a valid e-mail domain%s.\r\n",
		    vaddr->in_addr, vaddr->target,
		    smtp_sender_verify_mx_only ? " (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.",
		      vaddr->in_addr, vaddr->target,
		      smtp_sender_verify_mx_only ? " (no MX record)" : "",
		      ident_sender ? ident_sender : "", ident_sender ? "@" : "",
		      sender_host,
		      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! */
	    fprintf(out, "550 '%s' failure trying to verify address target '%s': %s.\r\n",
		    vaddr->in_addr, vaddr->target, binderrmsg);
	    fflush(out);
	    write_log(WRITE_LOG_SYS, "remote MAIL FROM: '%s' target '%s': DNS failed: %s; by %s%s%s%s%s%s.",
		      vaddr->in_addr, vaddr->target,
		      binderrmsg,
		      ident_sender ? ident_sender : "", ident_sender ? "@" : "",
		      sender_host,
		      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:
	    fprintf(out, "421 '%s' impossible error when trying to verify address target '%s': %s. (Try again later)\r\n",
		    vaddr->in_addr, vaddr->target, binderrmsg);
	    fflush(out);
	    panic(EX_SOFTWARE, "verify_sender(%s): bind_addr(%s) gave impossible result %d: %s.",
		  vaddr->in_addr, vaddr->target, binderrmsg, mxresult);
	    /* NOTREACHED */
	}
    }
#endif
    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 */
{
    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 *error;			/* hold error message */

    addr->in_addr = in_addr;		/* setup the input addr structure */
    /* build the mungeable addr string */
    addr->work_addr = preparse_address(in_addr, &error);
    if (addr->work_addr == NULL) {
	fprintf(out, "501 '%s' malformed address: %s.\r\n", in_addr, error);
	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();
	}
    }
    verify_addr_list(addr, &okay, &defer, &fail);

    /* we only submit one address at a time, so it should be on one, and only
     * one, of the output lists....
     */
    if (okay) {
	fprintf(out, "250 '%s' <%s@%s> Recipient Okay.\r\n", in_addr,
		okay->work_addr, (okay->target) ? okay->target : "(nodomain)");
	xfree(addr);
 	return 1;
    } else if (defer) {
	/* XXX hope that the other guy eventually gives up */
 	fprintf(out, "450 defer '%s' <%s@%s> cannot verify at this time: (ERR_%03ld) %s. (Try again later)\r\n",
		in_addr, defer->work_addr,
		(defer->target) ? defer->target : "(nodomain)",
		defer->error->info & ERR_MASK, defer->error->message);
	if (rcpt_p) {
	    /* XXX this may be very noisy.... */
	    write_log(WRITE_LOG_SYS, "remote %s: %s%s%s%s%s%s: '%s' <%s@%s> temporary failure returned to '%s' while verifying recipient: (ERR_%03ld) %s",
		      rcpt_p ? "RCPT TO" : "VRFY",
		      ident_sender ? ident_sender : "",
		      ident_sender ? "@" : "",
		      sender_host,
		      sender_host_addr ? "[" : "",
		      sender_host_addr ? sender_host_addr : "<no-sender-addr>",
		      sender_host_addr ? "]" : "",
		      in_addr,
		      defer->work_addr,
		      (defer->target) ? defer->target : "(nodomain)",
		      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_104:
	    error_code = 551;		/* XXX 550? mailbox not local */
	    break;
	case ERR_111:
	    error_code = 501;		/* syntax error in parameter */
	    break;
	case ERR_107:
	case ERR_109:
	    error_code = 451;		/* local error in processing */
	    break;
	case ERR_100:
	    error_code = 550;		/* mailbox not found */
	    break;
	default:
	    error_code = 553;		/* use this to flag un-xlated errors */
	    break;
	}
 	fprintf(out, "%d '%s' <%s@%s> not matched: (ERR_%03ld) %s.\r\n", error_code, in_addr,
		fail->work_addr, (fail->target) ? fail->target : "(nodomain)",
		fail->error->info & ERR_MASK, fail->error->message);
	if (rcpt_p) {
	    write_log(WRITE_LOG_SYS, "remote %s: %s%s%s%s%s%s: '%s' <%s@%s> recipient for sender '%s' not matched: (ERR_%03ld) %s",
		      rcpt_p ? "RCPT TO" : "VRFY",
		      ident_sender ? ident_sender : "",
		      ident_sender ? "@" : "",
		      sender_host,
		      sender_host_addr ? "[" : "",
		      sender_host_addr ? sender_host_addr : "<no-sender-addr>",
		      sender_host_addr ? "]" : "",
		      in_addr,
		      fail->work_addr,
		      (fail->target) ? fail->target : "(nodomain)",
		      sender,
		      fail->error->info & ERR_MASK,
		      fail->error->message);
	}
	xfree(addr);
 	return 0;
    }
    /* hmmm, it should have been in one of the lists from verify_addr_list()! */
    fprintf(out, "550 '%s' <%s@%s> is not deliverable here!\r\n", in_addr,
	    addr->work_addr, (addr->target) ? addr->target : "(nodomain)");
    if (rcpt_p) {
	/* XXX do we really need to log these? */
	write_log(WRITE_LOG_SYS, "remote %s: %s%s%s%s%s%s: '%s' <%s@%s> recipient for sender '%s' not matched by anything!",
		  rcpt_p ? "RCPT TO" : "VRFY",
		  ident_sender ? ident_sender : "", ident_sender ? "@" : "",
		  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 : "(nodomain)",
		  sender);
    }
    xfree(addr);
    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, errorp)
    char *hostnm;
    struct sockaddr_in *saddrp;
    struct hostent *shp;		/* result of gethostbyaddr() in caller */
    char const *shpmsg;			/* from gethoostbyaddr() in caller */
    char const **errorp;
{
    struct hostent *hp;
    char *p;
    int found;
    int has_alphas;
    int i;
    int allowed_to_be_broken = FALSE;
    int aresult;
    long bindflgs = BIND_DOMAIN_REQUIRED | BIND_DEFER_NO_CONN;
    struct rt_info rt_info;
    struct error *binderr = NULL;
    char *binderrmsg;
    static struct bindlib_private bindpriv =  BIND_TEMPLATE_ATTRIBUTES;

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

    if (smtp_hello_broken_allow) {
	    allowed_to_be_broken = match_ip(inet_ntoa(saddrp->sin_addr), smtp_hello_broken_allow);
    }
    if (*hostnm == '[') {
# 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 = index(hostnm, ']');
	if (!p) {
	    DEBUG1(DBG_REMOTE_LO, "verify_host(%s): Invalid host address, missing closing ']'.\n", hostnm);
	    *errorp = "Invalid host address, missing closing ']'";
	    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,
	       (unsigned long) inet, COPY_STRING(inet_ntoa(inet_makeaddr(inet, (unsigned long) 0))), /* HELO */
	       (unsigned long) saddrp->sin_addr.s_addr, COPY_STRING(inet_ntoa(saddrp->sin_addr))); /* actual */
	if (inet == (unsigned long)(-1)) {
	    DEBUG1(DBG_REMOTE_LO, "verify_host(%s): inet_addr() failed: bad host address form.\n", hostnm);
	    *errorp = "Invalid host address form";
	    return NULL;
	}
	if (inet != saddrp->sin_addr.s_addr) {
	    DEBUG3(DBG_REMOTE_LO, "verify_host(%s): [0x%lx] != [0x%lx].\n", hostnm, (unsigned long) inet, (unsigned long) saddrp->sin_addr.s_addr);
	    *errorp = "Host address does not match remote address";
	    if (smtp_hello_verify && !allowed_to_be_broken) {
		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));
		if (smtp_hello_verify_literal && !allowed_to_be_broken) {
		    return NULL;	/* broken DNS or mailer config; or forger */
		}
	    } else {
		found = 0;
		for (i = 0; hp->h_addr_list[i]; i++) {
		    if (bcmp(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) {
			return NULL;	/* broken DNS or mailer config; or forger */
		    }
		}
	    }
	} else {
	    *errorp = "Remote address PTR lookup failed";
	    if (smtp_hello_verify_literal && !allowed_to_be_broken) {
		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) {
	    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) {
		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) {
	    return NULL;
	}
    }
#if 0 /* XXX this isn't really necessary with bind_check_if_canonical_host(),
       * and gives too many hints to spammers
       */
    if (!has_alphas) {			/* gethostbyname() likes IP #s so don't go there! */
	/*
	 * XXX only people using the latest BIND resolver, as I strongly
	 * recommend, will have inet_aton().  If you get an error about it
	 * being undefined, and you're cognizant enough to find and read this
	 * comment, then PLEASE get the latest BIND release and compile & link
	 * smail against the resolver library it provides!
	 */
	if (!inet_aton(hostnm, (struct in_addr *) NULL)) { /* XXX hope all allow NULL! */
	    *errorp = "not a valid IP literal address, and not surrounted by `[]'";
	    DEBUG1(DBG_REMOTE_LO, "not a valid literal IP, no []'s: %s.\n", hostnm);
	    if (!allowed_to_be_broken) {
		return NULL;
	    }
	}
	*errorp = "IP literal addresses must be surrounded by `[]'";
	DEBUG1(DBG_REMOTE_LO, "literal IP without []'s: %s.\n", hostnm);
	if (!allowed_to_be_broken) {
	    return NULL;
	}
    }
#endif

    /* 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 FILE_NOMATCH:			/* There is no server available */
    case DB_NOMATCH:			/* no such domain */
	*errorp = binderrmsg;
	if (smtp_hello_verify && !allowed_to_be_broken) {
	    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) {
	    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) {
	    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) {
	    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) {
		    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) {
	    return NULL;		/* broken DNS or mailer config; or forger */
	}
    }

    return COPY_STRING(hostnm);
}
#endif


/*
 * 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;
{
    (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;
{
    fprintf(out_file, "421 %s SMTP command timeout, closing channel\r\n",
	    primary_name);
    write_log(WRITE_LOG_SYS, "SMTP connection timeout%s%s%s%s%s.",
	      sender_host? " while talking with ": "",
	      sender_host? sender_host: "",
	      sender_host_addr? " [": "",
	      sender_host_addr? sender_host_addr: "",
	      sender_host_addr? "]": "");
    if (smtp_remove_on_timeout) {
	unlink_spool();
    }
    exit(EX_TEMPFAIL);
}

/*
 * smtp_sig_unlink - unlink spool file and fast exit.
 *
 * This is useful for handling signals to abort reading a message in
 * with SMTP.
 */
static void
smtp_sig_unlink(sig)
    int sig;
{
    (void) signal(sig, SIG_IGN);
    if (out_file) {
	fprintf(out_file, "421 %s Service not available, closing channel\r\n",
		primary_name);
    }
    unlink_spool();
    exit(EX_OSFILE);
}

#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
