/*
#ident	"@(#)smail/src:RELEASE-3_2_0_121:header.c,v 1.59 2005/08/31 16:39:46 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.
 */

/*
 * header.c:
 *	routines to process message headers.
 *
 *	external functions: process_header, read_header, write_header
 */

#include "defs.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <ctype.h>
#include <pwd.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 "smail.h"
#include "alloc.h"
#include "list.h"
#include "main.h"
#include "parse.h"
#include "addr.h"
#include "field.h"
#include "main.h"
#include "spool.h"
#include "log.h"
#include "exitcodes.h"
#include "smailstring.h"
#include "dys.h"
#include "transport.h"
#include "header.h"
#include "extern.h"
#include "debug.h"
#include "smailport.h"

/* variables defined in this file */
charplist_t *header;			/* list of header fields */

/* variables local to this file */
static int use_resent;			/* TRUE if Resent- fields exist */
static int remove_bcc_fields;		/* TRUE to remove Bcc: header fields */
static char *to_fn;			/* name of To: field */
static char *from_fn;			/* name of From: field */
static char *reply_to_fn;		/* name of Reply-To: field */
static char *sender_fn;			/* name of the Sender: field */
static char *cc_fn;			/* name of the Cc: field */
static char *bcc_fn;			/* name of the Bcc: field */
static char *date_fn;			/* name of the Date: field */
static char *message_id_fn;		/* name of the Message-Id: field */
static int found_to;			/* TRUE if To: field was found */
static int found_message_id;		/* TRUE if Message-Id: field found */
static int found_from;			/* TRUE if From: field was found */
static int found_cc;			/* TRUE if Cc: field was found */
static int found_bcc;			/* TRUE if Bcc: field was found */
static int found_date;			/* TRUE if Date: field was found */
static int found_sender;		/* TRUE if Sender: field was found */
static int count_received;		/* count of Received: fields found */
static struct addr *header_from;	/* list of From: addrs */
static charplist_t *remote_header;	/* simple remote-form header */
static charplist_t *strict_header;	/* strict RFC822 header */

/* functions local to this file */
static void check_for_resent __P((void));
static void scan_header __P((struct addr **, char **));
static void new_field __P((char *));
static char *build_to_field __P((void));
static char *build_date_field __P((void));
static char *build_message_id_field __P((void));
static char *build_from_field __P((char *));
static int write_field __P((char *, FILE *, int));
static char *build_received_field __P((struct addr *));
static char *build_return_path __P((struct addr *));
static void build_remote_header __P((void));
static void build_strict_header __P((void));


/*
 * process_header - first pass processing of a header.
 *
 * Note the existence of certain headers and, if input rq is non-nil
 * fill it with a list of recipient addresses taken from either the
 * Resent-To: Resent-Cc: and Resent-Bcc: fields or from the To: Cc:
 * and Bcc: fields.
 */
char *
process_header(rq)
    struct addr **rq;			/* put list of recipients here */
{
    char *error;

    DEBUG(DBG_HEADER_MID, "process_header() called....\n");

    /*
     * initialize the data to be searched for in the header
     */
    use_resent = FALSE;
    found_to = FALSE;			/* nothing found yet */
    found_message_id = FALSE;
    found_from = FALSE;
    found_date = FALSE;
    found_sender = FALSE;
    found_cc = FALSE;
    found_bcc = FALSE;
    count_received = 0;
    header_from = NULL;			/* initialize From: addr list */
    strict_header = NULL;		/* no strict RFC822 header yet */

    /*
     * Bcc: fields are removed only if we are processing the header
     * for addresses, otherwise we assume that the user agent would
     * have removed them if it didn't want them passed along
     */
    remove_bcc_fields = (rq != NULL);

    check_for_resent();			/* setup use_resent */
    error = NULL;
    scan_header(rq, &error);		/* scan for fields and recipients */

    if (rq && error) {
	/* got an error message while extracting recipients, done */
	return error;
    }
    error = NULL;

    /*
     * if no recipient fields given, create a To: field containing
     * the known recipients
     */
    if (! (found_to || found_cc || found_bcc) && rq == NULL) {
	new_field(build_to_field());
    }

