/*
 *  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
 *          Server Mode and Honeywell H66 & DPS8 added   by
 *          Terry Carlin, Honeywell inc.
 *
 *
 *  For remote Kermit, format is either:
 *          kermit r                                    to receive files
 *  or      kermit s file ...                           to send files
 *
 *  or      kermit sv                                   server mode
 *
 *  or      kermit rc                                   receive into
 *                                                      working directory
 *
 *  or       kermit svc                                 server mode using
 *                                                      working directory
 */




/*
 *  Modification History:
 *
 *          - 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.
 *
 *          Added support for error packets and cleaned up the printing
 *              routines.
 *          Added support for Honeyewll running GCOS.
 *          Added simple server mode
 */

#include <stdio.h>          /* Standard UNIX definitions */
#include <fildes.h>
#include <fcntl.h>
#include <signal.h>
/* 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) */

#define MYEOL       '\r'    /* End-Of-Line character for UTS systems */

#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 */
        tmpfil,             /* User wanted temp file for output */
        cflg,                    /* cwd flag */
        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) */
        cnvfilnam,          /* -1 means do file name case conversions */
        filecount,          /* Number of files left to send */
        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 */
char    **filelist,         /* List of files to be sent */
        *filnam,            /* Current file name */
        recpkt[MAXPACKSIZ], /* Receive packet buffer */
        packet[MAXPACKSIZ]; /* Packet buffer */
char working_directory[40] = "/";
FILE    *fp,                /* File pointer for current disk file */
        *fddebug,           /* File for debugging output */
        *log;               /* File pointer for Logfile */

