/*
#ident	"@(#)smail/src:RELEASE-3_2_0_121:queue.c,v 1.100 2005/10/11 04:57:12 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.
 */

/*
 * queue.c:
 *	operations on the queue, such as reading them and writing to
 *	the queue through the spooling functions, and scanning for work.
 *
 *	external functions: queue_message, read_message, write_body,
 *			    log_incoming, check_grade, scan_queue,
 *			    swallow_smtp
 */

#include "defs.h"

#include <sys/types.h>
#include <sys/param.h>			/* for MAXLOGNAME if avail... */
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <limits.h>
#include <time.h>

#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
#  include <stdlib.h>
# endif
#endif
#ifdef HAVE_STRING_H
# if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H)
#  include <memory.h>
# endif
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#ifdef __STDC__
# include <stdarg.h>
#else
# include <varargs.h>
#endif

#if defined(HAVE_UNISTD_H)
# include <unistd.h>
#endif

#include <pcre.h>

#include "smail.h"
#include "smailarch.h"
#include "alloc.h"
#include "list.h"
#include "smailstring.h"
#include "dys.h"
#include "main.h"
#include "parse.h"
#include "addr.h"
#include "log.h"
#include "field.h"
#include "match.h"
#include "header.h"
#include "spool.h"
#include "exitcodes.h"
#include "debug.h"
#include "extern.h"
#include "smailport.h"

/*
 * maximum length of a user name
 */
#ifndef MAXUSER
# if defined(LOGIN_NAME_MAX)
#  define MAXUSER	(LOGIN_NAME_MAX-1)
# elif defined(MAXLOGNAME)
#  define MAXUSER	MAXLOGNAME
# else
#  define MAXUSER	16			/* default for NetBSD anyway.... */
# endif
#endif

#if (MAXUSER >= BUFSIZ)
# include "Something is wrong: MAXUSER >= BUFSIZ!" /* compile-time error! */
#endif

/* variables exported from this file */
int msg_grade;				/* grade level for this message */
unsigned long int msg_body_size = 0;	/* size of message body */
off_t start_msg_headers = 0;		/* where the message headers begin */
int sender_is_trusted = FALSE;		/* TRUE if sender address and Sender: are trusted */

/* variables local to this file */
static off_t start_msg_body;		/* where message body begins */

/* functions local to this file */
static int queue_envelope __P((struct addr *, char **));
static int queue_message_internal __P((FILE *, enum dot_usage, char **));
static int queue_message_with_dots __P((FILE *, enum dot_usage, char **));
static int queue_message_simple __P((FILE *, char **));
static int fixup_local_sender __P((char **));
static int put_line __P((char *, char **));
static int read_from_lines __P((void));
static char *forward_token __P((char *));
static char *backward_token __P((char *, char *));
static void finish_sender __P((char *, char *));
static int queue_compare __P((const void *, const void *));

/*
 * queue_message - spool a message.
 *
 * spool a message from a stdio file, possibly obeying the hidden-dot
 * protocol from the input.  It will always turn cr/lf into just a
 * newline, while never requiring this to be at the end of lines.
 *
 * If a caller wishes to log any errors that occured during spooling,
 * it must call log_spool_errors.
 *
 * return SUCCEED or FAIL.
 */
int
queue_message(f, dusage, list, errorp)
    register FILE *f;			/* input file */
    enum dot_usage dusage;		/* how to treat dots */
    struct addr *list;			/* list of recipient addr structures */
    char **errorp;			/* return error message here */
{
    DEBUG(DBG_QUEUE_HI, "queue_message():  called...\n");
    /* create the spool file */
    if (creat_spool() == FAIL) {
	*errorp = "spool file creat error";
	if (dusage == SMTP_DOTS) {
	    (void) swallow_smtp(f);
	}
	return FAIL;
    }
    errno = 0;
    *errorp = NULL;
    /* fill it with stuff */
    if (queue_envelope(list, errorp) == FAIL ||
	queue_message_internal(f, dusage, errorp) == FAIL ||
	write_spool(errorp) == FAIL ||
	fixup_local_sender(errorp) == FAIL)
    {
	if (*errorp == NULL) {
	    *errorp = "unknown spool file error";
	}
	unlink_spool();
	return FAIL;
    }
    /*
     * If we got this far then any system errors above are irrelevant and/or
     * unrelated.
     */
    errno = 0;
    /*
     * This seems to be the most logical place to scan the incoming content
     * with the header_checks and body_checks PCRE lists.
     *
     * We could try to do it as we read it, buffer by buffer maybe, so as to be
     * able to avoid ever having to write junk we don't want to our local disk,
     * but we don't necessarily have newline terminated buffers.  At least if
     * we do it before the fsync() we'll avoid forcing I/Os that we'll not
     * need, and if they happen to trickle out while we're scanning, well that
     * _should_ mean there's ample disk bandwidth that it doesn't matter.
     *
     * However doing it here will allow us to implement separate header_checks
     * and body_checks like some other mailers do, which might make sense since
     * the kinds of patterns we are likely to want to reject in headers will be
     * very different from those for message bodies.
     *
     * We should also consider implementing a watchdog timer so that we don't
     * exceed the minimum SMTP delay for a command response.  If the timer goes
     * off and we're not done scanning, well then we'll just have to deliver
     * the message.
     */
    if (header_checks_always_list) {
	int rc = egrep_spool_headers(header_checks_always_list, errorp);

	if (rc != MATCH_NOMATCH) {
	    DEBUG2(DBG_QUEUE_LO, "queue_message():  header_checks_always %s: %s\n",
		   (rc == MATCH_FAIL) ? "matching failed" : "matched", *errorp);
	    unlink_spool();
	    /* temporary, or permanent reject? */
	    errno = (rc == MATCH_FAIL) ? ERANGE : 0; /* XXX EPIPE? */
	    return FAIL;
	}
    }
    if (header_checks_list) {
	int rc = egrep_spool_headers(header_checks_list, errorp);

	switch (rc) {
	case MATCH_MATCHED:
	    DEBUG1(DBG_QUEUE_LO, "queue_message():  %s\n", *errorp);
	    if (recipients && bypass_content_filtering_recipients) {
		struct addr *keep;

		keep = keep_matching_addrs(recipients, bypass_content_filtering_recipients);
		if (keep) {
		    DEBUG(DBG_QUEUE_LO, "queue_message(): not rejecting due to header_checks, some recipient(s) matched bypass_content_filtering_recipients\n");
		    recipients = keep;
		    *errorp = NULL;
		} else {
		    unlink_spool();
		    /* we have to make sure this looks like a content filter reject! */
		    errno = 0;
		    return FAIL;
		}
	    } else {
		unlink_spool();
		/* we have to make sure this looks like a content filter reject! */
		errno = 0;
		return FAIL;
	    }
	    break;
	case MATCH_FAIL:
	    DEBUG1(DBG_QUEUE_LO, "queue_message(): body_checks matching failed %s\n", *errorp);
	    errno = ERANGE;		/* XXX EPIPE? */
	    return FAIL;
	}
    }
    if (body_checks_always_list) {
	int rc = egrep_spool_body(body_checks_always_list, errorp);

	if (rc != MATCH_NOMATCH) {
	    DEBUG2(DBG_QUEUE_LO, "queue_message():  body_checks_always %s: %s\n",
		   (rc == MATCH_FAIL) ? "matching failed" : "matched", *errorp);
	    unlink_spool();
	    /* temporary, or permanent reject? */
	    errno = (rc == MATCH_FAIL) ? ERANGE : 0; /* XXX EPIPE? */
	    return FAIL;
	}
    }
    if (body_checks_list) {
	int rc = egrep_spool_body(body_checks_list, errorp);

	switch (rc) {
	case MATCH_MATCHED:
	    DEBUG1(DBG_QUEUE_LO, "queue_message():  %s\n", *errorp);
	    if (recipients && bypass_content_filtering_recipients) {
		struct addr *keep;

		keep = keep_matching_addrs(recipients, bypass_content_filtering_recipients);
		if (keep) {
		    DEBUG(DBG_QUEUE_LO, "queue_message(): not rejecting due to body_checks, some recipient(s) matched bypass_content_filtering_recipients\n");
		    recipients = keep;
		    *errorp = NULL;
		} else {
		    unlink_spool();
		    /* we have to make sure this looks like a content filter reject! */
		    errno = 0;
		    return FAIL;
		}
	    } else {
		unlink_spool();
		/* we have to make sure this looks like a content filter reject! */
		errno = 0;
		return FAIL;
	    }
	    break;
	case MATCH_FAIL:
	    DEBUG1(DBG_QUEUE_LO, "queue_message(): body_checks matching failed %s\n", *errorp);
	    errno = ERANGE;		/* XXX EPIPE? */
	    return FAIL;
	}
    }

#ifdef HAVE_FSYNC
    if (fsync(spoolfile) < 0) {
	*errorp = "fsync write failure";
	unlink_spool();
	return FAIL;
    }
#endif
    /*
     * Update the spool file's last_modified time again to take into account
     * any excessive time the header and body checks above chewed up.
     */
    touch(input_spool_fn, (time_t) 0, (time_t) 0);
    msg_mtime = time((time_t *) NULL);

    return SUCCEED;
}

