/*************************************************************
 * vt100 terminal emulator - KERMIT protocol support
 *           
 *	     860823 DBW - Integrated and rewrote lots of code
 *           860811 Steve Drew multi filexfer, bugs, status line ect..
 *	v2.0 860809 DBW - Major rewrite
 *	v1.1 860720 DBW	- Switches, 80 cols, colors, bug fixes
 *	v1.0 860712 DBW	- First version released
 *
 *************************************************************/

#define MODULE_KERMIT 1
#include "vt100.h"

#define MAXPACKSIZ 94       /* Maximum msgpkt size */
#define CR         13       /* ASCII Carriage Return */
#define LF         10       /* ASCII line feed */
#define SP         32       /* ASCII space */
#define DEL       127       /* Delete (rubout) */

#define MAXTRY    5        /* Times to retry a msgpkt */
#define MYQUOTE  '#'       /* Quote character I will use */
#define MYRPTQ   '~'       /* Repeat quote character */
#define MYPAD      0       /* Number of padding characters I will need */
#define MYPCHAR    0       /* Padding character I need (NULL) */
#define MYEOL    '\n'      /* End-Of-Line character I need */

#define tochar(ch)  ((ch) + ' ')
#define unchar(ch)  ((ch) - ' ')
#define ctl(ch)     ((ch) ^ 64 )

/* Global Variables */

int
   size,      /* Size of present data */
   osize,     /* Size of last data entry */
   rpsiz,     /* Maximum receive msgpkt size */
   spsiz,     /* Maximum send msgpkt size */
   timint,    /* Time interval to wait */
   pad,       /* How much padding to send */
   n,         /* Packet number */
   tp,        /* total packets */
   numtry,    /* Times this msgpkt retried */
   retry,     /* total retries */
   oldtry,    /* Times previous msgpkt retried */
   rptflg,    /* are we doing repeat quoting */
   first,     /* is this the first time in a file */
   rpt,       /* current repeat count */
   next,      /* what is the next character */
   t;         /* current character */
long
   totbytes;  /* total bytes xfered on this file */

char 
   state,     /* Present state of the automaton */
   padchar,   /* Padding character to send */
   eol,       /* End-Of-Line character to send */
   quote,     /* Quote character in incoming data */
   rptq,      /* Quote character for repeats */
   ackpkt[MAXPACKSIZ+20], /* ACK/NAK packet buffer */
   msgpkt[MAXPACKSIZ+20], /* Message Packet buffer */
   filnam[40];            /* remote file name */
   snum[10];

void encode(), decode(), rpar(), spar();
   
FILE *fp;     /* file for send/receive */

int shutdown = 0;  /* shut down server after all xfers complete */

char *
getfname(name)   /* returns pointer to start of file name from file spec */
char *name;
    {
    int l;

    l = strlen(name);
    while(l && name[l] != '/' && name[l] != ':') l--;
    if (name[l] == '/' || name[l] == ':') l++;
    return(name += l);
    }
    
doksend(file,more)
char *file;
int more;
   {
   int retval;

   ttime = TTIME_LONG;
   if (!strcmp(file,"$")) { saybye(); return(2); }
   if (file[strlen(file)-1] == '$')  { 
             shutdown = 1; 
             file[strlen(file)-1] = '\0'; 
             }
   if ((fp = fopen(file,"r")) == NULL) {
      emits("Cannot open send file\n");
      return FALSE;
      }
   getready(file,more);
   emits("SEND");
   retval  = sendsw();
   if (retval) { x = 600; emits("DONE"); }
   curmode = 0;
   if (shutdown) saybye();
   emits("\n");
   fclose(fp);
   return(retval);
   }
 
dokreceive(file,more)
char *file;
int more;
   {
   int retval;

   ttime = TTIME_LONG;
   if (!strcmp(file,"$")) { saybye(); return(2); }
   if (file[strlen(file)-1] == '$')  { 
             shutdown = 1; 
             file[strlen(file)-1] = '\0'; 
             }
   if ((fp = fopen(file,"w")) == NULL) {
      emits("Cannot open file\n");
      return FALSE;
      }
   getready(file,more);
   emits("RECV");
   retval  = recsw();
   if (retval) { x = 600; emits("DONE"); }
   curmode = 0;
   if (shutdown) saybye();
   emits("\n");
   fclose(fp);
   return(retval);
   }

