/*
 *  K e r m i t  File Transfer Utility
 *
 *  UNIX Kermit, Columbia University, 1981, 1982, 1983
 *      Bill Catchings, Bob Cattani, Chris Maio, Frank da Cruz, Alan Crosswell
 *
 *  Also:   Jim Guyton, Rand Corporation
 *          Walter Underwood, Ford Aerospace
 *
 *  usage:  kermit c [lbe line baud escapechar]         to connect
 *          kermit s [d..iflb line baud] file ...       to send files
 *          kermit r [d..iflb line baud]                to receive files
 *
 *  where   c=connect, s=send, r=receive,
 *          d=debug, i=image mode, f=no filename conversion, l=tty line,
 *          b=baud rate, e=escape char.
 *
 *  For remote Kermit, format is either:
 *          kermit r                                    to receive files
 *  or      kermit s file ...                           to send files
 *
 */

/*
 *  Modification History:
 *
 *  Oct. 17 Included fixes from Alan Crosswell (CUCCA) for IBM_UTS:
 *          - Changed MYEOL character from \n to \r.
 *          - Change char to int in bufill so getc would return -1 on
 *            EOF instead of 255 (-1 truncated to 8 bits)
 *          - Added read() in rpack to eat the EOL character
 *          - Added fflush() call in printmsg to force the output
 *          NOTE: The last three changes are not conditionally compiled
 *                since they should work equally well on any system.
 *
 *          Changed Berkeley 4.x conditional compilation flag from
 *              UNIX4X to UCB4X.
 *          Added support for error packets and cleaned up the printing
 *              routines.
 */

#include <stdio.h>          /* Standard UNIX definitions */

/*Conditional compilation for different machines/operating systems */
/*One and only one of the following lines should be 1 */

#define UCB4X       0       /* Berkeley 4.x UNIX */
#define TOPS_20     0       /* TOPS-20 */
#define IBM_UTS     1       /* Amdahl UTS on IBM systems */
#define VAX_VMS     0       /* VAX/VMS (not yet implemented) */

/*Conditional compilation for the different Unix variants */
/*0 means don't compile it, nonzero means do */

#if UCB4X
#define V6_LIBS     0       /* Dont't use retrofit libraries */
#define NO_FIONREAD 0       /* We have ioctl(FIONREAD,...) for flushinput() */
#define NO_TANDEM   0       /* We have TANDEM line discipline (xon/xoff) */
#endif

#if IBM_UTS
#define V6_LIBS     0       /* Don't use retrofit libraries */
#define NO_FIONREAD 1       /* No ioctl(FIONREAD,...) for flushinput() */
#define NO_TANDEM   1       /* No TANDEM line discipline (xon/xoff) */
#endif

#if V6_LIBS
#include <retrofit/sgtty.h>
#include <retrofit/signal.h>
#include <retrofit/setjmp.h>
#else
#include <sgtty.h>
#include <signal.h>
#include <setjmp.h>
#endif

#if NO_TANDEM
#define TANDEM      0       /* define it to be nothing if it's unsupported */
#endif


/*Symbol Definitions */

#define MAXPACKSIZ  94      /* Maximum packet size */
#define SOH         1       /* Start of header */
#define CR          13      /* ASCII Carriage Return */
#define SP          32      /* ASCII space */
#define DEL         127     /* Delete (rubout) */
#define ESCCHR      '^'     /* Default escape character for CONNECT */

#define MAXTRY      10      /* Times to retry a packet */
#define MYQUOTE     '#'     /* Quote character I will use */
#define MYPAD       0       /* Number of padding characters I will need */
#define MYPCHAR     0       /* Padding character I need (NULL) */

#if IBM_UTS
#define MYEOL       '\r'    /* End-Of-Line character for UTS systems */
#else
#define MYEOL       '\n'    /* End-Of-Line character I need */
#endif

#define MYTIME      10      /* Seconds after which I should be timed out */
#define MAXTIM      60      /* Maximum timeout interval */
#define MINTIM      2       /* Minumum timeout interval */

#define TRUE        -1      /* Boolean constants */
#define FALSE       0


/*Macro Definitions */

/*
 * tochar: converts a control character to a printable one by adding a space.
 *
 * unchar: undoes tochar.
 *
 * ctl:    converts between control characters and printable characters by
 *         toggling the control bit (ie. ^A becomes A and A becomes ^A).
 */
#define tochar(ch)  ((ch) + ' ')
#define unchar(ch)  ((ch) - ' ')
#define ctl(ch)     ((ch) ^ 64 )


/*Global Variables */

int     size,               /* Size of present data */
        rpsiz,              /* Maximum receive packet size */
        spsiz,              /* Maximum send packet size */
        pad,                /* How much padding to send */
        timint,             /* Timeout for foreign host on sends */
        n,                  /* Packet number */
        numtry,             /* Times this packet retried */
        oldtry,             /* Times previous packet retried */
        ttyfd,              /* File descriptor of tty for I/O, 0 if remote */
        remote,             /* -1 means we're a remote kermit */
        image,              /* -1 means 8-bit mode */
        debug,              /* indicates level of debugging output (0=none) */
        filnamcnv,          /* -1 means do file name case conversions */
        filecount;          /* Number of files left to send */

char    state,              /* Present state of the automaton */
        padchar,            /* Padding character to send */
        eol,                /* End-Of-Line character to send */
        escchr,             /* Connect command escape character */
        quote,              /* Quote character in incoming data */
        **filelist,         /* List of files to be sent */
        *filnam,            /* Current file name */
        recpkt[MAXPACKSIZ], /* Receive packet buffer */
        packet[MAXPACKSIZ]; /* Packet buffer */

FILE    *fp,                /* File pointer for current disk file */
        *log;               /* File pointer for Logfile */

jmp_buf env;                /* Environment ptr for timeout longjump */
#ifdef UTSS1
int rpos;
char    fullbuff[1920];
#endif


