
/*
 *  UUCICO.C
 *
 *  $Header: Beta:src/uucp/src/uucico/RCS/uucico.c,v 1.1 90/02/02 11:56:01 dillon Exp Locker: dillon $
 *
 *  (C) Copyright 1987 by John Gilmore.
 *  Copying and use of this program are controlled by the terms of the Free
 *  Software Foundation's GNU Emacs General Public License.
 *
 *  Derived from:
 *  i[$]uuslave.c	 1.7 08/12/85 14:04:20
 *  which came from the ACGNJ BBS system at +1 201 753 9758.  Original
 *  author unknown.
 *
 *  Ported to Amiga by William Loftus
 *  Amiga Changes Copyright 1988 by William Loftus.  All rights reserved.
 *  Additional Major Changes (c)Copyright 1989 by Matthew Dillon, All rights reserved
 *
 * 14-Oct-89, moved modem_init() to before poll_sys.
 *
 * -r option	   (-r1 does a call out to all systems we have mail for)
 * -D[EVICE] dev    sets serial device name (automatic from Getty)
 * -U[NIT] unit     sets unit name (automatic from Getty)
 * -h0		    Ignore CD (carrier detect)
 * -7
 */


#include "includes.h"           /* System include files, system dependent */
#include "uucp.h"               /* Uucp definitions and parameters */
#include <log.h>
#include "version.h"

#define PROTOF_SHEREEQUALS  0x0001  /*	ProtoHacks  */

Prototype   int getname(int);
Prototype   int get_proto(void);
Prototype   int instr(char *, int, int);
Prototype   int twrite(const char *, int);
Prototype   void xlat_str(char *);
Prototype   int read_ctl(void);
Prototype   int do_outbound(void);
Prototype   int call_system(char *, int);
Prototype   int call_sysline(char *);
Prototype   int do_session(int);
Prototype   int top_level(int);
Prototype   int do_one_slave(void);
Prototype   int do_one_master(void);
Prototype   int yesno(char, int, int);
Prototype   int host_send_file(char *);
Prototype   int host_receive_file(char *);
Prototype   int local_send_file(char *, int *);
Prototype   int local_receive_file(void);
Prototype   int receive_file(FILE *, char *, char *, char *, int);
Prototype   int send_file(FILE *);

Prototype   short ProtoHacks;
Prototype   short PriMode;
Prototype   short OldPri;
Prototype   short IgnoreDTR;

#define MAX_FLAGS	40

extern int errno;

IDENT(".13");

static char *Copyright = COPYRIGHT;

char	ttynam[NAMESIZE],		/* Name of tty we use as serial port */
	srcnam[NAMESIZE],		/* Source file name */
	dstnam[NAMESIZE],		/* Dest file name */
	who[NAMESIZE] = "-",            /* Who sent the file */
	flags[MAX_FLAGS],		/* Flags from file xfer cmd */
	temp[NAMESIZE]; 		/* Temp file name */

int	ourpid = 0,			/* Our process ID */
	ignore_time_restrictions = 0,	/* Call out even if L.sys sez no */
	mode;				/* File mode from file xfer cmd */

char  host_name[MAX_HOST] = "AmigaUUCP";  /* Other guy's host name */
char  our_name[MAX_HOST];	/* Our uucp hostname, set from usenet.ctl */
char  path[128];
int   debug   = -1;	/* -1 indicates not set by command line or ctl file */
int   f_wait  = 0;	/* wait for a call (-w) or calls (-w -e) after outbnd */
int   loop    = 0;	/* Loop accepting logins if tty name specified */
int   curtemp = 0;
int   Overide = 0;	/* overide modem protocol	    */
int   Getty   = 0;	/* -Getty initiated		    */
int   IgnoreCD= 0;	/* xgetc() should ignore carrier?   */
int   OurNameOv= 0;
int   WindowSize = 999;
int   SevenWire= 0;
int   XDebug  = 0;	/* do not pass debug parameter to remote    */
int   DebugHandshake = 0;
short ProtoHacks;	 /*  protocol hacks  */
short PriMode;
short OldPri;
short IgnoreDTR = 0;

#define MAX_STRING	200	/* Max length string to send/expect */

#define MSGO2IDX	6

/* We print these prompts */

char msgo0[] = "login: ";
char msgo1[] = "Password:";
char msgo2[10+MAX_HOST] = { "\20Shere" };   /*  NO =    */
char msgo3[] = "\20ROK\0";
char msgo3a[]= "\20P";
char msgo3b[]= "\20Pg\0";
char msgo4[] = "\20OOOOOOO\0";

/* We expect to receive these strings */

char msgi0[] = "uucp\r";
char msgi1[] = "s8000\r";
/* char msgi2[] = "\20S*\0"; We now scan it specially FIXME */
char msgi3[] = "\20Ug\0";
char msgi4[] = "OOOOOO";

/*
 * Protocol switch data structure
 */

#define turnon	gturnon
#define rdmsg	grdmsg
#define wrmsg	gwrmsg
#define rddata	grddata
#define wrdata	gwrdata
#define turnoff gturnoff

