/*
 * $Header: /udir/vixie/src/mail11/d-1.7beta/RCS/mail11d.c,v 1.8 1991/10/29 06:49:05 vixie Exp $
 *
 * 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.
 */
/* 
 * mail11 protocol daemon
 * Keith Moore
 * January 1989
 *
 * This is intended as a replacement for the Ultrix mail11 daemon, or
 * for use on other systems that support some implementation of Digital
 * Network Architecture.
 *
 * See ChangeLog for revision history.
 *
 * Things to do:
 * - if sendmail cannot parse the message, send a copy of the message
 *   to the local postmaster.  Sendmail does this already but will never
 *   collect the message if the envelope fields aren't valid.
 */

#define VERSION_STRING "utk-mail11d v1.7"	/* for postmarks */

#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sysexits.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/timeb.h>
#include <sys/param.h>
#include "text.h"
#include "smtp.h"
#include "mail11.h"
#include "decnet.h"
#include "debug.h"
#include "defs.h"
#include "rfc822.h"

char *strsave P((char *));
void XlateFromLine P((char *, int));
char *XlateRecipient P((char *, int));
char *ConcoctDomain P((char *, char *));
char *MakeLowerCase P((char *));
char *StripThisNode P((char *));
char *HackHeaderRecip P((char *));
char *HackRecipientHeader P((char *));
char *RewritePath P((char *));
#ifdef NODE_DOMAIN_LOOKUP
char *getenv P((char *));
#endif
char *malloc P((unsigned int));
char *index P((char *, char));
char *rindex P((char *, char));

struct mail11_config server_config =
    { PROTOCOL_VERSION, ECO, CUST_ECO, OS_ULTRIX, 0, 0, 0, 0 };
struct mail11_config client_config;

#define BLOCK_SIZE	512

char *mail11_return_path = NULL;
char *mail11_from = NULL;
char *mail11_to = NULL;
char *mail11_cc = NULL;
char *mail11_subj = NULL;

#ifndef DEFAULT_SUFFIX
#define DEFAULT_SUFFIX ".DECnet"
#endif

char *debug_file = NULL;
char *default_suffix = DEFAULT_SUFFIX;
char *sender_domain = NULL;
char *local_domain = NULL;


int LM_ReadLN P((char *, int));

void BM_Init P(());
int BM_ReadLN P((char *, int));

struct modesw {
    void (*init)();
    int (*readln)();
    void (*fini)();
} ModeSw[] = {
#define LINE_MODE 0
    { NULL, LM_ReadLN, NULL },
#define BLOCK_MODE 1
    { BM_Init, BM_ReadLN, NULL },
}, *ModeP;

static int Mode;


struct recipient {
    struct recipient *next;
    int length;
    char address[1];
} *RecipientList = NULL;


/*
 * Command-line options
 */

struct option {
    char *text;
    char **ptr;
    char *value;
} options [] = {
    { "-default_suffix=", &default_suffix, "" },
    { "-debug_file=", &debug_file, "" },
};
    

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

    for (i = 0; i < sizeof options / sizeof *options; ++i) {
	int length = strlen (options[i].text);

	if (strncmp (str, options[i].text, (size_t) length) == 0)
	    if (options[i].text[length-1] == '=')
		*(options[i].ptr) = str + length;
	    else
		*(options[i].ptr) = options[i].value;
    }
}

main (argc, argv)
int argc;
char **argv;
{
    char cwd[MAXPATHLEN];

    getwd(cwd);

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

    if (debug_file)
	DEBUG_Open (debug_file);
    DECNET_AcceptConnection (&client_config, &server_config);

    if (server_config.iomode & BLKRECV) {
	Mode = BLOCK_MODE;
    } else {
	Mode = LINE_MODE;
    }
    ModeP = &ModeSw[Mode];

    DEBUG_Print ("cwd=%s\n", cwd);

    TranslateMail11ToRFC822 ();
    DEBUG_Close ();
    exit (0);
}


/*
 * Protocol engine states:
 */
#define FROM	0			/* waiting for From: header */
#define RCPT	1			/* waiting for recipients */
#define TO	2			/* waiting for To: header */
#define CC	3			/* waiting for Cc: header */
#define SUBJ	4			/* waiting for Subj: header */
#define BODY	5			/* reading in message body */

/*
 * Add a recipient to the recipient list.
 */

AddRecipient (addr, length)
char *addr;
int length;
{
    struct recipient *ptr;

    ptr = (struct recipient *)
	malloc (strlen (addr) + sizeof (struct recipient));
    strcpy (ptr->address, addr);
    ptr->length = length;
    ptr->next = RecipientList;
    RecipientList = ptr;
}