/*
 *  m a i n
 *
 *  Main routine - parse command and options, set up the
 *  tty lines, and dispatch to the appropriate routine.
 */

main(argc,argv)
int argc;                           /* Character pointers to and count of */
char **argv;                            /* command line arguments */
{
#ifndef UTSS1
    char *ttyname,                      /* tty name for LINE argument */
        *cp;                            /* char pointer */
    int speed,                          /* speed of assigned tty, */
        cflg, rflg, sflg;               /* flags for CONNECT, RECEIVE, SEND */
#else
    char *cp;
    int rflg,sflg;
    char *name;
    char *ttyname();
#endif

    struct sgttyb
        rawmode,                        /* Controlling tty raw mode */
        cookedmode,                     /* Controlling tty cooked mode */
        ttymode;                        /* mode of tty line in LINE option */

    if (argc < 2) usage();              /* Make sure there's a command line */

    cp = *++argv; argv++; argc -= 2;    /* Set up pointers to args */

/* Initialize these values and hope the first packet will get across OK */

    eol = CR;                           /* EOL for outgoing packets */
    quote = '#';                        /* Standard control-quote char "#" */
    pad = 0;                            /* No padding */
    padchar = NULL;                     /* Use null if any padding wanted */

#ifndef UTSS1
    speed = cflg = sflg = rflg = 0;     /* Turn off all parse flags */
    ttyname = 0;                        /* Default is remote mode */
#endif

#if UCB4X                               /* Default to 7-bit masking, CRLF */
    image = FALSE;                      /* translation and filename case */
    filnamcnv = TRUE;                   /* conversion for UNIX systems */
#else
    image = TRUE;                       /* Default to no processing for */
    filnamcnv = FALSE;                  /* non-UNIX systems */
#endif
#ifdef UTSS1
    image = FALSE;
    filnamcnv = TRUE;
#endif

    escchr = ESCCHR;                    /* Default escape character */
#ifdef UTSS1
    sflg = rflg = 0;
#endif

    while ((*cp) != NULL)               /* Parse characters in first arg. */
        switch (*cp++)
        {
#ifndef UTSS1
            case 'c': cflg++; break;    /* C = Connect command */
#endif
            case 's': sflg++; break;    /* S = Send command */
            case 'r': rflg++; break;    /* R = Receive command */

#ifndef UTSS1
            case 'd':                   /* D = Increment debug mode count */
                debug++; break;
#endif

            case 'f':
                filnamcnv = FALSE;      /* F = don't do case conversion */
                break;                  /*     on filenames */

#ifndef UTSS1
            case 'i':                   /* I = Image (8-bit) mode */
                image = TRUE; break;    /* (this is default for non-UNIX) */

            case 'l':                   /* L = specify tty line to use */
                if (argc--) ttyname = *argv++;
                else usage();
                if (debug) printf("Line to remote host is %s\n",ttyname);
                break;

            case 'e':                   /* E = specify escape char */
                if (argc--) escchr = **argv++;
                else usage();
                if (debug) printf("Escape char is \"c\"\n",escchr);
                break;

            case 'b':                   /* B = specify baud rate */
#endif
#if UCB4X
                if (argc--) speed = atoi(*argv++);
                else usage();
                if (debug) printf("Line speed to remote host is %d\n",speed);
                break;
#else
                printmsg("Speed setting implemented for Unix only.");
                exit(1);
#endif
        }

/*Done parsing */

#ifndef UTSS1
    if ((cflg+sflg+rflg) != 1)          /* Only one command allowed */
        usage();


    if (ttyname)                        /* If LINE was specified, we */
    {                                   /* operate in local mode */
        ttyfd = open(ttyname,2);        /* Open the tty line */
        if (ttyfd < 0)
        {
            printmsg("Cannot open %s",ttyname);
            exit(1);
        }
        remote = FALSE;                 /* Indicate we're in local mode */
    }
    else                                /* No LINE specified so we operate */
    {                                   /* in remote mode (ie. controlling */
        ttyfd = 0;                      /* tty is the communications line) */
        remote = TRUE;
    }


/*Put the proper tty into the correct mode */

    if (remote)                         /* If remote, use controlling tty */
    {
        gtty(0,&cookedmode);            /* Save current mode so we can */
        gtty(0,&rawmode);               /* restore it later */
        rawmode.sg_flags |= (RAW|TANDEM);
        rawmode.sg_flags &= ~(ECHO|CRMOD);
        stty(0,&rawmode);               /* Put tty in raw mode */
    }
    else                                /* Local, use assigned line */
    {
        gtty(ttyfd,&ttymode);
        ttymode.sg_flags |= (RAW|TANDEM);
        ttymode.sg_flags &= ~(ECHO|CRMOD);

#endif
#if UCB4X                               /* Speed changing for UNIX only */
        if (speed)                      /* User specified a speed? */
        {
            switch(speed)               /* Get internal system code */
            {
                case 110: speed = B110; break;
                case 150: speed = B150; break;
                case 300: speed = B300; break;
                case 1200: speed = B1200; break;
                case 2400: speed = B2400; break;
                case 4800: speed = B4800; break;
                case 9600: speed = B9600; break;

                default:
                    printmsg("Bad line speed.");
                    exit(1);
            }
            ttymode.sg_ispeed = speed;
            ttymode.sg_ospeed = speed;
        }
#endif /* UCB4X */
#ifndef UTSS1

        stty(ttyfd,&ttymode);           /* Put asg'd tty in raw mode */
    }


/*All set up, now execute the command that was given. */

    if (debug)
    {
        printf("Debugging level = %d\n\n",debug);

        if (cflg) printf("Connect command\n\n");
        if (sflg) printf("Send command\n\n");
        if (rflg) printf("Receive command\n\n");
    }

    if (cflg) connect();                /* Connect command */
#else
    if ((rflg + sflg) != 1) usage();
    name = ttyname(2);
    name[6] = 'u';
    name[7] = 'b';
    ttyfd = open(name,2);
    ioctl(ttyfd,TUBOCMD,0x01);
    remote = FALSE;
#endif

    if (sflg)                           /* Send command */
    {
        if (argc--) filnam = *argv++;   /* Get file to send */
        else
        {
#ifndef UTSS1
            if (remote)
                stty(0,&cookedmode);    /* Restore controlling tty's modes */
#endif
            usage();                    /* and give error */
        }
        fp = NULL;                      /* Indicate no file open yet */
        filelist = argv;                /* Set up the rest of the file list */
        filecount = argc;               /* Number of files left to send */
        if (sendsw() == FALSE)          /* Send the file(s) */
            printmsg("Send failed.");   /* Report failure */
        else                            /*  or */
            printmsg("done.");          /* success */
    }

    if (rflg)                           /* Receive command */
    {
        if (recsw() == FALSE)           /* Receive the file(s) */
            printmsg("Receive failed.");
        else                            /* Report failure */
            printmsg("done.");          /* or success */
    }

#ifndef UTSS1
    if (remote) stty(0,&cookedmode);    /* Restore controlling tty's modes */
#else
    close(ttyfd);
#endif
}