    /*
     * if no From: field exists, and no Sender: field exists, create
     * a From: field.
     *
     * otherwise, if no Sender: field exists and From: is not verified
     * to be the actual sender, create a Sender: field.
     */
    if (! (found_from || found_sender)) {
	if (!islocal) {
	    from_fn = use_resent ? "Resent-From" : "From";
	}
	new_field(build_from_field(from_fn));
    } else if (islocal && found_from && !found_sender) {
	/*
	 * With no, or multiple, addresses in the From: field, always generate
	 * a Sender: field.
	 */
	if (! header_from || header_from->succ) {
	    new_field(build_from_field(sender_fn));
	} else if (!sender_is_trusted) {
	    char *work_addr = COPY_STRING(header_from->work_addr);
	    char *target = NULL;
	    char *remain;
	    int parseflags = header_from->parseflags;
	    int form = parse_address(work_addr, &target, &remain, &parseflags);
	    struct passwd *pw;
	    int dobuild = FALSE;

	    /*
	     * if the sender is not trusted, allow the From: field only
	     * if it is in a form considered valid for the user.
	     */
	    switch (form) {
	    case FAIL:
		dobuild = TRUE;
		break;

		/*
		 * catch all of the remote cases here
		 */

	    default:
		if (! islocalhost(target) && !EQIC(target, visible_name)) {
		    dobuild = TRUE;
		    break;
		}
		form = parse_address(remain, (char **) NULL, &error, &parseflags);
		if (form != LOCAL) {
		    dobuild = TRUE;
		    break;
		}
		/*FALLTHROUGH*/

	    case LOCAL:
		pw = getpwbyname(FALSE, remain);
		if (pw == NULL || pw->pw_uid != real_uid) {
		    dobuild = TRUE;
		}
		break;
	    }
	    if (dobuild) {
		new_field(build_from_field(sender_fn));
	    }
	    xfree(work_addr);
	}
    }

    /*
     * if no date field given, create one
     */
    if (!found_date) {
	new_field(build_date_field());
    }

    /*
     * if no message-id field given, create one
     */
    if (!found_message_id) {
	new_field(build_message_id_field());
    }

    /*
     * note count of received lines if count is not already known
     */
    if (hop_count < 0) {
	hop_count = count_received;
    }

    return error;			/* return any error messages */
}

/*
 * set use_resent if there exist any standard Resent- fields in
 * the header
 */
static void
check_for_resent()
{
    register charplist_t *q;		/* index to headers */

    /* on the first pass check for Resent- headers */
    for (q = header; q; q = q->succ) {

	/*
	 * some systems (at least pcc for microport/286) can't handle
	 * an expression with four HDREQ macros in one if, so split it
	 * up into two if's.
	 */

	if (HDREQ("resent-to", q->text) ||
	    HDREQ("resent-from", q->text))
	{
	    use_resent = TRUE;
	    break;
	}
	if (HDREQ("resent-cc", q->text) ||
	    HDREQ("resent-bcc", q->text))
	{
	    use_resent = TRUE;
	    break;
	}
    }

    /* set the address field names based on this information */
    if (use_resent) {
	to_fn = "Resent-To";
	from_fn = "Resent-From";
	reply_to_fn = "Resent-Reply-To";
	sender_fn = "Resent-Sender";
	cc_fn = "Resent-Cc";
	bcc_fn = "Resent-Bcc";
	date_fn = "Resent-Date";
	message_id_fn = "Resent-Message-Id";
    } else {
	to_fn = "To";
	from_fn = "From";
	reply_to_fn = "Reply-To";
	sender_fn = "Sender";
	cc_fn = "Cc";
	bcc_fn = "Bcc";
	date_fn = "Date";
	message_id_fn = "Message-Id";
    }
}

/*
 * scan_header -  pass through searching for required headers.
 *
 * If we are extracting recipient addresses from the header, do
 * so here.
 */