int
getname(isshere)
int isshere;
{
    int data, count = 0;
    static char msgi[MAX_STRING+SLOP];	/* Incoming trash buffer */

    /* Read data until null character */

    while ((data = xgetc(BYTE_TO)) != EOF) {
	data &= 0x7F;
	if (data == 020)
	    break;
    }
    if (data == EOF)
	return FAIL;

    while ((data = xgetc(BYTE_TO)) != EOF && (data & 0x7F)) {
	data &= 0x7F;
	if (count == 0 && data != 'S')
	    continue;
	if (count > sizeof(msgi) - 2)
	    continue;
	if (data == 0x0A)       /*  hack fix for tuvie ? */
	    break;
	msgi[count++] = (char)data;
    }
    msgi[count] = 0;

    if (debug > 8)
	printf("GETNAME MSG (%d): %s\n", count, msgi);

    if (msgi[0] != 'S')
	return FAIL;
    if (isshere) {
	for (count = 1; msgi[count] && msgi[count] != '='; ++count);
	if (msgi[count] == '=')
	    ++count;
    } else {
	count = 1;
    }
    if (msgi[count]) {
	if (debug > 8)
	    printf("Compare host names: '%s' '%s'\n", host_name, msgi + count);
	strcpy (host_name, msgi + count);
    }
    strtok(host_name, " \t");     /*  put \0 after hostname */
    if (debug > 8)
	printf("Hostname is '%s'\n", host_name);
    return SUCCESS;
}

/*
 *  get_proto() checks the list of protos given by the foriegn machine
 *  checking for 'g' (which is the only proto we have).  Use only in master
 *  mode.
 */

int
get_proto()
{
    int data;

    while ((data = xgetc(BYTE_TO)) != EOF) {
	data &= 0x7F;
	if (data == 0)
	    break;
	if (data == 'g')
	    return(SUCCESS);
    }
    return FAIL;
}

/*
 * Medium level input routine.
 *
 * Look for an input string for the send-expect sequence.
 * Return 0 for matching string, 1 for timeout before we found it.
 * FIXME:  we only time out if the other end stops sending.  If it
 *	   keeps sending, we keep listening forever.
 */

instr(s, n, to)
char *s;
int n;
int to;     /*	timeout */
{
    int data,count,j;
    int i;
    static char msgi[512];  /* Incoming trash buffer */

    count = 0;
    if (to == 0)
	to = BYTE_TO;

    if (debug > 8) {
	printf("Expecting ");
	for (i = 0; i < n; i++)
	    printc(s[i]);
	printf("\n");
    }
    if (DebugHandshake) {
	printf("recvd: '");
	fflush(stdout);
    }

    while ((data = xgetc(to)) != EOF) {
	data &= 0x7F;

	msgi[count++] = data;

	if (DebugHandshake) {
	    if (data < 0x20)
		printf("^%c", data + '@');
	    else
		printf("%c", data);
	    fflush(stdout);
	}

	if (count == sizeof(msgi)) {    /*  throw away first half */
	    count = sizeof(msgi) / 2;
	    bcopy(msgi + sizeof(msgi) / 2, msgi, sizeof(msgi) / 2);
	}

	if (count >= n) {
	    for (i = n - 1, j = count - 1; i >= 0; i--, j--) {
		if (*(s+i) != msgi[j])
		    break;
	    }
	    if (i < 0) {
		if (debug > 8)
		    printf("\n");
		if (DebugHandshake)
		    printf("' (GOTIT!)\n");
		return(0);
	    }
	}
    }
    if (DebugHandshake)
	printf("' (TIMEOUT!)\n");

    if (debug > 8)
	printf("\n");
    msgi[count] = (char)0;
    return(1);
}

/*
 * Debugging hack for stuff written to the modem.
 */

int
twrite(s, n)
const char *s;
int  n;
{
    int i;

    if (debug > 8) {
	printf("Wrote:  ");
	for (i = 0; i < n; i++)
	    printc(s[i]);
	printf("\n");
    }
    return xwrite(s, n);
}

void
myexit()
{
    long task = FindTask(NULL);
    if (PriMode)
	SetTaskPri(OldPri);
}

/*
 * MAIN ROUTINE.
 *
 * This is called at program startup.  It parses the arguments to the
 * program (if any) and sets up to receive a call on the modem.
 *
 * If there are no arguments, we assume the caller is already on standard
 * input, waiting to do uucp protocols (past the login prompt), and we
 * just handle one caller.
 *
 * If there is an argument, it is the name of the tty device where we
 * should listen for multiple callers and handle login and password.
 */