/*
 * swallow_smtp - read and throw away SMTP message contents
 *
 * Read lines up to a line containing a single '.'.  Do not do
 * anything with these lines.  Return SUCCEED if we successfully read
 * up to a line containing only a single '.'.  Return FAIL otherwise.
 *
 * This is used to read in a complete message from an SMTP transaction
 * which will not be using because of some error.
 *
 * XXX perhaps this should count bytes and add them to msg_size so that we can
 * tell how much the client was hoping to send.
 */
int
swallow_smtp(f)
    register FILE *f;			/* input file */
{
    register int c;

    for (;;) {
	if ((c = getc(f)) == EOF) return FAIL;
	if (c == '.') {
	    if ((c = getc(f)) == EOF) return FAIL;
	    if (c == '\n') {
		return SUCCEED;
	    }
	    if (c == '\r') {
		if ((c = getc(f)) == EOF) return FAIL;
		if (c == '\n') {
		    return SUCCEED;
		}
	    }
	}
	while (c != '\n') {
	    if ((c = getc(f)) == EOF) return FAIL;
	}
    }
}

/*
 * queue_envelope - write out header filler and arguments for spool file
 *
 * save some space for the login username, which is computed last, and
 * write out any arguments, to the spool file.  The form used is one smail
 * command argument per line, allowing the use of process_args() in main
 * to decode the envelope information.
 */
static int
queue_envelope(list, errorp)
    struct addr *list;			/* input recipient list */
    char **errorp;			/* return error message here */
{
    register struct addr *cur;		/* current recipient addr to process */
    char *p = NULL;			/* temp */
    char buf[BUFSIZ];			/* temp storage (> sizeof("%u %u")) */

#if (MAXUSER >= BUFSIZ)
# include "ERROR: (MAXUSER >= BUFSIZ)!!!"
#endif

    /* XXX this should always be true now we call compute_local_sender() in intialize_state() */
    if (local_sender) {
	if (put_line(local_sender, errorp) == EOF) {
	    return FAIL;
	}
    } else {
	/*
	 * put MAXUSER spaces in a buffer to be used as padding for where the
	 * real local sender ID will be written.
	 */
	(void) memset(buf, ' ', (size_t) MAXUSER);
	buf[MAXUSER] = '\0';
	if (put_line(buf, errorp) == EOF) {
	    return FAIL;
	}
    }

    /*
     * write out the real uid and effective gid, as numbers.  Validation
     * of trusted_users will be done from the ids stored here.
     */
#if 0
    assert(sizeof(real_uid) <= sizeof(unsigned int));
    assert(sizeof(prog_egid) <= sizeof(unsigned int));
#endif
    (void) sprintf(buf, "%u %u", (unsigned int) real_uid, (unsigned int) prog_egid);
    if (put_line(buf, errorp) == EOF) {
	return FAIL;
    }

    /*
     * write out important state information in the form of command arguments.
     */
    if (extract_addresses) {
	/* signal that addresses are to be taken from the header */
	if (put_line("-t", errorp) == EOF) {
	    return FAIL;
	}
    }

    /* record the delivery mode that was specified for the message */
    switch (deliver_mode) {
    case BACKGROUND:
	p = "-odb";
	break;
    case FOREGROUND:
	p = "-odf";
	break;
    case QUEUE_MESSAGE:
	p = "-odq";
	break;
    case DELIVER_DEFAULT:		/* XXX impossible! */
	p = "";
	break;
    }
    if (put_line(p, errorp) == EOF) {
	return FAIL;
    }
    if (queue_only) {
	if (put_line("-Q", errorp) == EOF) {
	    return FAIL;
	}
    }

    /* tell which form of error recover is being used */
    switch (error_processing) {
    case MAIL_BACK:
	p = "-oem";
	break;
    case WRITE_BACK:
	p = "-oew";
	break;
    case TERMINAL:
	p = "-oep";
	break;
    case DEV_NULL:
	p = "-oeq";
	break;
    case ERROR_DEFAULT:			/* XXX impossible! */
	p = "-oem";
	break;
    }
    if (put_line(p, errorp) == EOF) {
	return FAIL;
    }

    if (dont_deliver) {
	if (put_line("-N", errorp) == EOF) {
	    return FAIL;
	}
    }
    if (hop_count >= 0) {
	(void) sprintf(buf, "-h%d", hop_count);
	if (put_line(buf, errorp) == EOF) {
	    return FAIL;
	}
    }
    if (! do_aliasing) {
	if (put_line("-n", errorp) == EOF) {
	    return FAIL;
	}
    }
    if (me_too) {
	if (put_line("-om", errorp) == EOF) {
	    return FAIL;
	}
    }

    /*
     * write the sender's full name, if one was given explicitly.  If it was
     * not given explicitly, we would not yet know what it is.
     */
    if (sender_name) {
	if (put_line("-F", errorp) == FAIL ||
	    put_line(sender_name, errorp) == FAIL)
	{
	    return FAIL;
	}
    }

    /*
     * write the sender address, if one was given explicitly.  If it was
     * not given explicitly, we would not yet know what it is.
     */
    if (sender) {
	if (put_line("-f", errorp) == FAIL ||
	    put_line(sender, errorp) == FAIL)
	{
	    return FAIL;
	}
    }

    /*
     * write the sending host, host address, host port, and sending protocol,
     * each if known
     */
    if (sender_host) {
	if (put_line("-oMs", errorp) == FAIL ||
	    put_line(sender_host, errorp) == FAIL)
	{
	    return FAIL;
	}
    }
    if (sender_host_invalid) {
	if (put_line("-oMn", errorp) == FAIL ||
	    put_line(sender_host_invalid, errorp) == FAIL)
	{
	    return FAIL;
	}
    }
    if (sender_host_addr) {
	if (put_line("-oMa", errorp) == FAIL ||
	    put_line(sender_host_addr, errorp) == FAIL)
	{
	    return FAIL;
	}
    }
    if (sender_host_really) {
	if (put_line("-oMR", errorp) == FAIL ||
	    put_line(sender_host_really, errorp) == FAIL)
	{
	    return FAIL;
	}
    }
    if (sender_host_really_invalid) {
	if (put_line("-oMN", errorp) == FAIL ||
	    put_line(sender_host_really_invalid, errorp) == FAIL)
	{
	    return FAIL;
	}
    }
    if (peer_is_localhost) {
	if (put_line("-oMI", errorp) == FAIL) {
	    return FAIL;
	}
    }
    if (sender_host_port) {
	if (put_line("-oMp", errorp) == FAIL ||
	    put_line(sender_host_port, errorp) == FAIL)
	{
	    return FAIL;
	}
    }
    if (sender_proto) {
	if (put_line("-oMr", errorp) == FAIL ||
	    put_line(sender_proto, errorp) == FAIL)
	{
	    return FAIL;
	}
    }

    /*
     * write the ident of the sending person if known
     */
    if (ident_sender) {
	if (put_line("-oMu", errorp) == FAIL ||
	    put_line(ident_sender, errorp) == FAIL)
	{
	    return FAIL;
	}
    }

    /*
     * write the method used to determine the ident of sender if known
     */
    if (ident_method) {
	if (put_line("-oMv", errorp) == FAIL ||
	    put_line(ident_method, errorp) == FAIL)
	{
	    return FAIL;
	}
    }

    /*
     * write out the local SMTP endpoint info, if known
     */
    if (smtp_local_addr) {
	if (put_line("-oMl", errorp) == FAIL ||
	    put_line(smtp_local_addr, errorp) == FAIL)
	{
	    return FAIL;
	}
    }
    if (smtp_local_port) {
	if (put_line("-oML", errorp) == FAIL ||
	    put_line(smtp_local_port, errorp) == FAIL)
	{
	    return FAIL;
	}
    }


    /*
     * write out the invoked program, too
     */
    if (put_line("-oMP", errorp) == FAIL ||
	put_line(program, errorp) == FAIL)
    {
	return FAIL;
    }

    /*
     * write out all of the recipient addresses.  If the -t flag was given,
     * this is the list of addresses that are explicitly not recipients.
     */
    for (cur = list; cur; cur = cur->succ) {
	if (put_line(cur->in_addr, errorp) == FAIL) {
	    return FAIL;
	}
    }

    /* envelope ends in one blank line */
    if (PUTSPOOL('\n', errorp) == EOF) {
	return FAIL;
    }

    /* remember where the envelope ends... tell_spool() doesn't work when writing. */
    start_msg_headers = msg_foffset + msg_max - msg_buf;
    DEBUG1(DBG_QUEUE_HI, "queue_envelope(): message headers start at %lu\n", (unsigned long int) start_msg_headers);

    return SUCCEED;
}