/*
 * Read a record from the remote DECnet client, and handle some errors.
 * Stuff a NUL after the end of the buffer.
 * If an error occurred while reading, print an error message to stdout.
 * Return EOF on error or end of file.
 */

int
ReadRecord (buf, size)
char *buf;
{
    int length;

    if (size > (BLOCK_SIZE + 1))
	size = BLOCK_SIZE + 1;
    if ((length = DECNET_Read (buf, size - 1)) < 0) {
	DECNET_Error ("DECNET_Read");
	return EOF;
    }
    if (length == 0 && DECNET_EOF ())
	return EOF;
    buf[length] = '\0';
    return length;
}

/*
 * The following routines handle reading RMS format files when the file
 * is sent in block mode.  The only format currently supported is
 * "variable length" with "carriage return" carriage control (CCTL=CR).
 * Each record consists of a 16-bit length field (in VAX byte order),
 * followed by that many bytes of record.  If the length field is odd,
 * a NUL byte is inserted after the record and before the next record
 * length field.  A length of 0xffff means end-of-file, but we might
 * never actually see this.
 *
 * Note that we have to peek into the stdio FILE struct here because
 * a leading 0x00 can mean end-of-message if it is alone in a record,
 * or it can be the first eight bits of a 16-bit 0x0000 indicating a
 * zero-length line.  This is icky and will not work if your stdio is
 * different from the one in BSD.  If you have trouble with _cnt, you
 * are basically screwed and you should look at your <stdio.h> to see
 * how else to do what I did.
 *
 * The stdio implementation of block mode, and the ModeSw[] stuff,
 * were done by Paul Vixie of DECWRL in September of 1991.
 */

FILE *BM_File;

void
BM_Init ()
{
    BM_File = DECNET_FDOpen_In ();
}

int
BM_ReadLN (buf, siz)
    char *buf;
    register int siz;
{
    register char *bufp = buf;
    register int ch;
    register int len;
    int odd;

    if (((ch = getc (BM_File)) < 0 || (!ch && !BM_File->_cnt))) {
	return EOF;		/* EOF or \0 alone in a packet is a "marker" */
    }
    len = ch;

    if (((ch = getc (BM_File)) < 0) || (ch == 0xff && len == 0xff)) {
	return EOF;		/* EOF or leading 0xff,0xff is a "marker" */
    }
    len += (ch << 8);
    odd = (len & 0x01);

    while (--len >= 0) {
	if ((ch = getc (BM_File)) < 0) {
	    return EOF;		/* EOF in the middle of the line is trouble */
        }
	if (--siz >= 0)
	    *bufp++ = ch;
    }
    if (odd)
	ch = getc (BM_File);
    *bufp = '\0';

    return bufp - buf;
}

/*
 * Read a single line from the message body of the message being sent.
 *
 * BUG: No error message is given if buffer isn't big enough -- the
 * record is silently truncated.
 */

int
LM_ReadLN (buf, size)
char *buf;
int size;
{
    int length;

    length = ReadRecord (buf, size);
    if (IsMarker (buf, length))
	return EOF;
    return length;
}