static void
scan_header(rq, error)
    struct addr **rq;			/* put recipients here, if non-NULL */
    char **error;			/* store error message here */
{
    register charplist_t *q;		/* temp for scanning header */

    for (q = header; q; q = q->succ) {
	if (hop_count < 0 && HDREQ("Received", q->text)) {
	    count_received++;		/* count Received: fields */
	} else if (HDREQ(to_fn, q->text)) {
	    found_to = TRUE;
	    if (islocal) {
		/* XXX q->text leaks */
		q->text = process_field(q->text, strchr(q->text, ':') + 1,
					visible_name, uucp_name,
					rq, (F_LOCAL | F_STRICT), error);
	    } else if (rq) {
		(void) process_field((char *) NULL, strchr(q->text, ':') + 1,
				     (char *) NULL, (char *) NULL,
				     rq, 0, error);
	    }
	} else if (HDREQ("apparently-to", q->text) ||
		   HDREQ("to", q->text) ||
		   HDREQ("resent-to", q->text))	{
	    found_to = TRUE;
	} else if (HDREQ(message_id_fn, q->text)) {
	    char *id = COPY_STRING(strchr(q->text, ':') + 1);

	    /*
	     * if the field is "empty" (only whitespace, comments, or nothing)
	     * then it is bogus and must be deleted....
	     */
	    strip_rfc822_whitespace(id);
	    strip_rfc822_comments(id);
	    if (!*id || EQ(id, " ")) {
		charplist_t **pq;

		for (pq = &header; *pq && *pq != q; pq = &(*pq)->succ) {
		    ;
		}
		/* XXX q->text leaks */
		if (*pq) {
		    *pq = q->succ;
		}
		/* XXX maybe we should log the original value??? */
		write_log(WRITE_LOG_SYS, "Error in %s field: ID value missing, rewriting with local ID", message_id_fn);
	    } else {
		found_message_id = TRUE;
	    }
	    xfree(id);
	} else if (HDREQ(from_fn, q->text)) {
	    found_from = TRUE;
	    if (islocal) {
		/* XXX q->text leaks */
		q->text = process_field(q->text, strchr(q->text, ':') + 1,
					(char *) NULL, (char *) NULL,
					&header_from, (islocal ? F_LOCAL : 0), error);
	    } else {
		(void) process_field((char *) NULL, strchr(q->text, ':') + 1,
				     (char *) NULL, (char *) NULL,
				     &header_from, 0, error);
	    }
	} else if (HDREQ(date_fn, q->text)) {
	    found_date = TRUE;
	} else if (HDREQ(sender_fn, q->text)) {
	    if (!sender_is_trusted) {
		charplist_t **pq;

		/*
		 * Non-trusted users can't supply their own Sender: fields,
		 * so delete this field.
		 */
		for (pq = &header; *pq && *pq != q; pq = &(*pq)->succ) {
		    ;
		}
		/* XXX q->text leaks */
		if (*pq) {
		    *pq = q->succ;
		}
		/* XXX maybe we should log the original value??? */
		write_log(WRITE_LOG_SYS, "Notice: deleted %s field supplied by non-trusted user", sender_fn);
	    } else {
		found_sender = TRUE;
		if (islocal) {
		    /* XXX q->text leaks */
		    q->text = process_field(q->text, strchr(q->text, ':') + 1,
					    (char *) NULL, (char *) NULL,
					    &header_from, (islocal ? F_LOCAL : 0), error);
		} else {
		    (void) process_field((char *) NULL, strchr(q->text, ':') + 1,
					 (char *) NULL, (char *) NULL,
					 &header_from, 0, error);
		}
	    }
	} else if (HDREQ(cc_fn, q->text)) {
	    found_cc = TRUE;
	    if (islocal) {
		/* XXX q->text leaks */
		q->text = process_field(q->text, strchr(q->text, ':') + 1,
					(char *) NULL, (char *) NULL,
					rq, (islocal ? F_LOCAL : 0), error);
	    } else if (rq) {
		(void) process_field((char *) NULL, strchr(q->text, ':') + 1,
				     (char *) NULL, (char *) NULL,
				     rq, 0, error);
	    }
	} else if (HDREQ(bcc_fn, q->text)) {
	    found_bcc = TRUE;
	    if (islocal) {
		/* XXX q->text leaks */
		q->text = process_field(q->text, strchr(q->text, ':') + 1,
					(char *) NULL, (char *) NULL,
					rq, (islocal ? F_LOCAL : 0), error);
	    } else if (rq) {
		(void) process_field((char *) NULL, strchr(q->text, ':') + 1,
				     (char *) NULL, (char *) NULL,
				     rq, 0, error);
	    }
	} else if (HDREQ("precedence", q->text)) {
	    /*
	     * a precedence field is specified, use it to set the message
	     * priority
	     */
	    int newgrd = parse_precedence(strchr(q->text, ':'));

	    if (newgrd) {
		msg_grade = newgrd;
	    }
	}
    }
}


/*
 * Create a To: header field
 */