/*
 * queue_message_internal - read the message into a spool file.
 *
 * Call either queue_message_with_dots() or queue_message_simple() to
 * spool a message to a spool file.
 */
static int
queue_message_internal(f, dusage, errorp)
    FILE *f;
    enum dot_usage dusage;
    char **errorp;			/* optionally store error here */
{
    *errorp = NULL;

    if (dusage == NO_DOT_PROTOCOL) {
	return queue_message_simple(f, errorp);
    } /* else */
    return queue_message_with_dots(f, dusage, errorp);
}

/*
 * queue_message_with_dots - read in a message and write it to the
 *			     spool file, with a dot protocol.
 *
 * Read in a message using one of the dot protocols (HIDDEN_DOTS,
 * DOT_ENDS_MESSAGE or SMTP_DOTS) specified in 'dusage'.  Note the
 * implementation here is tightly dependent on dusage having only these three
 * possible values.  This function must never see the fourth possible value,
 * NO_DOT_PROTOCOL, so it may only be called by the protective wrapper function
 * queue_message_intarnal(), defined above.)
 *
 * All of these protocols end a message when a dot is found on a line by
 * itself.
 *
 * The HIDDEN_DOTS protocol drops an initial dot from a line that contains
 * other data (i.e. a dot that isn't followed by end-of-line).
 *
 * The SMTP_DOTS protocol also drops an initial dot from a line that contains
 * other data, just like HIDDEN_DOTS, except that with SMTP_DOTS a message must
 * be terminated by a '.', with an encounter of EOF being an error.
 *
 * NOTE: also with SMTP_DOTS, the entire message is read from the input even if
 * a write to the spool file encounters an error.  Internally this is
 * implemented by not calling PUTSPOOL() if put_success has been set to FAIL.
 *
 * All of these protocols treat a <CR> <LF> sequence as equivalent to a single
 * newline in order to properly handle pure SMTP, though by implication they
 * also violate RFC 2822 section 2.3.7 by recognizing a bare <LF> as the end of
 * a line:
 *
 *   Conforming implementations MUST NOT recognize or generate any other
 *   character or character sequence as a line terminator.  Limits MAY be
 *   imposed on line lengths by servers (see section 4.5.3).
 *
 * and further in section 4.1.1.4 (DATA):
 *
 *   The custom of accepting lines ending only in <LF>, as a concession to
 *   non-conforming behavior on the part of some UNIX systems, has proven
 *   to cause more interoperability problems than it solves, and SMTP
 *   server systems MUST NOT do this, even in the name of improved
 *   robustness.  In particular, the sequence "<LF>.<LF>" (bare line
 *   feeds, without carriage returns) MUST NOT be treated as equivalent to
 *   <CRLF>.<CRLF> as the end of mail data indication.
 *
 * The authors of the above clearly consider worry of message truncation when
 * raw message data contains the "<LF>.<LF>" sequence as more troublesome than
 * the alternative of screwing up human users at the most critical point when
 * they're trying to diagnose problems (as typically a human tester will send
 * only newline-terminated input).  However their worry was ill-founded since
 * these days any client-SMTP wanting to prevent message truncation can very
 * easily use MIME (QP or BASE64) encoding to guarantee transparency without
 * the hassle of trying to guess whether the dot following a bare <LF> needs
 * dot-stuffing to be protected.
 *
 * return SUCCEED or FAIL.
 */