/*
 *  s e n d s w
 *
 *  Sendsw is the state table switcher for sending files.  It loops until
 *  either it finishes, or an error is encountered.  The routines called
 *  by sendsw are responsible for changing the state.
 *
 */

sendsw()
{
    char sinit(), sfile(), sdata(), seof(), sbreak();

    state = 'S';                        /* Send initiate is the start state */
    n = 0;                              /* Initialize message number */
    numtry = 0;                         /* Say no tries yet */
    while(TRUE)                         /* Do this as long as necessary */
    {
        if (debug) printf("sendsw state: %c\n",state);
        switch(state)
        {
            case 'S':   state = sinit();  break; /* Send-Init */
            case 'F':   state = sfile();  break; /* Send-File */
            case 'D':   state = sdata();  break; /* Send-Data */
            case 'Z':   state = seof();   break; /* Send-End-of-File */
            case 'B':   state = sbreak(); break; /* Send-Break */
            case 'C':   return (TRUE);           /* Complete */
            case 'A':   return (FALSE);          /* "Abort" */
            default:    return (FALSE);          /* Unknown, fail */
        }
    }
}


/*
 *  s i n i t
 *
 *  Send Initiate: send this host's parameters and get other side's back.
 */

char sinit()
{
    int num, len;                       /* Packet number, length */

    if (numtry++ > MAXTRY) return('A'); /* If too many tries, give up */
    spar(packet);                       /* Fill up init info packet */

#ifndef UTSS1
    flushinput();                       /* Flush pending input */
#endif

    spack('S',n,6,packet);              /* Send an S packet */
    switch(rpack(&len,&num,recpkt))     /* What was the reply? */
    {
        case 'N':  return(state);       /* NAK, try it again */

        case 'Y':                       /* ACK */
            if (n != num)               /* If wrong ACK, stay in S state */
                return(state);          /* and try again */
            rpar(recpkt);               /* Get other side's init info */

            if (eol == 0) eol = '\n';   /* Check and set defaults */
            if (quote == 0) quote = '#';

            numtry = 0;                 /* Reset try counter */
            n = (n+1)%64;               /* Bump packet count */
            return('F');                /* OK, switch state to F */

        case 'E':                       /* Error packet received */
            prerrpkt(recpkt);           /* Print it out and */
            return('A');                /* abort */

        case FALSE: return(state);      /* Receive failure, try again */

        default: return('A');           /* Anything else, just "abort" */
   }
 }


/*
 *  s f i l e
 *
 *  Send File Header.
 */

char sfile()
{
    int num, len;                       /* Packet number, length */
    char filnam1[50],                   /* Converted file name */
        *newfilnam,                     /* Pointer to file name to send */
        *cp;                            /* char pointer */

    if (numtry++ > MAXTRY) return('A'); /* If too many tries, give up */

    if (fp == NULL)                     /* If not already open, */
    {   if (debug) printf("   Opening %s for sending.\n",filnam);
        fp = fopen(filnam,"r");         /* open the file to be sent */
        if (fp == NULL)                 /* If bad file pointer, give up */
        {
            error("Cannot open file %s",filnam);
            return('A');
        }
    }

    strcpy(filnam1, filnam);            /* Copy file name */
    newfilnam = cp = filnam1;
    while (*cp != '\0')                 /* Strip off all leading directory */
        if (*cp++ == '/')               /* names (ie. up to the last /). */
            newfilnam = cp;

    if (filnamcnv)                      /* Convert lower case to upper  */
        for (cp = newfilnam; *cp != '\0'; cp++)
            if (*cp >= 'a' && *cp <= 'z')
                *cp ^= 040;

    len = cp - newfilnam;               /* Compute length of new filename */

    printmsg("Sending %s as %s",filnam,newfilnam);

    spack('F',n,len,newfilnam);         /* Send an F packet */
    switch(rpack(&len,&num,recpkt))     /* What was the reply? */
    {
        case 'N':                       /* NAK, just stay in this state, */
            num = (--num<0 ? 63:num);   /* unless it's NAK for next packet */
            if (n != num)               /* which is just like an ACK for */
                return(state);          /* this packet so fall thru to... */

        case 'Y':                       /* ACK */
            if (n != num) return(state); /* If wrong ACK, stay in F state */
            numtry = 0;                 /* Reset try counter */
            n = (n+1)%64;               /* Bump packet count */
            size = bufill(packet);      /* Get first data from file */
            return('D');                /* Switch state to D */

        case 'E':                       /* Error packet received */
            prerrpkt(recpkt);           /* Print it out and */
            return('A');                /* abort */

        case FALSE: return(state);      /* Receive failure, stay in F state */

        default:    return('A');        /* Something else, just "abort" */
    }
}