getready(file,more)
   char *file;
   int more;
   {
   if (!more) {   
       emits("Remote file name [");
       emits(getfname(file));
       emits("]: ");
       filename(filnam);
       if (filnam[0] == 0) strcpy(filnam,getfname(file));
       emits("Type <ESC> to abort transfer\n");
       }
   else strcpy(filnam,getfname(file));
   eatup();
   tp =  retry =  0; totbytes = 0L;  n =  numtry = 0;
   want_message = FALSE; /* tell readchar no error msgs status bar instead */
   statusline();
   x = 600;
   }


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

   state = 'S';
   while(TRUE) {
      switch(state) {
         case 'S':   state = sinit();  break;
         case 'F':   state = sfile();  break;
         case 'D':   state = sdata();  break;
         case 'Z':   state = seof();   break;
         case 'B':   state = sbreak(); break;
         case 'C':   return(TRUE);
         case 'A':   x = 600;
                     if (timeout == USERABORT) { sabort();  return(FALSE); }
                     if (timeout == TIMEOUT) emits("TMOUT");
                     else emits("ERROR");
                     return(FALSE);
         default:    return(FALSE);
         }
      }
   }

char sinit()
   {
   int num, len;
   
   retry++;
   if (numtry++ > MAXTRY) return('A');
   spar(msgpkt);

   spack('S',n,9,msgpkt);
   switch(rpack(&len,&num,ackpkt)) {
      case 'N':  return(state);
      case 'Y':  if (n != num) return(state);
                 rpar(ackpkt);
                 if (eol == 0) eol = '\n';
                 if (quote == 0) quote = '#';
                 numtry = 0;
                 retry--;
                 n = (n+1)%64;
                 return('F');
      case 'E':  return('A');
      case FALSE:if (timeout == USERABORT) state = 'A';
                 return(state);
      default:   return('A');
      }
    }

char sfile()
   {
   int num, len;

   retry++;
   if (numtry++ > MAXTRY) return('A');

   spack('F',n,strlen(filnam),filnam);
   switch(rpack(&len,&num,ackpkt)) {
      case 'N':
         num = (--num<0 ? 63:num);
         if (n != num) return(state);
      case 'Y':
         if (n != num) return(state);
         numtry = 0;
         retry--;
         n = (n+1)%64;
         first = 1;
         size = getpkt();
         return('D');
      case 'E':
         return('A');
      case FALSE: if (timeout == USERABORT) state = 'A';
                  return(state);
      default:    return('A');
      }
   }

char sdata()
   {
   int num, len;
   
   retry++;
   if (numtry++ > MAXTRY) return('A');

   spack('D',n,size,msgpkt);
   switch(rpack(&len,&num,ackpkt)) {
      case 'N':
         num = (--num<0 ? 63:num);
         if (n != num) return(state);
      case 'Y':
         if (n != num) return(state);
         numtry = 0;
         retry--;
         n = (n+1)%64;
         if ((size = getpkt()) == 0) return('Z');
         return('D');
      case 'E':
         return('A');
      case FALSE: if (timeout == USERABORT) state = 'A';
                  return(state);
      default:    return('A');
      }
   }

char seof()
   {
   int num, len;
   retry++;
   if (numtry++ > MAXTRY) return('A');

   if (timeout == USERABORT) { 
        timeout = GOODREAD;
        spack('Z',n,1,"D"); /* tell host to discard file */
        }
   else spack('Z',n,0,msgpkt);
   switch(rpack(&len,&num,ackpkt)) {
      case 'N':
         num = (--num<0 ? 63:num);
         if (n != num) return(state);
      case 'Y':
         if (n != num) return(state);
         numtry = 0;
         retry--;
         n = (n+1)%64;
         return('B');
      case 'E':
         return('A');
      case FALSE: return(state);
      default:    return('A');
      }
   }

char sbreak()
   {
   int num, len;
   retry++;
   if (numtry++ > MAXTRY) return('A');

   spack('B',n,0,msgpkt);
   switch (rpack(&len,&num,ackpkt)) {
      case 'N':
         num = (--num<0 ? 63:num);
         if (n != num) return(state);
      case 'Y':
         if (n != num) return(state);
         numtry = 0;
	 retry--;
         n = (n+1)%64;
         return('C');
      case 'E':
         return('A');
      case FALSE: return(state);
      default:    return ('A');
      }
   }

/* timeout equals USERABORT so lets end the file and quit  */
/* when host receives 'Z' packet with "D" in data field he */
/* should discard the file.                                */