static int
queue_message_with_dots(f, dusage, errorp)
    register FILE *f;			/* input file */
    enum dot_usage dusage;		/* how to treat dots */
    char **errorp;			/* optionally store error here */
{
    register int empty = TRUE;
    register int c;
    register int put_success = SUCCEED;	/* set to FAIL if PUTSPOOL() fails */

    DEBUG2(DBG_QUEUE_LO, "Enter your message, with all headers%s, end with a '.' on a line by itself%s....\n",
	   dusage != DOT_ENDS_MESSAGE ? ", escaping all leading dots with another dot" : "",
	   dusage != SMTP_DOTS ? " or with EOF" : "")

    /* read lines until dot on a line by itself, or EOF... */
    for (;;) {
	/* read the first character (of a line) */
	for (;;) {
	    errno = 0;
	    if ((c = getc(f)) == EOF) {
		if (errno == EINTR) {
		    continue;
		}
		DEBUG2(DBG_QUEUE_LO, "%sGot EOF%s.\n",
		       empty ? "ERROR:  " : "",
		       empty ? " before first char of first line" : "");
		goto read_eof;
	    }
	    break;
	}
	/* look for a dot at the beginning of the line */
	if (c == '.') {
	    /* read the next char to see if the dot is on a line by itself */
	    for (;;) {
		errno = 0;
		if ((c = getc(f)) == EOF) {
		    if (errno == EINTR) {
			continue;
		    }
		    DEBUG(DBG_QUEUE_LO, "ERROR:  Got EOF (after <.>).\n");
		    goto read_eof;
		}
		break;
	    }
	    /*
	     * a plain <LF> terminates a line
	     *
	     * Note we do violate RFC 2822 section 4.1.1.4 by allowing this
	     * lone <LF> to terminate a line.
	     */
	    if (c == '\n') {
		/* make sure errno != 0 if we encountered a write error */
		if (empty) {
		    *errorp = "No message input before dot";
		    DEBUG1(DBG_QUEUE_LO, "%s<LF>.\n", *errorp);
		    errno = ENOENT;
		    put_success = FAIL;
		} else if (put_success == FAIL && errno == 0) {
		    errno = EIO;			/* XXX got a better idea? */
		    DEBUG(DBG_QUEUE_LO, "ERROR:  Failed to queue message input (after <.><LF>).\n");
		} else {
		    DEBUG(DBG_QUEUE_LO, "Got '.'<LF>.\n");
		}
		return put_success;
	    }
	    /* <CRLF> (also) terminates a line */
	    if (c == '\r') {
		/* look to see what follows the <CR>... */
		for (;;) {
		    errno = 0;
		    if ((c = getc(f)) == EOF) {
			if (errno == EINTR) {
			    continue;
			}
			DEBUG(DBG_QUEUE_LO, "ERROR:  Got EOF (after <.><CR>).\n");
			goto read_eof;
		    }
		    break;
		}
		/* if it's <LF> then we have found a dot on a line by itself */
		if (c == '\n') {
		    /* make sure errno != 0 if we encountered a write error */
		    if (empty) {
			*errorp = "No message input before '.'";
			DEBUG1(DBG_QUEUE_LO, "%s<CR><LF>.\n", *errorp);
			errno = ENOENT;
			put_success = FAIL;
		    } else if (put_success == FAIL && errno == 0) {
			errno = EIO;			/* XXX got a better idea? */
			DEBUG(DBG_QUEUE_LO, "ERROR:  Failed to queue message input (after '.'<CR><LF>).\n");
		    } else {
			DEBUG(DBG_QUEUE_LO, "Got '.'<CR><LF>.\n");
		    }
		    return put_success;
		}
		/*
		 * if the .<CR> is not followed by a newline then:
		 *
		 *	if doing DOT_ENDS_MESSAGE then write out both the dot
		 *	and the <CR> (dot was followed by <CR>, but not <CRLF>,
		 *	so it's not alone on a line by itself);
		 *
		 *	else if doing either SMTP_DOTS or HIDDEN_DOTS then
		 *	write out just the <CR> (i.e. strip the "hidden" dot);
		 *
		 * and if either fails, and if doing HIDDEN_DOTS or
		 * DOT_ENDS_MESSAGE then return FAIL immediately (but SMTP_DOTS
		 * must read whole message so keep on trucking but remember
		 * that there was a failure)
		 */
		if (put_success == SUCCEED &&
		    ((dusage == DOT_ENDS_MESSAGE &&
		      PUTSPOOL('.', errorp) == FAIL) ||
		     PUTSPOOL('\r', errorp) == FAIL)) {
		    if (dusage != SMTP_DOTS) {
			/* errno from PUTSPOOL() should be safe here */
			return FAIL;
		    } else {
			put_success = FAIL;
		    }
		}
	    } else /* (c != '\r') */ {
		/*
		 * if doing SMTP_DOTS or HIDDEN_DOTS then don't write out the dot
		 */
		if (put_success == SUCCEED &&
		    (dusage == DOT_ENDS_MESSAGE && PUTSPOOL('.', errorp) == FAIL))
		{
		    /* XXX should we return FAIL now if not doing SMTP_DOTS? */
		    put_success = FAIL;
		}
	    }
	}
	empty = FALSE;
	/* read the rest of the line... */
	for (;;) {
	    /*
	     * read until we find a <LF> or <CR>
	     *
	     * Note once again we violate RFC 2822 section 4.1.1.4 by allowing
	     * a lone <LF> to terminate a line, though in this instance where
	     * the line did not begin with a dot, there's no worry of
	     * transparancy problems.
	     */
	    while (c != '\n' && c != '\r') {
		if (put_success == SUCCEED && PUTSPOOL(c, errorp) == FAIL) {
		    /* XXX should we return FAIL now if not doing SMTP_DOTS? */
		    put_success = FAIL;
		}
		for (;;) {
		    errno = 0;
		    if ((c = getc(f)) == EOF) {
			if (errno == EINTR) {
			    continue;
			}
			DEBUG(DBG_QUEUE_LO, "ERROR:  Got EOF in the middle of a line.\n");
			goto read_eof;
		    }
		    break;
		}
	    }
	    if (c == '\r') {
		/* always look for a <LF> after a <CR>... */
		for (;;) {
		    errno = 0;
		    if ((c = getc(f)) == EOF) {
			if (errno == EINTR) {
			    continue;
			}
			DEBUG(DBG_QUEUE_LO, "ERROR:  Got EOF after <CR> in a line.\n");
			goto read_eof;
		    }
		    break;
		}
		if (c != '\n') {
		    /* NOT the end of the line -- stuff out the bare <CR>... */
		    if (put_success == SUCCEED && PUTSPOOL('\r', errorp) == FAIL) {
			/* XXX should we return FAIL now if not doing SMTP_DOTS? */
			put_success = FAIL;
		    }
		    /*
		     * don't fall through to the PUTSPOOL() below -- otherwise
		     * the one at the top of the 'while' above would duplicate
		     * this <CR>...
		     */
		    continue;
		}
	    }
	    if (put_success == SUCCEED && PUTSPOOL(c, errorp) == FAIL) {
		/* XXX should we return now if not doing SMTP_DOTS? */
		put_success = FAIL;
	    }
	    /*
	     * end-of-the-line detected:  go back to the outer loop
	     */
	    break;
	}
    }

  read_eof:

    if (empty) {
	*errorp = "No message input found";
	DEBUG1(DBG_QUEUE_LO, "%s.\n", *errorp);
	errno = ENOENT;
	return FAIL;
    }

    if (dusage != SMTP_DOTS) {
	/* make sure errno != 0 if we encountered a write error */
	if (put_success == FAIL && errno == 0) {
	    errno = EIO;		/* XXX got a better idea? */
	    DEBUG(DBG_QUEUE_LO, "ERROR:  Failed to queue message input.\n");
	} else {
	    DEBUG(DBG_QUEUE_LO, "Read and queued message.\n");
	}
	return put_success;
    }

    /* else if doing SMTP_DOTS then EOF is always an error */
    if (errno == 0) {
	errno = EIO;			/* XXX got a better idea? */
    }
    *errorp = "Unexpected end of file on message input";
    DEBUG1(DBG_QUEUE_LO, "%s.\n", *errorp);
    return FAIL;
}

/*
 * queue_message_simple - read in a message with no dot protocol.
 *
 * Read in a message without any specific protocol.  No CR/LF mapping
 * is done and the end of message is given only with an EOF.
 *
 * Return SUCCEED or FAIL.
 */
static int
queue_message_simple(f, errorp)
    register FILE *f;
    char **errorp;			/* return error message here */
{
    register int empty = TRUE;
    register int c;

    DEBUG(DBG_QUEUE_LO, "Enter your message, with headers, end with EOF....\n");
    for (;;) {
	for (;;) {
	    errno = 0;
	    if ((c = getc(f)) == EOF) {
		if (errno == EINTR) {
		    /*
		     * note at this point SIGINT (and SIGALRM, etc.) should be
		     * trapped by a handler that never returns, so this should
		     * only keep us reading on SIGHUP, which we might get by
		     * accident if the wrong process is asked to reload config
		     * files.
		     */
		    DEBUG(DBG_QUEUE_LO, "\n(Continuing after EINTR, end with EOF....)\n");
		    continue;
		}
	    }
	    break;
	}
	if (c == EOF) {
	    break;
	}
	empty = FALSE;
	if (PUTSPOOL(c, errorp) == FAIL) {
	    return FAIL;
	}
    }
    if (empty) {
	*errorp = "No message input";
	errno = ENOENT;
	return FAIL;
    }
    return SUCCEED;
}


/*
 * fixup_local_sender - insert the login username at the start of the spoolfile
 *
 * to get the message into the spool file as fast as possible, the login name
 * of the user that ran smail is computed last and written into the space left
 * for it after everything else is through.
 *
 * This is ugly, and rather silly too now that we set local_sender early before
 * ever accepting the message....
 */