TranslateMail11ToRFC822 ()
{
    int length;
    struct smtp_reply *reply;
    int fatal_error = 0;
    char *recipient;
    struct recipient *ptr;
    char buf[65535];		/* 65534 + room for NUL at end */

    /*************************************************************************
     * Translate mail-11 From: header line                                   *
     * This produces the RFC821 envelope MAIL FROM address and also the      *
     * RFC822 header From: adderess                                          *
     *************************************************************************/

    if ((length = ReadRecord (buf, sizeof buf - 1)) < 0)
	return;
    DEBUG_Print ("F: %s\n", buf);
    
    /*
     * Open the channel to the SMTP daemon.  If this open fails save the
     * error message so we can report an error on the first recipient.
     */
    reply = SMTP_Open ();
    if ((reply->code & 0xf00) != 0x200)
	fatal_error++;
    
    /*
     * Send SMTP HELO command and sending host.
     */
    local_domain = MakeLowerCase (
		       ConcoctDomain (DECNET_NodeName, default_suffix)
		   );
    sender_domain = MakeLowerCase (
		       ConcoctDomain (DECNET_RemoteNode, default_suffix)
		    );
    if (!fatal_error) {
	reply = SMTP_Command ("HELO %s", sender_domain);
	if ((reply->code & 0xf00) != 0x200)
	    fatal_error++;
    }
    
    /*
     * Send SMTP MAIL FROM: command and return-path.
     * XlateFromLine() extracts both a return-path and the sender's address.
     */
    if (!fatal_error) {
	XlateFromLine (buf, length);
	reply = SMTP_Command ("MAIL From:<%s>", mail11_return_path);
	if ((reply->code & 0xf00) != 0x200)
	    fatal_error++;
    }

    /*************************************************************************
     * Read mail-11 recipient addresses until we see a marker.  These should *
     * already be expressed relative to the local system.  Verify each of    *
     * these and return status to the client.                                *
     *************************************************************************/
    DEBUG_Print ("BEGIN recipient list...\n");
    while (1) {
	if ((length = ReadRecord (buf, sizeof buf - 1)) < 0)
	    goto cleanup;
	if (IsMarker (buf, length)) {
	    break;
	}
	/*
	 * If we have an error to report as the result of SMTP_Open
	 * or sending the SMTP MAIL FROM line, report it here.
	 * Return an error for every recipient, but only send the
	 * message on the first recipient.
	 */
	if (fatal_error) {
	    mail11_error (reply->msg.head, DECNET_RemoteNode);
	}
	/*
	 * Give the recipient to the SMTP server and ask whether
	 * it is valid.  If recipient is bogus, send error code
	 * and error message to MAIL-11 client.  Otherwise, send
	 * success code, and add recipient to the recipient list.
	 */
	else {
	    char *ptr;

	    recipient = StripThisNode (DECNET_NodeName,
			    MakeLowerCase (XlateRecipient (buf, length))
		        );
	    if (ptr = RewritePath (recipient)) {
		recipient = ptr;
		if (ptr = rindex(recipient, '%'))
		    *ptr = '@';
	    }
	    reply = SMTP_Command ("RCPT To:<%s>", recipient);
	    if ((reply->code & 0xf00) != 0x200
	      && !strcasecmp("system", recipient)) {
		reply = SMTP_Command ("RCPT To:<%s>", "postmaster");
	    }
	    if ((reply->code & 0xf00) != 0x200) {
		mail11_error (reply->msg.head, buf);
		DEBUG_Print ("R: %s => ERROR\n", buf);
	    }
	    else {
		AddRecipient (recipient, strlen (recipient));
		DECNET_Write (success_code, VAX_WORD_SIZE);
		DEBUG_Print ("R: %s => OK\n", buf);
	    }
	}
    }
    DEBUG_Print ("END of recipient list\n");

    /*************************************************************************
     * Read mail-11 To: envelope record.                                     *
     * We cannot trust the recipients named in the mail-11 To: header to be  *
     * valid in the Internet world, since they may be VMS logical names used *
     * as mail aliases, or references to mailing lists of the form @filename,*
     * which looks a lot like an RFC822 source route.  So the mail-11 To:    *
     * address becomes an RFC822 X-To: header.                               *
     *************************************************************************/
    if ((length = ReadRecord (buf, sizeof buf - 1)) < 0)
	goto cleanup;
    if (length > 0)
	mail11_to = HackRecipientHeader (buf);
    DEBUG_Print ("T: %s\n", mail11_to ?mail11_to :"Nil");


    /*************************************************************************
     * Read mail-11 Cc: envelope record, but only if supported by the client.*
     * The mail-11 CC: line is copied to the RFC822 X-Cc: header.            *
     *************************************************************************/

    if (client_config.iomode & CCSEND) {	
	if ((length = ReadRecord (buf, sizeof buf - 1)) < 0)
	    goto cleanup;
	if (length > 0)
	    mail11_cc = HackRecipientHeader (buf);
	DEBUG_Print ("C: %s\n", mail11_cc ?mail11_cc :"Nil");
    }

    /*************************************************************************
     * Read mail-11 Subj: line and copy to RFC822 Subject: header            *
     *************************************************************************/

    if ((length = ReadRecord (buf, sizeof buf - 1)) < 0)
	goto cleanup;
    if (length > 0)
	mail11_subj = strsave (buf);
    DEBUG_Print ("S: %s\n", buf);

    /*************************************************************************
     * Read mail-11 message body and copy to RFC822 message body, after      *
     * generating RFC822 headers.                                            *
     *                                                                       *
     * Send any RFC822 headers that we have generated before sending the     *
     * message body proper.                                                  *
     *                                                                       *
     * If TRUST_RECEIVED_HEADER is #defined, we examine the first part of    *
     * the message body to see whether it already contains RFC822 headers.   *
     * If it does, we use these headers instead of the ones obtained from    *
     * the mail-11 envelope.  This is not enabled by default because the     *
     * addresses could be invalid even if the headers are correctly          *
     * formatted.  For example, if the message originated on BITNET, the     *
     * addresses could look like user@node instead of user@node.bitnet or    *
     * user@some.official.internet.domain .                                  *
     *************************************************************************/

    DEBUG_Print ("BEGIN message body...\n");

    if (!fatal_error) {
	reply = SMTP_Command ("DATA");
	if ((reply->code & 0xf00) != 0x300)
	    fatal_error++;
	else
	    ConcoctHeaders (buf, length);
    }

    if (ModeP->init)
	(*ModeP->init)();
    while ((length = (*ModeP->readln) (buf, sizeof buf - 1)) >= 0) {
	if (!fatal_error)
	    SMTP_WriteLine (buf, length);
    }
    if (ModeP->fini)
	(*ModeP->fini)();

    DEBUG_Print ("END of body\n");

    if (!fatal_error) {
	reply = SMTP_Command (".");
	if ((reply->code & 0xf00) == 0x200)
	    reply = SMTP_Command ("QUIT");
	else
	    fatal_error++;
    }

    /*************************************************************************
     * End of message body.  Here we try to deliver the message and return   *
     * a status code for each recipient, indicating whether the message was  *
     * delivered to that recipient.  Since the SMTP server only reports a    *
     * single status code after all messages have been delivered, the same   *
     * status is returned to the mail-11 client for each recipient.          *
     *************************************************************************/

    for (ptr = RecipientList; ptr != NULL; ptr = ptr->next) {
	DEBUG_Print ("final status for <%s> => %s\n",
		     ptr->address, fatal_error?"bad":"ok");
	if (fatal_error)
	    mail11_error (reply->msg.head, ptr->address);
	else
	    DECNET_Write (success_code, VAX_WORD_SIZE);
    }

    /* wait for sender to close */
    while (!DECNET_EOF ()) 
	DECNET_Read (buf, sizeof buf);

 cleanup:
    SMTP_Close ();
}