sabort()
   {
   emits("ABORT");
   n = (n+1)%64;
   retry--;
   state = 'Z';
   while (state == 'Z') state = seof();
   while (state == 'B') state = sbreak();
   return(FALSE);
   }
   
recsw()
   {
   char rinit(), rfile(), rdata(), rbreak();

   state = 'R';
   
   while(TRUE) {
      switch(state) {
         case 'R':   state = rinit(); break;
         case 'F':   state = rfile(); break;
         case 'D':   state = rdata(); break;
	 case 'Z':   state = rbreak(); break;
         case 'C':   return(TRUE);
         case 'A':   x = 600;
                     if (timeout == USERABORT){/* easy way to cleanly abort 
                                                  should really send and ACK
                                                  with "X" in data field and 
                                                  wait for host to abort but 
                                                  not all kermits support
                                                  this feature.           */
                         emits("ABORT");
                         spack('E',n,0,0); /* send an error packet back   */
                     }
                     else if (timeout == TIMEOUT) emits("TMOUT");
                     else emits("ERROR");           
                     return(FALSE);
         }
      }
   }

char rinit()
   {
   int len, num;
   retry++;
   if (numtry++ > MAXTRY) return('A');

   if (server) spack('R',n,strlen(filnam),filnam);
   else  spack('N',n,0,0);
   switch(rpack(&len,&num,msgpkt)) {
      case 'S':
         rpar(msgpkt);
         spar(msgpkt);
         spack('Y',n,9,msgpkt);
         oldtry = numtry;
         numtry = 0;
         retry--;
         n = (n+1)%64;
         return('F');
      case 'E':
         return('A');
      case FALSE:
         if (timeout == USERABORT) return('A');
         spack('N',n,0,0);
         return(state);
      default:
         return('A');
      }
   }

char rfile()
   {
   int num, len;
   retry++;
   if (numtry++ > MAXTRY) return('A');

   switch(rpack(&len,&num,msgpkt)) {
      case 'S':
         if (oldtry++ > MAXTRY) return('A');
         if (num == ((n==0) ? 63:n-1)) {
            spar(msgpkt);
            spack('Y',num,9,msgpkt);
            retry--;
            numtry = 0;
            return(state);
            }
         else return('A');
      case 'Z':
         if (oldtry++ > MAXTRY) return('A');
         if (num == ((n==0) ? 63:n-1)) {
            spack('Y',num,0,0);
            numtry = 0;
            retry--;
            return(state);
            }
         else return('A');
      case 'F':
         if (num != n) return('A');
         spack('Y',n,0,0);
         oldtry = numtry;
         numtry = 0;
         retry--;
         n = (n+1)%64;
         return('D');
      case 'B':
         if (num != n) return ('A');
         spack('Y',n,0,0);
         retry--;
         return('C');
      case 'E':
         return('A');
      case FALSE:
         if (timeout == USERABORT) return('A');
         spack('N',n,0,0);
         return(state);
      default:
         return ('A');
      }
   }

char rdata()
   {
   int num, len;
   retry++;
   if (numtry++ > MAXTRY) return('A');

   switch(rpack(&len,&num,msgpkt)) {
      case 'D':
         if (num != n) {
            if (oldtry++ > MAXTRY) return('A');
            if (num == ((n==0) ? 63:n-1)) {
               spack('Y',num,6,msgpkt);
               retry--;
               numtry = 0;
               return(state);
               }
            else return('A');
            }
         decode();
         spack('Y',n,0,0);
         oldtry = numtry;
         numtry = 0;
         retry--;
         n = (n+1)%64;
         return('D');
      case 'F':
         if (oldtry++ > MAXTRY) return('A');
         if (num == ((n==0) ? 63:n-1)) {
            spack('Y',num,0,0);
            numtry = 0;
            retry--;
            return(state);
            }
         else return('A');
      case 'Z':
         if (num != n) return('A');
         spack('Y',n,0,0);
         n = (n+1)%64;
         retry--;
         return('Z');
      case 'E':
         return('A');
      case FALSE:
         if (timeout == USERABORT) return('A');
         spack('N',n,0,0);
         return(state);
      default:
        return('A');
      }
   }

