
/*
 *  SENDMAIL / RMAIL
 *
 *  (C) Copyright 1989-1990 by Matthew Dillon,  All Rights Reserved.
 *
 *  SENDMAIL <file -f from -t to -s subject -c cc -b bcc -r
 *  RMAIL user
 *
 *  Example:	Sendmail <datafile -froot
 *
 *  From: line is automatically added but can be overriden by a From:
 *  header in the file.  stdin is made up of a list of headers, a blank
 *  line, and then data until EOF.
 *
 *  the -r option tells sendmail that this is incomming mail.
 *  If av[0] begins with an 'r' for RMail instead of an 's' for
 *  Sendmail, then the rmail argument format is used (rmail user),
 *  as well as forcing -r.
 */

#include "defs.h"

#include <fcntl.h>
#include <log.h>
#include <time.h>
#include <pwd.h>
#include "config.h"
#include "version.h"


IDENT(".15");

Local	void Usage(void);
Local	int brk(void);
Local	NODE *MakeNode(LIST *, char *);
Local	void MakeToFixNode(LIST *, char *, char *);
Local	void fixCallBack(char *, LIST *, int);
Local	void IntegrateHeader(char *, short);
Local	void FromFixup(LIST *);
Local	void ToFixup(LIST *);
Local	void MakeToAddrNode(LIST *, char *);
Local	FILE *TermMailToUUCP(FILE *);
Local	FILE *SendMailTo(LIST *, FILE *);
Local	FILE *OneMailTo(char *, FILE *);
Local	FILE *OneMailToPipe(char *, FILE *);
Local	FILE *OneMailToUser(char *, FILE *);
Local	FILE *OneMailToFile(char *, FILE *);
Local	FILE *OneMailToUUCP(char *, char *, FILE *);
Local	void DumpHeaderInfo(FILE *, int, int, int);
Local	void DumpHeader(FILE *, char *, LIST *);
Local	NODE *FindHeader(char *);
Local	void DumpCombineHeader(FILE *, char *, LIST *);
Local	char *atime(time_t *);
Local	int GetHourOffset(char *);
Local	void PostPend(char *, int);
Local	int EmptyList(LIST *);


#define RCVR_UUCP	1
#define RCVR_SENDMAIL	2

char	*UserName;
char	*RealName;
char	*NodeName;
char	CutNodeName[8];
char	*DomainName;
char	*TimeZoneName;	/*  All caps, ex: PST			 */
char	*DefaultNode;	/*  for addr formats we don't understand */
LIST	RecvList;	/*  Received:	    */
LIST	FromList;	/*  last one rules  */
LIST	ToList; 	/*  To: 	    */
LIST	CcList; 	/*  Cc: 	    */
LIST	BccList;	/*  Bcc:	    */
LIST	XccList;	/*  list of actual mail to be sent  */
LIST	SubjList;	/*  Subject:	    */
LIST	HdrList;	/*  other headers not specifically parsed   */
LIST	UUHostList;	/*  tracking mail sent to a given UUCP host */
char	ScrBuf[1024];
char	ScrBuf2[1024];
char	TempFileBuf[256];
int	Seq;		/*  UUCP sequence no	*/
char	Notify;
char	ROpt;		/*  RMAIL flag		*/
char	NoHdrOpt;	/*  ignore headers opt	*/
static char OrigFromLine[512];
static char FromLine[512];  /*	'From ' line, if ROpt   */