/*
 *  s d a t a
 *
 *  Send File Data
 */

char sdata()
{
    int num, len;                       /* Packet number, length */

    if (numtry++ > MAXTRY) return('A'); /* If too many tries, give up */

    spack('D',n,size,packet);           /* Send a D packet */
    switch(rpack(&len,&num,recpkt))     /* What was the reply? */
    {
        case 'N':                       /* NAK, just stay in this state, */
            num = (--num<0 ? 63:num);   /* unless it's NAK for next packet */
            if (n != num)               /* which is just like an ACK for */
                return(state);          /* this packet so fall thru to... */

        case 'Y':                       /* ACK */
            if (n != num) return(state); /* If wrong ACK, fail */
            numtry = 0;                 /* Reset try counter */
            n = (n+1)%64;               /* Bump packet count */
            if ((size = bufill(packet)) == EOF) /* Get data from file */
                return('Z');            /* If EOF set state to that */
            return('D');                /* Got data, stay in state D */

        case 'E':                       /* Error packet received */
            prerrpkt(recpkt);           /* Print it out and */
            return('A');                /* abort */

        case FALSE: return(state);      /* Receive failure, stay in D */

        default:    return('A');        /* Anything else, "abort" */
    }
}


/*
 *  s e o f
 *
 *  Send End-Of-File.
 */

char seof()
{
    int num, len;                       /* Packet number, length */
    if (numtry++ > MAXTRY) return('A'); /* If too many tries, "abort" */

    spack('Z',n,0,packet);              /* Send a 'Z' packet */
    switch(rpack(&len,&num,recpkt))     /* What was the reply? */
    {
        case 'N':                       /* NAK, just stay in this state, */
            num = (--num<0 ? 63:num);   /* unless it's NAK for next packet, */
            if (n != num)               /* which is just like an ACK for */
                return(state);          /* this packet so fall thru to... */

        case 'Y':                       /* ACK */
            if (n != num) return(state); /* If wrong ACK, hold out */
            numtry = 0;                 /* Reset try counter */
            n = (n+1)%64;               /* and bump packet count */
            if (debug) printf("   Closing input file %s, ",filnam);
            fclose(fp);                 /* Close the input file */
            fp = NULL;                  /* Set flag indicating no file open */

            if (debug) printf("looking for next file...\n");
            if (gnxtfl() == FALSE)      /* No more files go? */
                return('B');            /* if not, break, EOT, all done */
            if (debug) printf("   New file is %s\n",filnam);
            return('F');                /* More files, switch state to F */

        case 'E':                       /* Error packet received */
            prerrpkt(recpkt);           /* Print it out and */
            return('A');                /* abort */

        case FALSE: return(state);      /* Receive failure, stay in Z */

        default:    return('A');        /* Something else, "abort" */
    }
}


/*
 *  s b r e a k
 *
 *  Send Break (EOT)
 */

char sbreak()
{
    int num, len;                       /* Packet number, length */
    if (numtry++ > MAXTRY) return('A'); /* If too many tries "abort" */

    spack('B',n,0,packet);              /* Send a B packet */
    switch (rpack(&len,&num,recpkt))    /* What was the reply? */
    {
        case 'N':                       /* NAK, just stay in this state, */
            num = (--num<0 ? 63:num);   /* unless NAK for previous packet, */
            if (n != num)               /* which is just like an ACK for */
                return(state);          /* this packet so fall thru to... */

        case 'Y':                       /* ACK */
            if (n != num) return(state); /* If wrong ACK, fail */
            numtry = 0;                 /* Reset try counter */
            n = (n+1)%64;               /* and bump packet count */
            return('C');                /* Switch state to Complete */

        case 'E':                       /* Error packet received */
            prerrpkt(recpkt);           /* Print it out and */
            return('A');                /* abort */

        case FALSE: return(state);      /* Receive failure, stay in B */

        default:    return ('A');       /* Other, "abort" */
   }
}


/*
 *  r e c s w
 *
 *  This is the state table switcher for receiving files.
 */

recsw()
{
    char rinit(), rfile(), rdata();     /* Use these procedures */

    state = 'R';                        /* Receive-Init is the start state */
    n = 0;                              /* Initialize message number */
    numtry = 0;                         /* Say no tries yet */

    while(TRUE)
    {
#ifndef UTSS1
        if (debug) printf(" recsw state: %c\n",state);
#else
#endif
        switch(state)                   /* Do until done */
        {
            case 'R':   state = rinit(); break; /* Receive-Init */
            case 'F':   state = rfile(); break; /* Receive-File */
            case 'D':   state = rdata(); break; /* Receive-Data */
            case 'C':   return(TRUE);           /* Complete state */
            case 'A':   return(FALSE);          /* "Abort" state */
        }
    }
}


/*
 *  r i n i t
 *
 *  Receive Initialization
 */

char rinit()
{
    int len, num;                       /* Packet length, number */

#ifdef UTSS1
    write(ttyfd,"\360\021\135\177\021\000\001",7);
#endif
    if (numtry++ > MAXTRY) return('A'); /* If too many tries, "abort" */

    switch(rpack(&len,&num,packet))     /* Get a packet */
    {
        case 'S':                       /* Send-Init */
            rpar(packet);               /* Get the other side's init data */
            spar(packet);               /* Fill up packet with my init info */
            spack('Y',n,6,packet);      /* ACK with my parameters */
            oldtry = numtry;            /* Save old try count */
            numtry = 0;                 /* Start a new counter */
            n = (n+1)%64;               /* Bump packet number, mod 64 */
            return('F');                /* Enter File-Receive state */

        case 'E':                       /* Error packet received */
            prerrpkt(recpkt);           /* Print it out and */
            return('A');                /* abort */

        case FALSE:                     /* Didn't get packet */
            spack('N',n,0,0);           /* Return a NAK */
            return(state);              /* Keep trying */

        default:     return('A');       /* Some other packet type, "abort" */
    }
}


