/*
 * Copyright (c) 1989, Keith Moore
 *
 * You may use this program for peaceful purposes, and make modifications
 *     to the source as you see fit.
 * You may give copies of this program to others provided that this notice
 *     remains intact, and that any changes not approved by the author are
 *     clearly marked as such.
 * You may not sell this program without the author's permission.
 */
/*
 * Ultrix Interface to mail11
 * Keith Moore
 *
 * Usage: mail11 [options] from name node recipients...
 *
 * from - this is the sender's mailbox.
 * The remote DECnet node will prepend the local node name to the From:
 * address, so it should not be supplied as part of the "from" argument.  If 
 * the original sender is on some other node, then the "from" argument should
 * be the return-path in terms of the local system.  The sendmail $g macro
 * works nicely here.  NOTE: the VMS MAIL "reply" command sends replies to
 * this address.
 *
 * name - this is the sender's full name.  mail11 will quote it before
 * sending to DECnet.  If the full name is not available, use an empty string.
 *
 * node - this is the DECnet node to connect to.  mail11 will upper case it.
 *
 * recipients - are the recipients at that node.  They should be local
 * addresses for the receiving system, so the remote node name should
 * NOT be included.  It is possible that some node name will be included,
 * though, since the mail may be forwarded through several nodes.
 *
 * standard input - should be an RFC-822 formatted mail message.  The To: field
 * and Subject: field (if present) will be copied to the header of the mail11
 * message by the remote mailer program.  A Cc: field will also be copied if
 * present in the message, and if supported by the remote mailer.  All headers
 * will be copied as part of the message body.
 *
 * Suggested sendmail configuration:
 * Flags = nFs
 * n - Don't prepend a Unix-style From line
 * F - Do supply an RFC822 From: line
 * s - Strip quoting characters.
 * Note:  the 's' flag is the only way to work around sendmail brain damage.
 * Sendmail loses backslash quotes (\") in envelope information.  So mail11
 * has code to try and intelligently restore the quotes where they should
 * be.
 *
 * Even though this program can deal with multiple recipients on the same
 * host, the 'm' mailer flag should probably not be used.
 *
 * Rewrite rules:
 * remove the remote DECnet host name from the recipient names.
 * e.g. user@node.decnet => user
 * and  user@othernode.decnet => othernode::user
 * and  user@somewhere.else => user@somewhere.else
 *
 * write the From: address(es) as if they were local to the GATEWAY.
 * This is because the DECnet mail daemon on the other end automatically
 * prepends GATEWAY:: to the From: address that we supply.
 * e.g. user@gateway.host => user
 * and  user@other.host => user@other.host
 *
 * See ChangeLog for modification history.
 */

/*
 * To be done:
 *
 * Wrap long lines in the message header, and optionally in the
 * text.
 *
 * Don't generate any headers if they were none in the input stream.
 * This won't ever happen with sendmail, but might if other mailers
 * (or humans!) used this program.
 *
 * Implement an SMTP interface to this program, so we can handle multiple
 * recipients.
 *
 * Don't allow non-privileged users to forge mail using this program.
 * (They'll have to do it the hard way!)
 *
 * Eliminate redundant spaces caused by folded header lines.
 *
 * Eliminate linear white space in From: addresses (except in quoted strings)
 *
 * Make sure quoted addresses work.
 */


#define VERSION_STRING "v1.7"		/* for Received: postmark */

#include <stdio.h>
#include <sysexits.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <ctype.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/timeb.h>
#include <string.h>
#include "mail11.h"
#include "sysdep.h"

#ifdef __STDC__
#define P(x) x
#else
#define P(x) ()
#endif

char *malloc P((int));
char *calloc P((int, int));
int strcasecmp P((char *, char *));
void nerror P((char *));

struct recipient {
    struct recipient *next;		/* next recipient in list */
    char *untranslated_mailbox;		/* untranslated mailbox name */
    char *mailbox;			/* mailbox name */
    int flag;				/* delivery status flags */
#define PENDING -1			/* haven't tried yet */
};

char *hack_from P((char *));
char *hack_name P((char *));
char *hack_node P((char *));
char *hack_recipient P((char *));
char *path_from_addr P((char *));
char *calloc P((int, unsigned));
char *strsave P((char *));
char *extract_address P((char *));
char *extract_name P((char *));
char *build_to_line P((char *, struct recipient *));
char *strindex P((char *, char *));
void gather_headers();
void send_rfc822_headers();

/* The following characters are illegal or have special meaning to VMSMAIL */
#define BREAK_SET "@%!+\"\\."

/* define this to get the depercentification effect (a%b.enet -> b::a) */
/* #define FAKEDOM ".enet"			/*-*/

/*
 * Command line arguments
 */
char *argv_from = NULL;			/* best guess reply address */
char *argv_name = NULL;			/* sender's full name */
char *argv_node = NULL;			/* node to connect to */

/*
 * Recipient list
 */
struct recipient *recipient_list;	/* first element in list */
struct recipient *recipient_tail;	/* last element in list */

/*
 * VMS mail headers (converted from RFC822 headers)
 */
char *rfc822_apparently_to = NULL;	/* Apparently-to: header */
char *rfc822_cc = NULL;			/* Cc: header */
char *rfc822_from = NULL;		/* From: header */
char *rfc822_reply_to = NULL;		/* Reply-To: header */
char *rfc822_resent_cc = NULL;		/* Resent-Cc: header */
char *rfc822_resent_from = NULL;	/* Resent-From: header */
char *rfc822_resent_reply_to = NULL;	/* Resent-Reply_To: header */
char *rfc822_resent_sender = NULL;	/* Resent-Sender: header */
char *rfc822_resent_subject = NULL;	/* Resent-Subject: header */
char *rfc822_resent_to = NULL;		/* Resent-To: header */
char *rfc822_sender = NULL;		/* Sender: header */
char *rfc822_subject = NULL;		/* Subject: header */
char *rfc822_to = NULL;			/* To: header */

#define LINSIZ 512

char temp_buf[LINSIZ];			/* temporary storage for headers */

struct line {				/* linked list of header lines */
    struct line *next;
    int length;
    char line[1];
};
struct rfc822_header {
    struct line *head, *tail;
} rfc822_header;
#define HEADER_BANNER \
    "====== Internet headers and postmarks (see DECWRL::GATEWAY.DOC) ======"

/*
 * remote MAIL-11 object information
 */
int remote_object;			/* remote mail11 DECnet object */

struct mail11_config inconfig;
struct mail11_config outconfig = {
    PROTOCOL_VERSION, ECO, CUST_ECO, OS_ULTRIX, 0, 0, 0, 0, 0, 0,
};		/* pretend we are an ULTRIX (tm) system */


/*
 * Print out mailer options that are negotiated between the two systems.
 * (for debugging)
 */

show_config (str, ptr)
char *str;
struct mail11_config *ptr;
{
    char *osname;