main(ac, av)
char *av[];
{
    short isRMail = 0;
    short isSendMail = 1;
    FILE *fi;

    onbreak(brk);

    NewList(&RecvList);
    NewList(&FromList);
    NewList(&ToList);
    NewList(&CcList);
    NewList(&BccList);
    NewList(&XccList);
    NewList(&SubjList);
    NewList(&HdrList);
    NewList(&UUHostList);

    UserName = GetUserName();
    RealName = GetRealName();

    if (UserName == NULL) {
	puts("Sendmail: UserName config entry not found!");
	ulog(0, "UserName config missing");
	exit(1);
    }
    if (RealName == NULL) {
	puts("Sendmail: RealName config entry not found!");
	ulog(0, "RealName config missing");
	exit(1);
    }

    NodeName = FindConfig("NodeName");
    strncpy(CutNodeName, NodeName, 7);
    if (NodeName == NULL) {
	puts("Sendmail: NodeName config entry not found!");
	ulog(0, "NodeName config missing");
	exit(1);
    }
    DomainName = FindConfig("DomainName");
    if (DomainName == NULL) {
	puts("Sendmail: DomainName config entry not found! using .UUCP");
	DomainName = ".uucp";
    }
    DefaultNode = FindConfig("DefaultNode");
    TimeZoneName = FindConfig("TimeZone");

    LoadAliases();

    {
	char *ptr = av[0] + strlen(av[0]);

	/*
	 *  Skip path
	 */

	while (ptr >= av[0] && *ptr != ':' && *ptr != '/')
	    --ptr;
	++ptr;

	if (*ptr == 'r' || *ptr == 'R') {
	    isRMail = 1;
	    isSendMail = 0;
	}
    }

    if (isRMail) {
	if (ac <= 1) {
	    MakeNode(&BccList, "Mailer-Daemon");
	} else {
	    short i;
	    for(i = 1; i < ac; ++i)
		MakeNode(&BccList, av[i]);
	}
	UserName = "postmaster";    /*  XXX  */
	RealName = "Mr.Amiga";      /*  XXX  */
	ROpt = 1;		    /*	no header processing */
    }

    if (isSendMail) {
	short i;
	char *arg;
	for (i = 1; i < ac; ++i) {
	    arg = av[i];
	    if (*arg != '-')
		Usage();
	    arg += 2;
	    switch(arg[-1]) {
	    case 'f':
		UserName = av[i+1];
		sprintf(ScrBuf, "%s@%s%s", UserName, NodeName, DomainName);
		MakeNode(&FromList, ScrBuf);
		++i;
		break;
	    case 'R':
		RealName = av[++i];
		break;
	    case 't':
		MakeNode(&ToList, av[++i]);
		break;
	    case 'c':
		MakeNode(&CcList, av[++i]);
		break;
	    case 'b':
		MakeNode(&BccList, av[++i]);
		break;
	    case 's':
		MakeNode(&SubjList, av[++i]);
		break;
	    case 'r':
		if (strcmp(arg, "aw") == 0)
		    NoHdrOpt = 1;
		else
		    ++ROpt;
		break;
	    default:
		Usage();
	    }
	}
    }

    /*
     *	Read headers from input file.  Headers are not necessarily
     *	contained on a single line.  Maximum 4096 chars per header.
     */

    if (ROpt) {
	fgets(ScrBuf, sizeof(ScrBuf), stdin);
	strcpy(OrigFromLine, ScrBuf);
	if (strncmp(ScrBuf, "From ", 5) != 0) {
	    ulog(-1, "Receive mail, expected 'From ', got %s", ScrBuf);
	}
	strcpy(FromLine, "From ");
	PostPend(ScrBuf + 5, 1);

	while (fgets(ScrBuf, sizeof(ScrBuf), stdin) && strncmp(ScrBuf, ">From ", 6) == 0) {
	    strcpy(OrigFromLine, ScrBuf + 1);
	    PostPend(ScrBuf + 6, 1);
	}
	strcpy(ScrBuf2, OrigFromLine + 5);
	PostPend(ScrBuf2, 0);
    } else {
	ScrBuf[0] = '\n';
	if (NoHdrOpt == 0)
	    fgets(ScrBuf, sizeof(ScrBuf), stdin);
    }
    if (NoHdrOpt == 0) {
	static char Hdr[4096];
	short i = 0;		/*  index into Hdr  */

	while (ScrBuf[0] != '\n') {
	    char *ptr = ScrBuf;
	    while (*ptr && *ptr != ' ' && *ptr != 9 && *ptr != ':')
		++ptr;
	    if (*ptr == ':') {  /*  found new header */
		if (i)          /*  Dump old header  */
		    IntegrateHeader(Hdr, i);
		strcpy(Hdr, ScrBuf);
		i = strlen(Hdr);
	    } else {		/*  append to existing header	*/
		if (i == 0)
		    puts("Expected a Header!");
		strcpy(Hdr + i, ScrBuf);
		i = i + strlen(Hdr + i);
	    }

	    if (fgets(ScrBuf, sizeof(ScrBuf), stdin) == NULL)
		ScrBuf[0] = '\n';
	}
	if (i)
	    IntegrateHeader(Hdr, i);

	if (ScrBuf[0] != '\n') {
	    puts("sendmail: no mail");
	    ulog(0, "No Mail");
	    exit(1);
	}
    }

    /*
     *	Parse & fixup each To:, Cc:, and Bcc: field.
     *
     *	From:	we add the personal info arg from the password file
     *	To:	we expand any aliases
     *
     *	OpenLog() : log optimization
     */

    OpenLog();
    if (ROpt == 0)
	FromFixup(&FromList);
    if (ROpt) {
	ToFixup(&BccList);
    } else {
	ToFixup(&ToList);
	ToFixup(&CcList);
	ToFixup(&BccList);
    }
    CloseLog();

    /*
     *	If no Subject: field add a dummy one
     */

    if (EmptyList(&SubjList))
	MakeNode(&SubjList, "");

    if (EmptyList(&FromList)) {
	sprintf(ScrBuf, "%s@%s%s", UserName, NodeName, DomainName);
	MakeNode(&FromList, ScrBuf);
    }

    fi = SendMailTo(&XccList, stdin);
    if (fi && fi != stdin)
	fclose(fi);
    if (TempFileBuf[0])
	remove(TempFileBuf);
    UnLockFiles();

    /*
     *	Notify if T:MailRdy does not exist (created by us, deleted by
     *	notify when notify exits).  Prevents running multiple notifies.
     */

    if (Notify) {
	int fd = open("T:MailRdy", O_RDONLY);
	if (fd < 0) {
	    char *cmd;


	    if (cmd = FindConfig(MAILREADYCMD)) {
		fd = open("T:MailRdy", O_CREAT|O_TRUNC, 0666);
		if (fd >= 0)
		    close(fd);
		sprintf(ScrBuf, "Run %s -x T:MailRdy", cmd);
		Execute(ScrBuf, NULL, NULL);
	    }
	} else {
	    close(fd);
	}

	/*
	 *  signal for update for any utilities that support it.
	 */
	{
	    MsgPort *port;
	    Forbid();
	    if ((port = FindPort("T:MailRdy")) && port->mp_SigTask)
		Signal(port->mp_SigTask, 1 << port->mp_SigBit);
	    Permit();
	}
    }
    return(0);
}