char rbreak()
   { 
   int num, len;
   retry++;
   if (numtry++ > MAXTRY) return('A');

   switch(rpack(&len,&num,msgpkt)) {
      case 'B':
         spack('Y',n,0,0);
         return('C');
      case 'Z':
         spack('Y',n,0,0);
         return('Z');
      case 'E':
         return('A');
      case FALSE:
         spack('N',n,0,0);
         return(state);
      default:
        return('A');
      }
   }

spack(type,num,len,data)
char type, *data;
int num, len;
   {
   int i;
   char chksum, buffer[100];
   register char *bufp;
   
   dostats(type);
   bufp = buffer;
   for (i=1; i<=pad; i++) sendchar(padchar);

   *bufp++ = SOH;
   *bufp++ = tochar(len+3);
   chksum  = tochar(len+3);
   *bufp++ = tochar(num);
   chksum += tochar(num);
   *bufp++ = type;
   chksum += type;

   for (i=0; i<len; i++) {
      *bufp++ = data[i];
      chksum += data[i];
      }
   chksum = (((chksum&0300) >> 6)+chksum)&077;
   *bufp++ = tochar(chksum);
   *bufp++ = '\r';
   *bufp++ = '\n';
   *bufp   = 0;
   sendstring(buffer);
   }

rpack(len,num,data)
int *len, *num;
char *data;
   {
   int i, done;
   char type, cchksum, rchksum;
   char t = '\0';

    do {
       t = readchar();
       if (timeout != GOODREAD) return(FALSE);
       } while (t != SOH);

    done = FALSE;
    while (!done) {
       t = readchar();
       if (timeout != GOODREAD) return(FALSE);
       if (t == SOH) continue;
       cchksum = t;
       *len = unchar(t)-3;
       t = readchar();
       if (timeout != GOODREAD) return(FALSE);
       if (t == SOH) continue;
       cchksum = cchksum + t;
       *num = unchar(t);
       t = readchar();
       if (timeout != GOODREAD) return(FALSE);
       if (t == SOH) continue;
       cchksum = cchksum + t;
       type = t;
       for (i=0; i<*len; i++) {
          t = readchar();
          if (timeout != GOODREAD) return(FALSE);
          if (t == SOH) continue;
          cchksum = cchksum + t;
          data[i] = t;
          }
       data[*len] = 0;
       t = readchar();
       if (timeout != GOODREAD) return(FALSE);
       rchksum = unchar(t);
       t = readchar();
       if (timeout != GOODREAD) return(FALSE);
       if (t == SOH) continue;
       done = TRUE;
       }
   dostats(type);
   cchksum = (((cchksum&0300) >> 6)+cchksum)&077;
   if (cchksum != rchksum) return(FALSE);
   return((int)type);
   }

getpkt() {
   int i,eof;

   static char leftover[10] = { '\0', '\0', '\0', '\0', '\0',
			        '\0', '\0', '\0', '\0', '\0' };

   if (first == 1) {
      first = 0;
      *leftover = '\0';
      t = getc(fp);
      if (t == EOF) {
         first = 1;
         return(size = 0);
         }
      totbytes++;
      }
   else if (first == -1) {
      first = 1;
      return(size = 0);
      }
   for (size = 0; (msgpkt[size] = leftover[size]) != '\0'; size++) ;
   *leftover = '\0';
   rpt = 0;
   eof = 0;
   while (!eof) {
      next = getc(fp);
      if (next == EOF) {
         first = -1;
         eof   =  1;
         }
      else totbytes++;
      osize = size;
      encode(t);
      t = next;
      if (size == spsiz-3) return(size);
      if (size > spsiz-3) {
         for (i = 0; (leftover[i] = msgpkt[osize+i]) != '\0'; i++) ;
         size = osize;
         msgpkt[size] = '\0';
         return(size);
         }
      }
   return(size);
   }

void encode(a)
char a;
   {
   int a7,b8;

   if (p_mode == 1 && a == '\n') {
      rpt = 0;
      msgpkt[size++] = quote;
      msgpkt[size++] = ctl('\r');
      if (size <= spsiz-3) osize = size;
      msgpkt[size++] = quote;
      msgpkt[size++] = ctl('\n');
      msgpkt[size]   = '\0';
      return;
      }
   if (rptflg) {
      if (a == next && (first == 0)) {
         if (++rpt < 94) return;
         else if (rpt == 94) {
            msgpkt[size++] = rptq;
            msgpkt[size++] = tochar(rpt);
            rpt = 0;
            }
         }
      else if (rpt == 1) {
         rpt = 0;
         encode(a);
         if (size <= spsiz-3) osize = size; 
         rpt = 0;
         encode(a);
         return;
         }
      else if (rpt > 1) {
         msgpkt[size++] = rptq;
         msgpkt[size++] = tochar(++rpt);
         rpt = 0;
         }
      }
   a7 = a & 0177;
   b8 = a & 0200;
   if ((a7 < SP) || (a7==DEL)) {
      msgpkt[size++] = quote;
      a = ctl(a);
      }
   if (a7 == quote) msgpkt[size++] = quote;
   if ((rptflg) && (a7 == rptq)) msgpkt[size++] = quote;
   msgpkt[size++] = a;
   msgpkt[size] = '\0';
   }