    switch (ptr->os) {
    case OS_VMS:
	osname = "VMS";
	break;
    case OS_ULTRIX:
	osname = "ULTRIX";
	break;
    default:
	osname = "unknown";
	break;
    }
    fprintf (stderr, "%s configuration:\n", str);
    fprintf (stderr, "protocol_version = %d, eco = %d, cust_eco = %d\n",
	     ptr->protocol_version, ptr->eco, ptr->cust_eco);
    fprintf (stderr, "os = %d (%s), options = 0x%x\n",
	     ptr->os, osname, ptr->options);
    fprintf (stderr, "iomode = 0x%x <%s%s%s%s%s%s>\n",
	     ptr->iomode,
             (ptr->iomode & BLKSEND ? "BLKSEND," : "" ),
	     (ptr->iomode & BLKRECV ? "BLKRECV," : "" ),
	     (ptr->iomode & PFXSEND ? "PFXSEND," : "" ),
	     (ptr->iomode & PFXRECV ? "PFXRECV," : "" ),
	     (ptr->iomode & CCSEND  ? "CCSEND,"  : "" ),
	     (ptr->iomode & CCRECV  ? "CCRECV,"  : "" ));
    fprintf (stderr, "rfm = 0x%x, rat = 0x%x, spare1 = 0x%x, spare2 = 0x%x\n",
	     ptr->rfm, ptr->rat, ptr->spare1, ptr->spare2);
}

/*
 * Command-line options
 */

int showconfig = 0;			/* if 1, print config info to stderr */
int noheaders = 0;			/* if 1, don't copy RFC822 headers  */
int showdebug = 0;			/* if 1, print debugging info */

struct option {
    char *text;
    int *ptr;
    int value;
} options [] = {
    { "-showconfig", &showconfig, 1 },
    { "-noheaders", &noheaders, 1 },
    { "-debug", &showdebug, 1 },
};
    

/*
 * Set an option from the command line.
 */
do_option (str)
char *str;
{
    int i;
    int length;

    for (i = 0; i < sizeof options / sizeof *options; ++i) {
	length = strlen (options[i].text);
	if (strncmp (str, options[i].text, (size_t) length) == 0) {
	    if (options[i].text[length-1] == '=')
		*(options[i].ptr) = atoi (str + length);
	    else
		*(options[i].ptr) = options[i].value;
	    if (showdebug) {
		fprintf (stderr, "%s set to %d\n",
			 options[i].text+1, *(options[i].ptr));
	    }
	}
    }
}


main (argc, argv)
char **argv;
{
    int i;
    char *mail11_from;
    char *mail11_name;
    char *mail11_to;
    char *mail11_subj;
#ifdef CC
    char *mail11_cc;
#endif

    while (argc > 1 && (*argv[1] == '-')) {
	do_option (argv[1]);
	--argc;
	++argv;
    }

    if (argc < 4) {
	fprintf (stderr, "usage: %s from \"name\" to-node recipients...\n",
		 argv[0]);
	exit (EX_USAGE);
    }
    if (argc == 4)			/* no recipients */
	exit (EX_OK);

    /*
     * Process arguments.  Each of these must be transformed into a form
     * acceptable to the mail11 mailers.
     * 
     * The argv[1] argument probably corresponds to Return-Path:, and
     * is the last-resort reply address.
     * The argv[2] argument is the full name of argv[1].
     * The argv[3] argument is the DECnet node name.
     * argv[4]..argv[argc-1] are recipients.
     */
    argv_from = argv[1];
    argv_name = argv[2];
    argv_node = argv[3];

    for (i = 4; i < argc; ++i)
	add_recipient (argv[i]);

    if (showdebug)
	print_arguments ();
    
    /*
     * read the header portion of the message from stdin.
     * squirrel it away so we can spit it back out later.
     *
     * We do this before opening the DECnet channel so we
     * can tell the other end whether we have a Cc: line or
     * not.
     */
    gather_headers ();

    if (showdebug)
        print_headers ();

#ifdef CC
    /*
     * Set the Cc: flag if and only if we found a Cc: header.
     */
    if (rfc822_cc)
	outconfig.iomode |= CCSEND;
#endif
#ifdef BLOCKMODE
    /*
     * Say we are willing to send messages in block mode
     */
    outconfig.iomode |= BLKSEND;
    outconfig.rfm = RFM_VAR;	/* record format = variable length */
    outconfig.rat = RAT_CR;	/* record attributes = carraige return */
#endif

    /*
     * connect to mail11 object on remote node, and exchange config info.
     */
    remote_object = mail11_connect (hack_node (argv_node));

    /*
     * Figure out which of the several possible "From:" addresses
     * to give to MAIL-11.  This should be the address to which replies 
     * should be sent.
     */
    mail11_from = mail11_name = NULL;
    if (rfc822_resent_reply_to) {
	mail11_from = extract_address (rfc822_resent_reply_to);
	mail11_name = extract_name (rfc822_resent_reply_to);
    }
    else if (rfc822_resent_from) {
	mail11_from = extract_address (rfc822_resent_from);
	mail11_name = extract_name (rfc822_resent_from);
    }
    else if (rfc822_reply_to) {
	mail11_from = extract_address (rfc822_reply_to);
	mail11_name = extract_name (rfc822_reply_to);
    }
    else if (rfc822_from) {
	mail11_from = extract_address (rfc822_from);
	mail11_name = extract_name (rfc822_from);
    }
    else if (rfc822_resent_sender) {
	mail11_from = extract_address (rfc822_resent_sender);
	mail11_name = extract_name (rfc822_resent_sender);
    }
    else if (rfc822_sender) {
	mail11_from = extract_address (rfc822_sender);
	mail11_name = extract_name (rfc822_sender);
    }
    else if (argv_from) {
	mail11_from = argv_from;
	mail11_name = argv_name;
    }
    if (mail11_name == NULL && strcmp (mail11_from, argv_from) == 0)
	mail11_name = argv_name;
    if (mail11_from == NULL)
	mail11_from = "postmaster";
    if (mail11_name == NULL)
	mail11_name = "MAIL-11 Daemon";
    mail11_send_from (hack_from (mail11_from), hack_name (mail11_name));

    /*
     * send the list of recipients to the remote mail11 object
     * each recipient is verified at the other end, and status is 
     * passed back to us.  We keep stats on how many were good,
     * how many were bad, and how many were temporarily unavailable.
     */
    mail11_send_recipients (recipient_list);

    /*
     * Send the To: recipient line.
     */
    if (rfc822_resent_to)		/* resent-to: */
	mail11_to = rfc822_resent_to;
    else if (rfc822_to)			/* to: */
	mail11_to = rfc822_to;
    else if (rfc822_apparently_to)	/* apparently-to: */
	mail11_to = rfc822_apparently_to;
    else
	mail11_to = build_to_line (argv_node, recipient_list);
    mail11_put_envelope (mail11_to);

#ifdef CC
    /*
     * If the other end is expecting a Cc: line, send one.
     */
    if ((inconfig.iomode & CCRECV) == 0)
	;
    else {
	if (rfc822_resent_cc)
	    mail11_cc = rfc822_resent_cc;
	else if (rfc822_cc)
	    mail11_cc = rfc822_cc;
	else
	    mail11_cc = "";
	mail11_put_envelope (mail11_cc);
    }
#endif

    /*
     * Send the Subj: line
     */
    if (rfc822_subject)
	mail11_subj = rfc822_subject;
    else
	mail11_subj = "";
    mail11_put_envelope (mail11_subj);

    /*
     * Now we send the actual body of the message.  Since we lose the
     * rfc822 headers in the process, we copy these headers as part of the 
     * body.  To do: only copy the headers if the message originated
     * on another system, or if they contain additional information.
     */
    send_rfc822_message_body ();
    mail11_send_marker ();

    /* verify the status for each recipient */
    return (get_final_status (recipient_list));
}

/* 
 * Convert the From: line to a format acceptable to MAIL-11.  This consists
 * of *removing* Internet-style quoting (double quotes and/or backslash)
 * and putting double quotes around part of the address if necessary.
 */