/*
 *  r f i l e
 *
 *  Receive File Header
 */

char rfile()
{
    int num, len;                       /* Packet number, length */
    char filnam1[50];                   /* Holds the converted file name */

    if (numtry++ > MAXTRY) return('A'); /* "abort" if too many tries */

    switch(rpack(&len,&num,packet))     /* Get a packet */
    {
        case 'S':                       /* Send-Init, maybe our ACK lost */
            if (oldtry++ > MAXTRY) return('A'); /* If too many tries "abort" */
            if (num == ((n==0) ? 63:n-1)) /* Previous packet, mod 64? */
            {                           /* Yes, ACK it again with  */
                spar(packet);           /* our Send-Init parameters */
                spack('Y',num,6,packet);
                numtry = 0;             /* Reset try counter */
                return(state);          /* Stay in this state */
            }
            else return('A');           /* Not previous packet, "abort" */

        case 'Z':                       /* End-Of-File */
            if (oldtry++ > MAXTRY) return('A');
            if (num == ((n==0) ? 63:n-1)) /* Previous packet, mod 64? */
            {                           /* Yes, ACK it again. */
                spack('Y',num,0,0);
                numtry = 0;
                return(state);          /* Stay in this state */
            }
            else return('A');           /* Not previous packet, "abort" */

        case 'F':                       /* File Header (just what we want) */
            if (num != n) return('A');  /* The packet number must be right */
            strcpy(filnam1, packet);    /* Copy the file name */

            if (filnamcnv)              /* Convert upper case to lower */
                for (filnam=filnam1; *filnam != '\0'; filnam++)
                    if (*filnam >= 'A' && *filnam <= 'Z')
                        *filnam |= 040;

            if ((fp=fopen(filnam1,"w"))==NULL) /* Try to open a new file */
            {
                error("Cannot create %s",filnam1); /* Give up if can't */
                return('A');
            }
            else                        /* OK, give message */
                printmsg("Receiving %s as %s",packet,filnam1);

            spack('Y',n,0,0);           /* Acknowledge the file header */
            oldtry = numtry;            /* Reset try counters */
            numtry = 0;                 /* ... */
            n = (n+1)%64;               /* Bump packet number, mod 64 */
            return('D');                /* Switch to Data state */

        case 'B':                       /* Break transmission (EOT) */
            if (num != n) return ('A'); /* Need right packet number here */
#ifndef UTSS1
            spack('Y',n,0,0);           /* Say OK */
#else
            spacks1e('Y',n,0,0);        /* Say OK with no more reads */
#endif
            return('C');                /* Go to complete state */

        case 'E':                       /* Error packet received */
            prerrpkt(recpkt);           /* Print it out and */
            return('A');                /* abort */

        case FALSE:                     /* Didn't get packet */
            spack('N',n,0,0);           /* Return a NAK */
            return(state);              /* Keep trying */

        default:    return ('A');       /* Some other packet, "abort" */
    }
}


/*
 *  r d a t a
 *
 *  Receive Data
 */

char rdata()
{
    int num, len;                       /* Packet number, length */
    if (numtry++ > MAXTRY) return('A'); /* "abort" if too many tries */

    switch(rpack(&len,&num,packet))     /* Get packet */
    {
        case 'D':                       /* Got Data packet */
            if (num != n)               /* Right packet? */
            {                           /* No */
                if (oldtry++ > MAXTRY)
                    return('A');        /* If too many tries, abort */
                if (num == ((n==0) ? 63:n-1)) /* Else check packet number */
                {                       /* Previous packet again? */
                    spack('Y',num,6,packet); /* Yes, re-ACK it */
                    numtry = 0;         /* Reset try counter */
                    return(state);      /* Don't write out data! */
                }
                else return('A');       /* sorry, wrong number */
            }
            /* Got data with right packet number */
            bufemp(packet,len);         /* Write the data to the file */
            spack('Y',n,0,0);           /* Acknowledge the packet */
            oldtry = numtry;            /* Reset the try counters */
            numtry = 0;                 /* ... */
            n = (n+1)%64;               /* Bump packet number, mod 64 */
            return('D');                /* Remain in data state */

        case 'F':                       /* Got a File Header */
            if (oldtry++ > MAXTRY)
                return('A');            /* If too many tries, "abort" */
            if (num == ((n==0) ? 63:n-1)) /* Else check packet number */
            {                           /* It was the previous one */
                spack('Y',num,0,0);     /* ACK it again */
                numtry = 0;             /* Reset try counter */
                return(state);          /* Stay in Data state */
            }
            else return('A');           /* Not previous packet, "abort" */

        case 'Z':                       /* End-Of-File */
            if (num != n) return('A');  /* Must have right packet number */
            spack('Y',n,0,0);           /* OK, ACK it. */
            fclose(fp);                 /* Close the file */
            n = (n+1)%64;               /* Bump packet number */
            return('F');                /* Go back to Receive File state */

        case 'E':                       /* Error packet received */
            prerrpkt(recpkt);           /* Print it out and */
            return('A');                /* abort */

        case FALSE:                     /* Didn't get packet */
            spack('N',n,0,0);           /* Return a NAK */
            return(state);              /* Keep trying */

        default:     return('A');       /* Some other packet, "abort" */
    }
}

#ifndef UTSS1
/*
 *  c o n n e c t
 *
 *  Establish a virtual terminal connection with the remote host, over an
 *  assigned tty line.
 */

