/* * 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 * David Ragozin, Univ. Washinton * John Sambrook, Univ. Washington, Bioengineering. * * usage: kermit c [lbe line baud escapechar] to connect * kermit s [d..iflb line baud] file ... to send files * kermit r [d..iflb line baud] to receive files * * where c=connect, s=send, r=receive, * d=debug, i=image mode, f=no filename conversion, l=tty line, * b=baud rate, e=escape char. * * If UCB4X then * * After first c, s, or r enters command mode with 'Kermit: ' prompt * * Commands: c[lbe line baud esc.char] to (re)connect * s[diflb line baud] file ... to send file(s) * r[diflb line baud] to receive * ![cmd] for shell escape * q to quit * ? or h to print this list * NOTE: * In command mode l(ine) or b(aud) parameters for c,s or r * need not be specified if unchanged from previous command(s). * * endif UCB4X may work on others but tested only on 4.1 and 4.1c */ /* * Modification History: * Oct. 17 Included fixes from Alan Crosswell (CUCCA) for IBM_UTS: * - Changed MYEOL character from \n to \r. * - Change char to int in bufill so getc would return -1 on * EOF instead of 255 (-1 truncated to 8 bits) * - Added read() in rpack to eat the EOL character * - Added fflush() call in printmsg to force the output * NOTE: The last three changes are not conditionally compiled * since they should work equally well on any system. * * Changed Berkeley 4.x conditional compilation flag from * UNIX4X to UCB4X. * Added support for error packets and cleaned up the printing * routines. * * Feb. '84 Multiple command execution for UCB4X by David Ragozin (UW) * * Jul. '85 Ported to Data General MV/UX (hosted on AOS/VS) UNIX. * */ #include /* Standard UNIX definitions */ #include /* error definitions */ /* Conditional compilation for different machines/operating systems */ /* One and only one of the following lines should be 1 */ #define UCB4X 0 /* Berkeley 4.x UNIX */ #define TOPS_20 0 /* TOPS-20 */ #define VAX_VMS 0 /* VAX/VMS (not yet impletmented) */ #define IBM_UTS 0 /* Amdahl UTS on IBM systems */ #define MVUX 1 /* Data General MV/UX */ /* Conditional compilation for the different Unix variants */ /* 0 means don't compile it, nonzero means do */ #if UCB4X #define V6_LIBS 0 /* Dont't use retrofit libraries */ #define NO_FIONREAD 0 /* We have ioctl(FIONREAD,...) for flushinput() */ #define NO_TANDEM 0 /* We have TANDEM line discipline (xon/xoff) */ #endif #if IBM_UTS #define V6_LIBS 0 /* Don't use retrofit libraries */ #define NO_FIONREAD 1 /* No ioctl(FIONREAD,...) for flushinput() */ #define NO_TANDEM 1 /* No TANDEM line discipline (xon/xoff) */ #endif #if MVUX #define V6_LIBS 0 /* Don't use retrofit libraries */ #define NO_FIONREAD 1 /* No ioctl(FIONREAD,...) for flushinput() */ #define NO_TANDEM 1 /* No TANDEM line discipline (xon/xoff) */ #define CRMOD 0 /* No CRMOD on MV/UX, so ... */ #endif #if V6_LIBS #include #include #include #else #include #include #include #endif /* V6_LIBS */ #if MVUX #include #include #endif #if NO_TANDEM #define TANDEM 0 #endif /* ** Manifest constants. */ #define MAXPACK 94 /* maximum packet size */ #define SOH 1 /* start of header */ #define BELL 7 /* ASCII bell */ #define CR 13 /* ASCII carriage return */ #define SP 32 /* ASCII space */ #define DEL 127 /* delete (rubout) */ #define ESCCHR '^' /* default escape character */ #define MAXTRY 10 /* times to retry a packet */ #define MYQUOTE '#' /* quote character */ #define MYPAD 0 /* number of pad characters needed */ #define MYPCHAR 0 /* padding character */ #define SHELL "/bin/sh" /* default shell to use */ #define TTY "/dev/tty" #if IBM_UTS #define MYEOL '\r' /* EOL character for UTS */ #else #define MYEOL '\n' /* EOL character for others */ #endif #define MYTIME 10 /* timeout interval */ #define MAXTIM 60 /* maximum timeout interval */ #define MINTIM 2 /* minimum timeout interval */ #define CMDLINE BUFSIZ /* maximum command line size */ #define TRUE -1 /* boolean constants */ #define FALSE 0 #if MVUX #define LISTENID 16 /* task id of listener task */ #define TALKID 17 /* task id of talker task */ #define LISTENPRI 1 /* priority of listener */ #define TALKPRI 2 /* priority of talker */ #define STACK 1024 /* default stack size */ #endif /* ** Macro definitions. */ #define tochar(ch) ((ch) + ' ') /* control character to printable */ #define unchar(ch) ((ch) - ' ') /* printable to control character */ #define ctl(ch) ((ch) ^ 64 ) /* toggle control bit */ #define strip(ch) ((ch) & 0x7f) /* strip parity (eigth) bit */ #define stty(fd, p) ioctl(fd, TIOCSETP, p) #define gtty(fd, p) ioctl(fd, TIOCGETP, p) /* ** Type and structure definitions. */ typedef enum _cmdtype { cmd_connect, /* connect to remote */ cmd_help, /* print Kermit help */ cmd_none, /* no valid command */ cmd_quit, /* exit Kermit */ cmd_receive, /* receive files */ cmd_send, /* send files */ cmd_shell /* UNIX shell command */ } cmdtype; /* ** Imported functions. */ extern int fclose(); /* close stream */ extern char * fgets(); /* read string from stream */ extern FILE * fopen(); /* open stream */ extern char * getenv(); /* get environment variable */ extern int pclose(); /* close pipe from process */ extern FILE * popen(); /* open pipe to process */ extern char * strcpy(); /* copy string */ /* ** Local functions. */ static void bufemp(); /* empty buffer of data */ static int bufill(); /* get buffer of data */ static int clkint(); /* clock handler */ static int closeline(); /* close communication line */ static void connect(); /* connect function */ static void defaults(); /* establish default characteristics */ static void doconnect(); /* execute a connect command */ static void dohelp(); /* print help menu */ static void doreceive(); /* execute a receive command */ static void dosend(); /* execute a send command */ static void doshell(); /* execute a shell command */ static int error(); /* print error message */ static void flushinput(); /* flush input queue */ static int getcmdline(); /* get next command line */ static int mapspeed(); /* map baud rate */ static int openline(); /* open communications line */ static cmdtype parsecmd(); /* parse command line */ static void prerrpkt(); /* print error packet */ static void printmsg(); /* print message */ static int recsw(); /* receive file state machine */ static int rdata(); /* receive file data */ static int rfile(); /* receive file header */ static int rinit(); /* receive initialization */ static int rpack(); /* receive a packet */ static void rpar(); /* get others send-init parameters */ static int sbreak(); /* send a break */ static int sdata(); /* send file data */ static int sendsw(); /* send file state machine */ static int seof(); /* send eof */ static int sfile(); /* send file header */ static int sinit(); /* send initiate */ static int spack(); /* send packet */ static void spar(); /* load send-init parameters */ static void usage(); /* print usage and quit */ #if MVUX static void listener(); /* listener sub-task */ static void talker(); /* talker sub-task */ #endif /* ** Exported functions. */ int main(); /* program entry point */ /* ** Imported data structures. */ /* none */ /* ** Local data structures */ static FILE * crfp; /* communications read file pointer */ static FILE * cwfp; /* communications write file pointer */ static int lflag; /* true if line option given */ static int lopen; /* true if line is open */ static char llast[BUFSIZ]; /* last line name */ static char lname[BUFSIZ]; /* line name */ static FILE * lrfp; /* line read file pointer */ static FILE * lwfp; /* line write file pointer */ static int topen; /* true if terminal is open */ static char tname[BUFSIZ]; /* terminal name */ static FILE * trfp; /* terminal read file pointer */ static FILE * twfp; /* terminal write file pointer */ static int speed; /* baud rate of communications line */ static int size; /* size of present data */ static int rpsiz; /* maximum receive packet size */ static int spsiz; /* maximum send packet size */ static int pad; /* how much padding to send */ static int timint; /* timeout for foreign host on sends */ static int n; /* packet number */ static int numtry; /* number of retries for this packet */ static int oldtry; /* times previous packet retried */ static int terminate; /* termination flag for connect */ static int remote; /* -1 means we're a remote kermit */ static int image; /* -1 means 8 bit mode */ static int debug; /* debugging level */ static int convert; /* do file name case conversion */ static int file; /* current file number */ static int files; /* number of files to send */ static char * filename; /* current file name */ static char * filenames[256]; /* pointers to file names */ static char state; /* present state of the automaton */ static char padchar; /* padding character */ static char eol; /* end of line character to send */ static char escchr; /* connect command escape char */ static char quote; /* quote character in incoming data */ static char recpkt[MAXPACK];/* receive packet buffer */ static char packet[MAXPACK];/* packet buffer */ static char shargs[BUFSIZ]; /* arguments to a shell command */ static FILE * fp; /* file pointer for current disk file */ static jmp_buf env; /* jump buffer for timeouts */ /* * m a i n * * Main routine - Initialize and enter command loop: parse command and options, * set up the tty lines, dispatch to the appropriate routine and get next * command. */ int main(argc, argv) int argc; /* argument count */ char * argv[]; /* argument vector */ { int c; /* current character */ int ac; /* argument count */ char * av[256]; /* argument vector */ char ** avp; /* argument vector pointer */ cmdtype cmd; /* command */ char cmdline[BUFSIZ]; /* command line */ /* ** Set up so that first iteration uses the command line as ** the line to parse. Establish the default values for ** certain ``global'' variables. */ ac = argc - 1; avp = ++argv; defaults(); /* ** Top level loop. This loop obtains until the quit command is ** received. */ cmd = cmd_none; while (cmd != cmd_quit) { /* ** Parse and execute the current command line. */ cmd = parsecmd(ac, avp); /* ** In order to support the transfer of files, the communications ** lines must be opened and closed in a certain order. The code ** that follows is an attempt to do just that. */ if (cmd == cmd_connect) { remote = 0; /* connect is always local */ if (lflag) /* if line specified */ { if (lopen) /* if a line is open */ { if (strcmp(lname, llast) != 0) { closeline(lrfp, lwfp); lopen = 0; if (openline(lname, &lrfp, &lwfp) == -1) cmd = cmd_none; else { strcpy(lname, llast); lopen = 1; } } } else { if (openline(lname, &lrfp, &lwfp) == -1) { lopen = 0; cmd = cmd_none; } else { strcpy(lname, llast); lopen = 1; } } } else if (lopen) /* if line already open */ printmsg("Connection continued on line %s.\n", lname); else /* no line open; none given */ { error("No line specified for connect command.\n"); cmd = cmd_none; } } else if (cmd == cmd_send || cmd == cmd_receive) { if (lflag) /* if line specified */ { remote = 0; /* local */ if (lopen) /* if a line is open */ { if (strcmp(lname, llast) != 0) { closeline(lrfp, lwfp); lopen = 0; if (openline(lname, &lrfp, &lwfp) == -1) cmd = cmd_none; else { strcpy(lname, llast); lopen = 1; } } } else { if (openline(lname, &lrfp, &lwfp) == -1) { lopen = 0; cmd = cmd_none; } else { strcpy(lname, llast); lopen = 1; } } crfp = lrfp; cwfp = lwfp; } else if (lopen) /* open line; use it */ { remote = 0; crfp = lrfp; cwfp = cwfp; } else /* no open line, no line given; use tty */ { remote = 1; /* remote */ if (topen == 0) /* if tty not open */ { if (openline(tname, &trfp, &twfp) == -1) cmd = cmd_none; } crfp = trfp; cwfp = twfp; } } /* ** Dispatch to the appropriate execution function depending ** on the value of cmd. */ switch (cmd) { case cmd_connect: doconnect(); break; case cmd_receive: doreceive(); break; case cmd_send: dosend(); break; case cmd_shell: doshell(); break; case cmd_help: dohelp(); break; case cmd_quit: break; } /* ** If the last command was not a quit command then get the next ** command line from the user. */ if (cmd != cmd_quit) ac = getcmdline(avp = av, cmdline); } } /* * b u f e m p * * Put data from an incoming packet into a file. */ static void bufemp(buffer,len) char buffer[]; /* Buffer */ int len; /* Length */ { int i; /* Counter */ char t; /* Character holder */ for (i=0; iCRLF 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 */ } /* * c l k i n t * * Clock interrupt handler. */ static int clkint() /* Timer interrupt handler */ { longjmp(env,TRUE); /* Tell rpack to give up */ } /* * c l o s e l i n e * * Function closeline() closes the given file pointers. If an * error is detected then a diagnostic is written and -1 is returned. * If no error is detected then 0 is returned. */ static int closeline(rfp, wfp) FILE * rfp; /* read file pointer */ FILE * wfp; /* write file pointer */ { if (fclose(rfp) == -1) { error("Can't close read file pointer."); error("Error code: %d.", errno); return -1; } if (fclose(wfp) == -1) { error("Can't close write file pointer."); error("Error code: %d.", errno); return -1; } return 0; } /* * c o n n e c t * * Establish a virtual terminal connection with the remote host. */ static void connect() { /* ** Under MV/UX a true fork() call does not exist; however, we can ** create multiple asynchronous tasks with a program. We create ** two sub-tasks, ``listener'' and ``talker'' to handle the data ** communication. For the main task to regain control one or both ** of the tasks must set the terminate flag; therefore it is cleared ** before either task is initiated. */ terminate = 0; if (mtask(listener, STACK, LISTENID, LISTENPRI) != 0) { error("Can't initiate listener task."); error("Error code is: %d.\n", errno); return; } if (mtask(talker, STACK, TALKID, TALKPRI) != 0) { error("Can't initiate talker task."); error("Error code is: %d.\n", errno); return; } /* ** After creating the sub-tasks the main task (this thread) ** enters the following loop. The main task runs once every ** two seconds; on each iteration it checks the ``terminate'' ** flag. When it finds the terminate flag set it pops out of ** the loop and terminates the ``listener'' and ``talker'' ** sub-tasks. */ while (terminate == 0) sleep(2); if (midkill(LISTENID) == -1) { error("Can't terminate listener task."); return; } if (midkill(TALKID) == -1) { error("Can't terminate talker task."); return; } } /* * d e f a u l t s * * Establish default values for the myriad global variables. * */ static void defaults() { #if UCB4X | MVUX /* Default to 7-bit masking, CRLF */ image = FALSE; /* translation and filename case */ convert++; /* conversion for UNIX systems */ #else image = TRUE; /* Default to no processing for */ convert = FALSE; /* non-UNIX systems */ #endif eol = CR; /* eol for outgoing packets */ quote = '#'; /* for quoting control characters */ pad = 0; /* no padding */ padchar = 0; /* pad with ASCII nul */ escchr = ESCCHR; /* default escape character */ strcpy(lname, ""); /* no line name */ strcpy(llast, ""); /* no default either */ strcpy(tname, TTY); /* terminal name */ } /* * d o c o n n e c t * * Top level control for connect command. * */ static void doconnect() { /* ** Open the communications line to the terminal. This line can be ** closed when the connect command completes. */ if (openline(tname, &trfp, &twfp) == -1) return; /* ** Inform the user that the connection is established and how ** to return. Then invoke connect() to actually manage the ** connection. */ printf("Connected. Use %cc to return to command level.\n", escchr); connect(); printf("Disconnected.\n"); /* ** Close the line to the terminal. */ (void) closeline(trfp, twfp); } /* * d o h e l p * * Print a simple help menu on the terminal. * */ static void dohelp() { /* ** Print a help screen for the user. */ printf("\nKermit Help:\n\n"); printf("When you receive the prompt 'Kermit: ' you are expected to\n"); printf("reply with a command. The following commands are supported:\n\n"); printf("\tc[lbe line baud escape] - connect to remote.\n"); printf("\ts[diflb line baud escape] file(s) - send file(s).\n"); printf("\tr[diflb line baud escape] - receive file(s).\n"); printf("\t! [command] - shell escape.\n"); printf("\t? - this help screen.\n"); printf("\tq - quit.\n\n"); #if 0 printf("Commands: c[lbe line baud esc.char] (to reconnect)\n"); printf("or: s[diflb line baud] file ... (to send file(s))\n"); printf("or: r[diflb line baud] (to receive )\n"); printf("or: ![cmd] (for shell escape)\n"); printf("or: q (TO QUIT)\n"); #endif #if 0 printf("Usage: kermit c[le line esc.char] (connect mode)\n"); printf("or: kermit s[difl line] file ... (send mode)\n"); printf("or: kermit r[difl line] (receive mode)\n"); exit(1); #endif } /* * d o r e c e i v e * * Top level control for receive command. * */ static void doreceive() { /* ** Do the receive and print the resulting status. */ if (recsw() == FALSE) printmsg("Receive failed."); else printmsg("Receive completed."); } /* * d o s e n d * * Top level control for send command. * */ static void dosend() { /* ** Get the filename and do the send. */ filename = filenames[file++]; fp = NULL; /* Indicate no file open yet */ if (sendsw() == FALSE) /* Send the file(s) */ printmsg("Send failed.");/* Report failure */ else /* or */ printmsg("Done."); /* success */ } /* * d o s h e l l * * Function doshell() runs a shell command for the user. * */ static void doshell() { int args; /* true if args to shell */ char * s; /* work pointer */ int (* savehup)(); /* old SIGHUP value */ int (* saveint)(); /* old SIGINT value */ int (* savequit)(); /* old SIGQUIT value */ char path[BUFSIZ]; /* pathname to shell */ char * base; /* pointer to base name of shell */ int code; /* return code from wait() */ int pid; /* child process id */ /* ** Get the shell to use. The full pathname to the shell is kept in ** path; a pointer, base, is used to point at the basename of the ** shell. Finally, scan the shargs string to decide if any arguments ** were given. */ if (getenv("SHELL") != NULL) strcpy(path, getenv("SHELL")); else strcpy(path, SHELL); for (s = base = path; *s; ) if (*s++ == '/') base = s; for (s = shargs, args = 0; *s; s++) if (*s != ' ' && *s != '\t') args = 1; /* ** Save the current values of SIGHUP, SIGINT and SIGQUIT. ** Then execute the shell command line. Finally, restore ** signal handling. */ savehup = signal(SIGHUP, SIG_IGN); saveint = signal(SIGINT, SIG_IGN); savequit = signal(SIGQUIT, SIG_IGN); /* ** The child does an execl() call. If the call fails then ** a diagnostic is written and the child exits(). */ if ((pid = vfork()) == 0) /* child */ { if (args) execl(path, base, "-c", shargs, 0); else execl(path, base, 0); error("doshell: execl() has failed."); _exit(1); } /* ** The parent waits for the child to terminate, then restores ** signals. */ else /* parent */ { while (wait(&code) != pid) ; (void) signal(SIGHUP, savehup); (void) signal(SIGINT, saveint); (void) signal(SIGQUIT, savequit); /* ** Clean up the argument list for the next pass, if any. ** Write a new-line to stdout so that Kermit prompt is positioned ** correctly. */ strcpy(shargs, ""); fputs("\n", stdout); } } /* * e r r o r * * Print error message. * * If local, print error message with printmsg. * If remote, send an error packet with the message. */ /*VARARGS1*/ static int error(fmt, a1, a2, a3, a4, a5) char *fmt; { char msg[80]; int len; if (remote) { sprintf(msg,fmt,a1,a2,a3,a4,a5); /* Make it a string */ len = strlen(msg); spack('E',n,len,msg); /* Send the error packet */ } else printmsg(fmt, a1, a2, a3, a4, a5); } /* * f l u s h i n p u t * * Dump all pending input to clear stacked up NACK's. * (Implemented only for Berkeley Unix at this time). */ #if UCB4X&(~NO_FIONREAD) static void flushinput() { long int count; /* Number of bytes ready to read */ long int i; /* Number of bytes to read in loop */ ioctl(ttyfd, FIONREAD, &count); /* See how many bytes pending read */ if (!count) return; /* If zero, then no input to flush */ while (count) /* Loop till all are flushed */ { i = (count 0) cmdline[length - 1] = 0; /* ** Hand-craft a command line to expand the command line. Then ** use popen() / pclose() to get the results of the expansion. */ #if 0 sprintf(buf, "/bin/echo %s", cmdline); if ((pfp = popen(buf, "r")) == NULL) { perror("getcmdline: popen"); exit(1); } if ((s = fgets(cmdline, CMDLINE, pfp)) == NULL) { perror("getcmdline: fgets"); exit(1); } if (pclose(pfp) == -1) { perror("getcmdline: pclose"); exit(1); } #endif /* ** Finally, set up ac and av. Note that each argument in cmdline ** is terminated with a NULL so that it looks just like a 'real' ** argv from C runtimes. */ while (*s == ' ' || *s == '\t') /* skip whitespace */ s++; for (av[ac = 0] = NULL; *s; ac++) { av[ac] = s; /* point to argument */ while (*s && *s != ' ' && *s != '\t') /* skip argument body */ s++; if (*s) /* if more arguments */ *s++ = 0; while (*s == ' ' || *s == '\t') /* skip whitespace */ s++; } av[ac] = NULL; return ac; } #if MVUX /* * l i s t e n e r * * Special MV/UX sub-task to listen for characters from the remote system. */ static void listener() { int cc; /* I/O buffer */ while (1) { cc = fgetc(lrfp); fputc(cc, twfp); } } #endif /* * m a p s p e e d * * Map an integer baud rate (300, 1200, etc.) to the appropriate manifest * value (B300, B1200, etc.). Returns manifest or -1 for illegal baud. */ static int mapspeed(baud) int baud; /* input baud rate */ { int manifest; /* manifest value */ switch (baud) { case 110: manifest = B110; break; case 150: manifest = B150; break; case 300: manifest = B300; break; case 1200: manifest = B1200; break; case 2400: manifest = B2400; break; case 4800: manifest = B4800; break; case 9600: manifest = B9600; break; default: /* error case */ manifest = -1; break; } return manifest; } /* * o p e n l i n e * * Function openline() opens read and write file pointers to the * given file, assumed to be a communications line. If an error * is detected then a diagnostic is written and -1 is returned. * On no error, 0 is returned. * */ static int openline(name, rfp, wfp) char * name; /* device name to open */ FILE ** rfp; /* read file pointer */ FILE ** wfp; /* write file pointer */ { #if MVUX char * rmode = "j"; /* DG binary read */ char * wmode = "k"; /* DG binary write */ #else char * rmode = "r"; /* normal read */ char * wmode = "w"; /* normal write */ #endif if ((*rfp = fopen(name, rmode)) == NULL) { error("Can't open %s for mode %s.", name, rmode); error("Error code: %d.", errno); return -1; } if ((*wfp = fopen(name, wmode)) == NULL) { error("Can't open %s for mode %s.", name, wmode); error("Error code: %d.", errno); return -1; } setbuf(*rfp, NULL); setbuf(*wfp, NULL); return 0; } /* * p a r s e c m d * * Function parsecmd() does a simple minded parse of the given command * line. While it would be nice to conform the UNIX standard and use * getopt() to parse this is not done. The change would like cause more * conflict than good. Sigh. * * If the command line does not contain a Kermit command letter this * function returns ``cmd_none''. * */ static cmdtype parsecmd(ac, av) int ac; /* argument count */ char * av[]; /* argument vector */ { int c; /* current character */ cmdtype cmd; /* current command */ int index; /* current argument index */ char * s; /* work pointer */ /* ** The default Kermit standard for command lines requires that the ** first argument begin with a command, then optional arguments. ** Check that this is the case, returning cmd_none in the case of ** an empty command line. */ cmd = cmd_none; if (ac == 0) /* empty command line */ return cmd; /* ** Parse the first argument. This is the command plus any of the ** options. */ remote = 0; /* default to local */ s = av[index = 0]; switch (c = *s++) { case 'c': /* connect command */ cmd = cmd_connect; break; case 'r': /* receive command */ cmd = cmd_receive; remote = 1; /* default to remote */ break; case 's': /* send command */ cmd = cmd_send; remote = 1; /* default to remote */ break; case '!': /* shell command */ cmd = cmd_shell; while (av[++index] != NULL) { strcat(shargs, " "); strcat(shargs, av[index]); } break; case '?': /* help command */ cmd = cmd_help; break; case 'q': /* quit command */ cmd = cmd_quit; break; default: /* unknown command */ error("Not a valid command: '%c'. Use '?' for help.", c); return cmd_none; break; } /* ** If the command was one of [c,r,s] then continue parsing for options. ** Otherwise return immediately to caller. */ if (cmd == cmd_connect || cmd == cmd_receive || cmd == cmd_send) { while (*s) /* while more arguments */ { switch (c = *s++) { case 'd': debug++; /* debug */ break; case 'i': /* image mode */ image = 1; break; case 'f': /* convert file names */ convert = 1; break; case 'b': /* baud rate (speed) */ if (av[++index] == NULL) { error("Option 'b' requires baud rate."); return cmd_none; } speed = atoi(av[index]); break; case 'e': if (av[++index] == NULL) { error("Option 'e' requires escape character."); return cmd_none; } escchr = av[index][0]; break; case 'l': /* line */ lflag = 1; remote = 0; if (av[++index] == NULL) { error("Option 'l' requires a line name."); return cmd_none; } strcpy(lname, av[index]); strcpy(llast, av[index]); break; default: error("Not a valid option: '%c'. Use ? for help.", c); return cmd_help; break; } } /* ** Now, if the command was cmd_send, pick up all files. */ if (cmd == cmd_send) { for (files = 0, index++; av[index] != NULL; files++, index++) filenames[files] = av[index]; if (files == 0) { error("No files specified for send command."); return cmd_help; } } } return cmd; } /* * p r e r r p k t * * Print contents of error packet received from remote host. */ static void prerrpkt(msg) char * msg; { printf("Kermit aborting with following error from remote host:\n%s\n",msg); } /* * p r i n t m s g * * Print error message on standard output if not remote. */ /*VARARGS1*/ static void printmsg(fmt, a1, a2, a3, a4, a5) char * fmt; { if (! remote) { printf(fmt, a1, a2, a3, a4, a5); printf("\n"); fflush(stdout); /* force output (UTS needs it) */ } } /* * r d a t a * * Receive Data */ static int 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" */ } } /* * r e c s w * * This is the state table switcher for receiving files. */ static int recsw() { state = 'R'; /* Receive-Init is the start state */ n = 0; /* Initialize message number */ numtry = 0; /* Say no tries yet */ while(TRUE) { 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': return(TRUE); /* Complete state */ case 'A': return(FALSE); /* "Abort" state */ } } } /* * r f i l e * * Receive File Header */ static int rfile() { int num, len; /* Packet number, length */ char filnam1[50]; /* Holds the converted file name */ if (numtry++ > MAXTRY) return('A'); /* "abort" if too many tries */ switch(rpack(&len,&num,packet)) /* Get a packet */ { case 'S': /* Send-Init, maybe our ACK lost */ if (oldtry++ > MAXTRY) return('A'); /* If too many tries "abort" */ if (num == ((n==0) ? 63:n-1)) /* Previous packet, mod 64? */ { /* Yes, ACK it again with */ spar(packet); /* our Send-Init parameters */ spack('Y',num,6,packet); numtry = 0; /* Reset try counter */ return(state); /* Stay in this state */ } else return('A'); /* Not previous packet, "abort" */ case 'Z': /* End-Of-File */ if (oldtry++ > MAXTRY) return('A'); if (num == ((n==0) ? 63:n-1)) /* Previous packet, mod 64? */ { /* Yes, ACK it again. */ spack('Y',num,0,0); numtry = 0; return(state); /* Stay in this state */ } else return('A'); /* Not previous packet, "abort" */ case 'F': /* File Header (just what we want) */ if (num != n) return('A'); /* The packet number must be right */ strcpy(filnam1, packet); /* Copy the file name */ if (convert) /* Convert upper case to lower */ for (filename=filnam1; *filename != '\0'; filename++) if (*filename >= 'A' && *filename <= 'Z') *filename |= 040; if ((fp=fopen(filnam1,"w"))==NULL) /* Try to open a new file */ { error("cannot create %s",filnam1); /* Give up if can't */ return('A'); } else /* OK, give message */ printmsg("Receiving %s as %s",packet,filnam1); spack('Y',n,0,0); /* Acknowledge the file header */ oldtry = numtry; /* Reset try counters */ numtry = 0; /* ... */ n = (n+1)%64; /* Bump packet number, mod 64 */ return('D'); /* Switch to Data state */ case 'B': /* Break transmission (EOT) */ if (num != n) return ('A'); /* Need right packet number here */ 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 i n i t * * Receive Initialization */ static int 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 p a c k * * Read a Packet */ static int rpack(len,num,data) int *len, *num; /* Packet length, number */ char *data; /* Packet data */ { int i, done; /* Data character number, loop exit */ char t, /* Current input character */ type, /* Packet type */ cchksum, /* Our (computed) checksum */ rchksum; /* Checksum received from other host */ #if UCB4X | MVUX /* TOPS-20 can't handle timeouts... */ if (setjmp(env)) return FALSE; /* Timed out, fail */ signal(SIGALRM,clkint); /* Setup the timeout */ if ((timint > MAXTIM) || (timint < MINTIM)) timint = MYTIME; alarm(timint); #endif /* UCB4X */ while (t != SOH) /* Wait for packet header */ { t = fgetc(crfp); t &= 0177; /* Handle parity */ } done = FALSE; /* Got SOH, init loop */ while (!done) /* Loop to get a packet */ { t = fgetc(crfp); /* 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 = fgetc(crfp); /* 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 = fgetc(crfp); /* 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 = fgetc(crfp); /* 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 = fgetc(crfp); /* Get last character (checksum) */ rchksum = unchar(t); /* Convert to numeric */ t = fgetc(crfp); /* 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 UCB4X | MVUX alarm(0); /* Disable the timer interrupt */ #endif if (debug>1) /* Display incoming packet */ { if (data != NULL) data[*len] = '\0'; /* Null-terminate data to print it */ printf(" rpack type: %c\n",type); printf(" num: %d\n",*num); printf(" len: %d\n",*len); if (data != NULL) printf(" data: \"%s\"\n",data); } /* Fold in bits 7,8 to compute */ cchksum = (((cchksum&0300) >> 6)+cchksum)&077; /* final checksum */ if (cchksum != rchksum) return(FALSE); return(type); /* All OK, return packet type */ } /* r p a r * * Get the other host's send-init parameters * */ static void 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 */ } /* * s b r e a k * * Send Break (EOT) */ static int 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" */ } } /* * s d a t a * * Send File Data */ static int 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 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. * */ static int sendsw() { state = 'S'; /* Send initiate is the start state */ n = 0; /* Initialize message number */ numtry = 0; /* Say no tries yet */ while(TRUE) /* Do this as long as necessary */ { if (debug) printf("sendsw state: %c\n",state); switch(state) { case 'S': state = sinit(); break; /* Send-Init */ case 'F': state = sfile(); break; /* Send-File */ case 'D': state = sdata(); break; /* Send-Data */ case 'Z': state = seof(); break; /* Send-End-of-File */ case 'B': state = sbreak(); break; /* Send-Break */ case 'C': return (TRUE); /* Complete */ case 'A': return (FALSE); /* "Abort" */ default: return (FALSE); /* Unknown, fail */ } } } /* * s e o f * * Send End-Of-File. */ static int seof() { int num, len; /* Packet number, length */ if (numtry++ > MAXTRY) return('A'); /* If too many tries, "abort" */ spack('Z',n,0,packet); /* Send a 'Z' packet */ switch(rpack(&len,&num,recpkt)) /* What was the reply? */ { case 'N': /* NAK, just stay in this state, */ num = (--num<0 ? 63:num); /* unless it's NAK for next packet, */ if (n != num) /* which is just like an ACK for */ return(state); /* this packet so fall thru to... */ case 'Y': /* ACK */ if (n != num) return(state); /* If wrong ACK, hold out */ numtry = 0; /* Reset try counter */ n = (n+1)%64; /* and bump packet count */ if (debug) printf(" Closing input file %s, ", filename); fclose(fp); /* Close the input file */ fp = NULL; /* Set flag indicating no file open */ if (debug) printf("looking for next file...\n"); if (file == files) /* Any more files ? */ return('B'); /* No */ filename = filenames[file++]; if (debug) printf(" New file is %s\n", filename); 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 f i l e * * Send File Header. */ static int sfile() { int num, len; /* Packet number, length */ char filnam1[50], /* Converted file name */ *newfilnam, /* Pointer to file name to send */ *cp; /* char pointer */ if (numtry++ > MAXTRY) return('A'); /* If too many tries, give up */ if (fp == NULL) /* If not already open, */ { if (debug) printf(" Opening %s for sending.\n", filename); fp = fopen(filename,"r"); /* open the file to be sent */ if (fp == NULL) /* If bad file pointer, give up */ { error("Cannot open file %s", filename); return('A'); } } strcpy(filnam1, filename); /* 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 (convert) /* 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",filename,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 i n i t * * Send Initiate: send this host's parameters and get other side's back. */ static int 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 */ flushinput(); /* Flush pending input */ 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'); /* Anythig else, just "abort" */ } } /* * s p a c k * * Send a Packet */ static int spack(type,num,len,data) char type, *data; int num, len; { int i; /* Character loop counter */ char chksum, buffer[100]; /* Checksum, packet buffer */ register char *bufp; /* Buffer pointer */ if (debug>1) /* Display outgoing packet */ { if (data != NULL) data[len] = '\0'; /* Null-terminate data to print it */ printf(" spack type: %c\n",type); printf(" num: %d\n",num); printf(" len: %d\n",len); if (data != NULL) printf(" data: \"%s\"\n",data); } bufp = buffer; /* Set up buffer pointer */ for (i=1; i<=pad; i++) /* Issue any padding */ fputc(padchar, cwfp); *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> 6)+chksum)&077; /* Compute final checksum */ *bufp++ = tochar(chksum); /* Put it in the packet */ *bufp = eol; /* Extra-packet line terminator */ fwrite(buffer, 1, bufp - buffer + 1, cwfp); /* Send the packet */ } /* * s p a r * * Fill the data array with my send-init parameters * */ static void spar(data) char data[]; { data[0] = tochar(MAXPACK); /* 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 */ } #if MVUX /* * t a l k e r * * Special MV/UX sub-task to send keyboard characters to remote system. * */ static void talker() { int cc; /* I/O buffer */ while (1) { if ((cc = strip(fgetc(trfp))) == escchr) { if ((cc = strip(fgetc(trfp))) == escchr) fputc(escchr, lwfp); else { switch (cc) { case '#': /* simulate break */ break; case 'c': /* return to local machine */ case 'C': terminate = 1; break; case 'h': /* verify running status */ case 'H': fprintf(twfp, "\r\n(Kermit)\r\n"); fputs("\r\n\r\n", twfp); break; default: fputc(BELL, twfp); break; } } } else fputc(cc, lwfp); } } #endif /* * u s a g e * * Print summary of usage info and quit * */ static void usage() { #if UCB4X | MVUX printf("Usage: kermit c[lbe line baud esc.char] (connect mode)\n"); printf("or: kermit s[diflb line baud] file ... (send mode)\n"); printf("or: kermit r[diflb line baud] (receive mode)\n"); #else printf("Usage: kermit c[le line esc.char] (connect mode)\n"); printf("or: kermit s[difl line] file ... (send mode)\n"); printf("or: kermit r[difl line] (receive mode)\n"); #endif exit(1); }