static int
fixup_local_sender(errorp)
    char **errorp;			/* return error message here */
{
    register int i;			/* bytes to write */
	
    /*
     * truncate to MAXUSER chars, if for some reason we got a
     * larger user name, since that's all the room we left...
     */
    i = strlen(local_sender);
    if (i > MAXUSER) {
	i = MAXUSER;
    }

    /*
     * write the name to disk
     */
    (void) lseek(spoolfile, (off_t) 1, SEEK_SET);

    /*
     * Note 'i' is best left as a signed int as write() will either be declared
     * int or ssize_t and thus we need a signed comparision to avoid seeing a
     * '-1' error as a very large number and thinking the write was successful.
     */
    if (write(spoolfile, local_sender, (size_t) i) < i) {
	*errorp = xprintf("fixup_local_sender(): write() failed: %s", strerror(errno));
	return FAIL;
    }

    /*
     * if the current incore spool file region is still at the beginning of the
     * file, then make a copy there too to keep it in sync.
     */
    if (msg_foffset == 0) {
	(void) memcpy(msg_buf + 1, local_sender, (size_t) i);
    }

    return SUCCEED;
}

/*
 * put_line - write a complete line to the spool file
 *
 * the string passed is assumed to be an atomic argument for smail, which
 * should end in a newline.  To guard against newlines in the text of the
 * string to end an argument, newline is encoded as \n and \ is encoded as
 * \\.
 */
static int
put_line(s, errorp)
    register char *s;
    char **errorp;			/* return error message here */
{
    if (PUTSPOOL('!', errorp) == EOF) {
	return FAIL;
    }
    while (*s) {
	if (*s == '\\') {
	    /* encode \ as \\ and newline as \n */
	    if (PUTSPOOL('\\', errorp) == EOF || PUTSPOOL('\\', errorp) == EOF) {
		return FAIL;
	    }
	} else if (*s == '\n') {
	    if (PUTSPOOL('\\', errorp) == EOF || PUTSPOOL('n', errorp) == EOF) {
		return FAIL;
	    }
	} else {
	    if (PUTSPOOL(*s, errorp) == EOF) {
		return FAIL;
	    }
	}

	s++;
    }

    if (PUTSPOOL('\n', errorp) == EOF) {
	return FAIL;
    }

    return SUCCEED;
}


/*
 * read_message - scan through an open spool file
 *
 * read through the current open spool file to grab From_ and Header:
 * information.  Putting the sender address in the sender variable,
 * and storing the header information in the headers variable as
 * side effects.
 *
 * return the argument vector in *argv
 *
 * return NULL on failure.  The reason for failure will be logged
 * in the system log file and the spool file should be closed and
 * ignored for now.
 */
char **
read_message()
{
    size_t a = 32;			/* vectors allocated thus far */
    size_t i;				/* temp index */
    register int c;			/* character from spool file or EOF */
    register char **argv;		/* computed argument vector */
    int first = FALSE;			/* first char on a line? */
    char ls[MAXUSER + 1];		/* original local_sender from spoolfile */

    DEBUG(DBG_QUEUE_HI, "read_message() called...\n");

    /* initialize state before reading any new values from the spool file */
    initialize_state();

    i = 0;
    memset(ls, '\0', sizeof(ls));

    /* seek to the beginning of the region */
    if (seek_spool((off_t) 0L) == FAIL) {
	/* errors logged in seek_spool() */
	return NULL;
    }

    /* a silly check to validate spool file format */
    c = GETSPOOL();
    if (c != '!') {
	DEBUG1(DBG_QUEUE_LO, "read_message(): got '%c', not '!'\n", c);
	write_log(WRITE_LOG_SYS|WRITE_LOG_PANIC, "invalid spool file: %s/%s: bad spoolfile header",
		  spool_dir, input_spool_fn);
	return NULL;
    }

    /* get the original local_sender value from the spool file */
    while ((c = GETSPOOL()) != '\n' && c != ' ' && c != EOF && c != READ_FAIL && i < MAXUSER) {
	ls[i++] = c;
    }
    /* ignore the rest of the first line */
    while (c != '\n' && c != EOF && c != READ_FAIL) {
	c = GETSPOOL();
	if (c != ' ' && c != '\n' && c != EOF && c != READ_FAIL) {
	    DEBUG1(DBG_QUEUE_LO, "ignoring extra non-space char after local_sender: '%c'\n", c);
	}
    }
    ls[i] = '\0';
    DEBUG1(DBG_QUEUE_HI, "read_message(): read spoolfile local_sender: '%s'\n", ls);
    if (local_sender) {
	xfree(local_sender);
    }
    if (!ls[0]) {
	/* XXX maybe this 'error' is worthy of logging? */
	DEBUG1(DBG_QUEUE_LO, "no original local_sender in spool file, using $nobody (%s)\n", nobody);
	local_sender = COPY_STRING(nobody);
    } else {
	local_sender = COPY_STRING(ls);
    }

    /* read the original real UID and effective GID under which the spooling process was run */
    if (c != READ_FAIL && c != EOF) {
	char buf[BUFSIZ];		/* temp input buffer */
	char *p2 = buf;			/* temp for scanning buf */
	unsigned int stored_uid;	/* in case uid_t is a (unsigned) short */
	unsigned int stored_gid;	/* in case gid_t is a (unsigned) short */

	while ((c = GETSPOOL()) != '\n' && c != EOF && c != READ_FAIL && (size_t) (p2-buf) < sizeof(buf)) {
	    *p2++ = c;
	}
	*p2 = '\0';
	p2 = buf;
	if (*p2++ != '!') {		/* XXX should be an error if missing! */
	    DEBUG1(DBG_QUEUE_LO, "read_message(): got '%c', not '!'\n", (int) *(p2-1));
	    write_log(WRITE_LOG_SYS|WRITE_LOG_PANIC, "invalid spool file: %s/%s: no '!' before 'UID EGID'",
		      spool_dir, input_spool_fn);
	    return NULL;
	}
	(void) sscanf(p2, "%u %u", &stored_uid, &stored_gid);
#if 0
	assert(sizeof(real_uid) == sizeof(stored_uid));
	assert(sizeof(prog_egid) == sizeof(stored_gid));
#endif
	real_uid = stored_uid;
	prog_egid = stored_gid;
    }
    DEBUG2(DBG_QUEUE_HI, "read_message(): set from spoolfile: real_uid = %u, prog_egid = %u\n",
	   (unsigned int) real_uid, (unsigned int) prog_egid);

    if (c == READ_FAIL) {
	write_log(WRITE_LOG_SYS|WRITE_LOG_PANIC, "read failed: %s/%s: %s",
		  spool_dir, input_spool_fn, strerror(errno));
	return NULL;
    }
    if (c == EOF) {
	write_log(WRITE_LOG_SYS, "unexpected end of file: %s/%s: no command parameters found",
		  spool_dir, input_spool_fn);
	return NULL;
    }

    /*
     * note the real_uid and prog_euid credentials used by trusted_invoker()
     * are already over-written by those of the original invoker as stored in
     * the spool file
     */
    if (!trusted_invoker()) {
	/*
	 * the original invoker is not trusted, sender is initially set to be
	 * the original local_sender
	 *
	 * Note that messages received by SMTP from any remote peer will be
	 * given a local_sender value of $nobody but their recorded real_uid
	 * and prog_euid will still be that of the user listening on the SMTP
	 * port (normally 0,0 (until the day when smtpd finally drops root and
	 * runs as nobody, at which time it will spoof the "smail" real-UID)),
	 * so the sender address (and any Sender: headers) specified by SMTP
	 * clients will still be trusted.
	 */
	DEBUG1(DBG_QUEUE_MID, "original runtime user is not trusted, initializing sender to original local_sender: <%s>\n", local_sender);
	if (!sender) {
	    sender = COPY_STRING(local_sender);
	    islocal = TRUE;		/* current new sender is local */
	    sender_is_trusted = FALSE;	/* turn off trust of 'sender' */
	}
    } else {
	sender_is_trusted = TRUE;	/* we can trust $sender */
    }
    /*
     * if the sender address was already specified and the originator of the
     * message is trusted to supply a sender address, see if it is in local
     * form or not.
     */
    if (sender && sender_is_trusted) {
	char *error;			/* temp to store error message */

	islocal = (parse_address(sender, (char **) NULL, &error, (int *) NULL) == LOCAL);
    }

    /*
     * read the argument vectors stored in the spool file
     */
    DEBUG1(DBG_QUEUE_HI, "now reading arg vectors from spool file (at %lu)\n", (unsigned long) tell_spool());
    argv = (char **)xmalloc(a * sizeof(char *));
    i = 0;
    first = TRUE;
    while ((c = GETSPOOL()) != EOF && c != '\n') {
	struct str str;
	register struct str *sp = &str;	/* temp string */

	/* read one vector */
	STR_INIT(sp);
	do {
	    if (first) {
		first = FALSE;
		if (c == '!') {
		    continue;
		}
	    }
	    if (c == '\\') {
		c = GETSPOOL();
		if (c == EOF) {
		    STR_FREE(sp);
		    write_log(WRITE_LOG_SYS, "unexpected end of file: %s/%s",
			      spool_dir, input_spool_fn);
		    return NULL;	/* no message */
		} else if (c == 'n') {
		    c = '\n';
		} else if (c != '\\') {
		    write_log(WRITE_LOG_SYS, "format error in: %s/%s",
			      spool_dir, input_spool_fn);
		    return NULL;	/* format error */
		}
	    }
	    STR_NEXT(sp, c);
	} while ((c = GETSPOOL()) != EOF && c != '\n');

	/*
	 * finish off the argument string and store it in the vector
	 */
	STR_NEXT(sp, '\0');
	STR_DONE(sp);
	DEBUG1(DBG_QUEUE_HI, "read vector '%v' from spool file\n", STR(sp));
	argv[i++] = STR(sp);
	/* do we need a larger vector? */
	if (i >= a) {
	    a += 32;
	    argv = (char **) xrealloc((char *) argv, a * sizeof(char *));
	}
	first = TRUE;			/* look for '!' again */
    }

    /* finish off the arguments vector */
    argv[i] = NULL;

    /*
     * login name and arguments processed, scan for From_ lines, if
     * any exist.  As a side effect, this may set the sender if it
     * has not been set already.
     */
    if (read_from_lines() == FAIL) {
	/* read_from_lines logged the error */
	return NULL;
    }

    /*
     * If the sender is still not known by now, then use the original
     * local_sender, or Postmaster if nothing else is available.
     *
     * Note this may yet be overridden later if a -f or -r parameter is present
     * in argv.
     */
    if (sender == NULL) {
	if (ls[0]) {
	    sender = COPY_STRING(ls);
	} else {
	    /* love to gang up on that postmaster */
	    DEBUG(DBG_QUEUE_LO, "no login user in spool file, setting sender to 'Postmaster'\n");
	    sender = COPY_STRING("Postmaster");
	}
	islocal = TRUE;			/* sender appears to be local */
    }

    /*
     * now grab the header lines.
     */
    DEBUG1(DBG_QUEUE_HI, "calling read_header (offset=%lu)\n", (unsigned long) tell_spool());
    if (read_header() == FAIL) {
	/* read_header logged the error */
	return NULL;
    }

    /* save the start position for the message */
    start_msg_body = tell_spool();
    msg_body_size = (unsigned long) (msg_size - start_msg_body);

    DEBUG2(DBG_SPOOL_HI, "body starts at %lu, body size: %lu\n", (unsigned long) start_msg_body, msg_body_size);

    /* all done, return the arguments */
    return argv;
}