char *
hack_from (from)
char *from;
{
    register char *src, *dest;
    int do_quote = 0;

    /* 
     * Look for characters that have special meaning to VMS mailer, 
     * or that indicate that this is an Internet address.
     */
    do_quote = 0;
    for (src = from; *src; ++src)
	if (strchr (BREAK_SET, *src)) {
	    do_quote = 1;
	    break;
	}

    /*
     * if quoting is necessary, preserve case, but handle special chars
     * inside quotes
     */
    dest = temp_buf;
    if (do_quote) {
	*dest++ = '"';			/* initial quote */
	for (src = from; *src; ++src) {
	    if (*src == '\\' && src[1])	/* if backslash, skip to next char */
		++src;
	    if (*src == '"')		/* if quote, double it */
		*dest++ = '"';
	    *dest++ = *src;		/* copy character */
	}
	*dest++ = '"';			/* final quote */
	*dest = '\0';
    }
    /*
     * otherwise, copy the name and upper case it also.
     */
    else {
	dest = temp_buf;
	for (src = from; *src; ++src)
	    *dest++ = islower (*src) ? toupper (*src) : *src;
	*dest = '\0';
    }
    return strsave (temp_buf);
}

/*
 * Convert the sender's personal name into mail11 form.
 */
char *
hack_name (name)
char *name;
{
    register char *src, *dest;

    if (name == NULL || *name == '\0')	/* no personal name supplied */
	return NULL;

    dest = temp_buf;
    *dest++ = '"';			/* supply initial quote */
    for (src = name; *src; ++src)
	if (*src == '"')
	    *dest++ = '\'';		/* change " to  ' */
        else if (isprint (*src) || *src == ' ')
	    *dest++ = *src;
    *dest++ = '"';			/* supply trailing quote */
    *dest = '\0';
    return strsave (temp_buf);
}

/*
 * convert the node name into mail11 form (upper-case it)
 */
char *
hack_node (node)
char *node;
{
    register char *src, *dest;

    if (showdebug)
	fprintf (stderr, "hack_node (%s) => ", node);
    dest = temp_buf;
    src = node;
    while (*src) {
	if (*src == ':')
	    break;
	else if (*src == '"')	/* don't copy double quotes */
	    continue;
	else if (islower (*src))
	    *dest++ = toupper (*src);
	else
	    *dest++ = *src;
	++src;
    }
    *dest = '\0';
    if (showdebug)
        fprintf (stderr, "%s\n", temp_buf);
    return strsave (temp_buf);
}

/*
 * add a recipient to the recipient list
 */
add_recipient (mailbox)
char *mailbox;
{
    struct recipient *new =
	(struct recipient *) calloc (1, sizeof (struct recipient));
    char *p, *q;

    if (new == (struct recipient *) NULL) {
	perror ("add_recipient: calloc");
	exit (EX_OSERR);
    }
    new->untranslated_mailbox = strsave (mailbox);
    if (q = path_from_addr (p = hack_recipient (mailbox)))
	new->mailbox = q;
    else
	new->mailbox = p;
    new->next = (struct recipient *) NULL;
    new->flag = PENDING;
    if (recipient_tail == (struct recipient *) NULL)
	recipient_list = recipient_tail = new;
    else {
	recipient_tail->next = new;
	recipient_tail = new;
    }
}

/*
 * convert a%b.enet%c.enet to c::b::a
 */
char *
path_from_addr (addr)
char *addr;
{
#ifndef FAKEDOM
    return NULL;
#else
    char *more, *term, *path, *temp;

    for (more = addr;  *more && !index("%@.", *more);  more++) {
	if (!isalnum(*more))
	    return NULL;
    }
    term = more;
    if (*more == '.' && !strncasecmp(FAKEDOM, more+1, (sizeof FAKEDOM)-1)) {
	more += (sizeof FAKEDOM) - 1 /*\0*/ + 1 /*.*/;
    }
    if (!*more) {
	/* bottomed out, start returning */
	temp = malloc (term - addr + 1);
	strncpy (temp, addr, term - addr);
	return temp;
    }
    if (!(path = path_from_addr (more+1)))
	return NULL;
    /* we're on our way back up.  path is the partially-complete ::-path,
     * and addr..term-1 is the current node that needs to be prepended to
     * it.  as an optimization, if the node we're about to prepend is al-
     * ready on the front of the string, don't prepend it again, just
     * return through.
     */
    if ((temp = index (path, ':')) && temp[1] == ':'
     && (temp - path) == (term - addr)
     && (!strncasecmp (addr, path, term - addr)))
	return path;
    temp = malloc (strlen (path)
		   + (term - addr)
		   + 3 /*::\0*/);
    sprintf (temp, "%.*s::%s", term - addr, addr, path);
    free (path);
    return temp;
#endif
}

/*
 * Return true iff the entire string is quoted in RFC822 format.
 */
int
entire_string_is_quoted (str)
char *str;
{
    int inquote = 0;
    while (*str) {
	if (*str == '"') {
	    inquote = !inquote;
	    ++str;
	}
	else if (*str == '\\' && str[1])
	    str += 2;
	else if (inquote)
	    ++str;
	else
	    return 0;
    }
    return 1;
}

/*
 * Convert a mailbox from rfc822 into mail11 form.  This may include 
 * uppercasing and/or quoting to make it acceptable to the remote
 * DECnet object.
 *
 * This routine is rather complicated since we must handle several
 * kinds of quoting.
 *
 * 1) If the entire recipient address is quoted, it might be because
 *    the address is okay for MAIL-11 but either illegal in RFC822
 *    or too hairy to pass sendmail without getting munged.  In this
 *    case we must strip off the outer layer of quotes.  It is possible,
 *    however, that the address contains embedded quote marks (\") or
 *    other quoted characters (like \\) that must be comverted to
 *    unquoted form.
 *
 * 2) Otherwise the address should be of the form
 *    <address> ::= <routing-path> <local-address>
 *    <routing-path> ::=  0 or more <prefix> es
 *    <local-address> ::= <user-name> | <quoted-string> | '@' filename
 *    <prefix> ::= <node> '::' | <number> '.' <number> '::' | <protocol> '%'
 * 
 *    However, we try to make things easier for sendmail config file
 *    authors and shell script writers by quoting the <local-address>
 *    when necessary (i.e. when it contains illegal characters), by
 *    enclosing the <local-address> in double quotes.
 */