static char *
build_to_field()
{
    register struct addr *rq;		/* recipients to put in To: field */
    struct str str;			/* region for string macros */
    int col;				/* current output line column */

    rq = recipients;

    /*
     * Sendmail inserted an "Apparently-To:" field because RFC822 people
     * complained about it being against the rules to add a "To:" line.
     *
     * Well, it's not really -- they didn't know their own rules very well.
     *
     * The "To:" header is required, and it's fair to use the known recipient
     * list to re-construct it should it be found to be missing.
     *
     * Note that more recently Sendmail has defaulted to creating a "To:"
     * header containing the empty group:  "Undisclosed-recipients:;".  They
     * justify this as a way to avoid the worry that BCC recipients might be
     * revealed, but if any MTA is downright stupid enough to use BCC but not
     * include an empty "To:" header itself deserves to be blamed for leaks.
     */
#ifdef BOGUS_OLD_SENDMAIL_COMPATABILITY
    if (!islocal) {
	if (use_resent) {
	    to_fn = "Apparently-Resent-To";
	} else {
	    to_fn = "Apparently-To";
	}
    }
#endif

    /*
     * now we actually form the To: line from the known recipient
     * addresses
     */
    STR_INIT(&str);			/* initialize dynamic string region */

    /* form = To: recipient(, recipient)* */

    rq = recipients;
    /* special case, insert the first address */
    if (rq) {
	if (rq->in_addr[0] == '@') {
	    /* route-addr, put it in angle brackets */
	    str_printf(&str, "%s: <%s>", to_fn, rq->in_addr);
	} else {
	    /* otherwise just use the address */
	    str_printf(&str, "%s: %s", to_fn, rq->in_addr);
	}
	rq = rq->succ;
    }
    col = STR_LEN(&str);
    while (rq) {
	int len = strlen(rq->in_addr);

	STR_NEXT(&str, ',');
	col++;

	/*
	 * put at least 27-chars on a line, try to keep down below 72
	 * NOTE:  we aren't counting TAB characters hidden in the
	 *	  addresses.
	 */
	if (col > 27 && col + len > 72) {
	    STR_NEXT(&str, '\n');
	    STR_NEXT(&str, '\t');
	    col = 8;
	} else {
	    STR_NEXT(&str, ' ');
	    col++;
	}

	if (rq->in_addr[0] == '@') {
	    str_printf(&str, "<%s>", rq->in_addr);
	    len += 2;			/* 2 chars for the angel brackets */
	} else {
	    STR_CAT(&str, rq->in_addr);
	}
	col += len;
	rq = rq->succ;
    }
    STR_NEXT(&str, '\n');
    STR_NEXT(&str, '\0');

    STR_DONE(&str);
    return STR(&str);			/* return finished To: field */
}

/*
 * build a Date: field
 *
 * XXX could be combined with build_message_id_field()
 */
static char *
build_date_field()
{
    char *s = NULL;			/* returned header field */

    if (date_field) {
	s = expand_string(date_field, (struct addr *) NULL, (char *) NULL, (char *) NULL);
    }
    if (s == NULL || *s == '\0') {
	write_log(WRITE_LOG_PANIC, "Error in $date_field: %s", date_field);
	return "";
    }
    if (use_resent) {
	return xprintf("Resent-%s", s);
    } else {
	return COPY_STRING(s);
    }
}

/*
 * Form a Message-Id: header field.
 *
 * XXX could be combined with build_date_field()
 */
static char *
build_message_id_field()
{
    char *s = NULL;

    if (message_id_field) {
	s = expand_string(message_id_field, (struct addr *) NULL, (char *) NULL, (char *) NULL);
    }
    if (s == NULL || *s == '\0') {
	write_log(WRITE_LOG_PANIC, "Error in $message_id_field: %s", message_id_field);
	return "";
    }
    if (use_resent) {
	return xprintf("Resent-%s", s);
    } else {
	return COPY_STRING(s);
    }
}

/*
 * form a From: or Sender: header field
 */
static char *
build_from_field(fn)
    char *fn;				/* field name */
{
    char *s = NULL;

    if (from_field) {
	s = expand_string(from_field, (struct addr *) NULL, (char *) NULL, sender);
    }
    if (s == NULL || *s == '\0') {
	write_log(WRITE_LOG_PANIC, "Error in $from_field: %s", from_field);
	return "";
    }
    /*
     * XXX this is really stupid and inviting of errors, but the manual says:
     *
     *    "The expanded string must begin with ``From:'', which may be replaced
     *    by other strings to form an actual header field."
     *
     * However the original code always allowed it to be copied verbatim.
     *
     * The right way would have been to specify $from_field as only providing
     * the field content while always prepending the given field name (fn).
     */
    if (! HDREQ("from", s)) {
	write_log(WRITE_LOG_PANIC, "Error in $from_field, does not begin with 'From:': %s", from_field);
	return COPY_STRING(s);
    }
    return xprintf("%s%s", fn, s + 4);
}

/*
 * link in a brand new field with the given text
 */
static void
new_field(field)
    char *field;			/* new field to add */
{
    register charplist_t *f;		/* header field entry */

    f = (charplist_t *) xmalloc(sizeof(charplist_t)); /* create it */
    f->text = field;
    f->succ = header;			/* link it in */
    header = f;
}

/*
 * parse_precedence - parse a Precedence: field and return it's grade value
 *
 * use tokenize to parse for an atom (the first atom is used, the rest are
 * ignored).  Then lookup the atom in the grades string and return the
 * associated grade, if found, 0 otherwise.
 */
