/** xprkermit.c * * These are the protocol transfer routines for a simple Kermit upload/dnload * * Version 0.9--by Marco Papa. * * Version 1.0--updated by Stephen Walton. * Added several more user selections to XPRotocolSetup(): * bctr (block check type to request), limit (retry-limit), and * rtimo (timeout for me). "Real" C Kermit makes many of the * things declared as local to kermitproto.w user-settable, * such as the send packet size. * Also fixed several problems in kermitproto.w. * * Version 1.5--more features * Extensive changes. * (1) Created a SetupVars variable to hold setting-up information * in the XPR_IO structure. This will allow re-entrancy when and if * it comes. Right now, it mainly gives a way to set up defaults with * one assignment. * (2) Added the generic Kermit server functions FINISH, BYE, and CD. * (3) Added the ability to set options and execute the server functions * from a setup string (sent as IO->xpr_filename to XPRotocolSetup()). * * This code is copyright 1990 by Stephen Walton and Marco Papa. It may * be freely distributed in its original or in modified form, provided * this copyright notice is kept intact. It may not be sold for profit, * but may be included as part of a commercial program provided that * said inclusion does not increase the cost of that program beyond a * modest handling fee. **/ #include #include #include /* * xproto.h is the include file given in Appendix B. */ #include "xproto.h" #include "xprkermit.h" #include "kermitproto.h" /* * The following two strings must exist. */ char XPRname[] = "xprkermit.library"; char XPRid[] = "xprkermit 1.5 (9 December 1989)\r\n"; UWORD XPRrevision = 5; long atol(); /* * The callxx...() routines are described later. They provide the assembler * interface from the XPR library to the call-back routines. */ long calla(), callaa(), callad(), calladd(), calladda(), calldaa(), callda(), calld(); /* IMPORTANT: this makes it non-reentrant !!!! */ long (*xupdate) (), (*xswrite) (), (*xfopen) (), (*xfclose) (), (*xfread) (), (*xsread) (), (*xchkabort) (), (*xfnext) (), (*xffirst) (), (*xsflush) (), (*xfwrite) (), (*xgets) (), (*xfinfo) (); /* * External and forward declarations. */ char *malloc(), *strcpy(), *index(); void ioerr(), XPRLong(); SetupVars *setup(); extern char start; /* * The flags for the kermitproto.w module. */ char *p_pattern; /* wildcard pattern */ int parity; /* parity on? 0 for no parity--need for * proper 8th-bit quote */ int text; /* Text or binary mode? Flag 1 for text file, * 0 for binary file */ int convert; /* Convert file names to lower case? 0 for * literal files (no), 1 for translate (yes) */ int urpsiz; /* Kermit maximum packet size. Maximum * receive packet size user wants. */ char *cmarg; /* Character string containing Kermit server * cmd */ extern int bctr; /* Block check type to request. */ extern int limit; /* Retry limit. May increase on very noisy * lines. */ extern int rtimo; /* Timeout to request. */ int getfile; /* Host server. 0 = Receive (no) ; 1 = Get * (yes) */ long brkflag; static SetupVars Defaults = { {""}, /* No default file name. */ 0, /* Parity defaults to off. */ 1, /* Text file defaults to on. */ 1, /* Convert file names by default. */ 94, /* Default maximum packet length. */ 1, /* Default block check type. */ 5, /* Retry limit */ 10, /* Timeout (seconds) */ 0, /* Host is server? No by default. */ }; /** * * Send a file * **/ long XProtocolSend(IO) struct XPR_IO *IO; { struct XPR_UPDATE xpru; SetupVars *sv; brkflag = 0; if ((sv = setup(IO)) == NULL) return 0L; /* Initialize parameters. */ /* * Read the text/binary set using xpr_finfo if present. Else use the * value chosen in XPRotocolSetup. */ if (xfinfo) { /* * Use feature that calling xpr_finfo with a zero-length filename * returns setting of internal comm program Text/Binary flag. */ text = (callad(xfinfo, IO->xpr_filename, 2L) == 1 ? 0 : 1); } /* * Start the transfer. See 3.8 for a discussion on how to implement * xupdate. */ tchar('#'); /* * Copy filename, and put pointer in external Kermit variable. */ strcpy(sv->FileName, IO->xpr_filename); p_pattern = sv->FileName; /* * */ start = 's'; proto(); /* * If we got here through chkabort() say Aborted. */ xpru.xpru_updatemask = XPRU_MSG; if (brkflag) xpru.xpru_msg = "Aborted"; else xpru.xpru_msg = "Done"; (void) calla(xupdate, &xpru); if (brkflag) return (0L); else return (1L); } /** * * Receive a file. * **/ long XProtocolReceive(IO) struct XPR_IO *IO; { struct XPR_UPDATE xpru; long status; SetupVars *sv; brkflag = 0; if ((sv = setup(IO)) == NULL) return 0L; /* Initialize parameters. */ /* * Read the text/binary set using xpr_finfo if present. Else use the * value chosen in XPRotocolSetup. */ if (xfinfo) { /* * Use feature th at calling xpr_finfo with a zero-length filename * returns setting of internal comm program Text/Binary flag. */ text = (callad(xfinfo, "", 2L) == 1 ? 0 : 1); } /* * Start the transfer. See 3.8 for a discussion on how to implement * xupdate. */ tchar('#'); /* * */ if (getfile) { /* * Copy filename, and put pointer in external Kermit variable. */ cmarg = sv->FileName; start = 'r'; status = callaa(xgets, "Host Filename", cmarg); if (!status) return (0L); } else start = 'v'; proto(); /* * If we got here through chkabort() say Aborted. */ xpru.xpru_updatemask = XPRU_MSG; if (brkflag) xpru.xpru_msg = "Aborted"; else xpru.xpru_msg = "Done"; (void) calla(xupdate, &xpru); if (brkflag) return (0L); else return (1L); } /* * Perform a generic Kermit server command. */ static DoGeneric(IO, s) struct XPR_IO *IO; char *s; { SetupVars *sv; if ((sv = setup(IO)) == NULL) return 0; /* Set up transfer characteristics. */ brkflag = 0; start = 'g'; cmarg = s; proto(); if (brkflag) return (0); else return (1); } /* * Execute a Kermit FINISH command. */ static KermitFinish(IO) struct XPR_IO *IO; { return (DoGeneric(IO, "F")); } /* * Execute a Kermit BYE command. */ static KermitBye(IO) struct XPR_IO *IO; { return (DoGeneric(IO, "L")); } /* * Change directory on remote server. We need to return an error if the CD * fails on the remote end. */ static KermitCd(IO, dir) struct XPR_IO *IO; char *dir; { char CdCommand[100]; int retval; CdCommand[0] = 'C'; CdCommand[1] = tochar(strlen(dir)); strcpy(CdCommand + 2, dir); retval = DoGeneric(IO, CdCommand); return retval; } #if AZTEC_C /* * Case-independent string comparison for Aztec C. Modification of the * strcmp() routine from Henry Spencer's portable string library. Depends on * a toupper() function (as opposed to macro). */ int /* <0 for <, 0 for ==, >0 for > */ stricmp(s1, s2) char *s1; char *s2; { register char *scan1; register char *scan2; scan1 = s1; scan2 = s2; while (*scan1 != '\0' && toupper(*scan1) == toupper(*scan2)) { scan1++; scan2++; } /* * The following case analysis is necessary so that characters which look * negative collate low against normal characters but high against the * end-of-string NUL. */ if (*scan1 == '\0' && *scan2 == '\0') return (0); else if (*scan1 == '\0') return (-1); else if (*scan2 == '\0') return (1); else return (toupper(*scan1) - toupper(*scan2)); } #endif /** * * Setup * * First, a general-purpose comparison for either of the two possible returns * indicating a Yes push on a Boolean gadget. **/ #define XprBoolTrue(s) ((stricmp(s, "yes") == 0) || (stricmp(s, "on") == 0)) /* * Then, a small set of code to initialize a string based on a value. */ #define XprSet(value, string) (value ? \ (void) strcpy(string, YesString) : \ (void) strcpy(string, NoString)) \ static char YesString[] = "yes"; static char NoString[] = "no"; /* * If Setup() succeeds, flag same. Also, we don't need a file requester * on receive. */ #define SUCCESS (XPRS_SUCCESS | XPRS_NORECREQ) long XProtocolSetup(IO) struct XPR_IO *IO; { long (*xupdate) (), (*xgets) (), (*xoptions) (), (*xfinfo) (); struct XPR_UPDATE xpru; #define NOPTS 8 struct xpr_option opt[NOPTS], *popt[NOPTS]; #define MAXSTRING 6 char ValueStrings[NOPTS][MAXSTRING]; long status; int i, j; char buf[256]; SetupVars *sv, tempvar; if ((xupdate = IO->xpr_update) == NULL) return (XPRS_FAILURE); if ((xgets = IO->xpr_gets) == NULL) return (XPRS_FAILURE); /* * Allocate memory for file name buffer and options if first call. Copy * defaults into it and then merge in user changes. */ if ((sv = (SetupVars *) IO->xpr_data) == NULL) { if ((sv = (SetupVars *) malloc((unsigned) sizeof(SetupVars))) == NULL) return XPRS_FAILURE; *sv = Defaults; IO->xpr_data = (long *) sv; } tempvar = *sv; /* * In order to use xpr_options, we must have all of the three conditions * which follow true. Otherwise, either IO->xpr_filename is non-NULL, in * which case we assume it contains a setup string, or there is no * xpr_options, in which case we use xpr_gets to retrieve a setup string. */ if (IO->xpr_filename == NULL && IO->xpr_extension >= 1 && (xoptions = IO->xpr_options) != NULL) { /* * I use a counter, i, here, so that I can stick in more options * without needing to change a lot of numbers. I can also skip * options dynamically, as in Text below. */ #if 1 i = 0; /* Announce us. */ opt[i].xpro_description = "Kermit Commands 1.5"; opt[i].xpro_type = XPRO_HEADER; opt[i].xpro_value = NULL; opt[i].xpro_length = 0; i++; /* * First, do the Kermit server command items. */ opt[i].xpro_description = "Kermit FINISH"; opt[i].xpro_type = XPRO_COMMAND; opt[i].xpro_value = NULL; opt[i].xpro_length = 0; i++; opt[i].xpro_description = "Kermit BYE"; opt[i].xpro_type = XPRO_COMMAND; opt[i].xpro_value = NULL; opt[i].xpro_length = 0; i++; opt[i].xpro_description = "Kermit CD"; opt[i].xpro_type = XPRO_COMMPAR; buf[0] = '\0'; opt[i].xpro_value = buf; /* Use buf to hold dir name. */ opt[i].xpro_length = sizeof(buf) - 1; i++; opt[i].xpro_description = "Kermit Options"; opt[i].xpro_type = XPRO_COMMAND; opt[i].xpro_value = NULL; opt[i].xpro_length = 0; i++; /* show requester after loading pointers */ for (j = 0; j < i; j++) popt[j] = &opt[j]; status = callda(xoptions, (long) i, popt); /* check returned value */ if (status == -1L) return XPRS_FAILURE; /* Check returned value to see what we are to do. */ i = 1; if (status & (1L << i)) return (KermitFinish(IO) ? SUCCESS : XPRS_FAILURE); i++; if (status & (1L << i)) return (KermitBye(IO) ? SUCCESS : XPRS_FAILURE); i++; if (status & (1L << i)) return (KermitCd(IO, opt[i].xpro_value) ? SUCCESS : XPRS_FAILURE); #endif /* If we get to this point, we are to set options. */ i = 0; /* Start over. */ opt[i].xpro_description = "Kermit Options 1.5"; opt[i].xpro_type = XPRO_HEADER; opt[i].xpro_value = NULL; opt[i].xpro_length = 0; i++; /* Convert filename */ opt[i].xpro_description = "Convert Filename"; opt[i].xpro_type = XPRO_BOOLEAN; XprSet(tempvar.ConvertFlag, ValueStrings[i]); opt[i].xpro_value = ValueStrings[i]; opt[i].xpro_length = MAXSTRING; i++; /* host is server */ opt[i].xpro_description = "Host Server"; opt[i].xpro_type = XPRO_BOOLEAN; XprSet(tempvar.GetFlag, ValueStrings[i]); opt[i].xpro_value = ValueStrings[i]; opt[i].xpro_length = MAXSTRING; i++; /* * file type -- only show this if xpr_finfo not present, or returns * error when we try to get the file type. */ if ((xfinfo = IO->xpr_finfo) == NULL || (tempvar.TextFlag = (callad(xfinfo, "", 2L))) == 0) { tempvar.TextFlag = -1; /* Flag xfinfo failed. */ opt[i].xpro_description = "Text File"; opt[i].xpro_type = XPRO_BOOLEAN; XprSet(tempvar.TextFlag, ValueStrings[i]); opt[i].xpro_value = ValueStrings[i]; opt[i].xpro_length = MAXSTRING; i++; } else /* * Switch to 0 for binary, 1 for text; xpr_finfo returns 1 for * binary, 2 for text. */ tempvar.TextFlag -= 1; /* Packet size */ opt[i].xpro_description = "Packet Size"; opt[i].xpro_type = XPRO_LONG; (void) sprintf(ValueStrings[i], "%-d", tempvar.MaxPacket); opt[i].xpro_value = ValueStrings[i]; opt[i].xpro_length = MAXSTRING; i++; /* Block Check type */ opt[i].xpro_description = "Block Check (1, 2, 3)"; opt[i].xpro_type = XPRO_LONG; (void) sprintf(ValueStrings[i], "%-d", tempvar.BlockCheckType); opt[i].xpro_value = ValueStrings[i]; opt[i].xpro_length = MAXSTRING; i++; /* Retry Limit */ opt[i].xpro_description = "Maximum Retries"; opt[i].xpro_type = XPRO_LONG; (void) sprintf(ValueStrings[i], "%-d", tempvar.RetryLimit); opt[i].xpro_value = ValueStrings[i]; opt[i].xpro_length = MAXSTRING; i++; /* Timeout */ opt[i].xpro_description = "Timeout (seconds)"; opt[i].xpro_type = XPRO_LONG; (void) sprintf(ValueStrings[i], "%-d", tempvar.Timeout); opt[i].xpro_value = ValueStrings[i]; opt[i].xpro_length = MAXSTRING; i++; /* show requester after loading pointers */ for (j = 0; j < i; j++) popt[j] = &opt[j]; /* show requester */ status = callda(xoptions, (long) i, popt); /* check returned value */ if (status == -1L) return XPRS_FAILURE; i = 1; /* Skip header */ if (status & (1L << i)) tempvar.ConvertFlag = XprBoolTrue(opt[i].xpro_value) ? 1 : 0; i++; if (status & (1L << i)) tempvar.GetFlag = XprBoolTrue(opt[i].xpro_value) ? 1 : 0; i++; if (xfinfo == NULL || tempvar.TextFlag == -1) { if (status & (1L << i)) tempvar.TextFlag = XprBoolTrue(opt[i].xpro_value) ? 1 : 0; i++; } if (status & (1L << i)) tempvar.MaxPacket = atoi(opt[i].xpro_value); i++; if (status & (1L << i)) tempvar.BlockCheckType = atoi(opt[i].xpro_value); i++; if (status & (1L << i)) tempvar.RetryLimit = atoi(opt[i].xpro_value); i++; if (status & (1L << i)) tempvar.Timeout = atoi(opt[i].xpro_value); } else { if (IO->xpr_filename != NULL) strcpy(buf, IO->xpr_filename); /* Save setup string */ else { /* Prompt for command/options string */ sprintf(buf, "OC%c,G%c,T%c,P%d,B%d,R%d,O%d", tempvar.ConvertFlag ? 'Y' : 'N', tempvar.GetFlag ? 'Y' : 'N', tempvar.TextFlag ? 'Y' : 'N', tempvar.MaxPacket, tempvar.BlockCheckType, tempvar.RetryLimit, tempvar.Timeout); if (callaa(xgets, "Kermit Options", buf) == 0) return XPRS_FAILURE; /* Failed to set up? */ } switch (buf[0]) { case 'F': return (KermitFinish(IO) ? SUCCESS : XPRS_FAILURE); case 'B': return (KermitBye(IO) ? SUCCESS : XPRS_FAILURE); case 'C': return (KermitCd(IO, buf + 1) ? SUCCESS : XPRS_FAILURE); case 'O': if (SetupFromString(IO, buf, &tempvar) == 0) return XPRS_FAILURE; break; case '\0': break; default: ioerr(IO, "Unrecognized XPR Kermit setup string"); break; } } *sv = tempvar; /* Copy setups into safe place. */ /* * Return success and inform caller that we don't need a requester for * receive. */ return SUCCESS; } static char Delimiters[] = " \t\r\n,"; SetupFromString(IO, s, sv) struct XPR_IO *IO; char *s; SetupVars *sv; { char *p, *strtok(); char errbuf[50]; if (*s != 'O') return 0; /* Options string must start with O. */ s++; /* Skip leading O. */ /* * Hunt for options with strtok. We allow whitespace and commas to * separate options. */ for (p = strtok(s, Delimiters); p != NULL; p = strtok(NULL, Delimiters)) { switch (*p++) { /* Auto-increment to option */ case 'C': /* Case conversion. */ if (*p == 'Y' || *p == 'N') sv->ConvertFlag = (*p == 'Y'); else ioerr(IO, "Illegal C option format (must be Y or N)"); break; case 'G': /* Get files (Host server) */ if (*p == 'Y' || *p == 'N') sv->GetFlag = (*p == 'Y'); else ioerr(IO, "Illegal G option format (must be Y or N)"); break; case 'T': /* Text file */ if (*p == 'Y' || *p == 'N') sv->TextFlag = (*p == 'Y'); else ioerr(IO, "Illegal T option format (must be Y or N)"); break; case 'P': /* Maximum packet length */ sv->MaxPacket = atoi(p); break; case 'B': /* Block check type. */ sv->BlockCheckType = atoi(p); break; case 'R': /* Retry limit */ sv->RetryLimit = atoi(p); break; case 'O': /* Timeout */ sv->Timeout = atoi(p); break; default: sprintf(errbuf, "Illegal XPR Kermit option: %c", p[-1]); ioerr(IO, errbuf); break; } } return 1; } /** * * Cleanup * **/ long XProtocolCleanup(IO) struct XPR_IO *IO; { if (IO->xpr_data) free(IO->xpr_data); IO->xpr_data = NULL; return (1L); } int XPRParity(IO) struct XPR_IO *IO; { long (*xsetserial) (); struct XPR_UPDATE xpru; long status; /* check out parity */ if (xsetserial = IO->xpr_setserial) { status = calld(xsetserial, -1L); if (status & 0x00000001L) return 1; else return 0; } else return 0; /* Assume no parity if can't tell. */ } void XPRLong(IO, i) struct XPR_IO *IO; long i; { long (*xupdate) (); struct XPR_UPDATE xpru; char locbuf[80]; if ((xupdate = IO->xpr_update) == NULL) return; /* debug: show long value */ xpru.xpru_updatemask = XPRU_MSG; sprintf(locbuf, "%lx", i); xpru.xpru_msg = &locbuf[0]; calla(xupdate, &xpru); } /* * Copy setup variables into the local copies. If there are none, then * simply copy the defaults. */ static SetupVars * setup(IO) struct XPR_IO *IO; { register SetupVars *Current; /* * These are the call-backs we need. If any of them isn't provided, quit. * Could do some error reporting if at least xupdate is there. */ if ((xupdate = IO->xpr_update) == NULL) return (0L); if ((xswrite = IO->xpr_swrite) == NULL) return (0L); if ((xfopen = IO->xpr_fopen) == NULL) return (0L); if ((xfclose = IO->xpr_fclose) == NULL) return (0L); if ((xfread = IO->xpr_fread) == NULL) return (0L); if ((xsread = IO->xpr_sread) == NULL) return (0L); if ((xchkabort = IO->xpr_chkabort) == NULL) return (0L); if ((xfnext = IO->xpr_fnext) == NULL) return (0L); if ((xffirst = IO->xpr_ffirst) == NULL) return (0L); if ((xsflush = IO->xpr_sflush) == NULL) return (0L); if ((xfwrite = IO->xpr_fwrite) == NULL) return (0L); if ((xgets = IO->xpr_gets) == NULL) return (0L); xfinfo = IO->xpr_finfo; if (IO->xpr_data == NULL) { if ((IO->xpr_data = (long *) malloc((unsigned) sizeof(SetupVars))) == NULL) { ioerr(IO, "Out of memory!"); return NULL; } Current = (SetupVars *) IO->xpr_data; *Current = Defaults; } else Current = (SetupVars *) IO->xpr_data; parity = Current->ParityFlag = XPRParity(IO); text = Current->TextFlag; convert = Current->ConvertFlag; urpsiz = Current->MaxPacket; bctr = Current->BlockCheckType; limit = Current->RetryLimit; rtimo = Current->Timeout; getfile = Current->GetFlag; return Current; } /* * Have the comm program display an error message for us, using a temporary * XPR_UPDATE structure; used to display errors before Vars gets allocated */ void ioerr(IO, msg) struct XPR_IO *IO; char *msg; { struct XPR_UPDATE xpru; long (*xupdate) (); if (xupdate = IO->xpr_update) { xpru.xpru_updatemask = XPRU_ERRORMSG; xpru.xpru_errormsg = msg; (void) calla(xupdate, &xpru); } } #if AZTEC_C /* * Simple, re-entrant versions of malloc() and free to replace the ones in * the Aztec C libraries. The only reason to use these instead of AllocMem() * is that these remember the size of the stuff allocated. */ char *malloc(n) unsigned n; { long *p; if ((p = AllocMem((long) n + sizeof(long), MEMF_PUBLIC | MEMF_CLEAR)) == NULL) return NULL; p[0] = n; return ((char *) &p[1]); } free(p) char *p; { long *s = (long *) p; FreeMem(&s[-1], s[-1]); return 1; } #endif /** * * The following functions setup the proper registers for the call-back * functions. * **/ #ifndef _lint #asm public _callad _callad: movea.l 8(sp),a0 ; Second argument goes in a0 move.l 12(sp),d0 ; Third argument goes in d0 /* * Now this is a trick to avoid using another register. * Charlie taught me this... */ move.l 4(sp),-(sp) ; First argument is function rts public _calladda _calladda: movea.l 8(sp),a0 ; Second argument goes in a0 move.l 12(sp),d0 ; Third argument goes in d0 move.l 16(sp),d1 ; Fourth argument goes in d1 movea.l 20(sp),a1 ; Fifth argument goes in a1 move.l 4(sp),-(sp) ; First argument is function rts public _calldaa _calldaa: move.l 8(sp),d0 ; Second argument goes in d0 movea.l 12(sp),a0 ; Third argument goes in a0 movea.l 16(sp),a1 ; Fourth argument goes in a1 move.l 4(sp),-(sp) ; First argument is function rts public _calla _calla: movea.l 8(sp),a0 ; Second argument goes in a0 move.l 4(sp),-(sp) ; First argument is function rts public _calld _calld: move.l 8(sp),d0 ; Second argument goes in d0 move.l 4(sp),-(sp) ; First argument is function rts public _callaa _callaa: movea.l 8(sp),a0 ; Second argument goes in a0 movea.l 12(sp),a1 ; Third argument goes in a1 move.l 4(sp),-(sp) ; First argument is function rts public _callda _callda: move.l 8(sp),d0 ; Second argument goes in d0 movea.l 12(sp),a0 ; Third argument goes in a0 move.l 4(sp),-(sp) ; First argument is function rts public _calladd _calladd: movea.l 8(sp),a0 ; Second argument goes in a0 move.l 12(sp),d0 ; Third argument goes in d0 move.l 16(sp),d1 ; Fourth argument goes in d1 move.l 4(sp),-(sp) ; First argument is function rts #endasm /* * Could have added any other functions needed for other call-backs. * Could have written a fancier single one... Could've... */ #endif