char *
hack_recipient (mailbox)
char *mailbox;
{
    register char *src, *dest;
    int quote_mode = 0;

    if (mailbox == NULL)
	return "";

    /*
     * If entire mailbox string is quoted, remove quotes before sending.
     * While removing quotes, unquote any backslash-quoted characters.
     */
    if (entire_string_is_quoted (mailbox)) {
	src = mailbox;
	dest = temp_buf;

	++src;				/* skip initial quote */
	while (*src) {
	    if (*src == '\\' && src[1]) {
		src++;
		*dest++ = *src++;
	    }
	    else if (*src == '"' && src[1] == '\0') {
		++src;			/* skip ending quote if present */
		break;
	    }
	    else
		*dest++ = *src++;
	}
    }
    else {
	src = mailbox;
	dest = temp_buf;
	do {				/* while (*src) */
	    /* 
	     * If this is already a quoted string, just copy it intact.
	     */
	    if (*src == '"') {
		*dest++ = *src++;	/* copy initial quote */
		while (*src && *src != '"')
		    *dest++ = *src++;
		if (*src == '"')
		    *dest++ = *src++;	/* copy final quote */
	    }
	    else {
		/*
		 * This could either be a prefix or the local-address.
		 * Scan forward as far as the end of this token
		 * to see whether it contains any characters which
		 * require quoting.
		 */
		char *tmp = src;
		while (*tmp) {
		    if (*tmp == '\\' && tmp[1])
			++tmp;
		    if (*tmp == '"')	/* found a quoted string */
			break;
		    if (*tmp == '%')	/* VMS foreign protocol hack */
			break;
		    if (*tmp == ':' && tmp[1] == ':') /* DECnet node name */
			break;
		    if (index (BREAK_SET, *tmp)) {
			quote_mode = 1;
			break;
		    }
		    ++tmp;
		}
		/*
		 * If quoting, copy to end of string, converting to MAIL-11
		 * style quoting.  (\c => c, but \" => "")
		 */
		if (quote_mode) {
		    *dest++ = '"';
		    while (*src) {
			if (*src == '\\' && src[1]) {
			    ++src;
			    if (*src == '"')
				*dest++ = '"';
			    *dest++ = *src++;
			}
			else
			    *dest++ = *src++;
		    }
		    *dest++ = '"';		/* final quote */
		    /* NB (*src == '\0) here, terminating do..while loop */
		}
		/*
		 * Otherwise, copy to end of string, '%' or "::", uppercasing.
		 */
		else {
		    while (*src) {
			if (*src == '\\' && src[1]) {
			    ++src;
			    *dest++ = *src++;
			}
			else if (*src == '"') 	/* found quoted string */
			    break;
			else if (*src == '%') {	/* VMS protocol hook */
			    *dest++ = *src++;
			    break;
			}
			else if (*src == ':' && src[1] == ':') {
			    *dest++ = *src++;
			    *dest++ = *src++;
			    break;
			}
			else {
			    *dest++ = islower (*src) ? toupper(*src) : *src;
			    ++src;
			}
		    }
		}
	    }
	} while (*src);
    }
    *dest = '\0';
    return strsave (temp_buf);
}
		
/*
 * check to see whether src begins with pattern.
 * Pattern is known to be all-lower-case letters.
 * if so, return a pointer to the remainder of the header
 * return NULL if pattern not matched.
 */

char *
match_header (src, pattern)
register char *src, *pattern;
{
    do {
	if ((*pattern == *src) ||
	    (isupper (*src) && (*pattern == tolower (*src))))
	    ;
	else
	    return NULL;
	++src;
	++pattern;
    } while (*src && *pattern);
    while (*src == ' ' || *src == '\t')
	++src;
    return src;
}

/* 
 * Read in the header portion of the message, saving it in a linked list.
 * Save the contents of certain headers to generate mail11-style headers.
 */

void
gather_headers ()
{
    int length;
    char line[LINSIZ];
    char *ptr;

    rfc822_header.head = rfc822_header.tail = (struct line *)NULL;
    
    while ((length = get_header (stdin, line, sizeof line)) != EOF) {
	if (length == 0)
	    break;

#define CHECK(a,b) if (ptr = match_header (line, a)) b = strsave (ptr)
	CHECK ("apparently-to:", rfc822_apparently_to);
	CHECK ("cc:", rfc822_cc);
	CHECK ("from:", rfc822_from);
	CHECK ("reply-to:", rfc822_reply_to);
	CHECK ("resent-cc:", rfc822_resent_cc);
	CHECK ("resent-from:", rfc822_resent_from);
	CHECK ("resent-reply-to:", rfc822_resent_reply_to);
	CHECK ("resent-sender:", rfc822_resent_sender);
	CHECK ("resent-subject:", rfc822_resent_subject);
	CHECK ("resent-to:", rfc822_resent_to);
	CHECK ("sender:", rfc822_sender);
	CHECK ("subject:", rfc822_subject);
	CHECK ("to:", rfc822_to);

	/*local*/ {
	    struct line *l;
	    l = (struct line *) malloc(sizeof(struct line) + length + 2);
	    strcpy(l->line, "% ");
	    strcpy(l->line+2, line);
	    l->length = length+2;
	    l->next = (struct line *)NULL;
	    if (!rfc822_header.head)
		rfc822_header.head = l;
	    else
		rfc822_header.tail->next = l;
	    rfc822_header.tail = l;
	    if (showdebug)
		fprintf(stderr, "(hdr) %s\n", line);
	}
    }
    return;
}

/*
 * send the headers that we saved during gather_headers ()
 * they will become part of the message body.
 */
void
send_rfc822_headers ()
{
    char line[LINSIZ];
#ifdef RECEIVED
    char *getnodename ();
    char *arpadate ();
#endif
    struct line *l;

#ifdef RECEIVED
    sprintf (line, "Received: by %s.DECnet (utk-mail11 %s) ; %s",
	     getnodename (), VERSION_STRING, arpadate ());
    mail11_put_body (line, strlen (line));
#endif

    mail11_put_body ("", 0);		/* write a blank line */
    sprintf(line, "%% %s", HEADER_BANNER);
    mail11_put_body (line, strlen (line));
    for (l = rfc822_header.head;  l;  l = l->next)
	mail11_put_body (l->line, l->length);
}

/*
 * Send the body of the original message
 */
send_rfc822_message_body ()
{
    int length = 0;

    while ((length = get_line (stdin, temp_buf, sizeof (temp_buf))) != EOF)
	mail11_put_body (temp_buf, length);
    if (noheaders == 0)
	send_rfc822_headers ();
    mail11_end_body ();
}

/*
 * Read a single header line from fp, handling continuation lines.
 * Place the result in buf (NUL terminated).
 * Returns the length of the result.
 *
 * To do: get rid of multiple spaces while we are at it.
 */

get_header (fp, buf, bufsize)
FILE *fp;
char *buf;
int bufsize;
{
    int state = 0;
    int c;
    int length = 0;

    if (bufsize == 0)
	return 0;
    while (1) {
	c = getc (fp);
	if (c == EOF) {
	    *buf = '\0';
	    return EOF;
	}
	switch (state) {
	case 0:
	    /*
	     * Nothing seen yet
	     */
	    if (c == '\n' || c == ' ' || c == '\t') {
		*buf = '\0';
		return EOF;
	    }
	    else {
		if (bufsize > 1) {
		    *buf++ = c;
		    --bufsize;
		    ++length;
		}
		state = 1;
	    }
	    break;
	case 1:
	    /*
	     * In the middle of header text
	     */
	    if (c == '\n')
		state = 2;
	    else if (bufsize > 1) {
		*buf++ = c;
		--bufsize;
		++length;
	    }
	    break;
	case 2:
	    /*
	     * First character after a newline
	     */
	    if (c == '\n') {		/* blank line = end of headers */
		ungetc (c, fp);		/* push back, will see next time */
		*buf = '\0';
		return length;
	    }
	    else if (c == ' ' || c == '\t') {
		state = 3;
		if (bufsize > 1) {
		    *buf++ = ' ';
		    --bufsize;
		    ++length;
		}
	    }
	    else {			/* found another header line */
		ungetc (c, fp);
		*buf = '\0';
		return length;
	    }
	    break;
	case 3:
	    /*
	     * Seen a newline and spaces, but not a printable character.
	     */
	    if (c == ' ' || c == '\t') {
		state = 3;
	    }
	    else if (c == '\n') {	/* blank line = end of headers */
		*buf = '\0';
		return length;
	    }
	    else {			/* printable character, continue */
		if (bufsize > 1) {
		    *buf++ = c;
		    --bufsize;
		}
		state = 1;
	    }
	    break;
	}
    }
}