void
Usage()
{
    printf(
	"Sendmail -f user [-t address -c address -b address -s subject -r]\n"
	"RMail user\n"
    );
}

int
brk()
{
    return(0);
}

/*
 *  Strips string and creates named node which is appended to the
 *  given list.
 */

NODE *
MakeNode(list, str)
LIST *list;
char *str;
{
    NODE *node;
    char *ptr;

    while (*str == ' ' || *str == 9)
	++str;
    for (ptr = str + strlen(str) - 1; ptr >= str && (*ptr == ' ' || *ptr == 9); --ptr);
    ++ptr;
    *ptr = 0;
    node = malloc(sizeof(NODE) + strlen(str) + 1);
    node->ln_Name = (char *)(node + 1);
    strcpy(node->ln_Name, str);
    AddTail(list, node);
    return(node);
}

/*
 *
 */

void
MakeToFixNode(list, str, send)
LIST *list;
char *str;
char *send;
{
    char *ptr;
    short len;
    short c;

    while (str < send && (*str == ' ' || *str == 9))
	++str;
    for (ptr = send - 1; ptr >= str && (*ptr == ' ' || *ptr == 9); --ptr);
    ++ptr;

    len = ptr - str;
    if (len < 0)
	return;

    /*
     *	str[0..len-1]
     */

    c = str[len];
    str[len] = 0;

    if (ROpt) {     /*  disallow remote asking for special options */
	ulog(-1, "Received mail for %s", str);
	if (str[0] == '>' || str[0] == '<' || str[0] == '|' || str[0] == ':' || str[0] == '/') {
	    ulog(-1, "SendMail, bad user %s", str);
	    return;
	}
    }

    /*
     *	expand aliases.  If the str exists as an alias and the special
     *	:: (do not expand alias in To: field) is used, then the name of
     *	the alias is placed in the To: field.
     */

    if (UserAliasList(str, fixCallBack, (long)list, 1)) {
	NODE *node;

	sprintf(ScrBuf, "%s@%s%s", str, NodeName, DomainName);

	node = malloc(sizeof(NODE) + strlen(ScrBuf) + 1);
	node->ln_Name = (char *)(node + 1);
	strcpy(node->ln_Name, ScrBuf);
	AddTail(list, node);
    }
    str[len] = c;
}

void
fixCallBack(user, list, showto)
char *user;
LIST *list;
int showto;
{
    NODE    *node;

    if (user[0] == '<') {
	FILE *fi = fopen(user + 1, "r");
	char *buf = malloc(256);

	if (fi == NULL) {
	    ulog(-1, "Unable to open < file %s", user + 1);
	    return;
	}
	while (fgets(buf, 256, fi)) {
	    short i = 0;
	    short j;
	    while (buf[i] == ' ' || buf[i] == 9)
		++i;
	    if (buf[i] == 0 || buf[i] == '\n')
		continue;
	    for (j = i; buf[j] && buf[j] != '\n' && buf[j] != ' ' && buf[j] != 9; ++j);
	    buf[j] = 0;
	    UserAliasList(buf, fixCallBack, (long)list, showto);
	}
	fclose(fi);
	free(buf);
	return;
    }

    if (showto)
	ulog(-1, "Sendmail, Sending mail to %s", user);

    if (user[0] == '\\')
	++user;

    /*
     *	add to Xcc (actual send list).  If showto < 0 then the alias was
     *	given to us for display (To: field display) purposes only and
     *	should NOT be added to the Xcc list
     */

    if (showto >= 0) {
	node = malloc(sizeof(NODE) + strlen(user) + 1);
	node->ln_Name = (char *)(node + 1);
	strcpy(node->ln_Name, user);
	AddTail(&XccList, node);
    }

    /*
     *	If an alias element is to be shown in the To: field of the
     *	message, do so.  If it is a local user with no qualifications,
     *	then append our full domain name.
     */

    if (showto) {
	short localFix = 1;
	{
	    char *ptr;
	    for (ptr = user; *ptr; ++ptr) {
		if (*ptr == '!' || *ptr == '@' || *ptr == '%' || *ptr == ':') {
		    localFix = 0;
		    break;
		}
	    }
	}
	if (localFix)
	    sprintf(ScrBuf, "%s@%s%s", user, NodeName, DomainName);
	else
	    strcpy(ScrBuf, user);

	node = malloc(sizeof(NODE) + strlen(ScrBuf) + 1);
	node->ln_Name = (char *)(node + 1);
	strcpy(node->ln_Name, ScrBuf);
	AddTail(list, node);
    }
}