mail11_error (msg, recip)
    struct text_line *msg;
    char *recip;
{
    char buf[1000];

    DEBUG_Print ("mail11_error (%08X, %s)\n", msg, recip);
    DECNET_Write (error_code, VAX_WORD_SIZE);
#ifdef FAKEOUT_NMAIL
    sprintf(buf, "%%MAIL11D-E-FATAL: cannot send mail to %s", recip);
    DECNET_Write (buf, strlen (buf));
#endif
    while (msg) {
        DECNET_Write (msg->text, msg->length);
	msg = msg->next;
    }
    WriteMarker ();
}

/*
 * Generate the RFC822 headers and send them to the SMTP server.
 * The (buf,length) is of the first line of the Mail-11 text body;
 * we need it to decide whether the message already has headers in it.
 */
ConcoctHeaders (buf, length)
char *buf;
int length;
{
#ifdef TRUST_RECEIVED_HEADER
    if (length > 10 && strncmp (buf, "Received:", 9) == 0) {
	ConcoctRcvdHeader ();
	ConcoctToHeader ("Resent-to:");
	return;
    }
#endif

    ConcoctRcvdHeader ();
    SMTP_Printf ("Date: %s\n", RFC822_Today ());
    SMTP_Printf ("From: %s\n", mail11_from);
#ifdef FAKE_TO_AND_CC
    if (mail11_to)
	SMTP_Printf ("X-To: %s\n", mail11_to);
    if (mail11_cc)
	SMTP_Printf ("X-Cc: %s\n", mail11_cc);
    /*
     * RFC822 requires at least a To: or CC: header, so we
     * build a fake To: header here from the recipient list
     * to be rfc822-compliant.  If we don't do this, sendmail
     * generates an Apparently-to: header anyway.
     */
    ConcoctToHeader ("To:");
#else
    if (mail11_to)
	SMTP_Printf ("To: %s\n", mail11_to);
    if (mail11_cc)
	SMTP_Printf ("Cc: %s\n", mail11_cc);
    ConcoctToHeader ("Apparently-To:");
#endif
    if (mail11_subj)
	SMTP_Printf ("Subject: %s\n", mail11_subj);
    SMTP_WriteLine ("", 0);
}

/*
 * Determine whether a packet received from DECnet is an end-of-message
 * marker.
 */
IsMarker (buf, length)
char *buf;
int length;
{
    return (length == 1 && *buf == '\0');
}

WriteMarker ()
{
    DECNET_Write ("", sizeof (char));
}