/*
 * read_from_lines - scan From_ lines and build up a sender
 *
 * Scan through to the end of the From_ lines (if any exist).
 * As a side effect, build up a path to the sender, if the
 * sender is not already known.
 *
 * return SUCCESS or FAIL, and log reason for failure.
 */
static int
read_from_lines()
{
    struct str pstr;
    register struct str *path = &pstr;	/* build up sender path here */
    struct str lstr;
    register struct str *line = &lstr;	/* input line */
    int c;				/* input character */
    register char *lp;			/* temp for processing line */
    struct str ustr;
    register struct str *user = &ustr;	/* user name from From_ line */
    char *mark;				/* mark point in line */
    off_t line_mark;			/* seek position for start of line */

    DEBUG1(DBG_SPOOL_HI, "read_from_lines called (offset=%lu)\n", (unsigned long) tell_spool());
    /* if we already know the sender, then don't bother computing it */
    if (sender == NULL) {
	islocal = TRUE;			/* set FALSE if we find otherwise */
	STR_INIT(user);
	STR_INIT(path);
    }

    STR_INIT(line);

    /*
     * loop until we have read all of the From_ lines
     */
    for (;;) {
	/* read one complete line */
	STR_CLEAR(line);
	line_mark = tell_spool();
	while ((c = GETSPOOL()) != EOF && c != READ_FAIL && c != '\n') {
	    STR_NEXT(line, c);
	}
	if (c == READ_FAIL) {
	    write_log(WRITE_LOG_SYS, "read failed: %s/%s: %s",
		      spool_dir, input_spool_fn, strerror(errno));
	}
	if (c != EOF) {
	    STR_NEXT(line, c);
	}
	STR_NEXT(line, '\0');
	lp = STR(line);
	if (STR_LEN(line) < 20) {
	    /* less than 20 chars? couldn't possibly be a From_ line */
	    break;
	}
	/* skip beginning > character */
	if (*lp == '>') {
	    lp++;
	}
	/* do we have a From line? */
	if (strncmp(lp, "From", (size_t) 4) != 0 || !isspace((int) lp[4])) {
	    /* no, end of From_ lines */
	    break;
	}
	if (sender) {
	    continue;			/* we know sender scan next line */
	}
	/* extract the user */
	lp += 5;
	while (isspace((int) *lp)) {	/* scan for start of user name */
	    lp++;
	}
	mark = lp;			/* mark the start of the user name */
	/* skip over the user name token */
	lp = forward_token(lp);
	*lp = '\0';
	STR_CLEAR(user);
	STR_CAT(user, mark);

	/* search for a remote from host */
	lp = STR(line) + STR_LEN(line) - 2;
	while (isspace((int) *lp)) {
	    lp--;
	}
	lp[1] = '\0';			/* terminate end of potential host */
	lp = backward_token(STR(line), lp); /* back to start of host (?) */
	mark = lp;			/* host may be here */
	lp = backward_token(STR(line), lp-1); /* back to start of from (?) */
	if (strncmp(lp, "from", (size_t) 4) != 0 || !isspace((int) lp[4])) {
	    STR_CLEAR(path);		/* no remote from host, toss path */
	    DEBUG1(DBG_SPOOL_HI, "no remote from host in '%s', toss path\n",
		   STR(line));
	    continue;			/* next From_ line */
	}
	lp = backward_token(STR(line), lp-1); /* back to start of remote (?) */
	if (strncmp(lp, "remote", (size_t) 6) != 0 || !isspace((int) lp[6])) {
	    STR_CLEAR(path);		/* no remote from host, toss path */
	    DEBUG1(DBG_SPOOL_HI, "no remote from host in '%s', toss path\n",
		   STR(line));
	    continue;			/* next From_ line */
	}

	/*
	 * found a remote_from host, add it to the sender path
	 */
	if (STR_LEN(path) > 0) {
	    STR_NEXT(path, '!');	/* ! is separator */
	}
	STR_CAT(path, mark);
	islocal = FALSE;		/* not a local message */
	if (sender_host == NULL && sender_proto == NULL) {
	    sender_host = COPY_STRING(mark);
	    sender_proto = "uucp";
	}
    }

    STR_FREE(line);			/* finished processing input line */

    /*
     * if we are building up a sender, then finish doing so
     */
    if (sender == NULL) {
	STR_NEXT(path, '\0');		/* terminate path and user */
	STR_NEXT(user, '\0');
	finish_sender(STR(path), STR(user));
	STR_FREE(path);		/* free the storage used */
	STR_FREE(user);
    }

    /*
     * seek back to the beginning of the last line read, which was not
     * a From_ line
     */
    if (seek_spool((off_t) line_mark) == FAIL) {
	write_log(WRITE_LOG_SYS, "seek failed on: %s/%s: %s",
		  spool_dir, input_spool_fn, strerror(errno));
	return FAIL;
    }
    return SUCCEED;
}