/*
 * For each recipient in the list, query the remote DECnet object as to
 * the delivery status.  Return (a) EX_OK if all copies delivered okay,
 * (b) EX_TEMPFAIL if all copies "soft" failed, or (c) one of several
 * fatal status codes if any copy "hard" failed.
 */

get_final_status (list)
struct recipient *list;
{
    struct recipient *ptr;
    int num_recipients = 0;
    int good_recipients = 0;
    int temp_failures = 0;
    int exit_status = EX_OK;

    for (ptr = list; ptr; ptr = ptr->next) {
	if (ptr->flag == EX_OK)
	    ptr->flag = mail11_get_status (ptr);
    }
    if (list->next == NULL)
	return (list->flag);
    for (ptr = list; ptr; ptr = ptr->next) {
	if (ptr->flag == EX_OK)
	    good_recipients++;
	else if (ptr->flag == EX_TEMPFAIL)
	    temp_failures++;
	else
	    exit_status = ptr->flag;
	++num_recipients;
    }
    if (num_recipients == good_recipients)
	return (EX_OK);
    else if (num_recipients == temp_failures)
	return (EX_TEMPFAIL);
    return (exit_status);
}


/*
 * Search for character c within string str.  Return a pointer to
 * the first occurance, or NULL if not found.
 * This is just like index() or strchr() except that it handles backslash.
 *
 * TO DO: skip over quoted strings.
 */
char *
scan (str, c)
char *str, c;
{
    while (*str) {
	if (*str == '\\')
	    ++str;
	else if (*str == c)
	    return str;
	++str;
    }
    return NULL;
}

/*
 * Given an RFC-822 address header line, extract the address proper.
 * If angle brackets appear in the line, the address is within the
 * brackets.  Else the address is everything except comments.
 */
char *
extract_address (str)
char *str;
{
    char buf[LINSIZ];
    char *src;
    char *dest;

    dest = buf;

    if ((src = scan (str, '<')) && scan (src+1, '>')) {
	++src;
	while (*src != '>' && *src) {
	    if (*src == '\\' && src[1] != '\0')
		*dest++ = *src++;
	    *dest++ = *src++;
	}
    }
    else {
	int parens = 0;
	int in_quotes = 0;

	src = str;
	while (*src) {
	    if (!in_quotes && *src == '(') {
		++parens;
		++src;
	    }
	    else if (!in_quotes && *src == ')' && parens > 0) {
		--parens;
		++src;
	    }
	    else if (parens > 0)
		++src;
	    else if (*src == '\\' && src[1]) {
		*dest++ = *src++;
		*dest++ = *src++;
	    }
	    else if (*src == '"') {
		*dest++ = *src++;
		in_quotes = !in_quotes;
	    }
	    else if (!in_quotes && *src == ' ')	/* skip over unquoted spaces */
		++src;
	    else
		*dest++ = *src++;
	}
    }
    *dest = '\0';
    return strsave (buf);
}

/*
 * Given an RFC-822 address header line, extract the "personal name".
 * If angle brackets appear on the line, the name is everything else.
 * Otherwise, the name is anything inside parenthesis.
 * return NULL if we can't find a name.
 */
char *
extract_name (str)
char *str;
{
    char *src, *dest;
    char buf[LINSIZ];
    int parens = 0;

    if (str == NULL)
	return NULL;

    if ((src = scan (str, '<')) && scan (src+1, '>')) {
	src = str;
	dest = buf;
	while (*src) {
	    if (*src == '<') {
		parens = 1;
		++src;
	    }
	    else if (*src == '>' && parens) {
		parens = 0;
		++src;
	    }
	    else if (parens == 0) {
		if (*src == '\\' && src[1] != '\0')
		    *dest++ = *src++;
		if (!(*src == ' ' && (dest == buf || dest[-1] == ' ')))
		    *dest++ = *src++;
		else
		    ++src;
	    }
	    else
		++src;
	}
	while (dest > buf && dest[-1] == ' ') /* kill trailing spaces */
	    --dest;
	*dest = '\0';
	if (dest > buf + 1 && *buf == '"' && dest[-1] == '"') {
	    dest[-1] = '\0';
	    return strsave (buf + 1);
	}
	else
	    return strsave (buf);
    }
    else if ((src = scan (str, '(')) && scan (src+1, ')')) {
	++src;
	dest = buf;
	parens = 1;
	while (parens > 0 && *str) {
	    if (*src == '(') {
		++parens;
		++src;
	    }
	    else if (*src == ')') {
		--parens;
		--src;
	    }
	    else {
		if (*src == '\\' && src[1] != '\0')
		    *dest++ = *src++;
		if (!(*src == ' ' && (dest == buf || dest[-1] == ' ')))
		    *dest++ = *src++;
		else
		    ++src;
	    }
	}
	while (dest > buf && dest[-1] == ' ') /* kill trailing spaces */
	    --dest;
	*dest = '\0';
	if (dest > buf + 1 && *buf == '"' && dest[-1] == '"') {
	    dest[-1] = '\0';
	    return strsave (buf + 1);
	}
	else
	    return strsave (buf);
    }
    else 
	return NULL;
}


int
mail11_connect (node)
char *node;
{
    int remote_object;

#ifdef DECNET_ULTRIX
    extern char proxy_requested;	/* HACK! pass to DECnet routines */
    int length = sizeof inconfig;

    proxy_requested = 0;

    remote_object = dnet_conn (node, "#27", SOCK_SEQPACKET, &outconfig,
			       sizeof outconfig, &inconfig, &length);
    if (remote_object == EOF) {
	mail11_nerror ("mail11: connect");
    }
#endif

#ifdef SUNLINK_DNI
    extern int errno;
    struct ses_io_type io_type_buf;
    OpenBlock open_block;
    Image16 inconfig_buf;
    int i;
    char node_buf[8];		/* big enough for "63.1023\0" */

    /* if node starts with a digit, it should be a DECnet address
       in the form "[0-9]+\.[0-9]+" or simply "[0-9]+".  Sunlink DNI
       doesn't like the latter format.  So we convert all node
       numbers into the form areanum.nodenum. */

#define MAX_AREA 64
#define NODES_PER_AREA 1024
    if (isdigit (*node)) {
	char *ptr;
	int node_addr = 0;
	for (ptr = node; *ptr; ++ptr) {
	    if (isdigit (*ptr))
		node_addr = node_addr * 10 + (*ptr - '0');
	    else if (*ptr == '.' && node_addr < MAX_AREA)
		node_addr = node_addr * NODES_PER_AREA;
	    else {
		fprintf (stderr, "mail11: illegal node address \"%s\"\n", node);
		exit (EX_NOHOST);
	    }
	}
	if (node_addr >= NODES_PER_AREA)
	    sprintf (node_buf, "%d.%d",
		     node_addr / NODES_PER_AREA, node_addr % NODES_PER_AREA);
	else
	    sprintf (node_buf, "%d", node_addr);
    }
    else
	sprintf (node_buf, "%.7s", node);

    remote_object = open (DNET_DEV, O_RDWR);
    if (remote_object < 0) {
	fprintf (stderr, "mail11: unable to open %s", DNET_DEV);
	perror ("");
	exit (EX_OSERR);
    }
    if (ioctl (remote_object, SES_GET_LINK, 0) < 0) {
	/*
	 * This probably just means that DECnet isn't up right now.
	 */
	perror ("mail11: unable to get a logical link");
	exit (EX_TEMPFAIL);
    }
    io_type_buf.io_flags = SES_IO_RECORD_MODE | SES_IO_INT_MODE;
    io_type_buf.io_io_signal = 0;
    io_type_buf.io_int_signal = 0;
    if (ioctl (remote_object, SES_IO_TYPE, &io_type_buf) < 0) {
	nerror ("mail11: SES_IO_TYPE");
	exit (EX_OSERR);
    }

    /*
     * Build link access block
     */
    strncpy (open_block.op_node_name, node, NODE_LEN); /* assume okay */
    open_block.op_object_nbr = 27;
    *open_block.op_task_name = '\0';
    *open_block.op_userid = '\0';
    *open_block.op_account = '\0';
    *open_block.op_password = '\0';
    open_block.op_opt_data.im_length =
	write_config (open_block.op_opt_data.im_data, &outconfig);

    if (showdebug) {
	fprintf (stderr, "node = %s\n", node);
	fprintf (stderr, "node name = %s\n", open_block.op_node_name);
	fprintf (stderr, "object number = %d\n", open_block.op_object_nbr);
	fprintf (stderr, "opt data (%d bytes) =",
		 open_block.op_opt_data.im_length);
	for (i = 0; i < open_block.op_opt_data.im_length; ++i)
	    fprintf (stderr, " %02x", open_block.op_opt_data.im_data[i]);
	fprintf (stderr, "\n");
    }

    if (ioctl (remote_object, SES_LINK_ACCESS, &open_block) < 0) {
	fprintf (stderr, "mail11: Error establishing link to DECnet node %s:\n",
		 node);
	nerror ("mail11:");
	switch (errno) {
	case NODE_NAME:
	case BAD_NAME:
	    exit (EX_NOHOST);
	case NET_RESOUR:
	case NODE_DOWN:
	case OBJ_BUSY:
	case MANAGEMENT:
	case REMOTE_ABORT:
	case LOCAL_SHUT:
	case NODE_REAS:
	case NODE_UNREACH:
	    exit (EX_TEMPFAIL);
	}
	exit (EX_OSERR);
    }
    if (open_block.op_opt_data.im_length == 16)
	read_config (&inconfig, open_block.op_opt_data.im_data);

#endif
    if (showconfig) {
	show_config ("outgoing", &outconfig);
	show_config ("incoming", &inconfig);
    }
    return remote_object;
}