/*
 * rewrite A::B::C to C%B.foo%A.foo.  this function is recursive.  it returns
 * a dynamically allocated %-ified pseudo-address; it has made temporary mods
 * to the input path but hopefully has unmade them all.  if the input is not
 * a normal-looking path, we return NULL.
 */
char *
RewritePath (path)
char *path;
{
#ifndef PERCENTIFY
    return NULL;
#else
    char *more, *addr, *temp;

    for (more = path;  *more && *more != ':';  more++) {
	if (!isalnum(*more))
	    return NULL;
    }
    if (!*more) {
	/* returning from bottommost recursion */
	return strsave (path);
    }
    if (more[1] != ':')
	return NULL;
    if (!(addr = RewritePath (more+2)))
	return NULL;
    /* on our way back up.  addr is the completing %-form.  path..more-1 is the
     * node to be added to the %-form.  optimization alert: if the new node is
     * the same as the tail of the string (accounting for the suffix and %'s)
     * then don't add it again.
     */
    if (temp = rindex(addr, '%')) {
	char *dot = index(++temp, '.');
	if ((dot - temp) == (more - path)
	 && !strncasecmp(temp, path, more - path))
	    return addr;
    }
    temp = malloc (strlen (addr)
		   + (more - path)
		   + strlen (default_suffix)
		   + 1);
    *more = '\0';
    sprintf (temp, "%s%%%s%s", addr, path, default_suffix);
    *more = ':';
    free (addr);
    return temp;
#endif
}


/*
 * Translate the sender's address from DECnet syntax to RFC822 syntax.
 * This routine takes the simple, bulletproof approach.
 * Either the address is already a legal RFC822 address, or we just
 * enclose the whole thing in quotes.
 *
 * Note: at this point, the sender's node name is not part of
 * the address, which makes things simpler.
 *
 * Called by XlateFromLine ()
 */

static char *
HackSender (str)
register char *str;
{
    register char *ptr;
    /*
     * 1000 bytes should be enough for buf.  DECnet enforces a limit
     * of 512 bytes per record, and the From: line is a single
     * record.  We do bounds checking anyway.
     */
    char buf[1000];
    char *endbuf = &buf[990];

    if (ptr = RewritePath (str))
	return ptr;

    /*
     * See if sender has any wierd characters we need to quote.
     * Unhappily, we have to quote anything that is not simply
     * a username, at least until we can be sure it will be
     * interpreted correctly by the local mailer.
     */
    for (ptr = str; *ptr; ++ptr)
	if (strchr (":%\"<>()@[],", *ptr))
	    break;
    if (*ptr == '\0')
	return str;

    /*
     * Enclose the entire string in quotes, making sure to quote
     * any existing quote characters.  If, however, STRIP_QUOTES_IN_SENDER
     * is defined, we strip any quotes already in the string while doing
     * the quoting. utk-mail11 knows how to put the quotes in the right
     * place when handling replies to the DECnet world.
     *
     * e.g. IN%"moore@utkcs2" => "IN%\"moore@utkcs2\""
     * or, if STRIP_QUOTES_IN_SENDER is set,
     *      IN%"moore@utkcs2" => "IN%moore@utkcs2"
     */
    ptr = buf;
    *ptr++ = '"';
    while (*str && ptr < endbuf) {
#ifdef STRIP_QUOTES_IN_SENDER
	if (*str == '"') {
	    ++str;
	    continue;
	}
#else
	if (*str == '"')
	    *ptr++ = '\\';
#endif
	*ptr++ = *str++;
    }
    *ptr++ = '"';
    *ptr = '\0';
    return strsave (buf);
}

/* 
 * Given the sender's personal name, attempt to make it RFC822 compliant.
 * Enclose the whole thing in double quotes if necessary.
 *
 * Called by XlateFromLine ()
 */
static char *
HackSenderName (str)
char *str;
{
    char buf[1000];
    char *endbuf = &buf[990];
    register char *ptr;
    int need_quoting = 0;		/* true if need to quote the string */
    int paren_count = 0;		/* nesting level of parenthesis */

    if (str == NULL || *str == '\0')
	return NULL;

    /*
     * pre-scan string to determine whether all parenthesis are balanced,
     * and to make sure there aren't any funny characters in the string.
     */
    for (ptr = str; *ptr; ++ptr) {
	if (*ptr == '(')
	    paren_count++;
	else if (*ptr == ')') {
	    if (paren_count > 0)
		--paren_count;
	    else {
		need_quoting = 1;
		break;
	    }
	}
	else if (strchr (",<>\\\"", *ptr)) {
	    need_quoting = 1;
	    break;
	}
    }
    if (paren_count != 0)
	need_quoting = 1;

    if (need_quoting == 0)
	return str;
    /*
     * Now copy the personal name field to the buffer, quoting any characters
     * that are likely to confuse RFC822 mailers even if in a quoted string.
     */
    ptr = buf;
    *ptr++ = '"';
    while (*str && ptr < endbuf) {
	if (*str == '\\' || *str == '"')
	    *ptr++ = '\\';
	*ptr++ = *str++;
    }
    *ptr++ = '"';
    *ptr = '\0';
    return strsave (buf);
}