main(argc,argv)
int argc;
char *argv[];
{
    int     i;
    char    *poll_sys = (char *)NULL;   /* System name to poll, or none */
    short   rmode = 0;			/* 1 = master, 0 = slave	*/

    LogProgram = "uucico";
    LogHost = host_name;
    LogWho  = who;

    signal(SIGINT, sigint);
    atexit(myexit);

    /* FIXME, use getopt */
    /* scan command line arguments, kinda kludgy but it works */

    for (i = 1; i < argc; ++i) {
	char *ptr = argv[i];

	if (*ptr != '-') {
	    printf("uucico: warning, extra args ignored: %s\n", argv[i]);
	    break;
	}
	ptr += 2;
	switch (ptr[-1]) {
	case 'N':
	    strcpy(our_name, ptr);
	    OurNameOv = 1;
	    break;
	case 'D':       /*  Serial Device   */
	    {
		extern char *DeviceName;
		DeviceName = argv[++i];
	    }
	    break;
	case 'U':       /*  Serial Unit     */
	    {
		extern long DeviceUnit;
		DeviceUnit = ((*ptr >= '0' && *ptr <= '9') ? atoi(ptr) : atoi(argv[++i]));
	    }
	    break;
	case 'p':       /*  protocol hacks  */
	    if (strcmp(ptr, "ri") == 0) {   /*  -pri    */
		long task = FindTask(NULL);

		PriMode = 1;
		OldPri = SetTaskPri(task, 5);
		SetTaskPri(task, OldPri + 1);
	    } else {			    /*	-proto	*/
		ProtoHacks |= (*ptr) ? atoi(ptr) : atoi(argv[++i]);
	    }
	    break;
	case 'g':
	case 'G':
	    Getty = 1;
	    break;
	case 'h':
	    IgnoreCD = 1;
	    break;
	case 'w':
	    ++f_wait;
	    break;
	case 'r':
	    rmode = atoi(&argv[i][2]);
	    break;
	case 'X':
	    XDebug = 1;
	case 'x':
	    if (argv[i][2] == 'x') {
		DebugHandshake = 1;
		break;
	    }

	    debug = atoi(&argv[i][2]);
	    LogLevel = debug;
	    LogToStdout = 0;
	    printf("uucico: debug level set to %d\n", debug);
	    break;
	case 'o':
	    Overide = 1;
	    break;
	case 'n':
	    WindowSize = (*ptr) ? atoi(ptr) : 1;
	    break;
	case 'b':
	    system(GetConfigProgram(BATCHNEWS));
	    break;
	case 'S':
	    ignore_time_restrictions++;
	case 's':
	    poll_sys = &argv[i][2];
	    break;
	case 'e':
	    ++loop;
	    break;
	/* Is -t needed for MSDOS?  Why?  -- hoptoad!gnu */
	case 't':
	    curtemp++;
	    printf("uucico: using ~uutemp.$$$ for temp file\n");
	    break;
	case '7':
	    SevenWire = 1;
	    break;
	case 'd':
	    IgnoreDTR = (*ptr) ? !atoi(ptr) : 1;
	    break;
	default:
	    printf("uucico: warning, bad flag %s\n", argv[i]);
	    break;
	}
    }

    /* If argument provided, use it as name of comm port */

    /* FIXME, this needs some thought. */

    getcwd(path,128);
    if (chdir(GetConfigDir(UUSPOOL))) {
	perror("Can't chdir to Spool directory");
	exit(2);
    }

    read_ctl();

    /*
     * If running via getty/login, our debug stdout had better
     * go to a file, not to the usual stdout!
     */

    if (debug > 0 && Getty) {
	freopen("T:uuslave.log", "a", stdout);
    }

    /*setvbuf(stdout, NULL, _IOLBF, 0);*/

    /* Timestamp the long debug log */

    if (debug > 0) {
	long clock;

	time(&clock);
	printf("\014\nuuslave log on tty '%s' starting %s\n",
		ttynam, ctime(&clock));
    }

    /* Log our presence so we humans reading the logs can find the
       entries created by uuslave. */

    ulog(-1, "Startup %s", VERSION);

    amiga_setup();

    modem_init();

    if (poll_sys) {
	if (*poll_sys == '\0')
	    poll_sys = (char *)NULL;
	call_system(poll_sys, rmode);
	if (!f_wait)
	    goto end;
    } else {
	if (rmode) {
	    do_outbound();
	    if (!f_wait)
		goto end;
	}
    }

    do {
	/*
	 *  Set up serial channel, wait for incoming call.
	 */
	DEBUG(0, "\nRestarting\n", 0);

	if (Getty == 0 && Overide == 0)
	    openline();

	do_session(Getty);

	hangup();
	DEBUG(0, "\nEnd of call\n", 0);
    } while (loop && !Getty);

end:
    cleanup();
    return(0);
}

/*
 * translate embedded escape characters
 */

void
xlat_str(msg)
char	*msg;
{
    int i  = 0;
    int cr = 1;

    while (msg[i]) {
	if (msg[i] == '\\') {
	    switch (msg[++i]) {
	    case 'r':            /* carriage return */
		twrite("\r", 1);
		break;
	    case 'n':            /* line feed */
		twrite("\n", 1);
		break;
	    case '\\':           /* back slash */
		twrite("\\", 1);
		break;
	    case 't':            /* tab */
		twrite("\t", 1);
		break;
	    case 'b':
		SendBreak();
		break;
	    case 'd':            /* delay */
		Delay(180);
		break;
	    case 's':            /* space */
		twrite(" ", 1);
		break;
	    case 'c':            /* no CR at end */
		cr = 0;
		break;
	    default:		/* don't know so skip it */
		break;
	    }
	    ++i;
	} else {
	    twrite(msg + i, 1);
	    ++i;
	}
    }
    if (cr) {
	twrite("\r", 1);
    }
}

/*
 * Read the control file and grab a few parameters.
 */

int
read_ctl()
{
    char *nodename = FindConfig(NODENAME);
    char *debugstr = FindConfig(DEBUGNAME);

    if (nodename && OurNameOv == 0)
	strcpy(our_name, nodename);
    if (debugstr && debug < 0)
	debug = atoi(debugstr);
    return (1);
}

/*
 * Search spool queues for work, call the systems we need to call.
 */

int
do_outbound()
{
    return call_system((char *)NULL, 1);
}

/*
 * Call a specific system, or all systems that have work pending.
 */