/*
 * forward_token - scan past the following token
 *
 * return the position of the character after the first token after p.
 * a token ends in a space, but can include any chars in quotes, or
 * after a \.
 */
static char *
forward_token(p)
    register char *p;			/* start search here */
{
    register int inquote = FALSE;	/* not in a quote */

    while (isspace((int) *p))		/* scan to start of token */
	p++;

    /* loop exits by return when done */
    for (;;) {
	switch (*p++) {
	case '\\':
	    if (*p)
		p++;
	    break;
	case ' ':
	case '\t':
	case '\n':
	    if (!inquote) {
		return p-1;		/* past end of token */
	    }
	    break;
	case '\0':
	    return p-1;			/* past end of token */
	case '"':			/* start or end of a quote */
	    inquote = !inquote;
	    break;
	}
    }
}

/*
 * backward_token - scan to beginning of previous token
 *
 * return the position of the first character of a group
 * of non white-space characters before p.
 *
 * NOTE:  we don't take into account text in quotes in scanning
 * backwards.
 */
static char *
backward_token(s,p)
    register char *s;			/* string starts here */
    register char *p;			/* start search here */
{
    /* scan before end of token */
    while (isspace((int) *p)) {
	if (p == s) {
	    return p;
	}
	p--;
    }

    /* scan to start of token */
    while (*p && !isspace((int) *p)) {
	if (p == s) {
	    return p;
	}
	p--;
    }

    return p+1;
}

/*
 * finish_sender - build the sender address
 *
 * Build the sender address out of the path and user computed in
 * read_from_lines.
 *
 * Note this is only ever called if sender is NULL.
 */
static void
finish_sender(path, user)
    register char *path;		/* path to sender */
    register char *user;		/* user name of sender */
{
    char *target = NULL;		/* parsed target */
    char *remainder;			/* parsed remainder */
    register int form;			/* form from parse_address */

    if (user[0] == '\0') {
	return;				/* didn't find anything */
    }

    /* parse the user address, which may yield more path information */
    form = parse_address(user, &target, &remainder, (int *)0);

    if (form == FAIL) {
	/*
	 * failed to parse user, store an error for logging later
	 */
	write_log(WRITE_LOG_MLOG, "Xnotice: <> Error parsing sender address <%s>: %s", user, remainder);
	write_log(WRITE_LOG_PANIC, "Error parsing sender address <%s>: %s", user, remainder);
	sender = COPY_STRING("Postmaster");
    } else if (form == LOCAL && path[0] == '\0' && !islocal) {
	/*
	 * message was flagged as non-local, but we no longer have any
	 * host information, send it to the postmaster
	 */
	write_log(WRITE_LOG_MLOG, "Xnotice: <> Path to sender unknown:  sender = %s", user);
	write_log(WRITE_LOG_PANIC, "Path to sender unknown: sender = %s", user);
	sender = COPY_STRING("Postmaster");
    } else if (path[0] == '\0' && form == LOCAL) {
	/*
	 * simple case: no path, simple user, just make a copy of user
	 */
	sender = COPY_STRING(remainder);
    } else if (path[0] == '\0' && form == UUCP_ROUTE) {
	/*
	 * user is in !-route form, so put the user back together
	 */
	sender = xprintf("%s!%s", target, remainder);
    } else if (form == LOCAL) {
	/*
	 * simple user form, add it to path
	 */
	sender = xprintf("%s!%s", path, remainder);
    } else if (form == UUCP_ROUTE) {
	/*
	 * user is in !-route form, put it back together and add to path
	 */
	sender = xprintf("%s!%s!%s", path, target, remainder);
    } else {
	/*
	 * the difficult case, need to construct a !-route for user
	 */
	char *error;			/* store error here */
	char *route = build_uucp_route(remainder, &error, 0);

	if (route == NULL) {
	    /* found an error building the route */
	    write_log(WRITE_LOG_MLOG, "Xnotice: <> Error building sender: %s", error);
	    write_log(WRITE_LOG_PANIC, "Error building sender: %s", error);
	} else if (path[0] == '\0') {
	    /*
	     * no path, just put the user back together
	     */
	    sender = xprintf("%s!%s", target, route);
	} else {
	    /*
	     * put user back together and add to path
	     */
	    sender = xprintf("%s!%s!%s", path, target, route);
	}
    }
}


/*
 * write_body - write the message body to a stdio FILE pointer.
 *
 * the body of the message will be sent to the given file.  Pass
 * the the transport flags to send_spool in writing.
 *
 * return SUCCEED, WRITE_FAIL or READ_FAIL.
 */
int
write_body(f, flags)
    FILE *f;				/* write to this file */
    unsigned long flags;		/* transport flags */
{
    if (seek_spool(start_msg_body) == FAIL) {
	write_log(WRITE_LOG_SYS, "seek failed: %s/%s: %s",
		  spool_dir, input_spool_fn, strerror(errno));
	return READ_FAIL;
    }
    return send_spool(f, flags);
}


/*
 * check_grade - set grade for message to value from Precedence: field
 *
 * If a Precedence: field is specified for a message and this grade
 * is not the message's current grade, then change the grade for the
 * message, which will involve moving the file to a new name.
 */
void
check_grade()
{
    charplist_t *hq;			/* temp for scanning headers */

    /* grab the current grade */
    msg_grade = message_id[strlen(message_id) - 1];

    /* find the precedence field (or not) */
    for (hq = header; hq; hq = hq->succ) {
	if (HDREQ("precedence", hq->text)) {
	    /* found the precedence header */
	    int grade = parse_precedence(strchr(hq->text, ':') + 1);

	    if (grade && msg_grade != grade) {
		/*
		 * change the grade to the new grade, if we fail to
		 * change the grade, don't sweat it too badly.
		 */
		(void) new_grade(grade);
		msg_grade = grade;
	    }
	    break;
	}
    }
}

/*
 * log_incoming - put a message in the log file about the incoming message
 *
 * look for a message-id header field and log it, if it exists, otherwise
 * announce new mail without a previous message-id.
 */