/*
 * XlateFromLine parses the MAIL-11 From: line and extracts the
 * sender's reply address and personal name (if present), which are
 * coerced to RFC822 format.
 */
void
XlateFromLine (buf, length)
char *buf;
int length;
{
    char from[1000];
    char name[1000];
    char temp_buf[1000];
    register char *dest;
    register char *src;
    register int inquote = 0;
    char *sender_address, *sender_full_name;

    src = buf;
    dest = from;
    while (*src) {
	/*
	 * If we find a double quote, remember whether we are inside
	 * a string.
	 */
	if (*src == '"') {
	    inquote = !inquote;
	    *dest++ = *src++;
	}
	/*
	 * A space inside a quoted string just gets copied.
	 * A space outside a quoted string signals
	 * the end of the address.
	 */
	else if (*src == ' ') {
	    if (inquote)
		*dest++ = *src++;
	    else 
		break;
	}
	else
	    *dest++ = *src++;
    }
    *dest = '\0';		/* NUL terminate From: address */

    while (*src == ' ')		/* skip over spaces */
	++src;

    dest = name;		/* get personal name */
    if (*src) {
	/*
	 * Skip over the initial quote, then copy until either another
	 * quote appears or the end of the string.
	 */
	if (*src == '"')
	    ++src;
	while (*src) {
	    if (*src == '"')
		break;
	    else
		*dest++ = *src++;
	}
    }
    *dest = '\0';		/* NUL terminate personal name */

    sender_address = MakeLowerCase (HackSender (from));
    DEBUG_Print ("sa: %s\n", sender_address ?sender_address :"nil");

    sender_full_name = HackSenderName (name);
    DEBUG_Print ("sn: %s\n", sender_full_name ?sender_full_name :"nil");

    /*
     * Generate header From: line and envelope MAIL FROM: line
     * (which becomes Return-Path: on final delivery)
     * For now these are the same thing, but if we ever implement
     * smart From: address parsing then the From: line will
     * change but the envelope MAIL FROM: address will not.
     */

    if (*name)
	sprintf (temp_buf, "%s <%s@%s>", sender_full_name,
		 sender_address, sender_domain);
    else
	sprintf (temp_buf, "<%s@%s>", sender_address, sender_domain);
    mail11_from = strsave (temp_buf);
    DEBUG_Print ("mf: %s\n", mail11_from);
    sprintf (temp_buf, "%s@%s", sender_address, sender_domain);
    mail11_return_path = strsave (temp_buf);
}

/*
 * Build a Received: header and send it to the SMTP server.
 */
ConcoctRcvdHeader ()
{
#ifdef MULTILINE_RECEIVED
    SMTP_Printf (
"\
Received: from %s by %s with MAIL-11\n\
          (%s); %s\n\
",
		 sender_domain, local_domain, VERSION_STRING, RFC822_Today ());
#else
    SMTP_Printf ("Received: from %s; by %s; %s\n",
		 sender_domain, local_domain,
		 RFC822_Today ());
#endif
}

/*
 * Build an ersatz To: header from the envelope recipient list.
 * str is the header name, either "To:" or "Resent-to:".
 */
ConcoctToHeader (str)
char *str;
{
    int length;
    int current_column;
    struct recipient *ptr;

    SMTP_Printf ("%s ", str);
    current_column = 4;
    for (ptr = RecipientList; ptr != NULL; ptr = ptr->next) {
	length = ptr->length;
	if (length + current_column > 78 && current_column > 0) {
	    char *p;
	    SMTP_Printf ("\n");
	    current_column = 1;
	    for (p = str; *p; ++p) {
		SMTP_Printf (" ");
		current_column++;
	    }
	    SMTP_Printf (" ");
	    current_column++;
	}
	SMTP_Printf ("%s", ptr->address);
	current_column += ptr->length;
	if (ptr->next != NULL) {
	    SMTP_Printf (", ");
	    current_column += 2;
	}
    }
    SMTP_Printf ("\n");
}