/*
 * Mail-11 envelope writing routines
 */

/*
 * Send the mail-11 envelope From: line
 */

mail11_send_from (from, name)
char *from, *name;
{
    if (name)
	sprintf (temp_buf, "%s %s", from, name);
    else
	strcpy (temp_buf, from);
    if (showdebug)
        fprintf (stderr, "(from) ");
    mail11_put_envelope (temp_buf);
}

/*
 * Send the list of recipients
 */

mail11_send_recipients (list)
struct recipient *list;
{
    while (list != (struct recipient *) NULL) {
	mail11_send_recipient (list);
	list = list->next;
    }
    mail11_send_marker ();
}

/*
 * Send a single recipient.
 */

mail11_send_recipient (recipient)
struct recipient *recipient;
{
    if (showdebug)
        fprintf (stderr, "(rcpt) ");
    mail11_put_envelope (recipient->mailbox);
    recipient->flag = mail11_get_status (recipient);
    if (recipient->flag != EX_OK) {
        char *p = rindex(recipient->mailbox, ':');
        if (p && !strcasecmp("postmaster", p+1)) {
            fprintf(stderr, "\t(trying SYSTEM instead of POSTMASTER)\n");
            strcpy(p+1, "SYSTEM");      /* really, truly gross */
            mail11_put_envelope (recipient->mailbox);
            recipient->flag = mail11_get_status (recipient);
            if (recipient->flag == EX_OK) {
                fprintf(stderr,
                        "\t(yup, SYSTEM worked fine, let's keep going)\n");
            }
        }
    }
}

/*
 * Send any part of the envelope.
 */

mail11_put_envelope (string)
char *string;
{
    int length = strlen (string);
    if (showdebug)
        fprintf (stderr, ">>> %s\n", string);
    if (length == 0 || length == 1 && *string == '\0') {
	string = " ";
	length = 1;
    }
    if (write (remote_object, string, length) != length)
	mail11_error ("mail11_put_envelope");
}

/*
 * Send a marker indicating either the end of the recipient list or
 * the end of the message body.
 */

mail11_send_marker ()
{
    if (showdebug)
        fprintf (stderr, ">>> (marker)\n");
    if (write (remote_object, "", 1) < 0)
	mail11_error ("send_marker");
}

/*
 * Mail-11 message body output routines
 */
 
char outbuf[LINSIZ];		/* outbuf buffer for block mode */
char *bufptr = &outbuf[0];
#define x(b) (isprint(b) || ((b) == ' ')) ? b : '.'

mail11_write_buffer ()
{
    int length = bufptr - outbuf;
    int i, j;
    if (length == 0)
	return;
    if (showdebug) {
	fprintf (stderr, "(%d-byte record):\n", length);
	for (i = 0; i < length; i += 16) {
	    fprintf (stderr, ">>> ");
	    for (j = i; j < i + 16 && j < length; ++j)
		fprintf (stderr, "%02x%c", outbuf[j], j == i + 7 ? '-' : ' ');
	    fprintf (stderr, " ");
	    for (j = i; j < i + 16 && j < length; ++j) {
		fprintf (stderr, "%c", x(outbuf[j]));
		if (j == i + 7)
		    fprintf (stderr, "-");
	    }
	    fprintf (stderr, "\n");
        }
    }
    if (write (remote_object, outbuf, length) != length)
	mail11_error ("mail11_write_buffer");
}

mail11_put_byte (b)
unsigned char b;
{
    if (bufptr >= outbuf + sizeof outbuf) {
	mail11_write_buffer();
	bufptr = &outbuf[0];
    }
    *bufptr++ = b;
}

mail11_end_body ()
{
    if (inconfig.iomode & BLKRECV)
	mail11_write_buffer ();
}

mail11_put_body (buf, length)
char *buf;
int length;
{
    if (inconfig.iomode & BLKRECV) {
	/*
	 * block mode - write out record length in VAX byte order,
	 * followed by record.  If record length is odd write out
	 * an extra byte.
	 */
	int i;
	mail11_put_byte (length & 0xff);
	mail11_put_byte (length >> 8);
	for (i = 0; i < length ;++i)
	    mail11_put_byte (*buf++);
	if (length & 01)
	    mail11_put_byte (0);
    }
    else {
	/*
	 * Change a zero-length record, or one containing only
	 * a single NUL, into a record containing one space.
	 * Otherwise the remote server thinks we are done with the
	 * message.
	 */
	
	if (length == 0 || (length == 1 && *buf == '\0')) {
	    buf = " ";
	    length = 1;
	}
	if (showdebug)
	    fprintf (stderr, ">>> %.*s (%d)\n", length, buf, length);
	if (write (remote_object, buf, length) != length)
	    mail11_error ("put_line");
    }
}

int
mail11_read (buf, size)
char *buf;
size_t size;
{
    int length = read (remote_object, buf, (size_t) size);
    if (showdebug) {
	if (length == sizeof (int))
	    fprintf (stderr, "<<< 0x%x (4)\n", *((int *) buf));
	else
	    fprintf (stderr, "<<< %.*s (%d)\n", length, buf, length);
    }
    return length;
}

/*
 * Since the return code from the remote DECnet mail object does
 * not reliably tell us whether a failure is a permanent one
 * (i.e. no such user), or a temporary one (i.e. link is down),
 * we compare with a (hopefully small) list of message texts,
 * and return the appropriate error code if we see one of these.
 */