/*
 *  Integrates a header
 */

void
IntegrateHeader(hdr, len)
char *hdr;
short len;
{
    if (hdr[len-1] == '\n')     /*  strip trailing newline  */
	hdr[len-1] = 0;

    if (strncmp(hdr, "From:", 5) == 0) {
	MakeNode(&FromList, hdr + 5);
	return;
    }
    if (strncmp(hdr, "To:", 3) == 0) {
	MakeNode(&ToList, hdr + 3);
	return;
    }
    if (strncmp(hdr, "Cc:", 3) == 0) {
	MakeNode(&CcList, hdr + 3);
	return;
    }
    if (strncmp(hdr, "Bcc:", 4) == 0) {
	MakeNode(&BccList, hdr + 4);
	return;
    }
    if (strncmp(hdr, "Subject:", 8) == 0) {
	MakeNode(&SubjList, hdr + 8);
	return;
    }
    if (strncmp(hdr, "Received:", 9) == 0) {
	MakeNode(&RecvList, hdr + 9);
	return;
    }
    MakeNode(&HdrList, hdr);
}

/*
 *  Adds (personal info) to FromList based on passwd entry or, if
 *  that is not available, from the Config entry 'RealName' or
 *  ENV:REALNAME (env:REALNAME takes precedence)
 */

void
FromFixup(list)
LIST *list;
{
    NODE *node;
    NODE *nn;
    LIST tmpList;

    NewList(&tmpList);

    while (node = RemHead(list)) {
	/*
	 *  FIXME.  Fix getpwnam() and use pw_gecos entry.
	 */
	nn = malloc(sizeof(NODE) + strlen(node->ln_Name) + strlen(RealName) + 16);
	nn->ln_Name = (char *)(nn + 1);
	sprintf(nn->ln_Name, "%s (%s)", node->ln_Name, RealName);
	free(node);
	AddTail(&tmpList, nn);
    }
    while (node = RemHead(&tmpList))
	AddTail(list, node);
}

/*
 *  Converts an unparsed list of names into a list of single address
 *  fields, removing any personal idents from the entries.  These will
 *  be recombined after processing when the data file is written out.
 *
 *  Also expands sendmail aliases (UULIB:Aliases) (HACK)
 */

void
ToFixup(list)
LIST *list;
{
    NODE *node;
    LIST tmpList;

    NewList(&tmpList);

    while (node = RemHead(list)) {
	char *str = node->ln_Name;
	char *ptr;

	while (*str) {      /*  breakup fields by newline or comma */
	    for (ptr = str; *ptr && *ptr != '\n' && *ptr != ','; ++ptr);
	    MakeToAddrNode(&tmpList, str);
	    str = ptr;
	    while (*str == '\n' || *str == ',' || *str == ' ' || *str == 9)
		++str;
	}
	free(node);
    }
    while (node = RemHead(&tmpList))
	AddTail(list, node);
}

/*
 *  Extracts a single name / address (comma or newline delimited)
 *  field and creates a new node.
 */

void
MakeToAddrNode(list, str)
LIST *list;
char *str;
{
    char *p1, *p2;
    short ns = 0;	/*  non-whitespace encountered */

    for (p1 = str; *p1 && *p1 != ',' && *p1 != '\n'; ++p1) {
	if (*p1 == '(') {   /*  addr (name) OR (name) addr */
	    if (ns) {       /*  addr (name) */
		MakeToFixNode(list, str, p1);
	    } else {
		while (*p1 && *p1 != ',' && *p1 != '\n' && *p1 != ')')
		    ++p1;
		if (*p1 == ')') {
		    for (p2 = p1 + 1; *p2 && *p2 != ',' && *p2 != '\n'; ++p2);
		    MakeToFixNode(list, p1 + 1, p2);
		}
	    }
	    return;
	}

	if (*p1 == '<') {   /*  <addr>  */
	    for (p2 = p1 + 1; *p2 && *p2 != ',' && *p2 != '\n' && *p2 != '>'; ++p2);
	    if (*p2 == '>')
		MakeToFixNode(list, p1 + 1, p2);
	    return;
	}
	if (*p1 != ' ' && *p1 != 9)
	    ns = 1;
    }
    MakeToFixNode(list, str, p1);
}