int
parse_precedence(field)
    char *field;
{
    size_t len;				/* length of token */
    struct token *tok;			/* list of tokens from tokenize */
    register char *p = grades;		/* temp for scanning grades */
    char *error;			/* error message from tokenize */

    DEBUG(DBG_HEADER_HI, "parse_precedence called\n");
    error = tokenize(field, &tok, FALSE, TRUE);
    if (error) {
	/* tokenize didn't like the field for some reason */
	write_log(WRITE_LOG_MLOG, "Xnotice: <> Error in precedence field: %s", error);
	return '\0';			/* no precedence value */
    }
    if (tok->form != T_TEXT) {
	return '\0';			/* not a text (atom) token */
    }
    len = strlen(tok->text);
    DEBUG1(DBG_HEADER_MID, "precedence is %s\n", tok->text);
    while (p) {
	if (strncmpic(tok->text, p, len) == 0 && p[len] == ':') {
	    /* we have a match, grab the character */
	    return p[len+1];
	}
	/* skip to end of precedence string */
	p = strchr(p, ':');
	if (p) {
	    /* skip past grade character */
	    p = strchr(p, ':');
	    if (p) {
		p++;			/* now at next precedence string */
	    }
	}
    }

    DEBUG(DBG_QUEUE_MID, "unknown precedence\n");
    return '\0';			/* unknown precedence */
}


/*
 * read_header - initialize header structures, reading from spool file
 *
 * Read a header on the spool file.  The header data structure is
 * initialized to contain an entry for each field of the header.
 *
 * returns SUCCEED or FAIL, the actual failure is logged.
 */
int
read_header()
{
    struct str field;			/* input field */
    register int c;			/* current input char */
    int last_c = EOF;			/* last input char */
    charplist_t **link_addr;		/* forward link for next field */
    off_t line_mark = 0;		/* marks beginning of a line */

    link_addr = &header;

    /*
     * loop until done reading the header
     */
    for (;;) {
	STR_INIT(&field);		/* NOTE: do no re-use string */
	/*
	 * field name is some non-space chars followed by
	 * optional white space up to a ':'
	 *
	 * Note the allowance for whitespace before the colon, is from RFC
	 * 2822's "obsolete" header fields syntax:
	 *
	 *    4.5.8. Obsolete optional fields
	 *    
	 *    obs-optional    =       field-name *WSP ":" unstructured CRLF
	 *    
	 * assuming:
	 *
	 *    WSP             =       SP / HTAB
	 *
	 * Note that RFC 822 §3.2 did _not_ allow for whitespace before the
	 * colon:
	 *
	 *     field       =  field-name ":" [ field-body ] CRLF
	 *
	 *     field-name  =  1*<any CHAR, excluding CTLs, SPACE, and ":">
	 *
	 *     field-body  =  field-body-contents
	 *                    [CRLF LWSP-char field-body]
	 *
	 *     LWSP-char   =  SPACE / HTAB                 ; semantics = SPACE
	 *
	 * The same syntax for "field" is repeated in §B.1.
	 *
	 * So in fact the use of "*WSP" in RFC 2822 §4.5.8 is not a true
	 * requirement for compatability with RFC 822, though it could be
	 * considered part of a requirement for compatability with RFC 733
	 * which allowed LWSP-chars in the field name, as per §c which says "To
	 * aid in the creation and reading of field-names, the free insertion
	 * of LWSP-chars is allowed in reasonable places."  Examples of headers
	 * in RFC 733 §V.D.3 line up the colons with surrounding whitespace.
	 * However since RFC 822 explicitly obsoleted that syntax as per
	 * §C.1.1, so I've no idea why RFC 2822 partially resurrected it again.
	 * In any case Smail has always(?) allowed the 2822 behaviour.
	 */
	if (last_c == ':') {
	    /* we had a read ahead character past the last newline */
	    c = EOF;			/* don't allow null fieldnames */
	} else {
	    if (last_c != EOF) {
		STR_NEXT(&field, last_c);
		line_mark = tell_spool() - 1;
		last_c = EOF;
	    } else {
		/*
		 * save the beginning of the line, so we can go back to
		 * here if this is not a header
		 */
		line_mark = tell_spool();
	    }
	    while ((c = GETSPOOL()) != EOF && c != READ_FAIL && !isspace((int) c) && c != ':') {
		STR_NEXT(&field, c);
		last_c = c;
	    }
	    while (c != EOF && c != READ_FAIL && isspace((int) c)) {
		    STR_NEXT(&field, c);
		    c = GETSPOOL();
	    }
	    if (c == READ_FAIL) {
		write_log(WRITE_LOG_SYS, "read error in spool file <%s/%s>",
			  spool_dir, spool_fn);
		return FAIL;
	    }
	}

	/* not a valid field name, must be past the header */
	if (c != ':') {
	    break;
	}

	/*
	 * looks like we are in a header field, so add the ':' and then
	 */
	STR_NEXT(&field, c);
	last_c = c;

	/*
	 * Read the header field contents, terminated by newline which is not
	 * followed by a SPACE or TAB char, or by EOF.
	 */
	while ((c = GETSPOOL()) != EOF && c != READ_FAIL && !(last_c == '\n' && c != ' ' && c != '\t')) {
	    STR_NEXT(&field, c);
	    last_c = c;
	}
	if (c == READ_FAIL) {
	    write_log(WRITE_LOG_SYS, "read error in spool file <%s/%s>",
		      spool_dir, spool_fn);
	}

	last_c = c;			/* preserve this for the next field */

	STR_NEXT(&field, '\0');
	STR_DONE(&field);
#if 0
	append_charplist(&link_addr, STR(&field));
#else
	*link_addr = (charplist_t *) xmalloc(sizeof(charplist_t));
	(*link_addr)->text = STR(&field);
	link_addr = &(*link_addr)->succ;
#endif
	/*
	 * msg body starts now if we're at EOF, or if we have a second
	 * consecutive newline....
	 */
	if (c == EOF || c == '\n') {
	    line_mark = -1;		/* don't seek back to previous line */
	    STR_CLEAR(&field);		/* don't allow this field to be freed */
	    break;
	}
    }

    *link_addr = NULL;			/* terminate header queue */

    /*
     * all done reading the field, cleanup, seek to beginning of
     * message body (after last valid field line)
     */
    if (line_mark >= 0 && seek_spool((off_t) line_mark) == FAIL) {
	write_log(WRITE_LOG_SYS, "seek error on spool file <%s/%s>",
		  spool_dir, spool_fn);
	return FAIL;
    }
    if (STR(&field) && STR_LEN(&field)) {
	STR_FREE(&field);		/* free the field that wasn't */
    }

    return SUCCEED;
}