/*
 *  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 */
{
    char *ttyname,                      /* tty name for LINE argument */
        *cp;                            /* char pointer */
     char savefile[60];          /* work area for filename and cwd */
        int rflg, sflg;               /* flags for RECEIVE, SEND */
        int svflg;                   /* server mode */
        int cmd;                      /* for parser */
 extern int dobreak();                /* just need its address */
    if (argc < 2) usage();              /* Make sure there's a command line */

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

    printf("Kermit-GCOS V3.0\n");


/*  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 */

cflg = 0;   sflg = rflg = svflg = 0;     /* Turn off all parse flags */
    ttyname = 0;                        /* Default is remote mode */
    tmpfil = FALSE;                     /* Create perm files as default */
    image = FALSE;                      /* translation and filename case */
    cnvfilnam = TRUE;                   /* conversion for UNIX systems */

    while ((*cp) != NULL)               /* Parse characters in first */
        {
        cmd = *cp++;
        if (isupper(cmd))
            cmd = tolower(cmd);
        switch (cmd)
        {
            case 's':                   /* S = Send command */
                      sflg++;
                      break;
 
            case 'r':                   /* R = Receive command */
                      rflg++;
                      break;
            case 'v':    /*     server mode        */
                     if (sflg)
                         {
                         sflg = 0;
                         svflg++;
                         }
                     else
                         printf("Unknown mode\n");
                     break;

            case 'c':                  /* Change working directory */
                    printf("cwd: Enter working directory ");
                    gets(working_directory);
                    strcat(working_directory, "/");
                    cflg++;
                    break;
            case 'd':                   /* D = Increment debug mode count */
                if (!debug)
                     if ((fddebug = fopen("kermit.d", "w")) == NULL)
                        {
                        printf("Could not open kermit.d\n");
                        exit(1);
                        }
                debug++; break;

            case 'f':
                cnvfilnam = FALSE;      /* F = don't do case conversion */
                break;                  /*     on filenames */
 
            case 't':                   /* wants uploaded file to be temp */
                tmpfil++;
                working_directory[0] = '\0'; /* null out string */
                break;


        }
    }

/* Done parsing */

    if ((sflg+rflg+svflg) != 1)          /* Only one command allowed */
        usage();

     if ((tmpfil + cflg) > 1)
         {
         printf("Can't have working directory and temporary files\n");
         exit();
         }

       ttyfd = 0;        /* communications line */
        remote = TRUE;

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

     system("LINE 160");         /* change line length to 160 chars */

/* Set up tty break vectors */
     signal( _SIGBRK, dobreak);
 

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

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

        if (sflg) dbprint("Send command\n\n");
        if (rflg) dbprint("Receive command\n\n");
        if (svflg) dbprint("Server mode\n");
    }
 
    if (svflg)
         servsw();


    if (sflg)                           /* Send command */
    {
        if (argc--) filnam = *argv++;   /* Get file to send */
        else
        {
            usage();                    /* and give error */
        }
        if (cflg)                     /* see if working directory set */
            {
            strcpy(savefile, working_directory);
            strcat(savefile, filnam);
            filnam = savefile;
            }
        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 */
        state = 'S';                    /* Set up initial state */
        printf("Sending file\n");
         if (sendsw(sflg) == FALSE)   /* Send the file(s) */
            printmsg("Send failed.");   /* Report failure */
        else                            /*  or */
            printmsg("done.");          /* success */
    }




    if (rflg)                           /* Receive command */
    {
        state = 'R';                     /* Set up initial state */
         printf("Ready to receive file\n");
        if (recsw() == FALSE)           /* Receive the file(s) */
            printmsg("Receive failed.");
        else                            /* Report failure */
            printmsg("done.");          /* or success */
    }
   if (debug)
      fclose(fddebug);
}

 
 /*
  * Servsw is the state table switcher for Server mode.  It will call
  * sendsw() and recsw() to to the real work.
  */
 servsw()
 {
 int num, len, i;
 int pkttype;    /* packet type */
 char savename[60], *cptr;
 printf("Server mode\n");
 while(TRUE)
   {
   n = numtry = 0;
  switch((pkttype = rpack(&len, &num, recpkt)))
      {
      case 'I':        /* Info packet */
              rpar(recpkt);
              spar(packet);
              spack('Y',n,6,packet);
              break;
      case 'S':
              rpar(recpkt);
              spar(packet);
              spack('Y',n,6,packet);
              n = (n + 1) % 64;
              state = 'F';
              recsw();    /* go do the file receive */
              break;
      case 'R':
              if (cflg)     /* do cwd if there */
                  {
                  strcpy(savename,working_directory);
                  strcat(savename, recpkt);
                  }
              else
                  strcpy(savename, recpkt); /* save away filename */
              filnam = savename;
             fp = NULL;
             filecount = 0;
              state = 'S';              /* do send now */
              sendsw(FALSE);       /* send the file they want */
              break;
      case 'G':        /* Generic Kermit command */
              switch(recpkt[0])    /* Command is in first char */
                   {
                   case 'L':       /* logoff kermit and system */
                           spack('Y',n,0,0);
                           if (debug)
                                fclose(fddebug);
                           system("LOGOFF");   /* logoff */
                           break;  /* never get here but ??? */
                   case 'F':       /* finish kermit return to tss */
                          spack('Y',n,0,0);
                           return(TRUE);
                   case 'C':       /*  Change working directory */
                          spack('Y',n,0,0);
                          len = unchar(recpkt[1]);
                          cptr = working_directory;
                          for (i=2;len;i++,len--)
                            *cptr++ = recpkt[i];
                          *cptr++ = '/';
                          *cptr = '\0';
                          break;
                   default:
                          error("Unemplemented Generic kermit command");
                          break;
                    }
               break;
      case 'N':      /* NAK, ignore */
              break;
      default:
              if (debug)
                   dbprint("Unknown packet type %c\n", pkttype);
              error("Unknown packet type");
      }
   }
}
/*
 *  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(sflg)
int sflg;         /* need to know if comming in from send or serv */
{

    if (sflg)    /* if in send mode then */
    sleep(10);              /* give user a chance to start receive */
    n = 0;                              /* Initialize message number */
    numtry = 0;                         /* Say no tries yet */
    while(TRUE)                         /* Do this as long as necessary */
    {
        if (debug) dbprint("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.
 */

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 */


    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.
 */

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) dbprint("   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 (cnvfilnam)                      /* 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
 */

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.
 */

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) dbprint("   Closing input file %s, ",filnam);
            fclose(fp);                 /* Close the input file */
            fp = NULL;                  /* Set flag indicating no file open */

            if (debug) dbprint("looking for next file...\n");
            if (gnxtfl() == FALSE)      /* No more files go? */
                return('B');            /* if not, break, EOT, all done */
            if (debug) dbprint("   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)
 */

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()
{

    if (state == 'R')
    n = 0;                              /* Initialize message number */
    numtry = 0;                         /* Say no tries yet */

    while(TRUE)
    {
        if (debug) dbprint(" recsw state: %c\n",state);
        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
 */

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




    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
 */

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 */
            if (!tmpfil)          /* fix up to make quick access perm file*/
                  {
                  strcpy(filnam1, working_directory);
                  }
            else
                  filnam1[0] = '\0';
            strcat(filnam1, packet);    /* Copy the file name */
            if (!tmpfil)
           /* file names can only be 8 chars */
                filnam1[(strlen(working_directory) + 8)] = '\0'; 
            else
                filnam1[8] = '\0';
            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 */
            spack('Y',n,0,0);           /* Say OK */
            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
 */

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" */
    }
}

/*
 *      KERMIT utilities.
 */

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

spack(type,num,len,data)
char  *data;
int num, len, type;
{
    int i,chksum;                       /* Character loop counter */
    char  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 */
        dbprint("  spack type: %c\n",type);
        dbprint("         num:  %d\n",num);
        dbprint("         len:  %d\n",len);
        if (data != NULL)
            dbprint("        data: \"%s\"\n",data);
    }

    bufp = buffer;                      /* Set up buffer pointer */
    for (i=1; i<=pad; i++) putchar(padchar); /* 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 */
    *bufp = '\0';                       /* make it a string */
    write(1, buffer, bufp-buffer+1); /* Send the packet */
}

/*
 *  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 */

    while (t != SOH)                    /* Wait for packet header */
    {
        t = getchar();
        t &= 0177;                      /* Handle parity */
    }

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

        t = getchar();               /* Get character */
        if (!image) t &= 0177;          /* Handle parity */
        if (t == SOH) continue;         /* Resynchronize if SOH */
        cchksum = cchksum + t;          /* Update checksum */
        *num = unchar(t);               /* Packet number */




        t = getchar();               /* Get character */
        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 */
            t = getchar();           /* Get character */
            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 */

        t = getchar();               /* Get last character (checksum) */
        rchksum = unchar(t);            /* Convert to numeric */
        t = getchar();               /* get EOL character and toss it */
        if (!image) t &= 0177;          /* Handle parity */
        if (t == SOH) continue;         /* Resynchronize if SOH */
        done = TRUE;                    /* Got checksum, done */
    }


    if (debug>1)                        /* Display incoming packet */
    {
        if (data != NULL)
            data[*len] = '\0';          /* Null-terminate data to print it */
        dbprint("  rpack type: %c\n",type);
        dbprint("         num:  %d\n",*num);
        dbprint("         len:  %d\n",*len);
        if (data != NULL)
            dbprint("        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()
{
 static char savename[60];
    if (filecount-- <= 0)
         return(FALSE);
    if (debug)
         dbprint("    gnxtfl: filecount = %d\n", filecount);
    if (debug) dbprint("   gnxtfl: filelist = \"%s\"\n",*filelist);
    filnam = *(filelist++);
    if (cflg)     /* want cwd processing ? */
        {
        strcpy(savename, working_directory);
        strcat(savename, filnam);
        filnam = savename;
        }
    return(TRUE);
}


/*
 *  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 */
}


/*
 *  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()
{
    printf("Usage:    kermit s[d] file ...          (send mode)\n");
    printf("   or:    kermit r[dt]                  (receive mode)\n");
    printf("   or:    kermit sv                     (server mode)\n");
    printf("   or:    kermit sc file ...            (cwd & send)\n");
    printf("   or:    kermit rc                     (cwd & receive)\n");
    printf("   or:    kermit svc                    (cwd & server)\n");
    exit(1);
}

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

/*VARARGS1*/
printmsg(fmt, a1, a2, a3, a4, a5)
char *fmt;
{
    if (!remote)
    {
        printf("Kermit-GCOS: ");
        printf(fmt,a1,a2,a3,a4,a5);
        printf("\n");
        fflush(stdout);                 /* force output (UTS needs it) */
    }
   if (debug)
       dbprint(fmt, a1, a2, a3, a4, a5);
}




/*
 *  e r r o r
 *
 *  Print error message.
 *
 *  If local, print error message with printmsg.
 *  If remote, send an error packet with the message.
 */
static char prefmt[80] = "Kermit-GCOS:";
/*VARARGS1*/
error(fmt, a1, a2, a3, a4, a5)
char *fmt;
{
    char msg[80];
    int len;
    strcpy(&prefmt[12], fmt);
    if (remote)
    {
        sprintf(msg,prefmt,a1,a2,a3,a4,a5); /* Make it a string */
        len = strlen(msg);
        spack('E',n,len,msg);           /* Send the error packet */
    }
    else
        printmsg(prefmt, a1, a2, a3, a4, a5);

    return;
}

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


dbprint(fmt,arg1,arg2,arg3,arg4,arg5,arg6)
char *fmt;
{
   fprintf(fddebug,fmt,arg1,arg2,arg3,arg4,arg5,arg6);
}
/* Handle tty breaks comming from micro */
dobreak()
{
     fprintf(stderr, "*break*%c\n",007);
     if (debug)
         fclose(fddebug);
     exit();
}
/* the following is a datanet-8 model required for kermit-gcos to run */
/*                                                                    */
/* The TTY BREAK is disabled and a control-Y substituted for a break  */
/* the Backspace is used for character delete and the @ sign is passed*/
/* as data                                                            */
/*
MODEL PCTRAN  ASY -ASCI                                                &
                  -LINE 250                                            &
                  -LTSZ 256                                            &
                  -SY96                                                &
                  -STOP 1:2                                            &
                  -OVSZ 2048                                           &
                  -MCOL X'20'                                          &
                  -MLIN X'20'                                          &
                  -TYPE 23                                             &
                  -FILL X'00' 0                                        &
                  -BRK ATT1                                            &
                  -ATT0 X'17'                                          &
                  -ATT1 X'19'                                          &
                  -ATT2 X'1A'                                          &
-INEFF  X'00' IGN   0     0     *               *                      &
-INEFF  X'02' FUN   0     0     *               *                      &
-CR     X'03' FUN   0     0     X'0A':X'0D' X'0A':X'0D'                &
-MTROFF X'04' FUN   3     0     *               X'04'                  &
-INEFF  X'05' IGN   0     0     *               *                      &
-INEFF  X'06' IGN   0     0     *               *                      &
-INEFF  X'07' NOR   0     0     *               *                      &
-BAKSP  X'08' ANN   0     0     *               *                      &
-HT     X'09' NOR   0     0     *               *                      &
-LF     X'0A' FUN   0     0     X'0D'           X'0D':X'0A'            &
-VT     X'0B' FUN   0     0     *               *                      &
-CR     X'0D' FUN   0     0     X'0D':X'0A'     X'0D':X'0A'            &
-INEFF  X'0E' NOR   0     0     *               *                      &
-INEFF  X'0F' NOR   0     0     *               *                      &
-RPTL   X'10' FUN   0     0     *               *                      &
-INEFF  X'11' NOR   0     0     *               *                      &
-INEFF  X'12' NOR   0     0     *               *                      &
-INEFF  X'13' NOR   0     0     *               *                      &
-INEFF  X'14' NOR   0     0     *               *                      &
-ETX    X'15' FUN   0     0     *               *                      &
-INEFF  X'16' FUN   0     0     *               *                      &
-EFLIN  X'18' FUN   0     0     X'20':X'44':X'45':X'4C':X'0D':X'0A'    &
-INEFF  X'1B' NOR   0     0     *               *                      &
-INEFF  X'1C' FUN   0     0     *               *                      &
-INEFF  X'1D' FUN   0     0     *               *                      &
-INEFF  X'1E' FUN   0     0     *               *                      &
-INEFF  X'1F' FUN   0     0     *               *                      &
 *
 */
/* If the site has NPS, it must be configured to allow SOH (001),*
 * and all printable characters to pass thru as data.  It is     *
 * a good idea to configure the control-Y key for a break key.   *
 */