/*
 *  Send mail to <recipiants>
 *
 *	-Local mail
 *	-Machine path (UUCP)
 *
 *  NOTE:   now groups mail
 */

FILE *
SendMailTo(list, fi)
LIST *list;
FILE *fi;
{
    NODE *node;

    for (node = list->lh_Head; node != (NODE *)&list->lh_Tail; node = node->ln_Succ)
	fi = OneMailTo(node->ln_Name, fi);
    fi = TermMailToUUCP(fi);
    return(fi);
}

FILE *
OneMailTo(toaddr, rfi)
char *toaddr;
FILE *rfi;
{
    short i;
    char c;
    static char ToAddr[512];
    static char Buf[512];
    static char typeBuf[16];
    static char classBuf[16];
    static char addrBuf[128];

    if (toaddr[0] == '|')       /*  pipe through command    */
	return(OneMailToPipe(toaddr + 1, rfi));
    if (toaddr[0] == '>')       /*  copy to file            */
	return(OneMailToFile(toaddr + 1, rfi));

    strncpy(ToAddr, toaddr, sizeof(ToAddr));
    ToAddr[sizeof(ToAddr) - 1] = 0;

loop:
    for (i = 0; c = ToAddr[i]; ++i) {
	if (c == '!' || c == '%' || c == '@' || c == ':')
	    break;
    }
    if (c == 0)                 /*  local name  */
	return(OneMailToUser(ToAddr, rfi));

    /*
     *	Non-Local mail
     */

    i = ParseAddress(ToAddr, Buf, strlen(ToAddr));
    if (DomainLookup(Buf, strlen(Buf), typeBuf, classBuf, addrBuf)) {
	printf("type %s class %s addr %s ROUTE %s\n", typeBuf, classBuf, addrBuf, Buf);

	/*
	 *  Note distinction between mail destination and mail forwarder.
	 *  mail destination removes the first machine from the rmail line
	 *  mail forwarder does NOT
	 */

	if (strcmpi(classBuf, "UU") == 0) {
	    if (strcmpi(typeBuf, "MD") == 0)
		return(OneMailToUUCP(addrBuf, Buf + i + 1, rfi));
	    else
		return(OneMailToUUCP(addrBuf, Buf, rfi));
	} else if (strcmpi(classBuf, "LL") == 0) {  /*  local mail, loop */
	    for (i = 0; Buf[i] && Buf[i] != '!'; ++i);
	    if (Buf[i] == 0) {
		strcpy(Buf + i, "postmaster");
		--i;
	    }
	    strcpy(ToAddr, Buf + i + 1);
	    goto loop;
	} else {
	    ulog(-1, "Unsupported domain class: %s", classBuf);
	    printf("Unsupported domain class: %s\n", classBuf);
	}
	return(rfi);
    } else {
	ulog(-1, "Could not find domain for %s, no mail sent", Buf);
	printf("Unable to send mail to %s\n", Buf);
	return(rfi);
    }
}


FILE *
OneMailToPipe(toaddr, rfi)
char *toaddr;
FILE *rfi;
{
    FILE *fi;
    char *ptr;
    static long pos;

    if (TempFileBuf[0] == 0) {
	strcpy(TempFileBuf, TmpFileName("T:pipe"));
	fi = fopen(TempFileBuf, "w");
	if (fi == NULL) {
	    ulog(-1, "Unable to open temp file %s for command: %s", TempFileBuf, toaddr);
	    return(rfi);
	}
	DumpHeaderInfo(fi, RCVR_SENDMAIL, 0, 1);
	pos = ftell(fi);
	while (fgets(ScrBuf, sizeof(ScrBuf), rfi))
	    fputs(ScrBuf, fi);
	fclose(fi);
    }
    strcpy(ScrBuf, toaddr);

    ptr = toaddr;
    if (strncmpi(toaddr, "run", 3) == 0) {
	ptr += 3;
	while (*ptr == ' ' || *ptr == 9)
	    ++ptr;
    }
    while (*ptr && *ptr != ' ' && *ptr != 9)
	++ptr;

    if (*ptr == 0)
	strcat(ScrBuf, " ");
    sprintf(ScrBuf + (ptr - toaddr + 1), "<%s %s", TempFileBuf, ptr);
    if (Execute(ScrBuf, NULL, NULL) == 0)
	ulog(-1, "Couldn't execute %s", ScrBuf);

    fi = fopen(TempFileBuf, "r");
    if (fi) {
	if (rfi != stdin)
	    fclose(rfi);
	rfi = fi;
	fseek(rfi, pos, 0);
    } else {
	ulog(-1, "Couldn't reopen temp '%s', mail failed!", TempFileBuf);
	rfi = stdin;
    }
    return(rfi);
}