/*
 * write_header - write out the header to a stdio FILE
 *
 * inputs:
 *	file	- file to write to.
 *	addr	- involved address, (note only first in this linked list is used)
 *
 * outputs:
 *	SUCCEED or FAIL
 */
int
write_header(f, addr)
    FILE *f;				/* file to send header to */
    struct addr *addr;			/* for the per-address transport */
{
    unsigned long flags = addr->transport->flags; /* transport flags */
    charplist_t *q, *q2;		/* temp for processing header */
    int crlf = (flags & PUT_CRLF)?1:0;	/* put \r\n at end of each line? */
    char *s;

    /* output transport-specific "insert_header" headers */
    for (q2 = addr->transport->hdrinsert; q2; q2 = q2->succ) {
	s = expand_string(q2->text, addr, (char *) NULL, (char *) NULL);
	if (s == NULL || *s == '\0') {
	    write_log(WRITE_LOG_PANIC, "transport %v, Error in $insert_header: %s", addr->transport->name, q2->text);
	    return FAIL;
	}
	if (write_field(s, f, crlf) == FAIL) {
	    return FAIL;
	}
    }

    /* output a Return-Path: header if required */
    if (flags & PUT_RETURNPATH) {
	if (write_field(build_return_path(addr), f, crlf) == FAIL) {
	    return FAIL;
	}
    }

    /* output a Received: header if required */
    if (flags & PUT_RECEIVED) {
	if (write_field(build_received_field(addr), f, crlf) == FAIL) {
	    return FAIL;
	}
    }

    if (use_resent) {
	/*
	 * the situation gets complex if resent- headers are involved,
	 * so ignore the issue by not changing anything.
	 */
	q = header;
    } else if (flags & STRICT_TPORT) {
	/* output a strict header, building one if none exists */
	if (strict_header == NULL) {
	    build_strict_header();
	}
	q = strict_header;
    } else if (flags & LOCAL_XFORM) {
	/* output header as is */
	q = header;
    } else {
	/* output a normal remote header */
	if (remote_header == NULL) {
	    build_remote_header();
	}
	q = remote_header;
    }