connect()
{
    int pid,                            /* Holds process id of child */
        connected;                      /* Boolean connect flag */
    char bel = '\07',
        c;

    struct sgttyb
        rawmode,                        /* Controlling tty raw mode */
        cookedmode;                     /* Controlling tty cooked mode */

    if (remote)                         /* Nothing to connect to in remote */
    {                                   /* mode, so just return */
        printmsg("No line specified for connection.");
        return;
    }

    gtty(0,&cookedmode);                /* Save current mode so we can */
    gtty(0,&rawmode);                   /* restore it later */
    rawmode.sg_flags |= (RAW|TANDEM);
    rawmode.sg_flags &= ~(ECHO|CRMOD);
    stty(0,&rawmode);                   /* Put tty in raw mode */

    pid = fork();           /* Start fork to get typeout from remote host */

    if (pid)                        /* Parent: send type-in to remote host */
    {
        printmsg("connected...\r");
        connected = TRUE;               /* Put us in "connect mode" */
        while (connected)
        {
            read(0,&c,1);               /* Get a character */
            if ((c&0177) == escchr)     /* Check for escape character */
            {
                read(0,&c,1);
                if ((c&0177) == escchr)
                    write(ttyfd,&c,1);
                else
                switch (c&0177)
                {
                    case 'c':
                    case 'C':
                        connected = FALSE;
                        write(0,"\r\n",2);
                        break;

                    case 'h':
                    case 'H':
                        write(0,"\r\nYes, I'm still here...\r\n",26);
                        break;

                    default:
                        write(0,&bel,1);
                        break;
                }
            }
            else
            {                           /* If not escape charater, */
                write(ttyfd,&c,1);      /* write it out */
                c = NULL;               /* Nullify it (why?) */
            }
        }
        kill(pid,9);                    /* Done, kill the child */
        wait(0);                        /* and bury him */
        stty(0,&cookedmode);            /* Restore tty mode */
        printmsg("disconnected.");
        return;                         /* Done */
    }
    else                  /* Child does the reading from the remote host */
    {
        while(1)                        /* Do this forever */
        {
            read(ttyfd,&c,1);
            write(1,&c,1);
        }
    }
}
#endif

%
 *      KERMIT utilities.
 */

clkint()                                /* Timer interrupt handler */
{
    longjmp(env,TRUE);                  /* Tell rpack to give up */
}


/*
 *  s p a c k
 *
 *  Send a Packet
 */

spack(type,num,len,data)
char type, *data;
int num, len;
{
    int i;                              /* Character loop counter */
    char chksum, buffer[100];           /* Checksum, packet buffer */
    register char *bufp;                /* Buffer pointer */

    if (debug>1)                        /* Display outgoing packet */
    {
        if (data != NULL)
            data[len] = '\0';           /* Null-terminate data to print it */
        printf("  spack type: %c\n",type);
        printf("         num:  %d\n",num);
        printf("         len:  %d\n",len);
        if (data != NULL)
            printf("        data: \"%s\"\n",data);
    }

    bufp = buffer;                      /* Set up buffer pointer */
#ifdef UTSS1
    *bufp++ = '\360';
    *bufp++ = '\021';
    *bufp++ = '\135';
    *bufp++ = '\177';
    *bufp++ = '\021';
    *bufp++ = '\000';
    *bufp++ = '\001';
#endif
    for (i=1; i<=pad; i++) write(ttyfd,&padchar,1); /* Issue any padding */

    *bufp++ = SOH;                      /* Packet marker, ASCII 1 (SOH) */
    *bufp++ = tochar(len+3);            /* Send the character count */
    chksum  = tochar(len+3);            /* Initialize the checksum */
    *bufp++ = tochar(num);              /* Packet number */
    chksum += tochar(num);              /* Update checksum */
    *bufp++ = type;                     /* Packet type */
    chksum += type;                     /* Update checksum */

    for (i=0; i<len; i++)               /* Loop for all data characters */
    {
        *bufp++ = data[i];              /* Get a character */
        chksum += data[i];              /* Update checksum */
    }
    chksum = (((chksum&0300) >> 6)+chksum)&077; /* Compute final checksum */
    *bufp++ = tochar(chksum);           /* Put it in the packet */
    *bufp = eol;                        /* Extra-packet line terminator */
    write(ttyfd, buffer,bufp-buffer+1); /* Send the packet */
}

#ifdef UTSS1
/*
 *  s p a c k s 1 e
 *
 *  Send a Packet
 */

spacks1e(type,num,len,data)
char type, *data;
int num, len;
{
    int i;                              /* Character loop counter */
    char chksum, buffer[100];           /* Checksum, packet buffer */
    register char *bufp;                /* Buffer pointer */

    bufp = buffer;                      /* Set up buffer pointer */
    *bufp++ = '\360';
    *bufp++ = '\021';
    *bufp++ = '\135';
    *bufp++ = '\177';
    *bufp++ = '\021';
    *bufp++ = '\000';
    *bufp++ = '\000';
    for (i=1; i<=pad; i++) write(ttyfd,&padchar,1); /* Issue any padding */

    *bufp++ = SOH;                      /* Packet marker, ASCII 1 (SOH) */
    *bufp++ = tochar(len+3);            /* Send the character count */
    chksum  = tochar(len+3);            /* Initialize the checksum */
    *bufp++ = tochar(num);              /* Packet number */
    chksum += tochar(num);              /* Update checksum */
    *bufp++ = type;                     /* Packet type */
    chksum += type;                     /* Update checksum */

    for (i=0; i<len; i++)               /* Loop for all data characters */
    {
        *bufp++ = data[i];              /* Get a character */
        chksum += data[i];              /* Update checksum */
    }
    chksum = (((chksum&0300) >> 6)+chksum)&077; /* Compute final checksum */
    *bufp++ = tochar(chksum);           /* Put it in the packet */
    *bufp = eol;                        /* Extra-packet line terminator */
    write(ttyfd, buffer,bufp-buffer+1); /* Send the packet */
}

/*
 *  c r e a d
 */