int
call_system(sys, ifworkpend)
char	*sys;
{
    FILE    *lsys;
    static char buf[MAX_LSYS];
    static char sysnam[MAX_HOST];
    static char prev_name[MAX_HOST];
    int     called = FAIL;

    /*
     * Unix uucico just reads the directory, and calls the systems
     * in the order of the files in the directory.  We want more
     * control than that, though I'm not sure that L.sys order is
     * best either.  For example, in the first call after 11PM,
     * I'd like to call the sites that haven't been callable before
     * 11PM first, and finish up with the ones I've been able to call
     * all day.  FIXME.
     */

    if (! (lsys = fopen(MakeConfigPath(UULIB, "L.sys"), "r"))) {
	DEBUG(0, "uucico: can't open L.sys, errno %d\n", errno);
	return 0;
    }
    sysnam[0] = '\0';               /* Initially, no previous sys */

    /* Once per system in L.sys... */
    /* FIXME, handle continuation lines (trailing "\") */

    while (fgets(buf, sizeof buf, lsys)) {
	if (buf[0] == '#' || buf[0] == '\n')
	    continue;

	/*
	 * Grab the system name.  If same as previous, and
	 * the previous call worked, skip it.
	 */

	strcpy(prev_name, sysnam);
	(void) sscanf(buf, "%s", sysnam);
	if (!strcmp(sysnam, prev_name)) {
	    if (called == SUCCESS)
		continue;
	}

	/*
	 * If a system name was specified, skip til we find it
	 * If none was specified, only call if there is work.
	 */

	if (sys) {
	    if (strcmp(sys, sysnam) != 0)
		continue;
	    if (ifworkpend && !work_scan(sysnam)) {
		ulog(-1, "No work for system %s", sysnam);
		called = SUCCESS;
		continue;
	    }
	} else {
	    DEBUG(3,"searching for outbound to %s\n", sysnam);
	    if (!work_scan(sysnam)) {
		DEBUG(3,"no work for %s\n", sysnam);
		called = SUCCESS;	/* Don't try further */
		continue;
	    }
	    DEBUG(2, "uucico: found work for %s\n", sysnam);
	}

	called = call_sysline(buf);

	if (called == SUCCESS && sys)
	    break;
    }

    fclose(lsys);
    if (called == FAIL && sys)
	DEBUG(0, "Could not call system %s\n", sys);
    return 0;
}

/*
 *  Call out to a system, given its L.sys line.
 */

int
call_sysline(lsysline)
char *lsysline;
{
    static char    tempname[MAX_HOST + 30 + SLOP];
    char    *sysnam,
	    *times,
	    *acu,
	    *sbaud,
	    *telno,
	    *send,
	    *expct;
    int     baud;

    who[0] = '-'; who[1] = '\0';    /* No user now (for logit) */

    /* FIXME, use the values it is ignoring here */

    sysnam = strtok(lsysline, " \t");
    times =  strtok(NULL, " \t");   /* Time */
    acu =    strtok(NULL, " \t");   /* ACU  */
    sbaud =  strtok(NULL, " \t");   /* Baud */
    telno =  strtok(NULL," \t");    /* phone*/

    strcpy(host_name, sysnam);

    if (ignore_time_restrictions == 0) {
	if (CheckTimeRestrictions(times) == FAIL) {
	    ulog(-1, "Wrong Time To Call %s", sysnam);
	    return(FAIL);
	}
    }

    baud = atoi(sbaud);

    /*	FIX ME, acu not implemented ?	*/
    DEBUG(4, "Opening outgoing line %s\n", acu);
    if (openout(acu, baud) != SUCCESS)
	return FAIL;

    if (Overide == 0) {
	if (dial_nbr(telno)) {
	    ulog(-1, "FAILED call to %s", host_name);
	    return FAIL;
	}
    }

    /* FIXME, log tty, baud rate, ... */
    ulog(-1, "DIALED %s", host_name);

    /*
     * Process send-expect strings.
     * FIXME, deal with "-", BREAK, etc.
     */

    if (DebugHandshake)
	puts("CONNECTED, running send-expect strings");

    while (send = (char*)strtok((char *)NULL, " \t\n")) {
	if (send[0] != '"' || send[1] != '"' || send[2] != '\0') {
	    if (DebugHandshake)
		printf("Expect %s\n", send);
	    if (instr(send, strlen(send), 0))
		    goto bort1;
	} else if (DebugHandshake) {
	    puts("Expect Nothing");
	}

	if (expct = (char*)strtok((char *)NULL, " \t\n")) {
	    if (DebugHandshake)
		printf("Send: %s\n", expct);
	    /* FIXME secondary strings, e.g. ogin:-EOT-ogin: */
	    xlat_str(expct);
	}
    }

    /*
     * FIXME, there should be a way to detect login/passwd
     * failure here and keep doing the script rather than
     * continuing to expect Shere at another login: prompt.
     */

    ulog(-1, "SUCCEEDED call to %s", host_name);


    if (getname(1))         /*  get name        */
	goto bort1;
			    /*	send response	*/
    sprintf(tempname, "\20S%s -Q0 -x%d\0", our_name, (XDebug) ? 0 : debug);
    twrite(tempname, strlen(tempname)+1); /* Including null */

    /* wait for ok message, wait for protocol request
     * send protocol 'g' response */
    /* FIXME, we don't actually wait for the ROK message, since
     * it is immediately followed by the Pprotos message.  We
     * currently just look for a Pg message.  This needs work.
     * FIXME, WE CAN'T TALK TO SITES THAT SUPPORT more than 'g'.
     */

    if (instr(msgo3a, sizeof(msgo3a)-1, 0)) {
	if (!get_proto())
	   goto bort1;
    }


    twrite( msgi3, sizeof(msgi3)-1);

    ResetGIO();                         /* reset GIO protocol   */

    if (turnon(1))
	goto bort1;

    ulog(-1, "OK Startup");

    top_level(1);
    hangup();
    return SUCCESS;

bort1:
    hangup();
    return FAIL;
}

/* Handle a single uucp [slave] login session */