void decode()
   {
   USHORT  a, a7;
   char *buf;

   buf = msgpkt;
   rpt = 0;
   
   while ((a = *buf++) != '\0') {
      if (rptflg) {
         if (a == rptq) {
            rpt = unchar(*buf++);
            a = *buf++;
            }
         }
      if (a == quote) {
         a  = *buf++;
         a7 = a & 0177;
         if ((a7 >= 0100 && a7 <= 0137) || a7 == '?') a = ctl(a);
         }
      if (rpt == 0) rpt = 1;
      if (p_mode == 1 && a == '\r') continue;
      totbytes += rpt;
      for (; rpt > 0; rpt--) putc(a, fp);
      }
   return;
   }

void spar(data)
char data[];
   {
   data[0] = tochar(MAXPACKSIZ);
   data[1] = tochar(TTIME_LONG);
   data[2] = tochar(MYPAD);
   data[3] = ctl(MYPCHAR);
   data[4] = tochar(MYEOL);
   data[5] = MYQUOTE;
   data[6] = 'N';
   data[7] = '1';
   data[8] = MYRPTQ;
   data[9] = '\0';
   }

void rpar(data)
char data[];
   {
   spsiz   = unchar(data[0]);
   timint  = unchar(data[1]);
   pad     = unchar(data[2]);
   padchar = ctl(data[3]);
   eol     = unchar(data[4]);
   quote   = data[5];
   rptflg  = 0;
   if (data[6] == 0) return;
   if (data[7] == 0) return;
   if (data[8] == 0) return;
   rptq    = data[8];
   rptflg  = ((rptq > 040 && rptq < 0100) || (rptq > 0140 && rptq < 0177));
   }

saybye()
  {
  int len,num;
  shutdown = 0;
  spack('G',n,1,"F");  /* shut down server no more files */
  rpack(&len,&num,ackpkt);
  }

statusline()
  {
  emits ("\nFile:                 Pckt:   Pckt No:      Retrn:    Bytes:         Stat:      ");
  x = 48;
  curmode = 1;
  emits (filnam);
  return(0);
  }

dostats(type)
char type;
  {
   if (type != 'Y' && type != 'N' && type != 'G') {
      x = 224;
      emit(type);
      x = 312;
      sprintf(snum,"%4d",n+(tp * 64));
      emits(snum);
      if (n==63) tp++;
      x = 408;
      sprintf(snum,"%2d",retry-1);
      emits(snum);
      x = 488;
      sprintf(snum,"%6ld",(long)totbytes);
      emits(snum);
      }
  }

/* allow for multi file xfers separated by commas under kermit and XMODEM */

multi_xfer(name,mode,do_send)
char *name;
int (*mode)();
int do_send;
    {
    int done = 0;
    int status;
    char *p;
    
    p = name;  
    while(*p && *p != ',')   p++;
    if (*p == '\0') { 
         done = TRUE; 
         if (multi == 1) multi++;
         }
     else multi = 1;
    *p = '\0';
    status = ((*mode)(name, multi));
    if (status == TRUE) {
        if (do_send) emits("Sent File: ");
          else emits("Received File: ");
    	emits(name);
    	emits("\n");
    	emit(8);
        }
    else if (status == FALSE)
        {
        close(fd);
        if (do_send) emits("Send Failed: ");
          else emits("Receive Failed: ");
        emits(name);
        emits("\n");	
        emit(8);
        }    
    if (!done) multi_xfer(++p, mode, do_send);
    server = 0;
    multi = 0;
    }

/* gobble up all garb that we received while getting file name strings */

eatup()
   {
  while(CheckIO(Read_Request))  {
     	WaitIO(Read_Request);
     	BeginIO(Read_Request);
        }
   }   