cread(fd,buf,nbytes)
int fd;
char *buf;
int nbytes;
{
int i;
    i = fd;           /* this is to stop the compiler from complaining */
    for (i=0;i<nbytes;i++)
        buf[i] = fullbuff[i+rpos] & 0177;
    rpos += nbytes;
    return(nbytes);
}
#endif

/*
 *  r p a c k
 *
 *  Read a Packet
 */

rpack(len,num,data)
int *len, *num;                         /* Packet length, number */
char *data;                             /* Packet data */
{
    int i, done;                        /* Data character number, loop exit */
    char t,                             /* Current input character */
        type,                           /* Packet type */
        cchksum,                        /* Our (computed) checksum */
        rchksum;                        /* Checksum received from other host */

#if UCB4X                               /* TOPS-20 can't handle timeouts... */
    if (setjmp(env)) return FALSE;      /* Timed out, fail */
    signal(SIGALRM,clkint);             /* Setup the timeout */
    if ((timint > MAXTIM) || (timint < MINTIM)) timint = MYTIME;
    alarm(timint);
#endif /* UCB4X */
#ifdef UTSS1
    for(i=0;i<1920;i++) fullbuff[i] = 0;
    read(ttyfd,fullbuff,1920);
    rpos = 3;
#endif

    while (t != SOH)                    /* Wait for packet header */
    {
#ifndef UTSS1
        read(ttyfd,&t,1);
#else
        cread(ttyfd,&t,1);
#endif
        t &= 0177;                      /* Handle parity */
    }

    done = FALSE;                       /* Got SOH, init loop */
    while (!done)                       /* Loop to get a packet */
    {
#ifndef UTSS1
        read(ttyfd,&t,1);               /* Get character */
#else
        cread(ttyfd,&t,1);               /* Get character */
#endif
        if (!image) t &= 0177;          /* Handle parity */
        if (t == SOH) continue;         /* Resynchronize if SOH */
        cchksum = t;                    /* Start the checksum */
        *len = unchar(t)-3;             /* Character count */

#ifndef UTSS1
        read(ttyfd,&t,1);               /* Get character */
#else
        cread(ttyfd,&t,1);               /* Get character */
#endif
        if (!image) t &= 0177;          /* Handle parity */
        if (t == SOH) continue;         /* Resynchronize if SOH */
        cchksum = cchksum + t;          /* Update checksum */
        *num = unchar(t);               /* Packet number */

#ifndef UTSS1
        read(ttyfd,&t,1);               /* Get character */
#else
        cread(ttyfd,&t,1);               /* Get character */
#endif
        if (!image) t &= 0177;          /* Handle parity */
        if (t == SOH) continue;         /* Resynchronize if SOH */
        cchksum = cchksum + t;          /* Update checksum */
        type = t;                       /* Packet type */

        for (i=0; i<*len; i++)          /* The data itself, if any */
        {                               /* Loop for character count */
#ifndef UTSS1
            read(ttyfd,&t,1);           /* Get character */
#else
            cread(ttyfd,&t,1);               /* Get character */
#endif
            if (!image) t &= 0177;      /* Handle parity */
            if (t == SOH) continue;     /* Resynch if SOH */
            cchksum = cchksum + t;      /* Update checksum */
            data[i] = t;                /* Put it in the data buffer */
        }
        data[*len] = 0;                 /* Mark the end of the data */

#ifndef UTSS1
        read(ttyfd,&t,1);               /* Get last character (checksum) */
#else
        cread(ttyfd,&t,1);              /* Get last character (checksum) */
#endif
        rchksum = unchar(t);            /* Convert to numeric */
#ifndef UTSS1
        read(ttyfd,&t,1);               /* get EOL character and toss it */
#else
        cread(ttyfd,&t,1);              /* get EOL character and toss it */
#endif
        if (!image) t &= 0177;          /* Handle parity */
        if (t == SOH) continue;         /* Resynchronize if SOH */
        done = TRUE;                    /* Got checksum, done */
    }

#if UCB4X
    alarm(0);                           /* Disable the timer interrupt */
#endif

    if (debug>1)                        /* Display incoming packet */
    {
        if (data != NULL)
            data[*len] = '\0';          /* Null-terminate data to print it */
        printf("  rpack type: %c\n",type);
        printf("         num:  %d\n",*num);
        printf("         len:  %d\n",*len);
        if (data != NULL)
            printf("        data: \"%s\"\n",data);
    }
                                        /* Fold in bits 7,8 to compute */
    cchksum = (((cchksum&0300) >> 6)+cchksum)&077; /* final checksum */

    if (cchksum != rchksum) return(FALSE);

    return(type);                       /* All OK, return packet type */
}


/*
 *  b u f i l l
 *
 *  Get a bufferful of data from the file that's being sent.
 *  Only control-quoting is done; 8-bit & repeat count prefixes are
 *  not handled.
 */

bufill(buffer)
char buffer[];                          /* Buffer */
{
    int i,                              /* Loop index */
        t;                              /* Char read from file */
    char t7;                            /* 7-bit version of above */

    i = 0;                              /* Init data buffer pointer */
    while((t = getc(fp)) != EOF)        /* Get the next character */
    {
        t7 = t & 0177;                  /* Get low order 7 bits */

        if (t7 < SP || t7==DEL || t7==quote) /* Does this char require */
        {                                   /* special handling? */
            if (t=='\n' && !image)
            {                           /* Do LF->CRLF mapping if !image */
                buffer[i++] = quote;
                buffer[i++] = ctl('\r');
            }
            buffer[i++] = quote;        /* Quote the character */
            if (t7 != quote)
            {
                t = ctl(t);             /* and uncontrolify */
                t7 = ctl(t7);
            }
        }
        if (image)
            buffer[i++] = t;            /* Deposit the character itself */
        else
            buffer[i++] = t7;

        if (i >= spsiz-8) return(i);    /* Check length */
    }
    if (i==0) return(EOF);              /* Wind up here only on EOF */
    return(i);                          /* Handle partial buffer */
}