int
do_session(ontheline)
int ontheline;
{
    if (ontheline == 0) {
	/* output login request, verify uucp */
	twrite(msgo0,sizeof(msgo0)-1);
	if (instr(msgi0, sizeof(msgi0)-1, 0)) {
	    printf("uucico: invalid login name\n");
	    goto bort;
	}

	/* output password request, verify s8000 */
	twrite(msgo1,sizeof(msgo1)-1);
	if (instr(msgi1, sizeof(msgi1)-1, 0)) {
	    printf("uucico: invalid password\n");
	    goto bort;
	}

	printf("uucico: correct login\n");
    }

    /*
     *	send Shere=<myhost>
     *
     *	Apparently mac UUCP has a bug that only allows 7
     *	char host names, and it fails if it gets shere=<myhost>
     *	where <myhost> is > 7 chars.
     *
     *	Apparently some implementations of AmigaUUCP do not accept
     *	an SHere with an =<myhost>, so this is disabled unless -p1 is used.
     */

    if (ProtoHacks & PROTOF_SHEREEQUALS)
	sprintf(msgo2 + MSGO2IDX, "=%s", our_name);
    twrite(msgo2,strlen(msgo2)+1);

    /*
     *	get \020S<host> -Qn n	(??)
     */

    if (getname(0))
	goto bort;

    /* output ok message, output protocol request, wait for response */

    twrite(msgo3,sizeof(msgo3)-1);

    /* FIXME, make the protocol list here, and use it */
    twrite(msgo3b,sizeof(msgo3b)-1);
    if (instr(msgi3, sizeof(msgi3)-1, 0))
	    goto bort;

    ResetGIO();                         /* reset GIO protocol   */

    if (turnon(0))
	goto bort;

    ulog(-1, "OK Startup");
    top_level(0);

bort:
    if (debug > 0)
	printf("uucico: call complete\n");
    return (1);
}

/*
 * Handle transactions "at top level", as Unix uucp's debug log says.
 *
 * As master, we scan our queues for work and send requests to the
 * other side.	When done, we send a hangup request and switch to slave mode.
 *
 * As slave, we accept requests from the other side; when it is done,
 * it sends a hangup request, and we switch to master mode, if we have
 * any work queued up for that system.
 *
 * This repeats as long as either side has work to do.	When all the
 * queued work is done, we agree to hang up, terminate the packet protocol,
 * and return to the caller.  (We still haven't hung up the phone line yet.)
 *
 * A curious feature of the hangup protocol is that it is not a simple
 * question-answer.  The master says "H", asking about hangup.  The
 * slave responds "HY" saying OK.  The master then says "HY" also,
 * then both of them hang up.  Maybe this is to make sure the first HY
 * got ack'ed?  Anyway, an "H" is reported as HANGUP and an "HY" as
 * HANGNOW.  After we send an HY, we go back to listening for commands;
 * if the master sends something other than HY, we'll do it.
 */

#define HANGUP	2		/* Signal to switch master/slave roles */
#define HANGNOW 3		/* Signal to hang up now */
#define COPYFAIL	4	/* File copy failed */

int
top_level(master_mode)
int master_mode;
{
    static char    buf[MAXMSGLEN];	/* For hangup responses */


    if (master_mode) {
	(void) work_scan(host_name);    /* Kick off queue scan */
	goto master;
    }

    for (;;) {
    slave:			/*  SLAVE SIDE	*/
	for (;;) {
	    DEBUG(4, "*** TOP *** - slave\n", 0);
	    switch (do_one_slave()) {
	    case SUCCESS:
		break;
	    case FAIL:
		DEBUG(4, "*** DO_ONE_SLAVE FAIL *** - slave\n", 0);
		return FAIL;
	    case HANGUP:
		if (work_scan(host_name)) {
		    if (wrmsg("HN") != SUCCESS) {
			DEBUG(4, "*** WRMSG HN FAIL *** - slave\n", 0);
			return FAIL;
		    }
		    goto master;
		} else {
		    if (wrmsg("HY") != SUCCESS) {
			DEBUG(4, "*** WRMSG HY FAIL *** - slave\n", 0);
			return FAIL;
		    }
		    break;	/*  go to master mode */
		}
	    case HANGNOW:
		goto quit;
	    }
	}
    master:	/*  MASTER SIDE */
	for (;;) {
	    DEBUG(4, "*** TOP *** - master\n", 0);
	    switch (do_one_master()) {
	    case SUCCESS:
		break;
	    case FAIL:
		DEBUG(4, "*** DO_ONE_MASTER FAIL *** - master\n", 0);
		return FAIL;
	    case HANGUP:
		/* We wrote an H command, what's the resp? */
		if (rdmsg(buf, MAXMSGLEN) != SUCCESS) {
		    DEBUG(4, "*** RDMSG HANGUP FAIL *** - master\n", 0);
		    return FAIL;
		}
		if (buf[0] != 'H') {
		    DEBUG(4, "*** RDMSG HANGUP != 'H' *** - master\n", 0);
		    return FAIL;
		}
		if (buf[1] == 'N') {
		    goto slave;
		} else {
		    /*
		     *	send final HY?	not sure if this should happen, will
		     *	necessarily fail if the other side does not expect
		     *	it so do not return... continue on w/ exit code.
		     *
		     *	however, reduce timeout parameters
		     */

		    ++ReducedTimeout;
		    wrmsg("HY");
		    --ReducedTimeout;
		    goto quit;
		}
	    }
	}
    }

quit:
    /* Shut down the packet protocol */

    turnoff();

    /* Write the closing sequence */

    twrite(msgo4, sizeof(msgo4)-1);
    (void) instr(msgi4, sizeof(msgi4)-1, 0);

    twrite(msgo4, sizeof(msgo4)-1);

    strcpy(who, "-");
    ulog(-1, "OK Conversation complete");

    return SUCCESS;   /* Go byebye */
}