struct {
    char *text;
    int code;
} remote_messages[] = {
    { "remote node is unknown",  	EX_NOHOST }, /* VMS Mail */
    { "no such user", 		 	EX_NOUSER }, /* VMS Mail */
    { "No such node is defined", 	EX_NOHOST }, /* JNET */
    { "remote node is not currently reachable", EX_TEMPFAIL },
    { "disk quota exceeded",     	EX_TEMPFAIL }, /* VMS */
    { "insufficient system resources", 	EX_TEMPFAIL }, /* VMS */
    { "insufficient privilege",  	EX_NOUSER }, /* VMS */
    { "network partner exited",  	EX_TEMPFAIL }, /* VMS */
    { "device full",		  	EX_TEMPFAIL }, /* VMS */
    { "allocation failure",	  	EX_TEMPFAIL }, /* VMS */
    { "cannot receive new mail", 	EX_NOUSER }, /* VMS */
    /*
     * "invalid device name" most often occurs when the device
     * containing a user's sys$login directory is temporarily
     * not mounted.  Treating it as a temporary failure should
     * allow mail to be delivered.
     */
    { "invalid device name",     	EX_TEMPFAIL }, /* VMS */
};

int
parse_error_message (message)
char *message;
{
    int i;
    for (i = 0; i < sizeof remote_messages / sizeof *remote_messages; ++i)
	if (strindex (message, remote_messages[i].text))
	    return remote_messages[i].code;
    return 0;
}

int
mail11_get_status (recipient)
struct recipient *recipient;
{
    unsigned char code[4];
    unsigned int vaxcode;
    char message[300];
    int length;
    int exit_status = 0;

    if ((length = mail11_read ((char *)code, sizeof code))
	< (int) (sizeof code)) {
	if (length == EOF)
	    mail11_error ("network peer exited");
	else
	    mail11_error ("get_status");
    }
    if (code[0] & 01)
	return EX_OK;			/* success */

    vaxcode = (int)code[0]
	    | (((int)code[1]) << 8)
	    | (((int)code[2]) << 16)
	    | (((int)code[3]) << 24);

    fprintf (stderr,
	     "\nmail11: Error from DECnet MAIL object on node \"%s\",\n",
	     argv_node);
    fprintf (stderr, "        during mail delivery to <%s>.\n",
	     recipient->mailbox);
    if (showdebug)
	fprintf (stderr, "        (Username <%s> before translation.)\n",
		 recipient->untranslated_mailbox);
    fprintf (stderr, "        Remote error code is 0x%x, message is:\n",
	     vaxcode);
    do {
	length = mail11_read (message, sizeof message);
	if (length >= 0)
	    message[length] = '\0';
	if (length > 0 && *message != '\0')
	    fprintf (stderr, "%.*s\n", length, message);
	if (exit_status == 0)
	    exit_status = parse_error_message (message);
    } while (length > 0 && *message != '\0');

    if (exit_status != 0)
	return exit_status;
    else
	return EX_NOUSER;
}

mail11_nerror (string)
{
    nerror (string);
    mail11_errexit ();
}

mail11_error (string)
{
    perror (string);
    mail11_errexit ();
}

mail11_errexit ()
{
    switch (errno) {
    case ENETDOWN:			/* Network is down */
    case ENETUNREACH:			/* Network is unreachable */
    case ENETRESET:			/* Network dropped connection on reset */
    case ECONNABORTED:			/* Software caused connection abort */
    case ECONNRESET:			/* Connection reset by peer */
    case ETIMEDOUT:			/* Connection timed out */
    case ECONNREFUSED:			/* Connection refused */
    case EHOSTDOWN:			/* Host is down */
    case EHOSTUNREACH:			/* Host is unreachable */
	exit (EX_TEMPFAIL);
    case EADDRNOTAVAIL:			/* unrecognized node name */
    case ESRCH:				/* unrecognized object */
    case ENAMETOOLONG:			/* node name too long */
	exit (EX_NOHOST);
    case EINVAL:			/* invalid object name format */
	exit (EX_SOFTWARE);
    default:
	exit (EX_OSERR);
    }
}

char *
strsave (str)
char *str;
{
    int space = strlen (str) + 1;
    char *ptr = malloc (space);
    if (ptr == NULL) {
	perror ("strsave");
	exit (EX_OSERR);
    }
    strcpy (ptr, str);
    return ptr;
}

char *
strindex (big, little)
char *big, *little;
{
    int little_length = strlen (little);
    int big_length = strlen (big);

    while (big_length >= little_length) {
	if (*big == *little && !strncmp (big, little, (size_t) little_length))
	    return big;
	++big;
	--big_length;
    }
    return NULL;
}

int
get_line (fp, buf, bufsize)
FILE *fp;
char *buf;
int bufsize;
{
    int c;
    int length = 0;
    while ((c = getc (fp)) != EOF) {
	if (c == '\n')
	    break;
	if (bufsize > 1) {
	    *buf++ = c;
	    ++length;
	    --bufsize;
	}
    }
    *buf = '\0';
    if (length == 0 && c == EOF)
	return EOF;
    return length;
}

print_arguments ()
{
    struct recipient *ptr;
    if (argv_from) printf ("from: %s\n", argv_from);
    if (argv_name) printf ("name: %s\n", argv_name);
    printf ("node: %s\n", argv_node);
    for (ptr = recipient_list ; ptr; ptr = ptr->next)
	printf ("recipient: %s\n", ptr->mailbox);
}

print_headers ()
{
    if (rfc822_to) printf ("to: %s\n", rfc822_to);
    if (rfc822_subject) printf ("subj: %s\n", rfc822_subject);
    if (rfc822_cc) printf ("cc: %s\n", rfc822_cc);
    if (rfc822_reply_to) printf ("reply_to: %s\n", rfc822_reply_to);
}

#ifdef RECEIVED
char *
arpadate ()
{
    struct timeb t;
    char *ptr, *ctime ();
    struct tm *localtime ();
    static char timebuf[100];
    char *dayptr;
    char *format;

    ftime (&t);
    ptr = ctime(&t.time);
    /*
     * Hack to avoid two spaces in a row when day of month is one digit.
     * (either that or we could make the day of month "08" instead of " 8")
     */
    if (ptr[8] == ' ') {
	dayptr = &ptr[9];
	format = "%.3s, %.1s %.3s %.2s %.8s %s";
    }
    else {
	dayptr = &ptr[8];
	format = "%.3s, %.2s %.3s %.2s %.8s %s";
    }
    sprintf (timebuf, format,
	     &ptr[0],			/* day of week */
	     dayptr,			/* day of month */
	     &ptr[4],			/* month */
	     &ptr[22],			/* year */
	     &ptr[11],			/* hh:mm:ss */
	     timezone (t.timezone, localtime(&t)->tm_isdst));
    return timebuf;
}
#endif

/*
 * If there was no recipient line found in the message headers,
 * construct the VMS To: line from the recipient list.
 */

char *build_to_line (node, list)
char *node;
struct recipient *list;
{
    char buf[513];
    char *dest;
    char *src;
    struct recipient *ptr;

    dest = buf;
    for (ptr = list; ptr != NULL; ptr = ptr->next) {
	for (src = node; *src && dest < buf + 510; *dest++ = *src++);
	*dest++ = ':';
	*dest++ = ':';
	for (src = ptr->mailbox; *src && dest < buf + 511; *dest++ = *src++);
	if (ptr->next != NULL && dest < buf + 510) {
	    *dest++ = ',';
	    *dest++ = ' ';
	}
    }
    *dest = '\0';
    return strsave (buf);
}