/*
 *      b u f e m p
 *
 *  Put data from an incoming packet into a file.
 */

bufemp(buffer,len)
char  buffer[];                         /* Buffer */
int   len;                              /* Length */
{
    int i;                              /* Counter */
    char t;                             /* Character holder */

    for (i=0; i<len; i++)               /* Loop thru the data field */
    {
        t = buffer[i];                  /* Get character */
        if (t == MYQUOTE)               /* Control quote? */
        {                               /* Yes */
            t = buffer[++i];            /* Get the quoted character */
            if ((t & 0177) != MYQUOTE)  /* Low order bits match quote char? */
                t = ctl(t);             /* No, uncontrollify it */
        }
        if (t==CR && !image)            /* Don't pass CR if in image mode */
            continue;

        putc(t,fp);
    }
}


/*
 *  g n x t f l
 *
 *  Get next file in a file group
 */

gnxtfl()
{
    if (debug) printf("   gnxtfl: filelist = \"%s\"\n",*filelist);
    filnam = *(filelist++);
    if (filecount-- == 0) return FALSE; /* If no more, fail */
    else return TRUE;                   /* else succeed */
}


/*
 *  s p a r
 *
 *  Fill the data array with my send-init parameters
 *
 */

spar(data)
char data[];
{
    data[0] = tochar(MAXPACKSIZ);          /* Biggest packet I can receive */
    data[1] = tochar(MYTIME);           /* When I want to be timed out */
    data[2] = tochar(MYPAD);            /* How much padding I need */
    data[3] = ctl(MYPCHAR);             /* Padding character I want */
    data[4] = tochar(MYEOL);            /* End-Of-Line character I want */
    data[5] = MYQUOTE;                  /* Control-Quote character I send */
}


/* r p a r
 *
 *  Get the other host's send-init parameters
 *
 */

rpar(data)
char data[];
{
    spsiz = unchar(data[0]);            /* Maximum send packet size */
    timint = unchar(data[1]);           /* When I should time out */
    pad = unchar(data[2]);              /* Number of pads to send */
    padchar = ctl(data[3]);             /* Padding character to send */
    eol = unchar(data[4]);              /* EOL character I must send */
    quote = data[5];                    /* Incoming data quote character */
}


/*
 *  f l u s h i n p u t
 *
 *  Dump all pending input to clear stacked up NACK's.
 *  (Implemented only for Berkeley Unix at this time).
 */

#if UCB4X&(~NO_FIONREAD)
flushinput()
{
    long int count;                     /* Number of bytes ready to read */
    long int i;                         /* Number of bytes to read in loop */

    ioctl(ttyfd, FIONREAD, &count);     /* See how many bytes pending read */
    if (!count) return;                 /* If zero, then no input to flush */

    while (count)                       /* Loop till all are flushed */
    {
        i = (count<sizeof(recpkt)) ?    /* Read min of count and size of */
            count : sizeof(recpkt);     /*  the read buffer */
        read(ttyfd, recpkt, i);         /* Read a bunch */
        count -= i;                     /* Subtract from amount to read */
    }
}
#else
flushinput()            /* Null version for non-Berkeley Unix */
{}
#endif /* UCB4X&(~FIONREAD) */


/*
 *  Kermit printing routines:
 *
 *  usage - print command line options showing proper syntax
 *  printmsg -  like printf with "Kermit: " prepended
 *  error - like printmsg if local kermit; sends a error packet if remote
 *  prerrpkt - print contents of error packet received from remote host
 */

/*
 *  u s a g e
 *
 *  Print summary of usage info and quit
 */

usage()
{
#if UCB4X
    printf("Usage: kermit c[lbe line baud esc.char]      (connect mode)\n");
    printf("or:    kermit s[diflb line baud] file ...    (send mode)\n");
    printf("or:    kermit r[diflb line baud]             (receive mode)\n");
#else
    printf("Usage: kermit c[le line esc.char]            (connect mode)\n");
    printf("or:    kermit s[difl line] file ...          (send mode)\n");
    printf("or:    kermit r[difl line]                   (receive mode)\n");
#endif
    exit(1);
}

/*
 *  p r i n t m s g
 *
 *  Print message on standard output if not remote.
 */

/*VARARGS1*/
#ifndef UTSS1
printmsg(fmt, a1, a2, a3, a4, a5)
char *fmt;
{
    if (!remote)
    {
        printf("Kermit: ");
        printf(fmt,a1,a2,a3,a4,a5);
        printf("\n");
        fflush(stdout);                 /* force output (UTS needs it) */
    }
}
#else
printmsg(fmt, a1, a2, a3, a4, a5)
char *fmt;
{
}
#endif


/*
 *  e r r o r
 *
 *  Print error message.
 *
 *  If local, print error message with printmsg.
 *  If remote, send an error packet with the message.
 */

/*VARARGS1*/
#ifndef UTSS1
error(fmt, a1, a2, a3, a4, a5)
char *fmt;
{
    char msg[80];
    int len;

    if (remote)
    {
        sprintf(msg,fmt,a1,a2,a3,a4,a5); /* Make it a string */
        len = strlen(msg);
        spack('E',n,len,msg);           /* Send the error packet */
    }
    else
        printmsg(fmt, a1, a2, a3, a4, a5);

    return;
}
#else
error(fmt, a1, a2, a3, a4, a5)
char *fmt;
{
}
#endif

/*
 *  p r e r r p k t
 *
 *  Print contents of error packet received from remote host.
 */
#ifndef UTSS1
prerrpkt(msg)
char *msg;
{
    printf("Kermit aborting with following error from remote host:\n%s\n",msg);
    return;
}
#else
prerrpkt(msg)
char *msg;
{
}
#endif