/* * ================================== * K e r m i t File Transfer Utility * ================================== * * Derived from: 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 * * * This version of KERMIT was put together from the C-language example * which was given in the back of the "KERMIT PROTOCOL MANUAL", Fourth * Edition (revision 1, 4-Nov-83) which included the changes dated * 17-Oct-83 by Alan Crosswell (CUCCA) for the IBM_UTS flavor. * * This version is a cleaned up subset of the above release. Only * the file transfer operations are implemented, as the remote terminal * emulator has been implemented as a separate process. Much has been * done to make porting this code to different environments easier, * although there is some DEC-specific stuff still here, most notably * the stripping of filespecs to KERMIT "normal form". * * The 'f' flag now controls whether or not file specifications are * converted to KERMIT "normal form". The assumed EOL character is * the usual (15 octal). The 'i' flag puts us into image * mode (no newline to conversions & parity stripping). * * Added wildcarding on send operations. * * Also, CONNECT has been removed. The CONNECT function is provided * by a separate task. This task and the connect one will eventually * be spawned from a controlling parent which handles the user interface * menu-moron style. * * Finally, several changes were made for use with the DECUS C compiler. * The macros were removed and checksum variables were changed to unsigned * integers (safer anyway). * * By: * Bob Denny * Alisa Systems, Inc. * Pasadena, CA * 06-Jan-84 * * Modification history: * * 08-Jan-83 RBD bufill() - Fix so naked 's are not eaten in * ASCII mode. Add V4.0 fields to SEND-INIT packet. * 09-Jan-83 RBD Clean up this file so system-dependencies are * all in KERPOSIO.C. * 12-Jan-83 RBD Fix truncation of file type on incoming files * when converting to normal form. Add more symbolic * hooks: sleep, user exit, report file transfer * progress and symbolic exit status codes. Add * more intelligent handling of "A" states, reporting * the reason for the abort. Add send delay (30 sec). * Add server operation (GET, FINISH & LOGOUT). Change * names of init and restore generics as they are * responsible for all system-specific initialization. * */ /* * ******************* * ** W A R N I N G ** * ******************* * * The "Init-Info" server initialization transaction * called for in the Protocol V4 is temporarily disabled * here; the Kermit-32 we got had the support for 'I' * packets commented out ... To re-enable server init * protocol, remove the following #define: */ #define NOIPACKETS /************/ #define POS /************/ #ifdef POS #define RMS_11 #endif #ifdef RMS_11 #include /* RMS-flavor standard I/O */ #else #include /* Vanilla standard I/O */ #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 MAXTRY 10 /* Times to retry a packet */ #define MYQUOTE '#' /* Quote character I will use */ #define MYEOL '\r' /* I need a to terminate receives */ #define MYQBIN 'N' /* I don't quote binary values */ #define MYCHKT '1' /* I do basic single-character checksum */ #define MYREPT ' ' /* I don't do run-length encoding */ /* * System specific definitions */ #ifdef POS #define MYDELAY 30 /* Delay before send-init (remote) */ #define MYTIME 10 /* Seconds after which I should be timed out */ #define MYPAD 0 /* Number of padding characters I will need */ #define MYPCHAR 0 /* Padding character I need (NULL) */ #define MYCAPAS1 10 /* I have timeout capabilities only */ #define MAXTIM 60 /* Maximum timeout interval */ #define MINTIM 2 /* Minumum timeout interval */ #define DEF_BAUD 1200 /* Default baud rate */ #define COMM_DEV NULL /* NULL comm device name - hardwired for POS */ #endif /* * Global Variables */ unsigned 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 */ chkt, /* Checksum type */ capas1, /* Capabilities mask 1 */ n, /* Packet number */ numtry, /* Times this packet retried */ oldtry, /* Times previous packet retried */ remote, /* TRUE means we're a remote kermit */ image, /* TRUE means 8-bit image mode (no newline hacking) */ debug, /* indicates level of debugging output (0=none) */ stripfs, /* TRUE to strip filespecs to "normal form" */ exstat, /* Our exit status */ filecount, /* Number of files left to send */ aboflag; /* Async user abort sets this to TRUE */ char state, /* Present state of the automaton */ padchar, /* Padding character to send */ eol, /* End-Of-Line character to send */ qbin, /* Binary quoting character */ rept, /* Run-length encoding character */ quote; /* Control-quote character in incoming data */ char **filelist; /* List of --> to files to be sent */ char *commdev; /* ASCII device spec of communication line */ char filpat[32]; /* Current (wild) filespec pattern from list */ char filnam[64]; /* Current file name */ char recpkt[MAXPACKSIZ], /* Receive packet buffer */ packet[MAXPACKSIZ]; /* Packet buffer */ FILE *fp; /* File pointer for current disk file */ /* * Error message formats */ char *retfmt = "<%s> - Retry limit reached"; char *badpkt = "<%s> - '%c' packet out of sequence"; char *unkpkt = "<%s> - Illegal or unknown packet ('%c') received"; extern int $$ferr; /* * The following definitions map the system-specific functions * and status values. */ #ifdef POS #include #define SYS$INITIAL xk_set #define SYS$RESTORE xk_rst #define SYS$XMIT xk_wrt #define SYS$RCHAR xk_rch #define SYS$IFLUSH xk_flu #define SYS$GLFILES ps_gfil #define SYS$GRFILES ps_xfil #define SYS$PRMSG ps_pmsg #define SYS$ARMABORT ps_arm #define SYS$CANABORT ps_can #define SYS$USAGE ps_usage #define SYS$SLEEP sleep #define SYS$EXIT exits #define SYS$SUCC EX$SUC #define SYS$WARN EX$WAR #define SYS$ERR EX$ERR #define SYS$FATL EX$SEV #endif /* * m a i n * * Main routine - parse command and options, set up the * tty line, and dispatch to the appropriate routine. */ main(argc,argv) int argc; /* Character pointers to and count of */ char **argv; /* command line arguments */ { char *cp; /* char pointer */ int speed, /* speed of assigned tty, */ rflg, /* Operation: Receive */ sflg, /* Send */ gflg, /* Get (server rec.) */ glflg, /* Logout */ gfflg; /* Finish */ char tolower(); exstat = SYS$SUCC; /* Assume we'll succeed */ if (argc < 2) SYS$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 */ /* * 'Parse' (I abhor that word!) the command line & set * various flags. */ sflg = rflg = gflg = glflg = gfflg = 0; /* Turn off all parse flags */ speed = DEF_BAUD; /* Set default baud rate */ image = FALSE; /* ASCII mode by default */ stripfs =TRUE; /* Strip filespecs to "normal" form */ if(COMM_DEV == NULL) /* If hardwired comm line, then ... */ remote = FALSE; /* ... we cannot act as a remote */ else /* Otherwise ... */ remote = TRUE; /* Assume remote unless 'l' arg */ while ((*cp) != NULL) /* Parse characters in first arg. */ switch (tolower(*cp++)) { case 's': sflg++; break; /* S = Send command */ case 'r': rflg++; break; /* R = Receive command */ case 'g': gflg++; break; /* G = Get command (server rec.) */ case 'q': glflg++; break; /* Q = Generic Logout (server) */ case 'e': gfflg++; break; /* E = Generic finish (server) */ case 'd': /* D = Increment debug mode count */ debug++; break; case 'f': stripfs = FALSE; /* F = don't strip filespecs */ break; case 'i': image = TRUE; /* I = image mode */ break; case 'b': /* B = specify baud rate */ if (argc--) speed = atoi(*argv++); else SYS$USAGE(); if (debug) printf("Line speed to remote host is %d\n",speed); break; case 'l': /* L = Specify comm device spec */ if (argc--) commdev = *argv++; else SYS$USAGE(); remote = FALSE; /* We are in local mode */ if (debug) printf("Line to remote host is %s\n", commdev); break; default: /* Unknown option */ SYS$USAGE(); } /* Done parsing */ if ((sflg+rflg+gflg+glflg+gfflg) != 1) /* Only one command allowed */ SYS$USAGE(); /* * Set up the Communications Port as required by KERMIT * (and specified baud rate) after saving the current state. */ SYS$INITIAL(speed,commdev); /* * All set up, now execute the command that was given. */ if (debug) { printf("Debugging level = %d\n\n",debug); if (sflg) printf("Send command\n\n"); if (rflg) printf("Receive command\n\n"); if (gflg) printf("Get command\n\n"); if (glflg) printf("Generic logout\n\n"); if (gfflg) printf("Generic finish\n\n"); } if (sflg) /* Send command */ { filelist = argv; /* Set up the file list */ filecount = argc; /* Number of files send */ if(!gnxtfl()) /* Get 1st. If none ... */ { if (remote) /* Restore tty line if remote */ SYS$RESTORE(); SYS$GLFILES(); /* Prompt user for local file list */ if (remote) /* If remote, put tty back to raw */ SYS$INITIAL(); } if (sendsw() == FALSE) /* Send the file(s) */ { SYS$PRMSG("Send failed."); exstat = SYS$ERR; } } else if (rflg) /* Receive command */ { if (recsw() == FALSE) /* Receive the file(s) */ { SYS$PRMSG("Receive failed."); exstat = SYS$ERR; } } else if (gflg) /* Get (server) command */ { filelist = argv; /* Set up the file list */ filecount = argc; /* Number of files send */ if(filecount == 0) /* If none ... */ { if (remote) /* Restore tty line if remote */ SYS$RESTORE(); SYS$GRFILES(); /* Prompt user for remote file list */ if (remote) /* If remote, put tty back to raw */ SYS$INITIAL(); } strcpy(filnam, *filelist++); /* Get 1st file */ filecount--; if (getsw() == FALSE) /* Get the file(s) */ { SYS$PRMSG("Get failed."); exstat = SYS$ERR; } } else if(gfflg) /* Generic finish */ { if(gfinish() == FALSE) /* If failed */ { SYS$PRMSG("Failed to stop server."); exstat = SYS$ERR; } else SYS$PRMSG("Server KERMIT returned to command mode"); } else if(glflg) { if(glogout() == FALSE) /* If failed */ { SYS$PRMSG("Failed to log out server."); exstat = SYS$ERR; } else SYS$PRMSG("Server KERMIT logged off."); } SYS$RESTORE(); SYS$EXIT(exstat); } /* * 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. If in remote mode, * delays MYDELAY seconds before initiating. * */ sendsw() { char sinit(), sfile(), sdata(), seof(), sbreak(); if(remote) /* If we're a remote */ { SYS$SLEEP(MYDELAY); /* Delay for user to set up receive */ aboflag = FALSE; /* No aborts in remote mode */ } else /* If local */ SYS$ARMABO(&aboflag); /* Arm local user async abort */ 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(aboflag) /* If local user aborted */ { SYS$PRMSG("User aborted file transfer"); switch(state) { case 'S': state = 'A'; break; case 'F': state = 'A'; break; case 'D': state = 'Z'; break; } } 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': SYS$CANABO(); return (TRUE); /* Complete */ case 'A': SYS$CANABO(); return (FALSE); /* "Abort" */ default: SYS$CANABO(); 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 */ char ty; /* Received packet type */ char rpack(); if (numtry++ > MAXTRY) /* If too many tries, give up */ { SYS$PRMSG(retfmt, "sinit"); /* Say why */ return('A'); } spar(packet); /* Fill up init info packet */ SYS$IFLUSH(); /* Flush pending input */ spack('S',n,10,packet); /* Send an S packet */ switch(ty = 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 = MYEOL; /* 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: /* Anything else, just "abort" */ SYS$PRMSG(unkpkt, "sinit", ty); return('A'); } } /* * s f i l e * * Send File Header. */ char sfile() { int num, len; /* Packet number, length */ char filnam1[64], /* Converted file name */ *newfname, /* Pointer to file name to send */ *cp; /* char pointer */ char ty; /* Received packet type */ char rpack(); if (numtry++ > MAXTRY) /* If too many tries, give up */ { SYS$PRMSG(retfmt, "sfile"); /* Say why */ return('A'); } if (filnam[0] == NULL) /* If 1st wild open not done */ { if (fp == NULL || fnext(fp) == NULL) /* If bad spec or no wild files */ { error("Cannot open file \"%s\" RMS-11 code = %d", filpat, $$ferr); return('A'); } fgetname(fp, filnam); /* New filespec, get (1st) name */ } /* * If the 'stripfs' flag is set, convert the filename to KERMIT * "normal form" (at least take a good crack at it!). */ if(stripfs) { strcpy(filnam1, filnam); /* Make a copy of the file spec */ if(cp = strrchr(filnam1, ';')) /* Strip version number */ *cp = '\0'; if(!(newfname = strrchr(filnam1, ']'))) /* Bypass NODE::DEV:[DIR] */ { if(!(newfname = strrchr(filnam1, ':'))) newfname = filnam1; else newfname++; } else newfname++; if(strchr(filnam1, '.') == NULL) /* If no '.' */ strcat(filnam1, '.'); /* Tack one on */ } else newfname = filnam; SYS$PRMSG("Sending %s as %s",filnam,newfname); len = strlen(newfname); spack('F',n,len,newfname); /* Send an F packet */ switch(ty = 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: /* Something else, just "abort" */ SYS$PRMSG(unkpkt, "sfile", ty); return('A'); } } /* * s d a t a * * Send File Data */ char sdata() { int num, len; /* Packet number, length */ char ty; /* Received packet type */ if (numtry++ > MAXTRY) /* If too many tries, give up */ { SYS$PRMSG(retfmt, "sdata"); /* Say why */ return('A'); } spack('D',n,size,packet); /* Send a D packet */ switch(ty = 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: /* Anything else, "abort" */ SYS$PRMSG(unkpkt, "sdata", ty); return('A'); } } /* * s e o f * * Send End-Of-File. * * If the user-abort flag is on, stick a 'D' into the data * field, telling the remote to junk the just sent file, * and skip the rest of the file list, going directly to * state 'B'. */ char seof() { int num, len; /* Packet number, length */ char ty; /* Received packet type */ if (numtry++ > MAXTRY) /* If too many tries, give up */ { SYS$PRMSG(retfmt, "seof"); /* Say why */ return('A'); } if(aboflag) /* User abort? */ { packet[0] = 'D'; /* Yes, tell remote to junk file */ len = 1; } else len = 0; spack('Z',n,len,packet); /* Send a 'Z' packet */ switch(ty = 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); if(aboflag) /* If aborting */ return('B'); /* Skip rest of files */ /* * Attempt to open the "next" file in a wild sequence, * or if failed, get the next filespec in the list & * try again. */ if(debug) printf(" Looking for next file ...\n"); if(fnext(fp) == NULL) /* If no more wild matches */ { if(gnxtfl() == FALSE) /* If no more specs in list */ return('B'); /* Break, EOT, all done */ } else fgetname(fp, filnam); /* Next wild file is ready */ 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: /* Something else, "abort" */ SYS$PRMSG(unkpkt, "seof", ty); return('A'); } } /* * s b r e a k * * Send Break (EOT) */ char sbreak() { int num, len; /* Packet number, length */ char ty; /* Received packet type */ char rpack(); if (numtry++ > MAXTRY) /* If too many tries, give up */ { SYS$PRMSG(retfmt, "sbreak"); /* Say why */ return('A'); } spack('B',n,0,packet); /* Send a B packet */ switch (ty = 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: /* Something else, "abort" */ SYS$PRMSG(unkpkt, "sbreak", ty); return('A'); } } /* * r e c s w * * This is the state table switcher for receiving files. * * Respond crudely to user abort requests. We really should ACK * with 'Z' in the data field, then send an error packet only if * the sender fails to respond to the ACK+'Z'. But ... */ 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 */ if(remote) /* If remote */ aboflag = FALSE; /* No aborts */ else SYS$ARMABO(&aboflag); /* Arm user abort */ while(TRUE) { if(aboflag) /* If local user aborted */ { SYS$PRMSG("User aborted file transfer"); spack('E', n, 0, 0); /* Kill sender crudely */ state = 'A'; } if (debug) printf(" 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': SYS$CANABO(); return(TRUE); /* Complete state */ case 'A': SYS$CANABO(); return(FALSE); /* "Abort" state */ } } } /* * r i n i t * * Receive Initialization */ char rinit() { int len, num; /* Packet length, number */ char ty; /* Received packet type */ char rpack(); if (numtry++ > MAXTRY) /* If too many tries, give up */ { SYS$PRMSG(retfmt, "rinit"); /* Say why */ return('A'); } switch(ty = 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,10,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: /* Something else, "abort" */ SYS$PRMSG(unkpkt, "rinit", ty); return('A'); } } /* * r f i l e * * Receive File Header */ static char recfname[64]; /* File currently being received */ char rfile() { int num, len; /* Packet number, length */ char filnam1[64]; /* Work buffer for stripper */ register char *dp, *cp; /* Stripper work pointers */ char ty; /* Received packet type */ char rpack(); if (numtry++ > MAXTRY) /* If too many tries, give up */ { SYS$PRMSG(retfmt, "rfile"); /* Say why */ return('A'); } switch(ty = rpack(&len,&num,packet)) /* Get a packet */ { case 'S': /* Send-Init, maybe our ACK lost */ if (oldtry++ > MAXTRY) /* If too many tries, give up */ { SYS$PRMSG(retfmt, "rfile ('S' ACK)"); /* Say why */ return('A'); } 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,10,packet); numtry = 0; /* Reset try counter */ return(state); /* Stay in this state */ } else /* Not previous packet, "abort" */ { SYS$PRMSG(badpkt, "rfile", ty); return('A'); } case 'Z': /* End-Of-File */ if (oldtry++ > MAXTRY) /* If too many tries, give up */ { SYS$PRMSG(retfmt, "rfile ('Z' ACK)"); /* Say why */ 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 /* Not previous packet, "abort" */ { SYS$PRMSG(badpkt, "rfile", ty); return('A'); } case 'F': /* File Header (just what we want) */ if (num != n) /* The packet number must be right */ { SYS$PRMSG(badpkt, "rfile", ty); return('A'); } strcpy(filnam1, packet); /* Copy the file name */ /* * The following attempts to strip incoming filespecs to * KERMIT "normal form". */ if (stripfs) { /* * Point dp at the '.', adding one at the end if * there isn't one. Use the 1st '.' in the spec. */ while((dp = strchr(filnam1, '.')) == NULL) strcat(filnam1, '.'); /* * Truncate spec at 3rd alphameric after the dot * or 1st non-alphameric, whichever comes first. */ cp = dp + 1; while((*cp != NULL) && isalnum(*cp)) if((cp - dp) > 3) break; else cp++; *cp = '\0'; /* * Back up from '.' till we hit 1st alphameric or the * beginning. Then use up to 9 alphamerics as the * file name. */ cp = dp - 1; while((cp > filnam1) && isalnum(*cp)) --cp; if(!isalnum(*cp)) cp++; if((dp - cp) > 9) { cp[9] = '\0'; strcpy(recfname, cp); strcat(recfname, dp); } else strcpy(recfname, cp); } else strcpy(recfname, filnam1); if ((fp=fopen(recfname, (image)?"wn":"w"))==NULL) /* Open new file */ { error("Cannot create %s",recfname); /* Give up if can't */ return('A'); } else /* OK, give message */ SYS$PRMSG("Receiving %s as %s",packet,recfname); 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) /* Need right packet number here */ { SYS$PRMSG(badpkt, "rfile", ty); return('A'); } 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: /* Something else, "abort" */ SYS$PRMSG(unkpkt, "rfile", ty); return('A'); } } /* * r d a t a * * Receive Data * * Recognize 'D' in end-of-file packet as "delete file" signal */ char rdata() { int num, len; /* Packet number, length */ char ty; /* Received packet type */ char rpack(); if (numtry++ > MAXTRY) /* If too many tries, give up */ { SYS$PRMSG(retfmt, "rdata"); /* Say why */ return('A'); } switch(ty = rpack(&len,&num,packet)) /* Get packet */ { case 'D': /* Got Data packet */ if (num != n) /* Right packet? */ { /* No */ if (oldtry++ > MAXTRY) /* If too many tries, give up */ { SYS$PRMSG(retfmt, "rdata ('D' ACK)"); /* Say why */ return('A'); } 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 /* sorry, wrong number */ { SYS$PRMSG(badpkt, "rdata", ty); return('A'); } } /* 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) /* If too many tries, give up */ { SYS$PRMSG(retfmt, "rdata ('F' ACK)"); /* Say why */ return('A'); } 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 /* sorry, wrong number */ { SYS$PRMSG(badpkt, "rdata", ty); return('A'); } 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 */ if(len > 0 && packet[0] == 'D') /* Sender says "Junk it"? */ delete(recfname); /* Yup ... junk 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: /* Something else, "abort" */ SYS$PRMSG(unkpkt, "rdata", ty); return('A'); } } /* * g e t s w * * This is the state table switcher for 'get' from remote * server. Mimics the receiving files state machine after * kicking the server in the butt. Called for each file * in the list. */ getsw() { char iinit(), srcmd(), rinit(), rfile(), rdata(), gcomp(); state = 'I'; /* Init-Info is start state */ n = 0; /* Initialize message number */ numtry = 0; /* No tries yet */ if(remote) /* If remote */ aboflag = FALSE; /* No aborts */ else SYS$ARMABO(&aboflag); /* Arm user abort */ while(TRUE) /* Cycle ... */ { if(aboflag) /* If local user aborted */ { SYS$PRMSG("User aborted file transfer"); if(state != 'R' && state != 'G') /* If it got started */ spack('E', n, 0, 0); /* Kill sender crudely */ state = 'A'; } if(debug) printf(" getsw state: %c\n", state); switch(state) { case 'I': state = iinit(); break; /* Init-Info */ case 'G': state = srcmd(); break; /* Send "R" packet */ case 'R': state = rinit(); break; /* Receive-Init */ case 'F': state = rfile(); break; /* Receive-File */ case 'D': state = rdata(); break; /* Receive-Data */ case 'C': state = gcomp(); break; /* Request complate */ case 'B': SYS$CANABO(); return(TRUE); /* End of requests */ case 'A': SYS$CANABO(); return(FALSE); /* "Abort" state */ } } } /* * i i n i t * * Initialize Info: * Send this host's parameters and get other side's back. * Used for servers only. */ char iinit() { int num, len; /* Packet number, length */ char ty; /* Received packet type */ char rpack(); if (numtry++ > MAXTRY) /* If too many tries, give up */ { SYS$PRMSG(retfmt, "iinit"); /* Say why */ return('A'); } spar(packet); /* Fill up init info packet */ SYS$IFLUSH(); /* Flush pending input */ #ifdef NOIPACKETS return('G'); /* If I packets not supported */ #endif spack('I',n,10,packet); /* Send an I packet */ switch(ty = 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 I state */ return(state); /* and try again */ rpar(recpkt); /* Get other side's init info */ if (eol == 0) eol = MYEOL; /* Check and set defaults */ if (quote == 0) quote = '#'; numtry = 0; /* Reset try counter */ n = (n+1)%64; /* Bump packet count */ return('G'); /* OK, switch state to G */ case 'E': /* Error packet received */ prerrpkt(recpkt); /* Print it out and */ return('A'); /* abort */ case FALSE: return(state); /* Receive failure, try again */ default: /* Anything else, just "abort" */ SYS$PRMSG(unkpkt, "iinit", ty); return('A'); } } /* * s r c m d * * Send 'R' packet to remote server. Assumes filename is 'good' * so doesn't strip to "normal firm". */ char srcmd() { int len; /* Packet number, length */ if (numtry++ > MAXTRY) /* If too many tries, give up */ { SYS$PRMSG(retfmt, "srcmd"); /* Say why */ return('A'); } SYS$PRMSG("Requesting %s from server",filnam); len = strlen(filnam); SYS$IFLUSH(); /* Junk useless NAKs etc. */ spack('R',n,len,filnam); /* Send an R packet */ return('R'); /* Immediately to 'R' state */ } /* * g c o m p * * Complete state for server receive (GET) */ char gcomp() { if(filecount-- == 0) /* If no more files to request */ return('B'); /* Really "complete" */ strcpy(filnam, *filelist++); /* More, get next remote spec */ n = numtry = 0; /* Reset counters */ return('I'); /* Back to server init */ } /* * g f i n i s h * * GENERIC FINISH - Tell server to exit */ gfinish() { return(sgeneric('F')); } /* * g l o g o u t * * GENERIC LOGOUT - Tell server to logout */ glogout() { return(sgeneric('L')); } /* * s g e n e r i c * * Send a "generic" packet. Always sequence number 0, wait for * ACK. Return TRUE if ACK'ed OK, else FALSE if retries exhausted. * No state machine needed here. */ sgeneric(code) char code; { int num, len; char ty; char rpack(); n = numtry = 0; packet[0] = code; /* Load the generic command */ while(TRUE) /* Cycle ... */ { if(numtry++ > MAXTRY) /* Fail if retries done */ { SYS$PRMSG(retfmt, "sgeneric"); return(FALSE); } spack('G', n, 1, packet); /* Send generic command */ switch(ty = rpack(&len, &num, recpkt)) { case FALSE: /* Receive failure, try again */ case 'N': /* NAK, try again */ continue; case 'Y': /* ACK */ if (n != num) /* If wrong sequence number */ continue; /* Try again */ else return(TRUE); /* Operation complete */ case 'E': /* Error packet received */ prerrpkt(recpkt); /* Print it out and */ return(FALSE); /* Report failure */ default: /* Something else, "abort" */ SYS$PRMSG(unkpkt, "sbreak", ty); return('A'); } } } /* * KERMIT utilities. */ /* * 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 buffer[MAXPACKSIZ+16]; /* Checksum, packet buffer */ register char *bufp; /* Buffer pointer */ register unsigned int chksum; /* Checksum */ if (debug>1) /* Display outgoing packet */ { if (len != 0) 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 (len != 0) printf(" data: \"%s\"\n",data); } bufp = buffer; /* Set up buffer pointer */ for (i=1; i<=pad; i++) /* Transmit required padding */ *bufp++ = padchar; SYS$XMIT(buffer, pad); bufp = buffer; /* Reset buffer pointer */ *bufp++ = SOH; /* Packet marker, ASCII 1 (SOH) */ *bufp++ = len+3+' '; /* Send the character count */ chksum = len+3+' '; /* Initialize the checksum */ *bufp++ = num+' '; /* Packet number */ chksum += num+' '; /* Update checksum */ *bufp++ = type; /* Packet type */ chksum += type; /* Update checksum */ for (i=0; i> 6)+chksum)&077; /* Compute final checksum */ *bufp++ = chksum+' '; /* Put it in the packet */ *bufp++ = eol; /* Extra-packet line terminator */ if(debug > 1) printf(" checksum: %03o\n",chksum); SYS$XMIT(buffer,bufp-buffer); /* Send the packet */ } /* * r p a c k * * Read a Packet */ char 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 */ rchksum; /* Checksum received from other host */ register unsigned int cchksum; /* Our (computed) checksum */ /* Handle silly foreign timeouts */ if ((timint > MAXTIM) || (timint < MINTIM)) timint = MYTIME; t = 0377; /* Make t != SOH */ while (t != SOH) /* Wait for packet header */ { if(SYS$RCHAR(&t, timint) == FALSE) /* Read char with timeout */ return(FALSE); /* (timed out) */ t &= 0177; /* Handle parity */ } done = FALSE; /* Got SOH, init loop */ while (!done) /* Loop to get a packet */ { if(SYS$RCHAR(&t, timint) == FALSE) /* Read char with timeout */ return(FALSE); /* (timed out) */ if (!image) t &= 0177; /* If ASCII, strip 8th bit */ if (t == SOH) continue; /* Resynchronize if SOH */ cchksum = t; /* Start the checksum */ *len = t - ' ' - 3; /* Character count */ if(SYS$RCHAR(&t, timint) == FALSE) /* Read char with timeout */ return(FALSE); /* (timed out) */ if (!image) t &= 0177; /* Handle parity */ if (t == SOH) continue; /* Resynchronize if SOH */ cchksum = cchksum + t; /* Update checksum */ *num = t - ' '; /* Packet number */ if(SYS$RCHAR(&t, timint) == FALSE) /* Read char with timeout */ return(FALSE); /* (timed out) */ 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 */ if(SYS$RCHAR(&t, timint) == FALSE) /* Read char with timeout */ return(FALSE); /* (timed out) */ 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 */ if(SYS$RCHAR(&t, timint) == FALSE) /* Read checksum with timeout */ return(FALSE); /* (timed out) */ rchksum = t - ' '; /* Convert to numeric */ if(SYS$RCHAR(&t, timint) == FALSE) /* Read EOL with timeout & junk it */ return(FALSE); /* (timed out) */ 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 */ 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 ((char)cchksum != rchksum) { if(debug > 1) printf("Bad checksum. Ours = %03o Packet = %03o\n", (char)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++] = CR ^ 64; } buffer[i++] = quote; /* Quote the character */ if (t7 != quote) { t ^= 64; /* and uncontrolify */ t7 ^= 64; } } 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. Strip only * from sequences. */ static int cr_seen = FALSE; /* Newline hacking flag */ bufemp(buffer,len) char buffer[]; /* Buffer */ int len; /* Length */ { int i; /* Counter */ char t; /* Character holder */ for (i=0; i to */ if(t == CR && !cr_seen) /* If 1st */ { cr_seen == TRUE; /* Note it */ continue; /* For later */ } else { if(cr_seen && t != '\n') /* Put out last CR if this not LF */ putc(CR, fp); if(t != CR) /* Clear CR flag unless this is CR */ cr_seen = FALSE; } } putc(t,fp); } } /* * g n x t f l * * Get next file in a file group & handle wild filespecs */ gnxtfl() { if (filecount-- == 0) return(FALSE); /* If no more, fail */ if (debug) printf(" gnxtfl: filelist = \"%s\"\n",*filelist); strcpy(filpat, *(filelist++)); /* Get the filespec pattern */ fp = fwild(filpat, (image)?"rn":"r"); /* Set up possible wildcarding */ filnam[0] = '\0'; /* Mark 1st fnext() not done */ return(TRUE); /* Let sfile() handle errors */ } /* * s p a r * * Fill the data array with my send-init parameters * */ spar(data) char data[]; { register char *cp; /* This reduces code size */ cp = data; *cp++ = MAXPACKSIZ + ' '; /* Biggest packet I can receive */ *cp++ = MYTIME + ' '; /* When I want to be timed out */ *cp++ = MYPAD + ' '; /* How much padding I need */ *cp++ = MYPCHAR + ' '; /* Padding character I want */ *cp++ = MYEOL + ' '; /* End-Of-Line character I want */ *cp++ = MYQUOTE; /* Control-Quote character I send */ *cp++ = MYQBIN; /* Binary quote character I use */ *cp++ = MYCHKT; /* Checksum method I use */ *cp++ = MYREPT; /* My run-length encoding char */ *cp++ = MYCAPAS1 + ' '; /* My capabilities mask 1 */ } /* r p a r * * Get the other host's send-init parameters * */ rpar(data) char data[]; { register char *cp; cp = data; spsiz = *cp++ - ' '; /* Maximum send packet size */ timint = *cp++ - ' '; /* When I should time out */ pad = *cp++ - ' '; /* Number of pads to send */ padchar = *cp++ ^ 64; /* Padding character to send */ eol = *cp++ - ' '; /* EOL character I must send */ quote = *cp++; /* Incoming data quote character */ qbin = *cp++; /* Binary quoting character */ chkt = *cp++; /* His checksum type */ rept = *cp++; /* His run-length encoding char */ capas1 = *cp - ' '; /* His capabilities mask 1 */ } /* * Kermit printing routines: * * error - Calls SYS$PRMSG if local kermit; sends a error packet if remote * prerrpkt - Calls SYS$PRMSG to print incoming error packet */ /* * e r r o r * * Print error message. * * If local, print error message with SYS$PRMSG. * If remote, send an error packet with the message. */ /*VARARGS1*/ 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 SYS$PRMSG(fmt, 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; { SYS$PRMSG("Kermit aborting with following error from remote host:\n"); SYS$PRMSG("%s\n" ,msg); return; }