#ifdef SUNLINK_DNI
/*
 * Read config information from MAIL-11 format to a C structure
 * This work regardless of the local machine's byte order.
 */

int read_config (config, ptr)
struct mail11_config *config;
unsigned char *ptr;
{
    config->protocol_version = *ptr++;
    config->eco = *ptr++;
    config->cust_eco = *ptr++;
    config->os = *ptr++;
    config->options = *ptr++;
    config->options += (*ptr++ << 8);
    config->options += (*ptr++ << 16);
    config->options += (*ptr++ << 24);
    config->iomode = *ptr++;
    config->iomode += (*ptr++ << 8);
    config->iomode += (*ptr++ << 16);
    config->iomode += (*ptr++ << 24);
    config->rfm = *ptr++;
    config->rat = *ptr++;
    config->spare1 = *ptr++;
    config->spare2 = *ptr++;
}

/*
 * Convert a config structure into MAIL-11 format
 * This work regardless of the local machine's byte order.
 */

int write_config (buf, config)
unsigned char *buf;
struct mail11_config *config;
{
    *buf++ = config->protocol_version;
    *buf++ = config->eco;
    *buf++ = config->cust_eco;
    *buf++ = config->os;
    *buf++ = config->options & 0xff;
    *buf++ = (config->options >> 8) & 0xff;
    *buf++ = (config->options >> 16) & 0xff;
    *buf++ = (config->options >> 14) & 0xff;
    *buf++ = config->iomode & 0xff;
    *buf++ = (config->iomode >> 8) & 0xff;
    *buf++ = (config->iomode >> 16) & 0xff;
    *buf++ = (config->iomode >> 14) & 0xff;
    *buf++ = config->rfm;
    *buf++ = config->rat;
    *buf++ = config->spare1;
    *buf++ = config->spare2;
    return 16;
}

/*
 * Sunlink/DNI 5.1 doesn't give us a function to print out DECnet-
 * specific error messages...so here's one.
 */

nerror(string)
char *string;
{
    extern int errno;
    fprintf (stderr, "%s: ", string);
    switch (errno) {
    case ENETDOWN:
	fprintf (stderr, "DNI software has not been initialized\n");
	break;
    case EBUSY:
	fprintf (stderr, "No logical links available\n");
	break;
    case EWOULDBLOCK:
	fprintf (stderr, "No data available to be read\n");
	break;
    case NOT_CONNECTED:
	fprintf (stderr, "Specified logical link does not exist\n");
	break;
    case PROC_ERROR:
	fprintf (stderr, "Network software processing error\n");
	break;
    case TRUNCATED:
	fprintf (stderr, "Data was truncated\n");
	break;
    case BAD_LCN:
	fprintf (stderr, "Logical link number specified is invalid\n");
	break;
    case BY_OBJECT:
	fprintf (stderr, "Logical link normally closed or rejected\n");
	break;
    case NET_RESOUR:
	fprintf (stderr, "Insufficient resources at remote node\n");
	break;
    case NODE_NAME:
	fprintf (stderr, "Unrecognized node name\n");
	break;
    case NODE_DOWN:
	fprintf (stderr, "Remote node is not accepting new links\n");
	break;
    case BAD_OBJECT:
	fprintf (stderr, "Specified remote object does not exist\n");
	break;
    case OBJ_NAME:
	fprintf (stderr, "Specified task name is invalid\n");
	break;
    case OBJ_BUSY:
	fprintf (stderr, "Insufficient resources at remote node\n");
	break;
    case MANAGEMENT:
	fprintf (stderr, "Link disconnected by network\n");
	break;
    case REMOTE_ABORT:
	fprintf (stderr, "Link aborted by remote program\n");
	break;
    case BAD_NAME:
	fprintf (stderr, "Specified node or device name is invalid\n");
	break;
    case LOCAL_SHUT:
	fprintf (stderr, "Local node is not accepting new links\n");
	break;
    case ACCESS_CONT:
	fprintf (stderr, "Remote node rejected access information\n");
	break;
    case NODE_REAS:
	fprintf (stderr, "Insufficient resources at local node\n");
	break;
    case FAILED:
	fprintf (stderr, "Remote node failed to respond\n");
	break;
    case NODE_UNREACH:
	fprintf (stderr, "Remote node is unreachable\n");
	break;
    case ALREADY:
	fprintf (stderr, "Logical link identifier already in use\n");
	break;
    case UNINIT:
	fprintf (stderr, "network software is not initialized\n");
	break;
    case SEND_TIMEOUT:
	fprintf (stderr, "Controller didn't ack message from driver\n");
	break;
    case RECV_TIMEOUT:
	fprintf (stderr, "Timeout of expected recv from controller\n");
	break;
    case XLN_SEND_ERR:
	fprintf (stderr, "Controller error on transmit\n");
	break;
    case XLN_RECV_ERR:
	fprintf (stderr, "Controller error on receive\n");
	break;
    case USER_ABORT:
	fprintf (stderr, "Program aborted by interactive user\n");
	break;
    case INV_ACCESS_MODE:
	fprintf (stderr, "Invalid access attempt on read or write\n");
	break;
    case NO_DATA_AVAIL:
	fprintf (stderr, "No data available\n");
	break;
    case BAD_RECORD_STAT:
	fprintf (stderr, "Invalid value in SesRecord status field\n");
	break;
    case INVALID_SIZE:
	fprintf (stderr, "Size is negative or greater than max\n");
	break;
    case OUT_OF_SPACE:
	fprintf (stderr, "Out of space on controller\n");
	break;
    case COMM_FAIL:
	fprintf (stderr, "Unidentified controller failure\n");
	break;
    case BAD_COMMAND:
	fprintf (stderr, "Invalid command or ioctl function\n");
	break;
    case FLOW_CONTROL:
	fprintf (stderr, "Transmit failed, link is flow controlled\n");
	break;
    case CL_DATA_AVAIL:
	fprintf (stderr, "Remote node closed link, data available\n");
	break;
    case INT_DATA:
	fprintf (stderr, "Buffer contains interrupt data\n");
	break;
    case BEG_OF_MESSAGE:
	fprintf (stderr, "Buffer contains the beginning of a msg\n");
	break;
    case MID_OF_MESSAGE:
	fprintf (stderr, "Buffer contains the middle of a msg\n");
	break;
    case END_OF_MESSAGE: 
	fprintf (stderr, "Buffer contains the end of a msg\n");
	break;
    case COMPLETE:
        fprintf (stderr, "buffer contains a complete message\n");
	break;
    case UNKNOWN:
	fprintf (stderr, "Undefined error status received from remote node\n");
	break;
    case DUPE_NODE_NAME:
	fprintf (stderr, "Duplicate node name detected\n");
	break;
    case DUPE_NODE_NUM:
	fprintf (stderr, "Duplicate node number detected\n");
	break;
    case NODE_NUM_REQUIRED: 
	fprintf (stderr, "Node numbers are required for all nodes\n");
	break;
    case NOT_SUPPORTED:
	fprintf (stderr, "Function not yet supported\n");
	break;
    default:
	fprintf (stderr, "Unknown Sunlink/DNI error %d\n", errno);
	break;
    }
}

/*
 * I don't know how to get the DECnet node name under SunOS if it's
 * different from the hostname...
 */

char *
getnodename ()
{
    static char foo[65];
    gethostname (foo, sizeof foo);
    return foo;
}
#endif