/*
 * We are slave; get a command from the other side and execute it.
 *
 * Result is SUCCESS, FAIL, HANGUP, or HANGNOW.
 */

int
do_one_slave()
{
    static char msg[MAXMSGLEN]; 	   /* Master's message to us */

    /* Get master's command */
    if (rdmsg(msg, MAXMSGLEN) != SUCCESS)
	return FAIL;

    /* Print it for easy debugging */
    DEBUG(5,"\nCommand: %s\n\n", msg);

    switch (msg[0]) {
    case 'S':
	if (msg[1] != ' ')
	    break;
	return host_send_file(msg);
    case 'R':
	if (msg[1] != ' ')
	    break;
	return host_receive_file(msg);
    case 'X':
	break;
    case 'H':
	if (msg[1] == '\0') return HANGUP;
	if (msg[1] == 'Y')  return HANGNOW;
	if (msg[1] == 'N')  return SUCCESS;     /* Ignore HN to slave */
	break;
    }

    /* Unrecognized packet from the other end */

    DEBUG(0, "Bad control packet refused: %s\n", msg);
    return(yesno(msg[0], 0, 0));
}

/*
 *  Do one piece of work as master.
 *
 *  FIXME:  we don't handle the flags, e.g. -c, properly!
 *
 *  Now only dequeues queue file if all transfers were successful.
 */

int
do_one_master()
{
    FILE    *fd;
    char    *sname;
    static char cmnd[256];		/* Command character */
    static char buf[256];
    int     fail = SUCCESS;
    int     failAction = SUCCESS;
    int     failaccum = 0;
    int     num;
    int     delmeflag;
    static char notify[NAMESIZE];   /* A bit large...FIXME */
    char    *delList[16];	    /* delete files list   */
    short   di = 0;

    /*
     *	Get the next work item.  If no work left re-scan the directory
     *	just to be sure, and if still no work then do the right thing.
     */

    sname = work_next();
    if (!sname) {
	if (work_scan(host_name))
	    sname = work_next();
    }
    if (!sname) {
	/* No more work, time to hang up. */
	if (wrmsg("H") != SUCCESS)
		return FAIL;
	return HANGUP;
    }

    DEBUG(2, "Request file %s\n", sname);

    LockFile(sname);

    fd = fopen(sname, "r");
    if (fd == NULL) {
	UnLockFile(sname);
	DEBUG(0, "uucico: couldn't open %s\n", sname);
	return SUCCESS;
    }

    while (fgets(buf, sizeof buf, fd)) {
	DEBUG(3, "Queued request: %s", buf);

	if (buf[1] != ' ')
	    goto badnum;

	num = sscanf(buf, "%s %s %s %s %s %s %o\n",
	    cmnd, srcnam, dstnam, who, flags, temp, &mode, notify
	);

	switch (cmnd[0]) {
	case 'S':
	    if (num < 7 || num > 8)
		goto badnum;
	    fail = local_send_file(buf, &delmeflag);
	    if (delmeflag) {
		if (di == sizeof(delList)/sizeof(delList[0])) {
		    ulog(-1, "Too many source files in Cmd file! %s", sname);
		} else {
		    delList[di] = malloc(strlen(temp) + 1);
		    strcpy(delList[di], temp);
		    ++di;
		}
	    }
	    break;
	case 'R':
	    if (num != 5) {
		if (debug > 7)
		    printf("Invalid scanf %d/5 :%s:%s:%s\n", num, cmnd, srcnam, dstnam);
		goto badnum;
	    }
	    fail = local_receive_file();
	    break;
	default:
	badnum:
	    ulog(-1, "Illegal Work Request (%s): %s", sname, buf);
	    fail = REFUSED;
	    break;
	}

	switch(fail) {
	case SUCCESS:
	    break;
	case FAIL:
	    ++failaccum;
	    if (failAction == SUCCESS)
		failAction = FAIL;
	    ulog(-1, "Protocol Failure at (%s): %s", sname, buf);
	    break;
	case REFUSED:
	    ++failaccum;
	    failAction = REFUSED;
	    ulog(-1, "Work Refused (%s): %s", sname, buf);
	    break;
	}
    }
    fclose(fd);

    switch(failAction) {
    case SUCCESS:
	while (di) {
	    --di;
	    remove(delList[di]);
	    free(delList[di]);
	}
	fail = remove(sname);
	UnLockFile(sname);
	if (fail != 0) {
	    ulog(-1, "Unable to remove work file %s", sname);
	    DEBUG(0, "Can't remove, errno %d\n", errno);
	} else {
	    DEBUG(4, "Removed work file %s\n", sname);
	}
	break;
    case FAIL:
	UnLockFile(sname);
	break;
    case REFUSED:
	UnLockFile(sname);
	strcpy(buf, sname);
	{
	    short i;
	    for (i = strlen(buf); i >= 0 && buf[i] != ':' && buf[i] != '/'; --i);
	    ++i;
	    if ((buf[i]|0x20) == 'c')
		buf[i] = 'E';
	}
	if (strcmp(sname, buf) == 0) {
	    ulog(-1, "Removing %s", sname);
	    remove(sname);
	} else {
	    ulog(-1, "Renaming %s to %s", sname, buf);
	    rename(sname, buf);
	}
	break;
    }
    return(SUCCESS);
}

/*
 *  Send a yes/no packet
 */