FILE *
OneMailToUser(toaddr, rfi)
char *toaddr;
FILE *rfi;
{
    ++Notify;
    strcpy(ScrBuf, MakeConfigPath(UUMAIL, toaddr));
    return(OneMailToFile(ScrBuf, rfi));
}

FILE *
OneMailToFile(tofile, rfi)
char *tofile;
FILE *rfi;
{
    FILE *fo;
    static char DataFile[128];
    long pos;

    strcpy(DataFile, tofile);

    LockFile(DataFile);
    fo = fopen(DataFile, "a");
    if (fo == NULL) {
	strcpy(DataFile, "T:MailOverflow");
	fo = fopen(DataFile, "a");
	if (fo)
	    ulog(-1, "Could not append to %s, appending to %s", tofile, DataFile);
	else
	    ulog(-1, "Can't append to anywhere! (%s)", tofile);
    }
    if (fo) {
	DumpHeaderInfo(fo, RCVR_SENDMAIL, 0, 1);
	pos = ftell(fo);
	if (ROpt) {
	    if (fgets(ScrBuf, sizeof(ScrBuf), rfi))
		fputs(ScrBuf, fo);
	}
	while (fgets(ScrBuf, sizeof(ScrBuf), rfi)) {
	    if (ScrBuf[0] == 'F' && strncmp(ScrBuf, "From ", 5) == 0)
		strins(ScrBuf, " ");
	    fputs(ScrBuf, fo);
	}
	if (ScrBuf[0] != '\n')
	    fputs("\n", fo);
	fclose(fo);
	if (rfi != stdin)
	    fclose(rfi);
	rfi = fopen(DataFile, "r");
	fseek(rfi, pos, 0);
    }
    UnLockFile(DataFile);
    return(rfi);
}

/*
 *  UUCP MAILER.  Generate a control node for each destination.  When the
 *  mail is terminated, a batch file will be created for each destination
 *  host.
 */

FILE *
OneMailToUUCP(toaddr, skipaddr, rfi)
char *toaddr;
char *skipaddr;
FILE *rfi;
{
    UUHostNode *uh;
    struct Node *node;
    short i;
    int hostLen;
    int maxRMailLen = atoi(GetConfig(MAXRMAILLEN, "0"));

    /*
     *	find host, first element of 'toaddr'
     */

loop:
    for (i = 0; toaddr[i] && toaddr[i] != '!' && toaddr[i] != '.' && i != 7; ++i);

    /*
     *	search for UUHostNode in UUHostList, append to node list for
     *	rmail line.
     */

    for (uh = GetHead(&UUHostList); uh; uh = GetSucc(&uh->uh_Node)) {
	if (strlen(uh->uh_Node.ln_Name) == i && strnicmp(uh->uh_Node.ln_Name, toaddr, i) == 0)
	    break;
    }
    if (uh == NULL) {
	uh = malloc(sizeof(UUHostNode) + i + 1);
	uh->uh_Node.ln_Name = (char *)(uh + 1);
	uh->uh_Len = 10;    /*	'C rmail'   */
	uh->uh_NAddr = 0;
	strncpy(uh->uh_Node.ln_Name, toaddr, i);
	uh->uh_Node.ln_Name[i] = 0;
	AddTail(&UUHostList, &uh->uh_Node);
	NewList(&uh->uh_List);
    }

    while (toaddr[i] && toaddr[i] != '!')
	++i;
    if (toaddr[i] == '!')
	++i;

    hostLen = strlen(toaddr + i) + strlen(skipaddr) + 3;

    if (uh->uh_NAddr > 0 && uh->uh_Len + hostLen > maxRMailLen) {
	rfi = TermMailToUUCP(rfi);
	goto loop;
    }


    node = malloc(sizeof(struct Node) + hostLen + 1);
    node->ln_Name = (char *)(node + 1);
    AddTail(&uh->uh_List, node);

    if (toaddr[i]) {
	sprintf(node->ln_Name, "%s!%s", toaddr + i, skipaddr);
    } else {
	sprintf(node->ln_Name, "%s", skipaddr);
    }
    ++uh->uh_NAddr;
    uh->uh_Len += strlen(node->ln_Name) + 1;    /*  inc space */
    return(rfi);
}