void
log_incoming()
{
    charplist_t *hq;			/* temp for scanning headers */
    char *old_id = NULL;		/* previous message-id */
    char *trim_old_id = NULL;		/* trimmed previous message-id */
    char *resent_mid = NULL;		/* point to Resent-Message-Id field */
    char *mid = NULL;			/* point to Message-Id field */
    char *host_string = NULL;		/* combined host name and inet addr */
    time_t now;
    unsigned long int delay;

    for (hq = header; hq; hq = hq->succ) {
	if (HDREQ("resent-message-id", hq->text)) {
	    resent_mid = hq->text;
	    break;
	}
	if (HDREQ("message-id", hq->text)) {
	    mid = hq->text;
	}
    }
    if (resent_mid) {
	old_id = strchr(resent_mid, ':');
    } else if (mid) {
	old_id = strchr(mid, ':');
    }

    if (sender_host) {
	char *sender_really_different = NULL;
	char *sender_host_not_literal = NULL;

	if (sender_host_really) {
	    if (strcmpic(sender_host, sender_host_really) != 0) {
		sender_really_different = sender_host_really;
	    }
	}
	if (!sender_host_addr || (strcmpic(sender_host, sender_host_addr) != 0)) {
	    sender_host_not_literal = sender_host;
	}
	host_string = xprintf("%s%s%s%s%s%s%s",
			      sender_host_not_literal ? sender_host_not_literal : "",
			      sender_really_different ? "(" : "",
			      sender_really_different ? sender_really_different : "",
			      sender_really_different ? ")" : "",
			      sender_host_addr ? "[" : "",
			      sender_host_addr ? sender_host_addr : "",
			      sender_host_addr ? "]" : "");
    } else if (sender_host_addr) {
	host_string = xprintf("[%s]", sender_host_addr);
    }

    if (old_id) {
	register char *p;

	trim_old_id = xmalloc((size_t) strlen(old_id));
	for (p = trim_old_id, old_id++; *old_id; old_id++) {
	    if (!isspace((int) *old_id)) {
		*p++ = *old_id;
	    }
	}
	*p = '\0';
    }

    now = time((time_t *) NULL);
    if (now >= message_date()) {
	delay = now - message_date();
    } else {
	DEBUG2(DBG_NOTIFY_LO, "log_incoming(): is the clock running backwards?  now:%lu < message_date:%lu\n",
	       (unsigned long int) now, message_date());
	delay = 1;
    }

#if (SMAIL_LOG_STYLE == 2)
    /* Default new log style (as of 3.1.29) */
    /* This is SMAIL_LOG_STYLE=2 */
    write_log(WRITE_LOG_SYS, "Received FROM:%s%s%s%s%s PROGRAM:%s%s%s SIZE:%lu%s%s%s%s DELAY:%lu",
	      sender,
	      host_string ? " HOST:" : "",
	      host_string ? host_string : "",
	      sender_proto ? " PROTOCOL:" : "",
	      sender_proto ? sender_proto: "",
	      program,
	      trim_old_id ? " ORIG-ID:" : "",
	      trim_old_id ? trim_old_id : "",
	      (unsigned long) msg_size,
	      ident_sender ? " IDENT:" : "",
	      ident_sender ? ident_sender : "",
	      ident_method ? " ID-METHOD:" : "",
	      ident_method ? ident_method : "",
	      delay);
#else
# if (!defined(SMAIL_LOG_STYLE)) || (SMAIL_LOG_STYLE == 1)
    /* Old log style (as used by 3.1.28) */
    /* This is SMAIL_LOG_STYLE=1 */
    write_log(WRITE_LOG_SYS, "received%s%s%s%s%s%s%s%s%s%s%s%lu%s%s%s%s%s%s%lu%s",
	      "\n|\t     from: ",
	      sender,
	      host_string ? "\n|\t     host: " : "",
	      host_string ? host_string : "",
	      sender_proto ? "\n|\t protocol: " : "",
	      sender_proto ? sender_proto : "",
	      "\n|\t  program: ",
	      program,
	      trim_old_id ? "\n|\t  orig-id: " : "",
	      trim_old_id ? trim_old_id : "",
	      "\n|\t     size: ",
	      (unsigned long) msg_size,
	      " bytes",
	      ident_sender ? "\n|\t    ident: " : "",
	      ident_sender ? ident_sender : "",
	      ident_method ? "\n|\t ident-by: " : "",
	      ident_method ? ident_method : "",
	      "\n|\tarr-delay: ",
	      (unsigned long) delay,
	      " seconds");
# else
#  include "ERROR -  SMAIL_LOG_STYLE set to invalid value."
# endif
#endif
    if (host_string) {
	xfree(host_string);
    }
    if (trim_old_id) {
	xfree(trim_old_id);
    }
}

/*
 * scan_spool_dirs - scan for work and return spool files in sorted order
 *
 * look through all of the spool directories, and sort by precedence and
 * by creation date.
 *
 * the returned filename vector contains data which may be reused on
 * subsequent calls to scan_spool_dirs().
 */
char **
scan_spool_dirs(frozen)
    int frozen;				/* look for frozen messages instead? */
{
    static char **mv = NULL;		/* vector of messages */
    size_t mc = 0;			/* count of messages */
    static unsigned int mv_size = 32;	/* allocated vector entries */
    char *dn;				/* current directory to scan */
    static struct str str;		/* storage for filenames */
    register size_t i;			/* temp index */

    /* get the initial vector and string storage allocation */
    if (mv == NULL) {
	mv = (char **) xmalloc((size_t) mv_size * sizeof(*mv));
	STR_INIT(&str);
    } else {
	STR_CHECK(&str);
	STR_CLEAR(&str);
    }

    /* loop through all spool directories looking for work */
    for (dn = strcolon(spool_dirs); dn; dn = strcolon((char *)NULL)) {
	char *fn;			/* filename from directory */
	char *in_dn = xprintf(frozen ? "%s/error" : "%s/input", dn);

	DEBUG1(DBG_QUEUE_HI, "scan_spool_dirs() looking in %s\n", in_dn);

	for (fn = scan_dir(in_dn); fn; fn = scan_dir((char *)NULL)) {
	    int entry_grade;

	    /* see if the file matches the form of a spool file */
	    if (! isdigit((int) fn[0]) || strlen(fn) != SPOOL_FN_LEN) {
		DEBUG1(DBG_QUEUE_MID, "scan_spool_dirs() ignoring %s\n", fn);
		continue;		/* nope */
	    }
	    /*
	     * If we are scanning the queue BUT recipients is also set, then we
	     * use the contents of recipients to select out required messages
	     * to process.  Yes this is a bit of a bass-ackwards algorithm for
	     * this case, but....
	     */
	    if (parameters) {
		struct addr * ptr;
		char * msgid;
		int seen = FALSE;

		for (ptr = parameters; (ptr != NULL); ptr = ptr->succ) {
		    msgid = ptr->in_addr;
		    if (*msgid == 'm') /* Skip leading m if specified */
			msgid++;
		    if (strchr(msgid, '/')) /* use basename if path specified */
			msgid = strrchr(msgid, '/') + 1;
		    if ((seen = EQ(fn, msgid)))
			break;
		}
		if (!seen) {
		    DEBUG1(DBG_QUEUE_HI, "scan_spool_dirs() skipping %s (not in cmd-line list)\n", fn);
		    continue;
		}
	    }

	    /* See if the grade is within the range being processed */
	    /* But do not do this if printing the queue */
	    if (operation_mode != PRINT_QUEUE) {
		entry_grade =  fn[strlen(fn) - 1];
		if ((entry_grade < min_runq_grade) || (entry_grade > max_runq_grade)) {
		    DEBUG4(DBG_QUEUE_HI, "scan_spool_dirs() skipping %s (grade '%c' not in runq range '%c'-'%c')\n",
			   fn, entry_grade, min_runq_grade, max_runq_grade);
		    continue;	/* outside grade range */
		}
	    }

	    /* add to the list of files */
	    if (mc >= mv_size - 1) {
		/* note: we allow, in advance, for the last NULL entry */
		mv_size += 32;		/* get more entries */
		mv = (char **) xrealloc((char *) mv, (size_t) (mv_size * sizeof(*mv)));
	    }
	    /* use offsets for now, to be converted to (char *) later */
	    set_ptr(mv[mc++], STR_LEN(&str));
	    str_printf(&str, "%s/%s%N", in_dn, fn);	/* %N is nul byte */
	}
	xfree(in_dn);
    }

    /* close off the vectors */
    mv[mc] = NULL;

    /* convert the offsets in mv to (char *)'s */
    for (i = 0; i < mc; i++) {
	mv[i] = STR(&str) + get_ptr(mv[i]);
    }

    /* sort it */
    (void) qsort((char *)mv, mc, sizeof(*mv), queue_compare);

    return mv;
}

/*
 * queue_compare - compare two filenames which represent queue files
 */
static int
queue_compare(x, y)
    const void *x;
    const void *y;
{
    const char *a = *((const char * const *) x);
    const char *b = *((const char * const *) y);
    int a_grade = a[strlen(a) - 1];
    int b_grade = b[strlen(b) - 1];

    if (a_grade != b_grade) {
	return a_grade - b_grade;
    }

    return strcmpic(strrchr(a, '/'), strrchr(b, '/'));
}

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