int
yesno(c, true, err)
char c;
int true;
int err;
{
    char buf[21];

    buf[0] = c;
    buf[1] = true? 'Y': 'N';
    buf[2] = 0;
    if (err && !true)
	sprintf(buf+2,"%d", err);

    return (wrmsg(buf));
}

/*
 *  SLAVE MODE, Master wishes to send a file to us
 *
 *  SECURITY:	If file is not in list of allowed directories
 *		disallow transfer.  UUSPOOL:   is always in the
 *		list.
 *
 *		If file is for UUSPOOL: (the current dir), disallow "C." files
 *		NOTE: success return and file redirected to T: as this can
 *		occur only if somebody purposefully is trying to break us.
 *
 *  Return 0 = success
 */

int
host_send_file(msg)
char  *msg;
{
    FILE *fddsk;		    /* Disk file pointer */
    static char cmnd[256];		/* Command character */
    int r;
    int nor = 0;

    sscanf(msg,"%s %s %s %s %s %s %o",
	    cmnd, srcnam, dstnam, who, flags, temp, &mode);

    ulog(-1, "REQUESTED %s", msg);
    munge_filename(dstnam, dstnam);           /* Translate to local name */
    strcpy (temp, TmpFileName(dstnam));       /* Create a handy temp file */

    if (SecurityDisallow(dstnam, 'w')) {
	ulog(-1, "REQUEST FAILED -- SECURITY");
	return(yesno('S', 0, 4));
    }
    if (SecurityDisallow(dstnam, 'c') > 0) {
	ulog(-1, "REQUEST FAILED -- SECURITY, REMOTE TRIED TO SEND");
	ulog(-1, "US A COMMAND FILE: %s, FILE COPIED TO T:Bad-Cmd", dstnam);
	strcpy(dstnam, "T:Bad-Cmd");
	nor = 1;
    }

    /* FIXME: deal with file modes now that we fopen. */

    LockFile(temp);

    fddsk = fopen(temp, "wb" /*, mode|0600 */);
    if (fddsk == NULL) {
	UnLockFile(temp);
	/* Can't open file -- send error response */
	if (debug > 0) {
	    printf("Cannot open temp file %s (%s) for writing, errno=%d\n",
		temp,
		dstnam,
		errno
	    );
	}
	ulog(-1, "REQUEST FAILED -- TEMP FILE");
	return (yesno('S', 0, 4));
    }

    /* FIXME: Are the above permissions right?? */
    /* FIXME: Should we create directories for the file? */

    if (yesno('S',1, 0) != SUCCESS) {
	fclose(fddsk);
	unlink(temp);
	UnLockFile(temp);
	return(FAIL);
    }
    r = receive_file(fddsk, temp, dstnam, srcnam, nor);
    UnLockFile(temp);
    return(r);
}

/*
 *  SLAVE MODE, Master wants us to send a file to it
 *
 *  SECURITY:	If file is not in list of allowed directories
 *		disallow transfer.  UUSPOOL:   is always in the
 *		list.
 *
 *  0 = sucess
 */

int
host_receive_file(msg)
char  *msg;
{
    FILE *fddsk;     /* Disk file descriptor */
    int x;
    static char cmnd[256];		  /* Command character */

    ulog(-1, "REQUESTED %s", msg);

    sscanf(msg,"%s %s %s",cmnd,srcnam,dstnam);
    munge_filename(srcnam, temp);

    if (SecurityDisallow(temp, 'r')) {
	ulog(-1, "COPY FAILED -- SECURITY");
	return (yesno('S', 0, 4));
    }

    fddsk = fopen(temp, "rb");              /* Try to open the file */
    if (fddsk == NULL) {
	/* File didn't open, sigh. */
	if (debug > 0) {
	    printf("Cannot open file %s (%s) for reading, errno=%d\n",
		temp, srcnam, errno
	    );
	}
	ulog(-1, "DENIED CAN'T OPEN %s", temp);
	return(yesno('R', 0, 2));
    }

    if (yesno('R',1, 0) != SUCCESS) {
	fclose(fddsk);
	return(FAIL);
    }

    x = send_file(fddsk);

    switch (x) {
    default:
	return x;
    case COPYFAIL:
	/* We don't care if the copy failed, since the master
	   asked for the file and knows the result. */
	return SUCCESS;
    }
    return 1;
}

/*
 *  MASTER MODE, We want to send a file.
 *
 *  Return FAIL, SUCCESS, or COPYFAIL.
 *
 *  SUCCESS is returned either if the file was not found locally (local
 *  error, and the queued transfer should be flushed) or if it was moved
 *  successfully.  COPYFAIL indicates that the queued transfer should be
 *  left queued, and later retried.  FIXME, there are several failure points
 *  in the transaction and we need finer control here.
 */