FILE *
TermMailToUUCP(rfi)
FILE *rfi;
{
    static char ExecFile[128];
    static char RemoteExecFile[128];
    static char CommandFile[128];
    static char DataFile[128];
    static char RemoteDataFile[128];
    UUHostNode *uh;
    char *spoolDir = strdup(MakeConfigPath(UUSPOOL, ""));
    short foff = strlen(spoolDir);
    short failure = 0;

    while (uh = RemHead(&UUHostList)) {
	int seq;
	long pos;
	FILE *fi;

	Seq = seq = GetSequence(4);

	sprintf(CommandFile,    "%sC.%sA%04d", spoolDir, uh->uh_Node.ln_Name, seq);
	sprintf(DataFile,       "%sD.%sB%04d", spoolDir, uh->uh_Node.ln_Name, seq);
	sprintf(ExecFile,       "%sD.%sX%04d", spoolDir, uh->uh_Node.ln_Name, seq);
	++seq;
	sprintf(RemoteExecFile, "%sX.%sX%04d", spoolDir, CutNodeName, seq);
	++seq;
	sprintf(RemoteDataFile, "%sD.%sB%04d", spoolDir, CutNodeName, seq);

	LockFile(CommandFile);
	LockFile(DataFile);

	/*
	 *  Note, cannot run uuxqt from sendmail as uuxqt may call
	 *  sendmail itself!
	 */

	if (strncmp(uh->uh_Node.ln_Name, NodeName, 7) == 0) {
	    strcpy(ExecFile, RemoteExecFile);
	    strcpy(RemoteDataFile, DataFile);
	}

	fi = fopen(ExecFile, "w");

	if (fi == NULL) {
	    UnLockFile(CommandFile);
	    UnLockFile(DataFile);
	    failure = 1;
	    break;
	}

	fprintf(fi, "U %s %s\n", UserName, NodeName);
	fprintf(fi, "F %s\n", RemoteDataFile + foff);
	fprintf(fi, "I %s\n", RemoteDataFile + foff);
	fprintf(fi, "C rmail");

	{
	    struct Node *node;

	    while (node = RemHead(&uh->uh_List)) {
		fprintf(fi, " %s", node->ln_Name);
		free(node);
	    }
	    fputc('\n', fi);
	}
	fclose(fi);

	if (strnicmp(uh->uh_Node.ln_Name, NodeName, 7) != 0) {
	    fi = fopen(CommandFile, "w");
	    if (fi == NULL) {
		UnLockFile(CommandFile);
		UnLockFile(DataFile);
		failure = 1;
		break;
	    }

	    fprintf(fi, "S %s %s %s - %s 0666\n", DataFile + foff, RemoteDataFile + foff, UserName, DataFile + foff);
	    fprintf(fi, "S %s %s %s - %s 0666\n", ExecFile + foff, RemoteExecFile + foff, UserName, ExecFile + foff);
	    fclose(fi);
	}

	fi = fopen(DataFile, "w");
	if (fi == NULL) {
	    UnLockFile(CommandFile);
	    UnLockFile(DataFile);
	    remove(CommandFile);
	    failure = 1;
	    break;
	}

	DumpHeaderInfo(fi, RCVR_UUCP, 1, 0);
	pos = ftell(fi);

	if (ROpt) {
	    if (fgets(ScrBuf, sizeof(ScrBuf), rfi))
		fputs(ScrBuf, fi);
	}
	while (fgets(ScrBuf, sizeof(ScrBuf), rfi)) {
	    if (ScrBuf[0] == 'F' && strncmp(ScrBuf, "From ", 5) == 0)
		strins(ScrBuf, " ");
	    fputs(ScrBuf, fi);
	}
	fclose(fi);
	if (rfi != stdin)
	    fclose(rfi);
	fi = fopen(DataFile, "r");
	fseek(fi, pos, 0);
	rfi = fi;

	UnLockFile(CommandFile);
	UnLockFile(DataFile);
	free(uh);
    }
    if (failure) {
	puts("Fail");
	ulog(-1, "Output failed %s", CommandFile);
    }
    free(spoolDir);
    return(rfi);
}

void
DumpHeaderInfo(fi, rcvr, resend, local)
FILE *fi;
int rcvr, resend, local;
{
    char *source;
    time_t t;

    time(&t);

    /*
     *	Write header info
     */

    if (rcvr == RCVR_UUCP)
	source = "AmigaUUCP";
    else
	source = "Sendmail";

    if (ROpt) {
	if (resend)
	    fprintf(fi, "%s %s remote from %s\n", FromLine, atime(&t), NodeName);
	else
	    fprintf(fi, "%s %s\n", FromLine, atime(&t));
    } else {
	if (local)
	    fprintf(fi, "From %s %s\n", UserName, atime(&t));
	else
	    fprintf(fi, "From %s %s remote from %s\n", UserName, atime(&t), NodeName);
    }
    fprintf(fi, "Received: by %s%s (" VERSION "/Amiga)\n\tid AA%05d; %s\n",
	NodeName, DomainName, Seq, atime(&t)
    );
    DumpHeader(fi, "Received:", &RecvList);
    if (ROpt == 0) {
	time_t t2 = t + 3600 * GetHourOffset(TimeZoneName);
	struct tm *ut;

	if (FindHeader("Date:") == NULL)
	    fprintf(fi, "Date: %s\n", atime(&t));
	ut = localtime(&t2);
	fprintf(fi, "Message-Id: <%02d%02d%02d%02d%02d.AA%05d@%s%s>\n",
	    ut->tm_year % 100, ut->tm_mon + 1, ut->tm_mday, ut->tm_hour, ut->tm_min,
	    Seq, NodeName, DomainName
	);
    }

    /*
     *	From:, To:, Cc:, Subject: (Bcc: never written to header),
     *	and any other header fields
     */

    DumpHeader(fi, NULL, &HdrList);
    DumpHeader(fi, "From:", &FromList);
    DumpCombineHeader(fi, "To:", &ToList);
    if (!EmptyList(&CcList))
	DumpCombineHeader(fi, "Cc:", &CcList);
    DumpHeader(fi, "Subject:", &SubjList);

    fprintf(fi, "\n");
}