    /*
     * output the appropriate header fields, as selected above
     */
    for (; q; q = q->succ) {
	/*
	 * Remove the contents of bcc fields if addresses were extracted from
	 * the header.  Otherwise, we expect that the MUA would have removed
	 * them if they were not to be delivered.
	 *
	 * Bcc contents are *always* stripped from locally originating mail.
	 *
	 * Leave the empty Bcc header to conform to RFC-822 4.5.3.
	 */
	if ((remove_bcc_fields || islocal) && HDREQ(bcc_fn, q->text)) {
	    char bcc_head[sizeof("resent-bcc: \r\n\0")]; /* XXX magic string is max len */

	    strcpy(bcc_head, bcc_fn);
	    strcat(bcc_head, ":\n");
	    if (write_field(bcc_head, f, crlf) == FAIL) {
		return FAIL;
	    }
	    continue;
	}
	/* don't output redundant Return-Path headers */
	if ((flags & PUT_RETURNPATH) && HDREQ("Return-Path", q->text)) {
	    continue;
	}
	/* skip over any transport-specific "remove_header" headers */
	for (q2 = addr->transport->hdrremove; q2; q2 = q2->succ) {
	    s = expand_string(q2->text, addr, (char *) NULL, (char *) NULL);
	    if (s == NULL || *s == '\0') {
		write_log(WRITE_LOG_PANIC, "transport %v: Error in $remove_header: %s", addr->transport->name, q2->text);
		return FAIL;
	    }
	    if (s && HDREQ(s, q->text)) {
		break;
	    }
	}
	if (q2 == NULL && write_field(q->text, f, crlf) == FAIL) {
	    return FAIL;
	}
    }

    /* output transport-specific "append_header" headers */
    for (q2 = addr->transport->hdrappend; q2; q2 = q2->succ) {
	if (write_field(expand_string(q2->text, addr,
				      (char *) NULL, (char *) NULL), f, crlf) == FAIL) {
	    return FAIL;
	}
    }

    DEBUG(DBG_HEADER_LO, "\n");

    return SUCCEED;
}

/*
 * write_field - output a header field, possibly changing \n to \r\n.
 *
 * return SUCCEED or FAIL.
 */
static int
write_field(field, f, crlf)
    register char *field;		/* field to be written */
    FILE *f;				/* file to send to */
    int crlf;				/* TRUE to write \r\n for \n */
{
    register int c;

    if (field == NULL || field[0] == '\0') {
	/* empty field, don't write out anything */
	return SUCCEED;
    }
    DEBUG1(DBG_HEADER_LO, "%s", field);
    while ((c = *field++)) {
	/* write out c, possibly with a '\r' first */
	if ((c == '\n' && crlf && putc('\r', f) == EOF) || putc(c, f) == EOF) {
	    return FAIL;
	}
    }
    /* if field did not end with \n, supply it */
    if (field[-2] != '\n') {
	DEBUG(DBG_HEADER_LO, "\n");
	if (crlf) {
	    if (putc('\r', f) == EOF) {
		return FAIL;
	    }
	}
	if (putc('\n', f) == EOF) {
	    return FAIL;
	}
    }
    return SUCCEED;
}

/*
 * form a Received: header field
 */
static char *
build_received_field(addr)
    struct addr *addr;
{
    char *s = NULL;			/* returned header field */

    if (received_field) {
	s = expand_string(received_field, addr, (char *) NULL, (char *) NULL);
    }
    if (s == NULL || *s == '\0') {
	write_log(WRITE_LOG_PANIC, "Error in $received_field: %s", received_field);
	return "";
    }
    return s;
}

/*
 * build a Return-Path: header field.
 */
static char *
build_return_path(addr)
    struct addr *addr;
{
    char *return_addr;
    char *s;

    return_addr = get_return_addr(addr);

    /* rewrite the magic internal error sender to be the normal error sender */
    if (return_addr && EQ(return_addr, "+")) {
	*return_addr = '\0';
    }
    s = xprintf("Return-Path: <%s>", return_addr ? return_addr : "MAILER-DAEMON" );
    if (return_addr) {
	xfree(return_addr);
    }

    return s;
}


/*
 * build_remote_header - build a simple remote-form header
 *
 * For From:, Sender:, and Reply-To: fields prepend localhost! on addresses
 * which are pure !-routes, if mail is originated local then convert user to
 * user@localdomain and qualify known domain abbreviations.
 */