int
local_send_file(workstr, delmeflag)
char *workstr;
int *delmeflag;
{
    static char buf[MAXMSGLEN];    /* Used for both xmit and receive */
    FILE *fddsk;	    /* Disk file descriptor */
    int res;		    /* Result and file removal status */

    *delmeflag = 0;

    /* WHY are temp and srcnam switched?  FIXME!  And no notify? */

    sprintf(buf,"S %s %s %s %s %s 0%o %s",
	temp, dstnam, who, flags, srcnam, mode, who
    );

    ulog(-1, "REQUEST %s", buf);

    if (strchr(flags, 'c')) {
	munge_filename(srcnam, temp);
    } else {
	munge_filename(temp, temp);
    }
    LockFile(temp);
    fddsk = fopen(temp, "rb");
    if (fddsk == NULL) {
	UnLockFile(temp);
	/* FIXME -- handle queued request for nonexistent file */
	if (debug > 0)
	    printf("Can't open file %s (%s), errno=%d\n",
		temp,
		srcnam,
		errno
	    );
	ulog(-1, "NOT FOUND %s", temp);
	/* return COPYFAIL;*/
	return SUCCESS;     /*	assume file previously sent */
    }

    /* Tell the other side we want to send this file */

    if (wrmsg(buf) != SUCCESS) {
	fclose(fddsk);
	UnLockFile(temp);
	return (FAIL);
    }

    /* See what they have to say about it */

    if (rdmsg(buf, MAXMSGLEN) != SUCCESS) {
	fclose(fddsk);
	UnLockFile(temp);
	return FAIL;
    }
    if ((buf[0] != 'S') || (buf[1] != 'Y')) {
	ulog(-1, "REQUEST DENIED %s", buf);
	fclose(fddsk);
	UnLockFile(temp);
	return(REFUSED);
    }
    res = send_file(fddsk); /* FAIL, SUCCESS, or COPYFAIL */

    /* Delete the source file if it was just a copy */

    if (res != SUCCESS) {
	UnLockFile(temp);
	return res;
    }
    if (strchr(flags, 'c')) {   /* If copied direct from source */
	UnLockFile(temp);
	return res;		/* ...just return. */
    }
    *delmeflag = 1;
    UnLockFile(temp);

    return res;
}

/*
 *  MASTER MODE, We wish to receive a specific file so we ask for it
 *
 *  Return 0 = success
 */

int
local_receive_file()
{
    static char buf[MAXMSGLEN];
    FILE *fddsk;		    /* Disk file pointer */
    int r;

    /* FIXME, test dest file access before we ask for it. */

    sprintf(buf,"R %s %s %s %s %s 0%o %s",
	srcnam, dstnam, who, flags, temp, mode, who
    );

    munge_filename(dstnam, dstnam);           /* tlate to local name      */
    strcpy (temp, TmpFileName(dstnam));       /* Create a handy temp file */

    /* FIXME: deal with file modes now that we fopen. */
    /* FIXME: Are the above permissions right?? */
    /* FIXME: Should we create directories for the file? */

    LockFile(temp);
    fddsk = fopen(temp, "wb" /*, mode|060 */);

    if (fddsk == NULL) {
	UnLockFile(temp);
	/* Can't open temp file -- send error response */
	if (debug > 0) {
	    printf("Cannot open temp file %s (%s) for writing, errno=%d\n",
		temp,
		dstnam,
		errno
	    );
	}
	ulog(-1, "REQUEST FAILED -- TEMPFILE");
	return FAIL;
    }

    ulog(-1, "REQUEST %s", buf);
    if (wrmsg(buf) != SUCCESS) {
	fclose(fddsk);
	UnLockFile(temp);
	printf("uucico: problem sending request\n");
	return FAIL;
    }

    /* See what the other side has to say about it */

    if (rdmsg(buf, MAXMSGLEN) != SUCCESS) {
	fclose(fddsk);
	UnLockFile(temp);
	return FAIL;
    }
    if ((buf[0] != 'R') || (buf[1] != 'Y')) {
	ulog(-1, "REQUEST DENIED %s", buf);
	fclose(fddsk);
	UnLockFile(temp);
	return (REFUSED);
    }

    r = receive_file(fddsk, temp, dstnam, srcnam, 0);
    UnLockFile(temp);
    return(r);
}

/*
 *  General receive file
 */

int
receive_file(fddsk, temp, dstnam, srcnam, norename)
FILE *fddsk;
char	*temp, *dstnam, *srcnam;
{
    int status;
    int error = 0;		    /* No errors so far */

    if (rddata(fddsk) != SUCCESS)
	error++;
    status = fclose(fddsk);         /* Make sure the data got here */
    if (status != 0) {
	error++;
	DEBUG(0, "fclose errno=%d\n", errno);
    }

    /*
     *	Move the file from its temp location to its real location,
     *	This needs to be able to copy a file if a simple rename
     *	does not suffice.  Should create directories if necesary.
     *	should use source ]name if target is a directory (i.e. no
     *	target source name
     */

    unlink(dstnam);

    if (norename)       /*  for security redirect   */
	status = 0;
    else
	status = rename(temp, dstnam);

    if (status != 0) {
	error++;
	if (debug > 0) {
	    printf("Cannot rename file %s to %s, errno=%d\n",
		temp, dstnam, errno);
	}
    }

    ulog(-1, "COPY %s", error ? "FAILED": "SUCCEEDED");

    return(yesno('C', error == 0, 5));
}

/*
 * general file send routine
 * Return SUCCESS, FAIL, or COPYFAIL.
 */

int
send_file(fddsk)
FILE *fddsk;	 /* Disk file pointer */
{
    static char ansbuf[MAXMSGLEN];

    if (wrdata(fddsk) != SUCCESS) {
	fclose(fddsk);
	return COPYFAIL;
    }
    fclose(fddsk);

    /* Await the "CY" or "CNddd" packet, and toss it. */

    while (1) {
	if (rdmsg(ansbuf, MAXMSGLEN) != SUCCESS)
	    return COPYFAIL;
	if (ansbuf[0] != 'C') {
	    DEBUG(0,"\nDidn't get 'CY' or 'CN', got %s\n", ansbuf);
	    /* and loop looking for C message */
	} else if (ansbuf[1] == 'Y') {
	    ulog(-1, "REQUESTED %s", ansbuf);
	    return SUCCESS;
	} else {
	    ulog(-1, "COPY FAILED %s", ansbuf);
	    return COPYFAIL;
	}
    }
    return COPYFAIL;
}