/*
 * Translate an envelope recipient name.  Basically this consists of
 * stripping double quotes for now.  This means that DECnet mail users
 * cannot use this gateway to send mail to addresses that contain a
 * quoted string.
 *
 * VMS mail should probably allow the user to put double quotes in
 * an address by doubling them, so then we would translate "" => " here.
 * But VMS MAIL doesn't allow this, at least by version 5.0.
 * So we follow the convention used by PMDF and translate single quote
 * to double quote, \d to double quote, and \s to single quote.
 *
 * Doubled double quotes are converted to a single double quote
 * (in case they ever fix VMS MAIL, or in case other MAIL-11 implementations
 * allow this kind of quoting).  Single double quotes are deleted.
 */
char *
XlateRecipient (buf, length)
char *buf;
int length;
{
    static char output[100];
    register char *src = buf;
    register char *dst = output;

    while (*src)
	if (*src == '"' && src[1] == '"') {
	    *dst++ = '"';
	    src += 2;
	}
	else if (*src == '"')
	    ++src;
	else if (*src == '\'') {
	    *dst++ = '"';
	    ++src;
	}
	else if (*src == '\\') {
	    src++;
	    switch (*src) {
	    case 'd':
	    case 'D':
		*dst++ = '"';
		++src;
		break;
	    case 's':
	    case 'S':
	    case '\'':
		*dst++ = '\'';
		++src;
		break;
	    case '\\':
	    default:
		*dst++ = *src++;
	    }
	}
	else
	    *dst++ = *src++;
    *dst = '\0';
    DEBUG_Print ("XlateRecipient: <%s> => <%s>\n", buf, output);
    return output;
}

/*
 * Quick-and-dirty translation from DECnet node names to domains.
 * Eventually we should add hooks to the Internet domain name server.
 *
 * ALWAYS does a strsave() even if it has nothing to do. (important)
 */