static void
build_remote_header()
{
    charplist_t **rhq = &remote_header; /* where to put next field */
    charplist_t *hq;			/* temp for scanning header */
    char *error;			/* (ignored) error message */

    /*
     * copy the standard header to the remote header, transforming
     * sender and recipient fields as appropriate.
     */
    for (hq = header; hq; hq = hq->succ) {
	*rhq = (charplist_t *) xmalloc(sizeof(**rhq));
	(*rhq)->succ = NULL;
	if (HDREQ(from_fn, hq->text) || HDREQ(sender_fn, hq->text) || HDREQ(reply_to_fn, hq->text)) {
	    /*
	     * field containing a sender address:
	     *    prepend uucp_host! in !-routes
	     *    append @visiblename after local-form addresses
	     *    qualify known domain abbreviations, if local
	     */
	    (*rhq)->text = process_field(hq->text,
					 strchr(hq->text, ':') + 1,
					 visible_name,
					 uucp_name,
					 (struct addr **) NULL,
					 (islocal ? F_LOCAL : 0),
					 &error);
	} else if (islocal && (HDREQ(to_fn, hq->text) ||
			       HDREQ(cc_fn, hq->text) ||
			       HDREQ(bcc_fn, hq->text))) {
	    /*
	     * locally generated field containing recipient addresses:
	     *    append @visible_name after local-form addresses
	     *    qualify known domain abbreviations
	     */
	    (*rhq)->text = process_field(hq->text,
					 strchr(hq->text, ':') + 1,
					 visible_name,
					 (char *) NULL,
					 (struct addr **) NULL,
					 F_LOCAL,
					 &error);
	} else {
	    (*rhq)->text = hq->text;
	}
	rhq = &(*rhq)->succ;
    }
}

/*
 * build_strict_header - build a somewhat RFC822 conformant header
 *
 * For addresses which are in local form, in !-route form or in
 * remainder%target form, append @visiblename and prepend a !-route
 * path back to the sender.  This essentially qualifies the address
 * within the context of the current host, which will then be
 * routed through on replies to handle the !-routes.
 */
static void
build_strict_header()
{
    char *error;			/* error messages */
    charplist_t **shq = &strict_header; /* where to put next field */
    charplist_t *hq;			/* temp for scanning header */
    char *sender_path;			/* path to sender's machine */

    /*
     * build a path back to the sender's machine, which is a !-route
     * back to the sender with the last component (!username) removed
     * this will be passed to process_field as the uucp_name parameter
     * for recipient fields.
     */
    sender_path = build_uucp_route(sender, &error, 0);
    if (sender_path == NULL) {
	/*
	 * errors will generate a NULL sender path, so nothing will
	 * be prepended to addresses in recipient fields
	 */
	/* XXX paniclog too? */
	write_log(WRITE_LOG_MLOG,
		  "Xnotice: <> error building path to sender <%s>: %s",
		  sender, error);
    } else {
	char *p = sender_path + strlen(sender_path);

	/*
	 * back up over the trailing "!token" and terminate the string
	 */
	p = back_address_token(sender_path, p);
	if (p == NULL) {
	    /* XXX paniclog too? */
	    write_log(WRITE_LOG_MLOG,
		      "Xnotice: <> error building path to sender <%s>: unexpected end of token",
		      sender);
	    sender_path = NULL;
	} else if (p == sender_path) {
	    sender_path = NULL;
	} else {
	    p[-1] = '\0';		/* terminate the path */
	}
    }

    /*
     * copy the standard header to a new strict header, performing
     * the necessary transformations on sender and recipient fields
     */
    for (hq = header; hq; hq = hq->succ) {
	*shq = (charplist_t *) xmalloc(sizeof(**shq));
	(*shq)->succ = NULL;
	if (HDREQ(from_fn, hq->text) ||
	    HDREQ(sender_fn, hq->text) ||
	    HDREQ(reply_to_fn, hq->text)) {
	    /*
	     * field containing a sender address:
	     *    append @visible_name after local-form addresses
	     *    qualify known domain abbreviations, if local
	     */
	    (*shq)->text = process_field(hq->text,
					 (strchr(hq->text, ':') + 1),
					 visible_name,
					 (char *) NULL,
					 (struct addr **) NULL,
					 ((islocal ? F_LOCAL : 0) | F_STRICT),
					 &error);
	} else if (HDREQ(to_fn, hq->text) ||
		   HDREQ(cc_fn, hq->text) ||
		   HDREQ(bcc_fn, hq->text)) {
	    /*
	     * field containing a recipient address:
	     *    append @visible_name after !-route, local-form or u%d form
	     *    prepend sender_path! before the same set of addresses
	     *    qualify known domain abbreviations, if local
	     */
	    (*shq)->text = process_field(hq->text,
					 (strchr(hq->text, ':') + 1),
					 visible_name,
					 EQIC(sender_path, visible_name) ? (char *) NULL : sender_path,
					 (struct addr **) NULL,
					 ((islocal ? F_LOCAL : 0) | F_STRICT),
					 &error);
	} else {
	    (*shq)->text = hq->text;
	}

	shq = &(*shq)->succ;
    }
}

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


syntax highlighted by Code2HTML, v. 0.9.1