void
DumpHeader(fi, field, list)
FILE *fi;
char *field;
LIST *list;
{
    NODE *node;

    for (node = list->lh_Head; node != (NODE *)&list->lh_Tail; node = node->ln_Succ) {
	if (field)
	    fprintf(fi, "%s %s\n", field, node->ln_Name);
	else
	    fprintf(fi, "%s\n", node->ln_Name);
    }
}

NODE *
FindHeader(field)
char *field;
{
    NODE *node;
    short len = strlen(field);

    for (node = HdrList.lh_Head; node != (NODE *)&HdrList.lh_Tail; node = node->ln_Succ) {
	if (strncmp(node->ln_Name, field, len) == 0)
	    return(node);
    }
    return(NULL);
}

void
DumpCombineHeader(fi, field, list)
FILE *fi;
char *field;
LIST *list;
{
    NODE *node;
    short ci = 0;

    fprintf(fi, "%s ", field);
    for (node = list->lh_Head; node != (NODE *)&list->lh_Tail; node = node->ln_Succ) {
	if (ci && ci + strlen(node->ln_Name) > 70) {
	    fprintf(fi, ",\n\t");
	    ci = 0;
	}
	if (ci)
	    fprintf(fi, ", ");
	fprintf(fi, "%s", node->ln_Name);
	ci += strlen(node->ln_Name) + 2;
    }
    fprintf(fi, "\n");
}

char *
atime(pt)
time_t *pt;
{
    static char buf[40];
    static char *mo[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
			    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
			};
    static char *dow[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
    struct tm *ut = localtime(pt);

    sprintf(buf, "%s, %d %s %02d %02d:%02d:%02d %s",
	dow[ut->tm_wday], ut->tm_mday, mo[ut->tm_mon],
	ut->tm_year % 100, ut->tm_hour, ut->tm_min, ut->tm_sec,
	TimeZoneName
    );
    return(buf);
}

int
GetHourOffset(tz)
char *tz;
{
    short i;

    static struct {
	char *Name;
	short Hours;
    } TZAry[] = {
	"GMT",  0,
	"UT",   0,
	"UTC",  0,
	"PST",  8,
	"MST",  7,
	"CST",  6,
	"EST",  5,
	"AST",  4,
	"PDT",  7,
	"MDT",  6,
	"CDT",  5,
	"EDT",  4,
	"ADT",  3,
	"CET",  -1,
	"MET",  -1,     /*  alternate, please use CET   */
	"MEZ",  -1,     /*  alternate, please use CET   */
	"CED",  -2,     /*  central european daylight saving time   */
	"MED",  -2,     /*  alternate, please use CED   */
	"CEST", -2,     /*  alternate, please use CED   */
	"MESZ", -2,     /*  alternate, please use CED   */
	"JST",  -9,
	NULL, 0
    };
    for (i = 0; TZAry[i].Name; ++i) {
	if (strncmp(tz, TZAry[i].Name, 3) == 0)
	    return((int)TZAry[i].Hours);
    }
    ulog(-1, "Unknown Timezone: %s", tz);
    printf("Unknown Timezone: %s", tz);
    return(6);
}

void
PostPend(str, frend)
char *str;
int frend;
{
    char *ptr;
    if (frend) {
	ptr = str + strlen(str);
	while (ptr > str && *ptr != ' ' && *ptr != 9) {
	    if (*ptr == '\n')
		*ptr = 0;
	    --ptr;
	}
	str = ptr + 1;
    }
    for (ptr = str; *ptr && *ptr != ' ' && *ptr != 9 && *ptr != '\n'; ++ptr);
    if (frend)
	*ptr++ = '!';
    *ptr = 0;
    for (ptr = FromLine + 5; *ptr && *ptr != ' ' && *ptr != 9; ++ptr);
    strins(ptr, str);
}

EmptyList(list)
LIST *list;
{
    if (list->lh_Head == (NODE *)&list->lh_Tail)
	return(1);
    return(0);
}