char *
ConcoctDomain (node, default_suffix)
char *node;
char *default_suffix;
{
    char buf[100];
    char *ptr;

#ifdef NODE_DOMAIN_LOOKUP
    sprintf (buf, "%s_DOMAIN", node);
    if ((ptr = getenv (buf)) == NULL) {
#else
    if (1) {
#endif
	sprintf (buf, "%s%s", node, default_suffix);
	return strsave (buf);
    }
    else {
	return strsave (ptr);
    }
}

/*
 * Look for a node at the front of a string, followed by ::.  If found,
 * return pointer to character after second :.  If not found, return NULL.
 */
char *
BeginsWithNode(node, addr)
register char *node, *addr;
{
    register int nch, ach;

    while (nch = *node++) {
	if (!(ach = *addr++)) return NULL;
	if (islower(nch)) nch = toupper(nch);
	if (islower(ach)) ach = toupper(ach);
	if (nch != ach) return NULL;
    }
    if (*addr++ != ':') return NULL;
    if (*addr++ != ':') return NULL;
    return addr;
}

/*
 * Strip off this node if it appears at the front of this address
 * (really return pointer to character after ::)
 */
char *
StripThisNode(node, addr)
char *node;
char *addr;
{
    char *x;

    while (x = BeginsWithNode(node, addr))
	addr = x;
    return addr;
}

/*
 * translate a string to lower case
 */
char *
MakeLowerCase (s)
register char *s;
{
    register char *t = s;
    register int ch;

    for (;  ch = *t;  t++)
	if (isupper(ch))
	    *t = tolower(ch);
    return s;
}

/* 
 * Given a MAIL11 header TO/CC, do what's neccessary to turn it into
 * an acceptable RFC822 header TO/CC.  This may mean turning into
 *
 *	LIST:; (mail11-string-with-parens-stripped-out)
 *
 * Called by TranslateMail11ToRFC822 ();
 */
static char *
HackHeaderRecip (str)
register char *str;
{
    char buf[1000];
    char *endbuf = &buf[990];
    char *personal_name = "", *saveptr;
    register char *ptr, ch;
    int need_quoting = 0;		/* true if need to quote the string */
    int all_alnum = 1;
    register parens;
    enum {nothing, leading, trailing} found = nothing;

    if (str == NULL || *str == '\0')
	return NULL;

    /*
     * pre-scan string to determine whether there are any characters which
     * will upset a RFC822 parser.  Things that will parse okay don't
     * matter since the header recips have no hard semantic value and
     * we pretty much write mail11 replies off in any case.  Note that a
     * leading @ is a special case; we assume it's a distribution list
     * and we always quote it.
     */
    parens = 0;
    for (ptr = str;  *ptr && !((parens == 0) && isspace(*ptr));  ++ptr) {
	if (!isalnum(*ptr))
	    all_alnum = 0;
	if (ptr[0] == '<' && ptr == str) {
	    found = leading;
        }
	else if (found == leading && ptr[0] == '>'
	    && (ptr[1] == '\0' || isspace(ptr[1]))
	) {
	    found = trailing;
        }
	else if (strchr("()<>", *ptr)) {
	    need_quoting = 1;
	DEBUG_Print("needs quoting\n");
	}
	/* @ at front of string or following non-alphanum is vms distlist */
	if (*ptr == '@' && (ptr == str || !isalnum(ptr[-1]))) {
	    /* note that all_alnum was already cleared above */
	    need_quoting = 1;
	    break;
	}
	/* handle quotes, since spaces therein aren't personal-name delims */
	if (*ptr == '"')
	    parens = 1 - parens;
    }
    if (isspace(*ptr)) {
	/* stopped on a blank, how nice.  must mean a personal name. */
	saveptr = ptr;
	*saveptr = '\0';
	personal_name = ptr+1;
	while (*personal_name && isspace(*personal_name))
	    personal_name++;
    }
    if (found == leading) {
	/* found leading < but not trailing >.  force need_quoting true */
	need_quoting = 1;
    }

    if (all_alnum) {
	/* if it's an all-alphanumeric string, then several things
	 * are true.  First, we don't need to check any of the quoting
	 * stuff below.  Second, we need to qualify it with the sender's
	 * host name.  (Note: our host name will still be here on the TO:).
	 */
	sprintf (buf, "%s@%s", str,
	    ConcoctDomain (DECNET_RemoteNode, default_suffix)
	);
	if (*personal_name) {
	    strcat (buf, " (");
	    strcat (buf, HackSenderName (personal_name));
	    strcat (buf, ")");
	    *saveptr = ' ';
        }
	return strsave (buf);
    }

    if (need_quoting) {
DEBUG_Print("needs quoting\n");
	/*
         * Looks like it needs to be rewritten.  Add a "mail11:;" on the front
         * of the string since we have to have something for RFC822; add some
         * parens around the real string; and strip out all the bad characters.
         */
        ptr = buf;
        strcpy(ptr, "mail11:; (");
        ptr += 10;
        while ((ch = *str++) && (ptr < endbuf)) {
	    if (!strchr("<>()", ch))
	        *ptr++ = ch;
        }
        *ptr++ = ')';
        *ptr = '\0';
	if (*personal_name)
	    *saveptr = ' ';
        return strsave (buf);
    }

    if (*personal_name) {
	/* no quoting, no special case on all-alnum, but there was a
	 * personal name.  format it as "%s (%s)" and return that.
	 */
	sprintf (buf, "%s %s", str,  HackSenderName (personal_name));
	*saveptr = ' ';
	return strsave (buf);
    }

    /* boring: the string we got is exactly appropriate. */
    return strsave (str);
}

/*
 * given a mail11 to or cc header (with commas), call HackHeaderRecip on
 * each.  this involves breaking the string at commas and reassembling it.
 */
char *
HackRecipientHeader (recips)
char *recips;
{
    register char *recip;
    struct text_msg *hacked_recips;
    struct text_line *hacked_recip;
    int length, total_length;

    if (!recips || !*recips)
	return "";

    DEBUG_Print ("HackRecipientHeader: input <%s>\n", recips);
    recip = strtok (recips, ",");
    hacked_recips = TEXT_Init ();
    total_length = 0;
    do {
	register char *last = recip + strlen(recip) - 1;
	register char *ptr;

	/* delete leading and trailing blanks */
	while (isspace(*recip))
	    recip++;
	while (last >= recip && isspace(*last))
	    *last-- = '\0';
	if (!*recip)
	    break;

	/* hack the recipient and store it on our list */
	recip = StripThisNode (DECNET_NodeName,
		    MakeLowerCase (HackHeaderRecip (recip))
	        );

	if (ptr = RewritePath (recip))
	    recip = ptr;

	length = strlen (recip);
	TEXT_Append (hacked_recips, recip, length);
	total_length += length + 1 /*strlen(",")*/;
    } while (recip = strtok (NULL, ","));

    recips = recip = malloc (total_length + 1);
    for (hacked_recip = hacked_recips->head;
	 hacked_recip != NULL;
	 hacked_recip = hacked_recip->next)
    {
	 strcpy(recip, hacked_recip->text);
	 recip += hacked_recip->length;
	 *recip++ = ',';
	 free (hacked_recip);
    }
    recip[-1] = '\0';
    DEBUG_Print ("HackRecipientHeader: output <%s>\n", recips);
    return recips;
}

