[net.sources] New UNIX Kermit

tsc2597@acf4.UUCP (Sam Chin) (02/28/85)

Here is the much asked for new version of KERMIT for BSD 4.2.
*** Warning *** This is supposedly a prerelease version but it
seems to work fine as far as i've used it - which isn't much. This
is the first of five shar files. I tried to keep them small but some
of the files were 60K in length and I didn't want to split them. I
hope the number of requests for this justifies the posting. Let's hope
Okstate reads this and doesn't attempt a simultaneous posting.

                                         Sam Chin
                                         allegra!cmcl2!acf4!tsc2597
                                         tsc2597@nyu-acf4

------------------ C U T ------ H E R E ----------------------------
# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# Wrapped by acf4!tsc2597  on Wed Feb 27 22:18:35 EST 1985
# Contents:  ckcmd.c ckcmd.h ckconu.c ckdebu.h ckermi.ann
 
echo x - ckcmd.c
sed 's/^@//' > "ckcmd.c" <<'@//E*O*F ckcmd.c//'
char *cmdv = "Unix cmd package V1.0(014) 1 Feb 85";

/*  C K C M D  --  Interactive command package for Unix  */
/*
 Modelled after the DECSYSTEM-20 command parser (the COMND JSYS)

 Features:
 . parses and verifies keywords, text strings, numbers, and other data
 . displays appropriate menu or help message when user types "?"
 . does keyword and filename completion when user types ESC
 . accepts any unique abbreviation for a keyword
 . allows keywords to have attributes, like "invisible"
 . can supply defaults for fields omitted by user
 . provides command line editing (character, word, and line deletion)
 . accepts input from keyboard, command files, or redirected stdin
 . allows for full or half duplex operation, character or line input
 . settable prompt, protected from deletion

 Functions:
  cmsetp - Set prompt
  cmsavp - Save current prompt
  prompt - Issue prompt
  cmini  - Clear the command buffer (before parsing a new command)
  cmres  - Reset command buffer pointers (before reparsing)
  cmkey  - Parse a keyword
  cmnum  - Parse a number
  cmifi  - Parse an input file name
  cmofi  - Parse an output file name
  cmfld  - Parse an arbitrary field
  cmtxt  - Parse a text string
  cmcfm  - Parse command confirmation (end of line)

 Return codes:
  -3: no input provided when required
  -2: input was invalid
  -1: reparse required (user deleted into a preceding field)
   0 or greater: success
  See individual functions for greater detail.

 Before using these routines, the caller should #include ckcmd.h, and
 set the program's prompt by calling cmsetp().  If the file parsing
 functions cmifi and cmofi are to be used, this module must be linked
 with a ckz??? file system support module for the appropriate system,
 e.g. ckzbsd for Berkeley Unix.  If the caller puts the terminal in
 character wakeup ("cbreak") mode with no echo, then these functions will
 provide line editing -- character, word, and line deletion, as well as
 keyword and filename completion upon ESC and help strings, keyword, or
 file menus upon '?'.  If the caller puts the terminal into character
 wakeup/noecho mode, care should be taken to restore it before exit from
 or interruption of the program.  If the character wakeup mode is not
 set, the system's own line editor may be used.

 Author: Frank da Cruz (SY.FDC@CU20B),
 Columbia University Center for Computing Activities, Jan 1985.
 Copyright (C) 1985, Trustees of Columbia University in the City of New York.
 Permission is granted to any individual or institution to copy or use this
 software except for explicitly commercial purposes, provided this copyright
 notice is retained.
*/

/* Includes */

#include <stdio.h>			/* Standard C I/O package */
#include <ctype.h>			/* Character types */
#include "ckcmd.h"			/* Command parsing definitions */
#include "ckdebu.h"			/* Formats for debug() */

/* Local variables */

int psetf = 0,				/* Flag that prompt has been set */
    cc = 0,				/* Character count */
    dpx = 0;				/* Duplex (0 = full) */

int hw = HLPLW,				/* Help line width */
    hc = HLPCW,				/* Help line column width */
    hh,					/* Current help column number */
    hx;					/* Current help line position */

#define PROML 60			/* Maximum length for prompt */

char cmprom[PROML+1];			/* Program's prompt */
char *dfprom = "Command? ";		/* Default prompt */

int cmflgs;				/* Command flags */

char cmdbuf[CMDBL+4];			/* Command buffer */
char hlpbuf[HLPBL+4];			/* Help string buffer */
char atmbuf[ATMBL+4];			/* Atom buffer */
char filbuf[ATMBL+4];			/* File name buffer */

/* Command buffer pointers */

static char *bp,			/* Current command buffer position */
    *pp,				/* Start of current field */
    *np;				/* Start of next field */

/*  C M S E T P  --  Set the program prompt.  */

cmsetp(s) char *s; {
    char *strncpy();
    psetf = 1;				/* Flag that prompt has been set. */
    strncpy(cmprom,s,PROML - 1);	/* Copy the string. */
    cmprom[PROML] = NUL;
}


/*  C M S A V P  --  Save a copy of the current prompt.  */

cmsavp(s,n) int n; char s[]; {
    strncpy(s,cmprom,n-1);
    s[n] = NUL;
}


/*  P R O M P T  --  Issue the program prompt.  */

prompt() {
    if (psetf == 0) cmsetp(dfprom);	/* If no prompt set, set default. */
    printf("\r%s",cmprom);		/* Print the prompt. */
}


/*  C M R E S  --  Reset pointers to beginning of command buffer.  */

cmres() {  
    cc = 0;				/* Reset character counter. */
    pp = np = bp = cmdbuf;		/* Point to command buffer. */
    cmflgs = -5;			/* Parse not yet started. */
}


/*  C M I N I  --  Clear the command and atom buffers, reset pointers.  */

/*
The argument specifies who is to echo the user's typein --
  1 means the cmd package echoes
  0 somebody else (system, front end, terminal) echoes
*/

cmini(d) int d; {
    for (bp = cmdbuf; bp < cmdbuf+CMDBL; bp++) *bp = NUL;
    *atmbuf = NUL;
    dpx = d;
    cmres();
}

/*  C M N U M  --  Parse a number in the indicated radix  */

/*  For now, only works for positive numbers in base 10.  */

/*
 Returns
   -3 if no input present when required,
   -2 if user typed an illegal number,
   -1 if reparse needed,
    0 otherwise, with n set to number that was parsed
*/
cmnum(xhlp,xdef,radix,n) char *xhlp, *xdef; int radix, *n; {
    int x; char *s;

    if (radix != 10) {			/* Just do base 10 for now */
	printf("cmnum: illegal radix - %d\n",radix);
	return(-1);
    }

    x = cmfld(xhlp,xdef,&s);
    debug(F101,"cmnum: cmfld","",x);
    if (x < 0) return(x);    /* Parse a field */

    if (digits(atmbuf)) {		/* Convert to number */
	*n = atoi(atmbuf);
	return(x);
    } else {
	printf("\n?not a number - %s\n",s);
	return(-2);	
    }
}

/*  C M O F I  --  Parse the name of an output file  */

/*
 Depends on the external function zchko(); if zchko() not available, use
 cmfld() to parse output file names.

 Returns
   -3 if no input present when required,
   -2 if permission would be denied to create the file,
   -1 if reparse needed,
    0 or 1 otherwise, with xp pointing to name.
*/
cmofi(xhlp,xdef,xp) char *xhlp, *xdef, **xp; {
    int x; char *s;

    if (*xhlp == NUL) xhlp = "Output file";
    *xp = "";

    if ((x = cmfld(xhlp,xdef,&s)) < 0) return(x);

    if (chkwld(s)) {
	printf("\n?Wildcards not allowed - %s\n",s);
	return(-2);
    }
    if (zchko(s) < 0) {
	printf("\n?Write permission denied - %s\n",s);
	return(-2);
    } else {
	*xp = s;
	return(x);
    }
}

/*  C M I F I  --  Parse the name of an existing file  */

/*
 This function depends on the external functions:
   zchki()  - Check if input file exists and is readable.
   zxpand() - Expand a wild file specification into a list.
   znext()  - Return next file name from list.
 If these functions aren't available, then use cmfld() to parse filenames.
*/
/*
 Returns
   -4 EOF
   -3 if no input present when required,
   -2 if file does not exist or is not readable,
   -1 if reparse needed,
    0 or 1 otherwise, with:
	xp pointing to name,
    	wild = 1 if name contains '*' or '?', 0 otherwise.
*/
cmifi(xhlp,xdef,xp,wild) char *xhlp, *xdef, **xp; int *wild; {
    int i, x, xc, y; char *sp;

    cc = xc = 0;			/* Initialize counts & pointers */
    *xp = "";
    if ((x = cmflgs) != 1) {		/* Already confirmed? */
	x = getwd();			/* No, get a word */
    } else {
	cc = setatm(xdef);		/* If so, use default, if any. */
    }
    *xp = atmbuf;			/* Point to result. */
    *wild = chkwld(*xp);

    while (1) {
	xc += cc;			/* Count the characters. */
	debug(F111,"cmifi: getwd",atmbuf,xc);
    	switch (x) {
	    case -4:			/* EOF */
	    case -2:			/* Out of space. */
	    case -1:			/* Reparse needed */
	    	return(x);

/* cont'd... */

/* ...cmifi(), cont'd */


	    case 0:			/* SP or NL */
	    case 1:
	    	if (xc == 0) *xp = xdef;    /* If no input, return default. */
		else *xp = atmbuf;
		if (**xp == NUL) return(-3); /* If field empty, return -3. */
		
		/* If filespec is wild, see if there are any matches */

		*wild = chkwld(*xp);
		debug(F101," *wild","",*wild);
		if (*wild != 0) {
		    y = zxpand(*xp);
		    if (y == 0) {
			printf("\n?No files match - %s\n",*xp);
			return(-2);
		    } else if (y < 0) {
			printf("\n?Too many files match - %s\n",*xp);
			return(-2);
		    } else return(x);
		}

		/* If not wild, see if it exists and is readable. */

		y = zchki(*xp);
		if (y == -3) {
		    printf("\n?Read permission denied - %s\n",*xp);
		    return(-2);
		} else if (y == -2) {
		    printf("\n?File not readable - %s\n",*xp);
		    return(-2);
		} else if (y < 0) {
		    printf("\n?File not found - %s\n",*xp);
		    return(-2);
		}
		return(x);
/* cont'd... */

/* ...cmifi(), cont'd */


	    case 2:			/* ESC */
	    	if (xc == 0) {
		    if (*xdef != '\0') {
			printf("%s ",xdef); /* If at beginning of field, */
			addbuf(xdef);	/* supply default. */
			cc = setatm(xdef);
		    } else {		/* No default */
			putchar(BEL);
		    }
		    break;
		} 
		if (*wild = chkwld(*xp)) {  /* No completion if wild */
		    putchar(BEL);
		    break;
		}
		sp = atmbuf + cc;
		*sp++ = '*';
		*sp-- = '\0';
		y = zxpand(atmbuf);	/* Add * and expand list. */
		*sp = '\0';		/* Remove *. */

		if (y == 0) {
		    printf("\n?No files match - %s\n",atmbuf);
		    return(-2);
		} else if (y < 0) {
		    printf("\n?Too many files match - %s\n",atmbuf);
		    return(-2);
		} else if (y > 1) {	/* Not unique, just beep. */
		    putchar(BEL);
		} else {		/* Unique, complete it.  */
		    znext(filbuf);	/* Get whole name of file. */
		    sp = filbuf + cc;	/* Point past what user typed. */
		    printf("%s ",sp);	/* Complete the name. */
		    addbuf(sp);		/* Add the characters to cmdbuf. */
		    setatm(pp);		/* And to atmbuf. */
		    *xp = atmbuf;	/* Return pointer to atmbuf. */
		    return(cmflgs = 0);
		}
		break;

/* cont'd... */

/* ...cmifi(), cont'd */


	    case 3:			/* Question mark */
	    	if (*xhlp == NUL)
	    	    printf(" Input file specification");
		else
		    printf(" %s",xhlp);
		if (xc > 0) {
		    sp = atmbuf + cc;	/* Insert * at end */
		    *sp++ = '*';
		    *sp-- = '\0';
		    y = zxpand(atmbuf);
		    *sp = '\0';
		    if (y == 0) {		    
			printf("\n?No files match - %s\n",atmbuf);
			return(-2);
		    } else if (y < 0) {
			printf("\n?Too many file match - %s\n",atmbuf);
			return(-2);
		    } else {
			printf(", one of the following:\n");
			clrhlp();
			for (i = 0; i < y; i++) {
			    znext(filbuf);
			    addhlp(filbuf);
			}
			dmphlp();
		    }
		} else printf("\n");
		printf("%s%s",cmprom,cmdbuf);
		break;
	}
    x = getwd();
    }
}



/*  C H K W L D  --  Check for wildcard characters '*' or '?'  */

chkwld(s) char *s; {

    for ( ; *s != '\0'; s++) {
    	if ((*s == '*') || (*s == '?'))
	    return(1);
    }
    return(0);
}

/*  C M F L D  --  Parse an arbitrary field  */
/*
 Returns
   -3 if no input present when required,
   -2 if field too big for buffer,
   -1 if reparse needed,
    0 otherwise, xp pointing to string result.
*/
cmfld(xhlp,xdef,xp) char *xhlp, *xdef, **xp; {
    int x, xc;

    cc = xc = 0;			/* Initialize counts & pointers */
    *xp = "";
    if ((x = cmflgs) != 1) {		/* Already confirmed? */
	x = getwd();			/* No, get a word */
    } else {
	cc = setatm(xdef);		/* If so, use default, if any. */
    }
    *xp = atmbuf;			/* Point to result. */

    while (1) {
	xc += cc;			/* Count the characters. */
	debug(F111,"cmfld: getwd",atmbuf,xc);
	debug(F101," x","",x);
    	switch (x) {
	    case -4:			/* EOF */
	    case -2:			/* Out of space. */
	    case -1:			/* Reparse needed */
	    	return(x);
	    case 0:			/* SP or NL */
	    case 1:
	    	if (xc == 0) *xp = xdef;    /* If no input, return default. */
		else *xp = atmbuf;
		if (**xp == NUL) x = -3;    /* If field empty, return -3. */
		return(x);
	    case 2:			/* ESC *** (maybe treat as SP) */
	    	if (xc == 0) {
		    printf("%s ",xdef);	/* If at beginning of field, */
		    addbuf(xdef);	/* supply default. */
		    cc = setatm(xdef);
		} else {
		    putchar(BEL);	/* Beep if already into field. */
    	    	}		    
		break;
	    case 3:			/* Question mark */
	    	if (*xhlp == NUL)
		    printf(" Please complete this field");
		else
	            printf(" %s",xhlp);
		printf("\n%s%s",cmprom,cmdbuf);
		break;
	}
    x = getwd();
    }
}

/*  C M T X T  --  Get a text string, including confirmation  */

/*
  Print help message 'xhlp' if ? typed, supply default 'xdef' if null
  string typed.  Returns

   -1 if reparse needed or buffer overflows.
    1 otherwise.

  with cmflgs set to return code, and xp pointing to result string.
*/

cmtxt(xhlp,xdef,xp) char *xhlp; char *xdef; char **xp; {

    int x, xc;

    cc = xc = 0;			/* Start counters off at 0 */
    *xp = "";				/* And pointer to null string. */
    *atmbuf = NUL;			/* And empty atom buffer. */
    if ((x = cmflgs) != 1) {
	x = getwd();			/* Get first word. */
	*xp = pp;			/* Save pointer to it. */
    }
    while (1) {
	xc += cc;			/* Accumulate count. */
	debug(F111,"cmtxt: getwd",atmbuf,xc);
	switch (x) {
	    case -4:			/* EOF */
	    case -2:			/* Overflow */
	    case -1:			/* Deletion */
	        return(x);
	    case 0:			/* Space */
		break;
	    case 1:			/* CR or LF */
	        if (xc == 0) *xp = xdef;
		return(x);
	    case 2:			/* ESC */
	    	if (xc == 0) {
		    printf("%s ",xdef);
		    cc = addbuf(xdef);
		} else {
		    putchar(BEL);
		}
		break;
	    case 3:			/* Question Mark */
	    	if (*xhlp == NUL)
		    printf(" Text string");
		else
		    printf(" %s",xhlp);
		printf("\n%s%s",cmprom,cmdbuf);
		break;
            default:
	    	printf("\n?Unexpected return code from getwd() - %d\n",x);
		return(-2);
        }
	x = getwd();
    }
}

/*  C M K E Y  --  Parse a keyword  */

/*
 Call with:
   table    --  keyword table, in 'struct keytab' format;
   n        --  number of entries in table;
   xhlp     --  pointer to help string;
   xdef     --  pointer to default keyword;

 Returns:
   -3       --  no input supplied and no default available
   -2       --  input doesn't uniquely match a keyword in the table
   -1       --  user deleted too much, command reparse required
    n >= 0  --  value associated with keyword
*/

cmkey(table,n,xhlp,xdef) struct keytab table[]; int n; char *xhlp, *xdef; {
    int i, y, z, zz, xc;
    char *xp;

    xc = cc = 0;			/* Clear character counters. */

    if ((zz = cmflgs) == 1) 		/* Command already entered? */
	setatm(xdef);
    else zz = getwd(); 

debug(F101,"cmkey: table length","",n);
while (1) {
    xc += cc;
    debug(F111,"cmkey: getwd",atmbuf,xc);

    switch(zz) {
	case -4:			/* EOF */
	case -2:			/* Buffer overflow */
    	case -1:			/* Or user did some deleting. */
	    return(zz);

	case 0:				/* User terminated word with space */
	case 1:				/* or newline */
	    if (cc == 0) setatm(xdef);
	    y = lookup(table,atmbuf,n,&z);
	    switch (y) {
		case -2:
		    printf("\n?Ambiguous - %s\n",atmbuf);
		    return(cmflgs = -2);
		case -1:
		    printf("\n?Invalid - %s\n",atmbuf);
		    return(cmflgs = -2);
		default:
		    break;
	    }
	    return(y);

/* cont'd... */

/* ...cmkey(), cont'd */

	case 2:				/* User terminated word with ESC */
	    if (cc == 0) {
	    	if (*xdef != NUL) {	/* Nothing in atmbuf */
		    printf("%s ",xdef);	/* Supply default if any */
		    addbuf(xdef);
		    cc = setatm(xdef);
		    debug(F111,"cmkey: default",atmbuf,cc);
		} else {
		    putchar(BEL);	/* No default, just beep */
		    break;
		}
	    }
	    y = lookup(table,atmbuf,n,&z); /* Something in atmbuf */
	    debug(F111,"cmkey: esc",atmbuf,y);
	    if (y == -2) {
		putchar(BEL);
		break;
    	    }
	    if (y == -1) {
		printf("\n?Invalid - %s\n",atmbuf);
		return(cmflgs = -2);
	    }
	    xp = table[z].kwd + cc;
    	    printf("%s ",xp);
	    addbuf(xp);
	    debug(F110,"cmkey: addbuf",cmdbuf,0);
	    return(y);

/* cont'd... */

/* ...cmkey(), cont'd */

	case 3:				/* User terminated word with "?" */
	    y = lookup(table,atmbuf,n,&z);
	    if (y > -1) {
		printf(" %s\n%s%s",table[z].kwd,cmprom,cmdbuf);
		break;
	    } else if (y == -1) {
		printf("\n?Invalid\n");
		return(cmflgs = -2);
	    }

	    if (*xhlp == NUL)
	    	printf(" One of the following:\n");
	    else
	    	printf(" %s, one of the following:\n",xhlp);

	    clrhlp();
	    for (i = 0; i < n; i++) {	
		if (!strncmp(table[i].kwd,atmbuf,cc)
		    	&& !test(table[i].flgs,CM_INV))
		    addhlp(table[i].kwd);
	    }
	    dmphlp();
	    printf("%s%s", cmprom, cmdbuf);
	    break;

    	default:	    
	    printf("\n%d - Unexpected return code from getwd\n",zz);
	    return(cmflgs = -2);
        }
	zz = getwd();
    }
}

/*  C M C F M  --  Parse command confirmation (end of line)  */

/*
 Returns
   -2: User typed anything but whitespace or newline
   -1: Reparse needed
    0: Confirmation was received
*/

cmcfm() {
    int x, xc;

    debug(F101,"cmcfm: cmflgs","",cmflgs);

    xc = cc = 0;
    if (cmflgs == 1) return(0);

    while (1) {
	x = getwd();
	xc += cc;
	debug(F111,"cmcfm: getwd",atmbuf,xc);
        switch (x) {
	    case -4:			/* EOF */
	    case -2:
	    case -1:
		return(x);

	    case 0:			/* Space */
	    	continue;
	    case 1:			/* End of line */
	    	if (xc > 0) {
		    printf("?Not confirmed - %s\n",atmbuf);
		    return(-2);
    	    	} else return(0);		    
	    case 2:
	    	putchar(BEL);
		continue;

            case 3:
	    	if (xc > 0) {
		    printf("\n?Not confirmed - %s\n",atmbuf);
		    return(-2);
		}
	        printf("\n Type a carriage return to confirm the command\n");
		printf("%s%s",cmprom,cmdbuf);
		continue;
	}
    }
}

/* Keyword help routines */


/*  C L R H L P -- Initialize/Clear the help line buffer  */

clrhlp() {				/* Clear the help buffer */
    hlpbuf[0] = NUL;
    hh = hx = 0;
}


/*  A D D H L P  --  Add a string to the help line buffer  */

addhlp(s) char *s; {			/* Add a word to the help buffer */
    int j;

    hh++;				/* Count this column */

    for (j = 0; j < hc; j++) {		/* Fill the column */
	if (*s != NUL)			/* First with chars from the string */
	    hlpbuf[hx++] = *s++;
	else {
	    if (hh < (hw / hc))		/* Then with spaces */
	    	hlpbuf[hx++] = SP;
	    else {
		hlpbuf[hx++] = NUL;	/* If last column, no spaces. */
		dmphlp();		/* Print it. */
		return;
		}
    	} 
    }
    if (*s != NUL)			/* Still some chars left in string? */
	hlpbuf[hx-1] = '+';		/* Mark as too long for column. */
}


/*  D M P H L P  --  Dump the help line buffer  */

dmphlp() {				/* Print the help buffer */
    hlpbuf[hx++] = NUL;
    printf(" %s\n",hlpbuf);
    clrhlp();
}

/*  L O O K U P  --  Lookup the string in the given array of strings  */

/*
 Call this way:  v = lookup(table,word,n,&x);

   table - a 'struct keytab' table.
   word  - the target string to look up in the table.
   n     - the number of elements in the table.
   x     - address of an integer for returning the table array index.

 The keyword table must be arranged in ascending alphabetical order, and
 all letters must be lowercase.

 Returns the keyword's associated value ( zero or greater ) if found,
 with the variable x set to the array index, or:

  -3 if nothing to look up (target was null),
  -2 if ambiguous,
  -1 if not found.

 A match is successful if the target matches a keyword exactly, or if
 the target is a prefix of exactly one keyword.  It is ambiguous if the
 target matches two or more keywords from the table.
*/

lookup(table,cmd,n,x) char *cmd; struct keytab table[]; int n, *x; {

    int i, v, cmdlen;

/* Lowercase & get length of target, if it's null return code -3. */

    if ((((cmdlen = lower(cmd))) == 0) || (n < 1)) return(-3);

/* Not null, look it up */

    for (i = 0; i < n-1; i++) {
	if (!strcmp(table[i].kwd,cmd) ||
           ((v = !strncmp(table[i].kwd,cmd,cmdlen)) &&
             strncmp(table[i+1].kwd,cmd,cmdlen))) {
		*x = i;
		return(table[i].val);
	     }
	if (v) return(-2);
    }	

/* Last (or only) element */

    if (!strncmp(table[n-1].kwd,cmd,cmdlen)) {
	*x = n-1;
	return(table[n-1].val);
    } else return(-1);
}

/*  G E T W D  --  Gets a "word" from the command input stream  */

/*
Usage: retcode = getwd();

Returns:
 -4 if end of file (e.g. pipe broken)
 -2 if command buffer overflows
 -1 if user did some deleting
  0 if word terminates with SP or tab
  1 if ... CR
  2 if ... ESC
  3 if ... ?

With:
  pp pointing to beginning of word in buffer
  bp pointing to after current position
  atmbuf containing a copy of the word
  cc containing the number of characters in the word copied to atmbuf
*/
getwd() {

    int c;				/* Current char */
    static int inword = 0;		/* Flag for start of word found */
    int quote = 0;			/* Flag for quote character */
    int echof = 0;			/* Flag for whether to echo */

    pp = np;				/* Start of current field */
    debug(F101,"getwd: cmdbuf","",(int) cmdbuf);
    debug(F101," bp","",(int) bp);
    debug(F101," pp","",(int) pp);
    debug(F110," cmdbuf",cmdbuf,0);

    while (bp < cmdbuf+CMDBL) {		/* Loop */

	echof = 0;			/* Flag for whether to echo */

	if ((c = *bp) == NUL) {		/* Get next character */
	    if (dpx) echof = 1;		/* from reparse buffer */
	    c = getchar();		/* or from tty. */
	    if (c == EOF) return(-4);
	}

	if (quote == 0) {

	    if (c == '\\') {		/* Quote character */
	       quote = 1;
	       continue;
    	    }
	    if (c == FF) {		/* Formfeed. */
	    	c = NL;			/* Replace with newline */
	    	system("clear");	/* and clear the screen. */
	    }

	    if (c == HT) c = SP; 	/* Substitute space for tab. */

/* cont'd... */

/* ...getwd(), cont'd */

    	    if (c == SP) {		/* If space */
		*bp++ = c;		/* deposit it in buffer. */
		if (echof) putchar(c);	/* echo it. */
		if (inword == 0) {	/* If leading, gobble it. */
		    pp++;
		    continue;
		} else {		/* If terminating, return. */
		    np = bp;
		    setatm(pp);
		    inword = 0;
		    return(cmflgs = 0);
		}
	    }
	    if (c == NL) { 		/* CR, LF */
		*bp = NUL;		/* End the string */
		if (echof) putchar(c);	/* Echo the typein */
		np = bp;		/* Where to start next field. */
		setatm(pp);		/* Copy this field to atom buffer. */
		inword = 0;
		return(cmflgs = 1);
	    }
	    if (c == '?') { 		/* Question mark */
		putchar(c);
		*bp = NUL;
		setatm(pp);
		return(cmflgs = 3);
    	    }
	    if (c == ESC) { 		/* ESC */
		*bp = NUL;
		setatm(pp);
		return(cmflgs = 2);
	    }
	    if (c == BS || c == RUB) { 	/* Character deletion */
		if (bp > cmdbuf) {	/* If still in buffer... */
		    printf("\b \b");	/* erase character from screen, */
		    bp--;		/* point behind it, */
		    if (*bp == SP) inword = 0; /* Flag if current field gone */
		    *bp = NUL;		/* Erase character from buffer. */
		} else {		/* Otherwise, */
		    putchar(BEL);	/* beep, */
		    cmres();		/* and start parsing a new command. */
		}
		if (pp < bp) continue;
		else return(cmflgs = -1);
	    }
	    if (c == LDEL) { 		/* ^U, line deletion */
	    	while ((bp--) > cmdbuf) {
	    	    printf("\b \b");
		    *bp = NUL;
		}
		cmres();		/* Restart the command. */
		inword = 0;
		return(cmflgs = -2);
	    }

/* cont'd... */

/* ...getwd(), cont'd */

    	    if (c == WDEL) { 		/* ^W, word deletion */
	    	if (bp <= cmdbuf) {	/* Beep if nothing to delete */
		    putchar(BEL);
		    cmres();
		    return(cmflgs = -1);
		}
		bp--;
		for ( ; (bp >= cmdbuf) && (*bp == SP) ; bp--) {
		    printf("\b \b");
		    *bp = NUL;
		}
		for ( ; (bp >= cmdbuf) && (*bp != SP) ; bp--) {
		    printf("\b \b");
		    *bp = NUL;
		}
		*bp++ == NUL;
		inword = 0;
		return(cmflgs = -1);
	    }
	    if (c == RDIS) { 		/* ^R, redisplay */
		*bp = NUL;
		printf("\n%s%s",cmprom,cmdbuf);
		continue;
	    }
    	}
	if (echof) putchar(c);		/* If tty input, echo. */
	inword = 1;			/* Flag we're in a word. */
	quote = 0;			/* Turn off quote. */
	*bp++ = c;			/* And deposit it. */
    }					/* end of big while */
    putchar(BEL);			/* Get here if... */
    printf("\n?Buffer full\n");
    return(cmflgs = -2);
}

/* Utilility functions */

/* A D D B U F  -- Add the string pointed to by cp to the command buffer  */

addbuf(cp) char *cp; {
    int len = 0;
    while ((*cp != NUL) && (bp < cmdbuf+CMDBL)) {
    	*bp++ = *cp++;			/* Copy and */
    	len++;				/* count the characters. */
    }	
    *bp++ = SP;				/* Put a space at the end */
    *bp = NUL;				/* Terminate with a null */
    np = bp;				/* Update the next-field pointer */
    return(len);			/* Return the length */
}

/*  S E T A T M  --  Deposit a string in the atom buffer  */

setatm(cp) char *cp; {
    char *ap;
    cc = 0;
    ap = atmbuf;
    *ap = NUL;
    while (*cp == SP) cp++;
    while ((*cp != SP) && (*cp != NL) && (*cp != NUL)) {
	*ap++ = *cp++;
	cc++;
    }
    *ap++ = NUL;
    return(cc);				/* Return length */
}

/*  D I G I T S  -- Verify that all the characters in line are digits  */

digits(s) char *s; {
    while (*s) {
        if (!isdigit(*s)) return(0);
        s++;
    }
    return(1);
}

/*  L O W E R  --  Lowercase a string  */

lower(s) char *s; {
    int n = 0;
    while (*s) {
	if (isupper(*s)) *s = tolower(*s);
	s++, n++;
    }
    return(n);
}

/*  T E S T  --  Bit test  */

test(x,m) int x, m; { /*  Returns 1 if any bits from m are on in x, else 0  */
    return((x & m) ? 1 : 0);
}
@//E*O*F ckcmd.c//
chmod u=rw,g=r,o=r ckcmd.c
 
echo x - ckcmd.h
sed 's/^@//' > "ckcmd.h" <<'@//E*O*F ckcmd.h//'
/*  C K C M D . H  --  Header file for cmd package  */

/* Sizes of things */

#define HLPLW  78			/* Width of ?-help line */
#define HLPCW  19			/* Width of ?-help column */
#define CMDBL  200			/* Command buffer length */
#define HLPBL  100			/* Help string buffer length */
#define ATMBL  100			/* Command atom buffer length*/

/* Special characters */

#define NUL  '\0'			/* Null */
#define HT   '\t'			/* Horizontal Tab */
#define NL   '\n'			/* Newline */
#define FF   0014			/* Formfeed    (^L) */
#define RDIS 0022			/* Redisplay   (^R) */
#define LDEL 0025			/* Delete line (^U) */
#define WDEL 0027			/* Delete word (^W) */
#define ESC  0033			/* Escape */
#define RUB  0177			/* Rubout */

#ifndef BEL
#define BEL  0007			/* Bell */
#endif

#ifndef BS
#define BS   0010			/* Backspace */
#endif

#ifndef SP
#define SP   0040			/* Space */
#endif

/* Keyword table flags */

#define CM_INV 1			/* Invisible keyword */

/* Keyword Table Template */

struct keytab {				/* Keyword table */
    char *kwd;				/* Pointer to keyword string */
    int val;				/* Associated value */
    int flgs;				/* Flags (as defined above) */
};
@//E*O*F ckcmd.h//
chmod u=rw,g=r,o=r ckcmd.h
 
echo x - ckconu.c
sed 's/^@//' > "ckconu.c" <<'@//E*O*F ckconu.c//'
char *connv = "Connect Command for Unix, V4.0(002) 24 Jan 85";

/*  C O N N E C T  --  Dumb terminal connection to remote system  */

/*
 This module should work under all versions of Unix.  It calls externally
 defined system-depended functions for i/o, but depends upon the existence
 of the fork() function.
*/

#include "ckermi.h"

extern int local, speed, escape, handsh, duplex, parity, flow, seslog;
extern char ttname[], sesfil[];

int i, active;				/* Variables global to this module */
char *chstr();
#define LBUFL 100			/* Line buffer */
char lbuf[LBUFL];

/*  C O N E C T  --  Perform terminal connection  */

conect() {
    int pid, n;
    char c;
    char errmsg[50], *erp;

	if (!local) {
	    printf("Sorry, you must 'set line' first\n");
	    return;
	}
	if (speed < 0) {
	    printf("Sorry, you must 'set speed' first\n");
	    return;
        }
	if ((escape < 0) || (escape > 0177)) {
	    printf("Your escape character is not ASCII - %d\n",escape);
	    return;
	}
	if (ttopen(ttname) < 0) {
	    erp = errmsg;
	    sprintf(erp,"Sorry, can't open %s",ttname);
	    perror(errmsg);
	    return;
    	}
    	printf("Connecting thru %s, speed %d.\r\n",ttname,speed);
	printf("The escape character is %s (%d).\r\n",chstr(escape),escape);
	printf("Type the escape character followed by C to get back,\r\n");
	printf("or followed by ? to see other options.\r\n");
	if (seslog) printf("(Session logged to %s.)\r\n",sesfil);

/* cont'd... */

/* ...connect, cont'd */


/* Condition console terminal and communication line */	    

    	if (conbin() < 0) {
	    printf("Sorry, can't condition console terminal\n");
	    return;
    	}
	if (ttvt(speed,flow) < 0) {
	    conres();
	    printf("Sorry, Can't condition communication line\n");
	    return;
    	}
	pid = fork();			/* All ok, make a fork */
	if (pid) {			
	    active = 1;			/* This fork reads, sends keystrokes */
	    while (active) {
		c = coninc() & 0177;
		if (c == escape) {   	/* Look for escape char */
		    c = coninc() & 0177;
		    doesc(c);
		} else {		/* Ordinary character */
		    ttoc(dopar(c));	/* Send it out with desired parity */
		    if (duplex) {	/* Half duplex? */
			conoc(c);	/* Yes, also echo it. */
			if (seslog) zchout(ZSFILE,c);	/* And maybe log it. */
    	    	    }			
		}
    	    }
	    kill(pid,9);		/* Done, kill inferior. */
	    wait(0);			/* Wait till gone. */
	    conres();			/* Reset the console. */
	    ttclos();			/* Reset & close communication line. */
	    printf("C-Kermit Disconnected\n");
	    return;
	} else {			/* Inferior reads, prints port input */
	    while (1) {
		c = ttinc(0) & 0177;	/* Wait for a character. */
		conoc(c);
		if (seslog) zchout(ZSFILE,c);
		n = ttchk();		/* Any more left in buffer? */
		if (n > 0) {
		    if (n > LBUFL) n = LBUFL;  /* Get them all at once. */
		    if ((n = ttxin(n,lbuf)) > 0) {
			for (i = 0; i < n; i++) lbuf[i] &= 0177;
			conxo(n,lbuf);
			if (seslog) zsoutx(ZSFILE,lbuf,n);
		    }
	    	}
	    }
    	}
}

/*  H C O N N E  --  Give help message for connect.  */

hconne() {
    int c;
    char *hlpmsg = "\
\r\nC to close the connection, or:\
\r\n  S for status\
\r\n  ? for help\
\r\n  B to send a BREAK\
\r\n  0 to send a null\
\r\n escape character twice to send the escape character.\r\n\r\n";

    conol(hlpmsg);			/* Print the help message. */
    conol("Command>");			/* Prompt for command. */
    c = coninc();
    conoc(c);				/* Echo it. */
    conoll("");
    c &= 0177;				/* Strip any parity. */
    return(c);				/* Return it. */
}


/*  C H S T R  --  Make a printable string out of a character  */

char *
chstr(c) int c; {
    static char s[8];
    char *cp = s;

    if (c < SP) {
	sprintf(cp,"CTRL-%c",ctl(c));
    } else sprintf(cp,"'%c'\n",c);
    cp = s;
    return(cp);
}

/*  D O E S C  --  Process an escape character argument  */

doesc(c) char c; {
    int d;
  
    c &= 0177;
    while (1) {
	if (c == escape) {		/* Send escape character */
	    d = dopar(c);
	    ttoc(d);
	    return;
    	} else				/* Or else look it up below. */
	    if (isupper(c)) c = tolower(c);

	switch (c) {

	case 'c':			/* Close connection */
	case '\03':
	    active = 0;
	    conol("\r\n");
	    return;

	case 'b':			/* Send a BREAK */
	case '\02':
	    ttsndb();
	    return;

	case 's':			/* Status */
	case '\023':
	    conol("\r\nConnected thru ");
	    conoll(ttname);
	    if (seslog) {
		conol(", logging to ");
		conol(sesfil);
            }
	    return;

	case '?':			/* Help */
	    c = hconne();
	    continue;

	case '0':			/* Send a null */
	    c = '\0';
	    d = dopar(c);
	    ttoc(d);
	    return;

	case SP:			/* Space, ignore */
	    return;

	default:			/* Other */
	    conoc(BEL); 		/* Invalid esc arg, beep */
	    return;
    	}	    
    }
}    
@//E*O*F ckconu.c//
chmod u=rw,g=r,o=r ckconu.c
 
echo x - ckdebu.h
sed 's/^@//' > "ckdebu.h" <<'@//E*O*F ckdebu.h//'
#define F000 0				/* Formats for debug() */

#define F001 1
#define F010 2
#define F011 3
#define F100 4
#define F101 5
#define F110 6
#define F111 7

@//E*O*F ckdebu.h//
chmod u=rw,g=r,o=r ckdebu.h
 
echo x - ckermi.ann
sed 's/^@//' > "ckermi.ann" <<'@//E*O*F ckermi.ann//'
 5-Feb-85 16:10:21-EST,5392;000000000001
Mail-From: SY.FDC created at  5-Feb-85 16:09:41
Date: Tue 5 Feb 85 16:09:41-EST
@From: Frank da Cruz <SY.FDC@CU20B.ARPA>
Subject: Info-Kermit Digest V2 #1 -- New Unix Kermit
To: Info-Kermit-Members@CU20B.ARPA
cc: Info-Unix@BRL-TGR.ARPA
Reply-To: Info-Kermit@CU20B
Queries-To: Info-Kermit-Request@CU20B

Info-Kermit Digest         Tue,  5 Feb 1985       Volume 2 : Number  1

  ANNOUNCEMENTS -
      New Unix Kermit Available for Testing

----------------------------------------------------------------------

My apologies for the long delay since the last issue of the Info-Kermit
Digest, which was Vol.1, No.46, dated 31 December 1984.  This first issue
of Volume 2 is to announce a test release of the new Unix Kermit.  In
subsequent issues, I'll attempt to catch up on other overdue items.

A new Kermit program has been written in C, initially for 4.2 Berkeley Unix.
The features of this program include:

@. Full implementation of the Kermit protocol, except for Attribute packets:
  - Acts as server
  - Talks to server
  - All packet encoding and error checking options are provided
  - File transfer interruption
  - Filename collision avoidance
  - Binary and text file transfer
@. Modular construction for easy portability to other systems
@. An interactive command parser as well as Unix-style command line arguments
@. Command and initialization files
@. Piped operation
@. Improved terminal connect, with optional logging
@. Logs for debugging, packets, and transactions
@. Communication with IBM mainframes

Several items on the wish list were not done for lack of time.  They will
probably be added in the future:

@. File attributes
@. Command macros
@. Login scripts
@. Raw file transmit

The new program is called "C-Kermit" because it is intended as a basis for
Kermit programs for any systems that have C compilers.  Its version number
is 4.0, to distinguish it from earlier releases of Unix Kermit, the most
recent of which was 3.0.

This prerelease test version of the program runs only under Berkeley Unix 4.2.
We also intend to bring it to the following systems within the coming weeks:

@. DEC Pro-350 and Pro-380 with Venix (a Unix v7 derivative)
@. Amdahl UTS on IBM 370-series mainframes
@. Apple Macintosh (maybe)

Support for other systems will have to be added elsewhere.  The program is
being "pre-released" at this time for two reasons:

1. It seems to be perfectly usable on Berkeley 4.2 systems, and is an
   improvement over the previous version.

2. The modular design may need some adjustment to accommodate certain systems.
   Before a great deal of additional coding is done, it is highly desirable
   to get the design and specification of the system-dependent modules stable.

Therefore, please take the files, read the documentation, try running the
program on your Berkeley Unix system if you have one, and send comments or bug
reports to me as soon as you can.  If you have a Unix system that is not
Berkeley Unix, or a non-Unix system with a C compiler, please take a look at
the system-dependent modules to see how they could be adapted to your system;
again, if you have any suggestions or criticisms of the design, please let me
know.  I'm particularly interested in issues of portability.  After a round or
two of this, perhaps the design can be agreed upon, and then those who would
like to contribute support for Version 6, System III, System V, Xenix, PC/IX,
etc etc, can do so without fear of running into other people's changes for
other systems.  Before attempting to adapt C-Kermit to a new system, please
let me know so I can tell you whether someone else is already at work on the
same thing, and perhaps put you in touch.

The files are on CU20B as KER:CK*.*, available via anonymous FTP.  The file
CKERMI.DOC provides user-level documentation as well as a description of the
program organization and hints for adapting it to new systems.  Within several
days the files should also be available on BITNET via KERMSRV (to get started
with KERMSRV, type SMSG RSCS MSG CUVMA KERMSRV HELP), and to Unix systems via
UUCP from Oklahoma State University, Stillwater, OK.

Here's how to UUCP to OK State:

You need to set up "okstate" as a site in your "L.sys" UUCP dialing file
using the information listed below.  You can then issue the following 
command on your system:

	uucp okstate\!/u/kermit/ck\* /usr/spool/uucppublic

   (this example will retrieve the new Unix version of Kermit)

The "/usr/spool/uucppublic" is chosen as the destination on your system since
the destination must be WIDE OPEN (drwxrwxrwx) to everyone.  You should
not remove files from your uucppublic until the entire transfer is complete
including any redials that are necessary.  If you do remove some files
our system may retransmit them, resulting in a higher phone bill for you. 

-- UUCP Login information --

Site Name    :  okstate
Phone number :  (405) 624-6953  (one line only)
Login name   :  uucpker
Password     :  thefrog
Hours        :  10:00pm - 10:00am central time (7 day per week)
Problem      :  okstate!uucp-support  (UUCP)
  reports    :  uucp-support%okstate@csnet-relay  (ARPA)

The phone number is for 300/1200 baud (bell compatible).

------------------------------

End of Info-Kermit Digest
*************************
-------
@//E*O*F ckermi.ann//
chmod u=rw,g=r,o=r ckermi.ann
 
echo Inspecting for damage in transit...
temp=/tmp/shar$$; dtemp=/tmp/.shar$$
trap "rm -f $temp $dtemp; exit" 0 1 2 3 15
cat > $temp <<\!!!
     985    4187   25236 ckcmd.c
      45     202    1139 ckcmd.h
     199     735    4764 ckconu.c
      10      29     151 ckdebu.h
     124     836    5310 ckermi.ann
    1363    5989   36600 total
!!!
wc  ckcmd.c ckcmd.h ckconu.c ckdebu.h ckermi.ann | sed 's=[^ ]*/==' | diff -b $temp - >$dtemp
if [ -s $dtemp ]
then echo "Ouch [diff of wc output]:" ; cat $dtemp
else echo "No problems found."
fi
exit 0

tsc2597@acf4.UUCP (Sam Chin) (02/28/85)

# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# Wrapped by acf4!tsc2597  on Wed Feb 27 22:19:17 EST 1985
# Contents:  ckermi.doc ckermi.h ckermi.mak
 
echo x - ckermi.doc
sed 's/^@//' > "ckermi.doc" <<'@//E*O*F ckermi.doc//'
-*-Text-*-

		       	 Version 4.0 of Unix Kermit


			     D R A F T   #   3


			       Frank da Cruz			

			    Columbia University
		      Center for Computing Activities

			      February 4, 1985


  NOTE -- This preliminary documentation is in plain text.  It needs
  to become input for two text formatters - Scribe and Nroff, the
  former for inclusion in the Kermit User Guide, and the latter for
  Unix man pages.  This might be done by defining formatting macros
  in M4, which generate the appropriate Scribe and Nroff commands for
  sectioning, itemization, description, etc.  (Volunteers?)

  ANOTHER NOTE -- This documentation describes a pre-release test version
  of Unix Kermit.  The program and documentation are very likely to change
  during the testing period.  At the end of the testing period, the
  organization of the program and its functional characteristics should be
  relatively stable, so that further changes will provide either additional
  functionality or bug fixes.

C-Kermit is a completely new implementation of Kermit, written modularly and
transportably in C.  The protocol state transition table is written in
'wart', a (not proprietary) lex-like preprocessor for C.  System-dependent
primitive functions are isolated into separately compiled modules so that the
program should be easily portable among Unix systems and also to non-Unix
systems that have C compilers.

Features:

  Local operation: yes
  Remote operation: yes
  Transfer text files: yes
  Transfer binary files: yes
  Wildcard send: yes
  File transfer interruption: yes (per-file and entire batch)
  Filename collision avoidance: yes (selectable)
  Can time out: yes
  8th-bit prefixing: yes (used only when necessary)
  Repeat count prefixing: yes
  Alternate block checks: yes (6-bit and 12-bit checksums, 16-bit CRC)
  Terminal emulation: yes (more efficient than previous releases)
  Communication settings: yes (flow, handshake, speed, parity, duplex)
  Transmit BREAK: yes
  IBM mainframe communication: yes
  Transaction logging: yes
  Session logging: yes
  Debug logging: yes
  Packet logging: yes
  Act as server: yes
  Talk to server: yes
  Advanced server functions: yes
  Local file management: yes
  Command/Init files: yes

Missing (not done yet):

  File attributes: no
  Command macros: no
  Login scripts: no
  Raw file transmit: no

C-Kermit provides traditional Unix command line operation as well as
interactive command prompting and execution.  The command line options
provide access to a minimal subset of C-Kermit's capabilities; the
interactive command set is far richer.


COMMAND LINE OPERATION:

The C-Kermit command line syntax has been changed from that of earlier
releases of Unix Kermit to conform to the "Proposed Syntax Standards for
Unix System Commands" put forth by Kathy Hemenway and Helene Armitage of
AT&T Bell Laboratories in Unix/World Vol.1, No.3, 1984.  The rules that
apply are:

@. Command names must be between 2 and 9 characters ("kermit" is 6).
@. Command names must include lower case letters and digits only.
@. An option name is a single character.
@. Options are delimited by '-'.
@. Options with no arguments may be grouped (bundled) behind one delimiter.
@. Option-arguments cannot be optional.
@. Arguments immediately follow options, separated by whitespace.
@. The order of options does not matter.
@. '-' preceded and followed by whitespace means standard input.

A group of bundled options may end with an option that has an argument.

C-Kermit command line options may specify either actions or settings.  If
C-Kermit is invoked with a command line that specifies no actions, then it
will issue a prompt and begin interactive dialog.

Action options specify either protocol transactions or terminal connection.

-s fn   Send the specified file or files.  If fn contains wildcard (meta)
        characters, the Unix shell expands it into a list.  If fn
        is '-' then kermit sends from standard input, which must
        come from a file ('kermit -s - < foo.bar') or a parallel process
        ('ls -l | kermit -s -'); you cannot use this mechanism to send
	terminal typein.  If you want to send a file whose name is "-"
	you can precede it with a path name, as in 'kermit -s ./-'.

-r      Receive a file or files.  Wait passively for files to arrive.

-k      Receive (passively) a file or files, sending them to standard
	output.  This option can be used in several ways:

	kermit -k
	    Displays the incoming files on your screen; to be used only
	    in local mode (see below).

	kermit -k > fn
	    Sends the incoming file or files to the named file, fn.
	    If more than one file arrives, all are concatenated together
            into the single file fn.

	kermit -k | command
	    Pipes the incoming data (single or multiple files) to the
	    indicated command, as in 'kermit -k | sort > sorted.stuff'.

-a fn   If you have specified a file transfer option, you may specify an
	alternate name for a single file with the -a option.  For example,
	'kermit -s foo -a bar' sends the file foo with the name bar.
	If more than one file arrives or is sent, only the first file is
	affected by the -a option.  'kermit -ra baz' stores the first
	incoming file under the name baz.

-x	Begin server operation.  May be used in either local or remote mode.

Before proceeding, a few words about remote and local operation are
necessary.  C-Kermit is "local" if it is running on PC or workstation that
you are using directly, or if it is running on a multiuser system and
transferring files over an external communication line -- not your job's
controlling terminal or console.  C-Kermit is remote if it is running on a
multiuser system and transferring files over its own controlling terminal's
communication line, connected to your PC or workstation.

If you are running C-Kermit on a PC, it is in local mode by default,
with the "back port" designated for file transfer and terminal connection.
If you are running C-Kermit on a multiuser (timesharing) system, it is
in remote mode unless you explicitly point it at an external line for
file transfer or terminal connection.  The following command sets
C-Kermit's "mode":

-l dev  Line  -- Specify a terminal line to use for file transfer and
	terminal connection, as in 'kermit -l /dev/ttyi5'.

When an external line is being used, you might also need some additional
options for successful communication with the remote system:

-b n    Baud  -- Specify the baud rate for the line given in the -l option,
	as in 'kermit -l /dev/ttyi5 -b 9600'.  This option should always be
	included with the -l option, since the speed of an external line
	is not necessarily what you expect.

-p x    Parity -- e,o,m,s,n (even, odd, mark, space, or none).  If parity
	is other than none, then the 8th-bit prefixing mechanism will be
	used for transferring 8-bit binary data, provided the opposite
	Kermit agrees.  The default parity is none.

-t      Specifies half duplex, line turnaround with XON as the handshake
	character.

The following commands may be used only with a C-Kermit which is local --
either by default or else because the -l option has been specified.

-g fn   Actively request a remote server to send the named file or files;
	fn is a file specification in the remote host's own syntax.  If
  	fn happens to contain any special shell characters, like '*',
	these must be quoted, as in 'kermit -g x\*.\?'.

-f	Send a 'finish' command to a remote server.

-c	Establish a terminal connection over the specified or default
	communication line, before any protocol transaction takes place.
	Get back to the local system by typing the escape character
	(normally Control-Backslash) followed by the letter 'c'.

-n	Like -c, but after a protocol transaction takes place; -c and -n
	may both be used in the same command.

On a timesharing system, the -l and -b options will also have to be included
with the -r, -k, or -s options if the other Kermit is on a remote system.

If C-Kermit is in local mode, the screen (stdout) is continously updated to
show the progress of the file transer.  A dot is printed for every 4 data
packets, other packets are shown by type (e.g. 'S' for Send-Init), 'T' is
printed when there's a timeout, and '%' for each retransmission.  In
addition, you may type (to stdin) certain interrupt commands during file
transfer:

  Control-F:  Interrupt the current File, and go on to the next (if any).
  Control-B:  Interrupt the entire Batch of files, terminate the transaction.
  Control-R:  Resend the current packet
  Control-A:  Display a status report for the current transaction.

(These interrupt characters differ from the ones used in other Kermit
implementations to avoid conflict with Unix shell interrupt characters.)

Several other command-line options are provided:

-i	Specifies that files should be sent or received exactly "as is"
	with no conversions.  This option is necessary for transmitting
	binary files.  It may also be used to slightly boost efficiency
	in Unix-to-Unix transfers of text files by eliminating CRLF/newline
	conversion.

-w	Write-Protect -- Avoid filename collisions for incoming files.

-q	Quiet -- Suppress screen update during file transfer, for instance
	to allow a file transfer to proceed in the background.

-d	Debug --Record debugging information in the file debug.log in 
	the current directory.  Use this option if you believe the program
	is misbehaving, and show the resulting log to your local
	kermit maintainer.

-h	Help -- Display a brief synopsis of the command line options.

The command line may contain no more than one protocol action option.

Files are sent with their own names, except that lowercase letters are
raised to upper, pathnames are stripped off, tilde ('~') characters changed
to 'X', and if the file name begins with a period, an 'X' is inserted before
it.  Incoming files are stored under their own names except that uppercase
letters are lowered, and, if -w was specified, a "generation number" is
appended to the name if it has the same name as an existing file which would
otherwise be overwritten.  If the -a option is included, then the same rules
apply to its argument.  The file transfer display shows any transformations
performed upon filenames.

During file transfer, files are encoded as follows:

@. Control characters are converted to prefixed printables.

@. Sequences of repeated characters are collapsed via repeat counts, if
  the other Kermit is also capable of repeated-character compression.

@. If parity is being used on the communication line, data characters with 
  8th (parity) bits on are specially prefixed, provided the other Kermit
  is capable of 8th-bit prefixing (if not, 8-bit binary files cannot be
  successfully transferred).

@. Conversion is done between Unix newlines and carriage-return-linefeed 
  sequences unless the -i option was specified.


Command Line Examples:

 	kermit -l /dev/ttyi5 -b 1200 -cn -r

This command connects you to the system on the other end of ttyi5 at
1200 baud, where you presumably log in and run Kermit with a 'send'
command.  After you escape back, C-Kermit waits for a file (or files) to
arrive.  When the file transfer is completed, you are again connected to
the remote system so that you can logout.

	kermit -l /dev/ttyi4 -b 1800 -cntp m -r -a foo

This command is like the preceding one, except the remote system in this
case uses half duplex communication with mark parity.  The first file
that arrives is stored under the name foo.

	kermit -l /dev/ttyi6 -b 9600 -nf

This command would be used to shut down a remote server and then connect
to the remote system, in order to log out or to make further use of it.

	kermit -l /dev/ttyi6 -b 9600 -qg foo.\* &

This command causes C-Kermit to be invoked in the background, getting a group
of files from a remote server (note the quoting of the '*' character).  No
display occurs on the screen, and the keyboard is not sampled for
interruption commands.  This allows other work to be done while file
transfers proceed in the background.

	kermit -l /dev/ttyi6 -b 9600 -g foo.\* > foo.log < /dev/null &

This command is like the previous one, except the file transfer display has
been redirected to the file foo.log.  Standard input is redirected to prevent
C-Kermit from sampling it for interruption commands.

	kermit -iwx

This command starts up C-Kermit as a server.  Files are transmitted with
no newline/carriage-return-linefeed conversion; the -i option is necessary 
for binary file transfer and useful for Unix-to-Unix transfers.  Incoming 
files that have the same names as existing files are given new, unique names.

	kermit -l /dev/ttyi6 -b 9600

This command sets the communication line and speed.  Since no action is
specified, C-Kermit issues a prompt and enters an interactive dialog with
you.  Any settings given on the command line remain in force during the
dialog, unless explicitly changed.

	kermit

This command starts up Kermit interactively with all default settings.

A final example shows how Unix Kermit might be used to send an entire
directory tree from one system to another, using the tar program as Kermit's
standard input and output.  On the orginating system, in this case the
remote, type (for instance):

        tar cf - /usr/fdc | kermit -is -

This causes tar to send the directory /usr/fdc (and all its files and all
its subdirectories and all their files...) to standard output instead of a
tape; kermit receives this as standard input and sends it as a binary file.
On the receiving system, in this case the local one, type (for instance):

	kermit -il /dev/ttyi5 -b 9600 -k | tar xf -

Kermit receives the tar archive, and sends it via standard output to tar,
which extracts it into a replica of the original directory tree.


INTERACTIVE OPERATION

C-Kermit's interactive command prompt is "C-Kermit>".  In response to this
prompt, you may type any valid command.  C-Kermit executes the command and
then prompts you for another command.  The process continues until you
command the program to terminate.

Commands begin with a keyword, normally an English verb, such as "send".
You may omit trailing characters from any keyword, so long as you specify
sufficient characters to distinguish it from any other keyword valid in that
field.  Certain commonly-used keywords (such as "send", "receive", "connect")
have special non-unique abbreviations ("s" for "send", even though this would
not otherwise distinguish it from "set").

You may type '?' at any point to get a help message explaining what is
possible, or expected, at that point.  You may type ESC to request
completion of the current keyword or file name, or insertion of a default
value (the result will be a beep if the requested operation fails).  You may
edit your commands with DEL or Backspace (erase rightmost character),
Control-W (erase rightmost word), or Control-U (erase entire command).  The
screen should show the effects of this editing.  On a hardcopy terminal, you
can type Control-R to redisplay the current command to show the effects of
any editing.  The command is entered by typing carriage return, linefeed, or
formfeed.  If you make any mistakes, you will receive an informative error
message and a new prompt -- make liberal use of '?' and ESC to feel your way
through the commands.  One important command is "help" -- you should use it
the first time you run C-Kermit.

If you need to include special characters (like '?') in a command, you
may precede them with a backslash ('\') and they will be accepted without
triggering any special function.

Interactive C-Kermit accepts commands from files as well as from the
keyboard.  When you enter interactive mode, C-Kermit looks for the file
@.kermrc in your home or current directory (first it looks in the home
directory, then in the current one) and executes any commands it finds there.
These commands must be in interactive format, not Unix command-line format.
A "take" command is also provided for use at any time during an interactive
session.  Command files may be nested to any reasonable depth.

Here is a brief list of C-Kermit interactive commands:

  !             Execute a Unix shell command.
  bye           Terminate and log out a remote Kermit server.
  close         Close a log file.
  connect       Establish a terminal connection to a remote system.
  cwd           Change Working Directory.
  directory     Display a directory listing.
  echo          Display arguments literally.
  exit          Exit from the program, closing any open logs.
  finish        Instruct a remote Kermit server to exit, but not log out.
  get           Get files from a remote Kermit server.
  help          Display a help message for a given command.
  log           Open a log file -- debugging, packet, session, transaction.
  quit          Same as 'exit'.
  receive       Passively wait for files to arrive.
  remote        Issue file management commands to a remote Kermit server.
  send          Send files.
  server        Begin server operation.
  set           Set various parameters.
  show          Display values of 'set' parameters.
  space         Display current disk space usage.
  statistics    Display statistics about most recent transaction.
  take          Execute commands from a file.

The 'set' parameters are:

  block-check       Level of packet error detection.
  delay             How long to wait before sending first packet.
  duplex            Specify which side echoes during 'connect'.
  end-of-packet     Terminator for outbound packets.
  escape-character  Character to prefix "escape commands" during 'connect'.
  file              Set various file parameters.
  flow-control      Communication line full-duplex flow control.
  handshake         Communication line half-duplex turnaround character.
  line              Communication line device name.
  packet-length     Maximum length for packets.
  pad-character     Character to use for inter-packet padding.
  padding           How much inter-packet padding to use.
  parity            Communication line character parity.
  prompt            Change the C-Kermit program's prompt.
  speed             Communication line speed.
  start-of-packet   Packet prefix character.
  timeout           Timer interval to detect lost packets.

The 'remote' commands are:

  cwd           Change remote working directory.
  delete        Delete remote files.
  directory     Display a listing of remote file names.
  help          Request help from a remote server.
  host          Issue a command to the remote host in its own command language.
  space         Display current disk space usage on remote system.
  type          Display a remote file on your screen.

Most of these commands are described adequately in the Kermit User Guide.
Special aspects of certain Unix Kermit commands are described below.  The
following notation is used:

fn       A Unix file specification, possibly containing the "wildcard"
         characters '*' or '?' ('*' matches all character strings, '?' matches 
         any single character).

fn1      A Unix file specification which may not contain '*' or '?'.

rfn      A remote file specification in the remote system's own syntax, which
         may denote a single file or a group of files.

rfn1     A remote file specification which should denote only a single file.

n        A decimal number between 0 and 94.

c        A decimal number between 0 and 127 representing the value of an
         ASCII character.

cc       A decimal number between 0 and 31, or else exactly 127,
	 representing the value of an ASCII control character.

[ ]      Any field in square braces is optional.

{x,y,z}  Alternatives are listed in curly braces.


The 'send' command:

  Syntax: send fn
      or: send fn1 rfn1

Send the file or files denoted by fn to the other Kermit, which should be
running as a server, or which should be given the 'receive' command.  Each
file is sent under its own name (as described above, or to the extent
specified by the 'set file names' command).  If the second form is used,
i.e. with fn1 denoting a single Unix file, rfn1 may be specified as a name
to send it under.  The 'send' command may be abbreviated to 's', even though
's' is not a unique abbreviation for a top-level C-Kermit command.

The wildcard (meta) characters '*' and '?' are accepted in fn.  If '?' is to
be included, it must be prefixed by '\' to override its normal function of
providing help.  '*' matches an string, '?' matches any single character.
Other notations for file groups, like 'x[a-m]y', are not supported.  When
fn contains '*' or '?' characters, there is a limit to the number of files
that can be matched, which varies from system to system.  If you get the
message "Too many files match" then you'll have to make a more judicious
selection.  If fn was of the form 'usr/longname/anotherlongname/*' then
C-Kermits string space will fill up rapidly -- try doing a cwd to the path
in question and reissuing the command.


The 'receive' command:

  Syntax: receive
      or: receive fn1

Passively wait for files to arrive from the other Kermit, which must be
given the 'send' command -- the 'receive' command does not work in
conjunction with a server.  If fn1 is specified, store the first incoming
file under that name.  The 'receive' command may be abbreviated to 'r'.


The 'get' command:

  Syntax: get rfn
      or: get
            rfn
            fn1

Request a remote Kermit server to send the named file or files.  Since a
remote file specification (or list) might contain spaces, which normally
delimit fields of a C-Kermit command, an alternate form of the command is
provided to allow the inbound file to be given a new name: type 'get' alone
on a line, and you will be prompted separately for the remote and local
file specifications.  As with 'receive', if more than one file arrives as
a result of the 'get' command, only the first will be stored under the 
alternate name given by fn1; the remaining files will be stored under their
own names if possible.  If a '?' is to be included in the remote file
specification, you must prefix it with '\' to suppress its normal function
of providing help.


The 'server' command:

This command places C-Kermit in server mode on the currently selected 
communication line.  All further commands must arrive as valid Kermit
packets from the Kermit on the other end of the line.  The Unix Kermit
server can respond to the following commands:

    Command            Server Response
      GET                Sends files
      SEND               Receives files
      FINISH             Exits to level from which it was invoked
      REMOTE DIRECTORY   Sends directory lising
      REMOTE DELETE      Removes files
      REMOTE CWD         Changes working directory
      REMOTE TYPE        Sends files to your screen
      REMOTE SPACE       Reports about its disk usage
      REMOTE WHO         Shows who's logged in
      REMOTE HOST        Executes a Unix shell command
      REMOTE HELP        Lists these capabilities

Note that the Unix Kermit server cannot respond to a BYE command; it
cannot log itself out.  If the Kermit server is directed at an external line
(i.e. it is in "local mode") then the console may be used for other work if
you have 'set file display off', or else it may be used to observe file
transfers and enter status or interruption commands.

The 'remote', 'bye', and 'finish' commands:

C-Kermit may itself request services from a remote Kermit server.  In
addition to the 'send' and 'get' commands, the following may also be used:

  remote cwd [directory]
    If the optional remote directory specification is included, you will
    be prompted on a separate line for a password, which will not echo as
    you type it.

  remote delete rfn        delete remote file or files.
  remote directory [rfn]   directory listing of remote files.
  remote host command      command in remote host's own command language.
  remote space             disk usage report from remote host.
  remote type              display remote file or files on the screen.
  remote who [user]        display information about who's logged in.
  remote help              display remote server's capabilities.

  bye and finish:

When connected to a remote Kermit server, these commands cause the remote
server to terminate; 'finish' returns it to Kermit or system command level
(depending on the implementation or how it was invoked); 'bye' also requests
it to log itself out.


The 'log' and 'close' commands:

  Syntax: log {debugging, packets, session, transactions} [fn1]  

C-Kermit's progress may be logged in various ways.  The 'log' command
opens a log, the 'close' command closes it.  In addition, all open logs
are closed by the 'exit' and 'quit' commands.  A name may be specified for
a log file; if the name is omitted, the file is created with a default
name as shown below.

log debugging
    This produces a voluminous log of the internal workings of C-Kermit,
    of use to Kermit developers or maintainers in tracking down suspected
    bugs in the C-Kermit program.  Use of this feature dramatically slows
    down the Kermit protocol.  Default name: debug.log.

log packets
    This produces a record of all the packets that go in and out of the
    communication port.  This log is of use to Kermit maintainers who are
    tracking down protocol problems in either C-Kermit or any Kermit that
    C-Kermit is connected to.  Default name:  packet.log.

log session
    This log will contain a copy of everything you see on your screen during
    the 'connect' command, except for local messages or interaction with
    local escape commands.  Default name:  session.log.

log transactions
    The transaction log is a record of all the files that were sent or
    received while transaction logging was in effect.  It includes time
    stamps and statistics, filename transformations, and records of any
    errors that may have occurred.  The transaction log allows you to have
    long unattended file transfer sessions without fear of missing some
    vital screen message.  Default name:  transaction.log.

The 'close' command explicitly closes a log, e.g. 'close debug'.


Local file management commands:

Unix Kermit allows some degree of local file management from interactive
command level:

directory [fn]
    Displays a listing of the names, modes, sizes, and dates of files
    matching fn (which defaults to '*').  Equivalent to 'ls -l'.

cwd [directory-name]
    Changes Kermit's working directory to the one given, or to the your
    default directory if the directory name is omitted.  Equivalent to 'cd'.

space
    Display information about disk space and/or quota in the current
    directory and device.

! command
    The command is executed by the Unix shell.  Use this for all other
    file management commands.  This command has certain peculiarities:
    . A space must separate the '!' from the shell command.
    . A 'cd' command executed in this manner will have no effect -- use the
      C-Kermit 'cwd' command instead. 

The 'set' and 'show' commands:

Since Kermit is designed to allow diverse systems to communicate, it is
often necessary to issue special instructions to allow the program to adapt
to peculiarities of the another system or the communication path.  These
instructions are accomplished by the 'set' command.  The 'show' command may
be used to display current settings.  Here is a brief synopsis of settings
available in the current release of C-Kermit:

block-check {1, 2, 3}
    Determines the level of per-packet error detection.  1 is a 6-bit 
    checksum, folded to include the values of all bits from each character.
    2 is a 12-bit checksum.  3 is a 16-bit cyclic redundancy check.  The
    higher the block check, the better the error detection and correction
    and the higher the resulting overhead.  Type 1 is most commonly used; it 
    is supported by all Kermit implementations, and it has proven adequate in
    most circumstances.  Types 2 or 3 would be used to advantage when
    transferring 8-bit binary files over noisy lines.

delay n
    How many seconds to wait before sending the first packet after a 'send'
    command.  Used in remote mode to give you time to escape back to your
    local Kermit and issue a 'receive' command.  Normally 5 seconds.

duplex {full, half}
    For use during 'connect'.  Specifies which side is doing the echoing;
    'full' means the other side, 'half' means C-Kermit must echo typein itself.

end-of-packet cc
    Specifies the character needed by the other Kermit to recognize the end
    of a packet.  C-Kermit sends this character at the end of each packet.
    Normally 13 (carriage return), which most Kermit implementations
    require.  Other Kermits require no terminator at all, still others may
    require a different terminator, like linefeed (10).

escape-character cc
    For use during 'connect' to get C-Kermit's attention.  The escape
    character acts as a prefix to an 'escape command', for instance to
    close the connection and return to C-Kermit or Unix command level.
    The normal escape character is Control-Backslash (28).


file {display, names, type, warning}
    Establish various file-related parameters:

    display {on, off}
	Normally 'on'; when in local mode, display progress of file
	transfers on the screen (stdout), and listen to the keyboard (stdin)
	for interruptions.  If off (-q on command line) none of this is
	done, and the file transfer may proceed in the background oblivious
	to any other work concurrently done at the console terminal.

    names {converted, literal}
        Normally converted, which mean that outbound filenames have path
	specifications stripped, lowercase letters raised to upper,
	tildes and extra periods changed to 'X's, and an 'X' inserted in
	front of any name that starts with period.  Incoming files have
	uppercase letters lowered.  Literal means that none of these
	conversions are done; therefore, any directory path appearing in a
	received file specification must exist and be write-accessible.
	Literal naming is obviously of limited utility, and intended
	principally for file transfers between very similar Unix systems.

    type {binary, text}
	Normally text, which means that conversion is done between Unix
	newline characters and the carriage-return/linefeed sequences
	required by the canonical Kermit file transmission format, and in
	common use on non-Unix systems.  Binary means to transmit file
	contents without conversion.  Binary (-i in command line notation)
	is necessary for binary files, and desirable in all Unix-to-Unix
	transactions to cut down on overhead.

    warning {on, off}
	Normally off, which means that incoming files will silently
	overwrite existing files of the same name.  When on (-w on command
	line) Kermit will check if an arriving file would overwrite an
	existing file; if so, it will construct a new name for the arriving
	file, of the form foo~n, where foo is the name they share and n is a
	"generation number"; if foo exists, then the new file will be called
	foo~1.  If foo and foo~1 exist, the new file will be foo~2, and so on.

flow-control {none, xon/xoff}
    Normally xon/xoff for full duplex flow control.  Should be set to 'none'
    if the other system cannot do xon/xoff flow control.

handshake {xon, xoff, cr, lf, bell, esc, none}
    Normally none.  Otherwise, half-duplex communication line turnaround
    handshaking is done, which means Unix Kermit will not reply to a packet
    until it has received the indicated handshake character or has timed out
    waiting for it.

line [device-name]
    The device name for the communication line to be used for file transfer
    and terminal connection, e.g. /dev/ttyi3.  If you specify a device name,
    Kermit will be in local mode, and you should remember to issue any other
    necessary 'set' commands, such as 'set speed'.  If you omit the device
    name, Kermit will revert to its default mode of operation.

packet-length n
    Specify the maximum packet length to use.  Normally 90.  Shorter packet
    lengths can be useful on noisy lines, or with systems or front ends or
    networks that have small buffers.  The shorter the packet, the higher
    the overhead, but the lower the chance of a packet being corrupted by
    noise, and the less time to retransmit corrupted packets.

pad-character cc
    C-Kermit normally does not need to have incoming packets preceded
    with pad characters.  This command allows C-Kermit to request the
    other Kermit to use cc as a pad character.  Default cc is NUL,
    ASCII 0.

padding n
    How many pad characters to ask for, normally 0.

parity {even, odd, mark, space, none}
    Specify character parity for use in packets and terminal connection,
    normally none.  If other than none, C-Kermit will seek to use the
    8th-bit prefixing mechanism for transferring 8-bit binary data, which
    can be used successfully only if the other Kermit agrees; if not,
    8-bit binary data cannot be successfully transferred.

prompt [string]
    The given string will be substituted for "C-Kermit>" as this program's
    prompt.  If the string is omitted, the prompt will revert to "C-Kermit>".

speed {0, 110, 150, 300, 600, 1200, 1800, 2400, 4800, 9600}
    The baud rate for the external communication line.  Cannot be used to
    change the speed of your own console terminal.  Many Unix systems are
    set up in such a way that you must give this command after a 'set line'
    command before you can use the line.

start-of-packet cc
    The Kermit packet prefix is Control-A (1).  The only reasons it should
    ever be changed would be:  Some piece of equipment somewhere between the
    two Kermit programs will not pass through a Control-A; or, some piece of
    of equipment similarly placed is echoing its input.  In the latter case,
    the recipient of such an echo can change the packet prefix for outbound
    packets to be different from that of arriving packets, so that the
    echoed packets will be ignored.  The opposite Kermit must also be told
    to change the prefix for its inbound packets.  Unix Kermit presently can
    be told to change only its outbound packet prefix.

timeout n
    Normally, each Kermit partner sets is packet timeout interval based on
    what the opposite Kermit requests.  This command allows you to override
    the normal procedure and specify a timeout interval.  If you specify 0,
    then no timeouts will occur, and Unix Kermit will wait forever for
    expected packets to arrive.

The 'show' command:

  Syntax: show {parameters, versions}

The show command displays the values of all the 'set' parameters described
above.  If you type 'show versions', then C-Kermit will display the version
numbers and dates of all its internal modules.  You should use the 'show
versions' command to ascertain the vintage of your Kermit program before
reporting problems to Kermit maintainers.


The 'statistics' command:

The statistics command displays information about the most recent Kermit
protocol transaction, including file and communication line i/o, as well
as what encoding options were in effect (such as 8th-bit prefixing,
repeat-count compression).


The 'take' and 'echo' commands:

  Syntax: take fn1

The 'take' command instructs C-Kermit to execute commands from the named
file.  The file may contain any interactive C-Kermit commands, including
'take'; command files may be nested to any reasonable depth.  The 'echo'
command may be used within command files to issue greetings, announce
progress, etc.

Command files may be used in lieu of command macros, which have not been
implemented in this version of C-Kermit.  For instance, if you commonly
connect to a system called 'B' that is connected to the other end of the
communication line which is plugged into ttyh7 and which runs at 4800 baud,
you could create a file called b containing the commands

	set line /dev/ttyh7
	set speed 4800
	echo Connecting to System B...
	connect

and then simply type 'take b' (or 't b' since no other commands begin with
the letter 't') whenever you wished to connect to system B.

For connecting to IBM mainframes, a number of 'set' commands are required;
these, too, are conveniently collected into a 'take' file:

	set speed 1200
	set parity mark
	set handshake xon
	set flow-control none
	set duplex half

An implicit 'take' command is executed upon your .kermrc file upon
C-Kermit's initial entry into interactive dialog.  The .kermrc file should
contain 'set' or other commands you want to be in effect at all times.
For instance, you might want override the default action when incoming files
have the same names as existing files -- in that case, put the command

	set file warning on

in your .kermrc file.

Commands executed from take files are not echoed at the terminal.  If you
want to see the commands as well as their output, you could feed the
command file to C-Kermit via redirected stdin, as in 'kermit < x'.


The 'connect' command:

The connect comand links your terminal to another computer as if it were
a local terminal to that computer, through the device specified in the most
recent 'set line' command, or through the default device if your system
is a PC or workstation.  All characters you type at your keyboard
are sent out the communication line, all characters arriving at the
communication port are displayed on your screen.  Current settings of speed,
parity, duplex, and flow-control are honored.  If you have issued a 'log
session' command, everything you see on your screen will also be recorded
to your session log.  This provides a way to "capture" files from systems
that don't have Kermit programs available.

To get back to your own system, you must type the escape character, which
is Control-Backslash (^\) unless you have changed it with the 'set escape'
command, followed by a single-character command, such as 'c' for "close
connection".  Single-character commands include:

  C  -  Close the connection
  B  -  Send a BREAK signal
  0  -  (zero) send a null
  S  -  Give a status report about the connection
  ^\ -  Send Control-Backslash itself (whatever you have defined the
	escape character to be, typed twice in a row sends one copy of it).

Lower-case and control equivalents for these letters are also accepted.  A
space typed after the escape character is ignored.  Any other character will
produce a beep.

No special provisions are made for controlling modems or dialers.  Thus, to
dial out through a Hayes-like modem, you would have to type the modem
commands yourself.

	set line /dev/dialer
	set speed 1200
	connect
	ATD5551212
	
Here, ATD is the dialing command.  You would look for a return code from the
modem (in this case '1' if a connection is made, '3' if no carrier
detected), and act accordingly.

'c' is an acceptable non-unique abbreviation for 'connect'.


The 'help' command:

Syntax: help
    or: help keyword
    or: help {set, remote} keyword

Brief help messages or menus are always available at interactive command
level by typing a question mark at any point.  A slightly more verbose form
of help is available through the 'help' command.  The 'help' command with
no arguments prints a brief summary of how to enter commands and how to
get further help.  'help' may be followed by one of the top-level C-Kermit
command keywords, such as 'send', to request information about a command.
Commands such as 'set' and 'remote' have a further level of help.  Thus you
may type 'help', 'help set', or 'help set parity'; each will provide a
successively more detailed level of help.


The 'exit' and 'quit' commands:

These two commands are identical.  Both of them do the following:

@. Attempt to insure that the terminal is returned to normal.
@. Relinquish access to any communication line assigned via 'set line'.
@. Close any open log files.

After exit from C-Kermit, your default directory will be the same as when
you started the program.


C-Kermit under Berkeley Unix:

C-Kermit may be interrupted at command level or during file transfer by
typing Control-C.  The program will perform its normal exit function,
restoring the terminal.  If a protocol transaction was in progress, an
error packet will be sent to the opposite Kermit so that it can terminate
cleanly.

During execution of a system command, C-Kermit can often be returned to
command level by typing a single Control-C.

C-Kermit may also be interrupted by ^Z to put the process in the background.
In this case the terminal is not restored.  You will have to type
Control-J followed by "reset" followed by another Control-J to get your
terminal back to normal.  C-Kermit can be halted in a similar manner by
typing Control-Backslash, except that instead of moving it to the
background, a core dump is produced.

Control-C, Control-Z, and Control-\ lose their normal functions during
terminal connection and also during file transfer when the controlling tty
line is being used for packet i/o.

The BSD implementation of C-Kermit has code to take advantage of a special
nonstandard kernel-mode line driver, which boosts the speed of packet i/o
significantly.  The problem is that "raw" mode, needed for packet i/o, also
implies "cbreak" (character wakeup) mode, which is very expensive.  The new
line driver is a modification of the "berknet" driver, which allowed raw mode
i/o to take place with process wakeup only upon receipt of a linefeed.  The
Berknet driver, unfortunately, strips off the high order bit of each
character and does not allow the line terminator to be specified.  The
modification allows all 8 bits to pass through unmolested, allows the wakeup
character to be specified, and allows the buffer to be tested or cleared.

If you are running C-Kermit in "quiet mode" in the foreground, then
interrupting the program with a console interrupt like Control-C will not
restore the terminal to normal conversational operation.  This is because
the system call to enable console interrupt traps will cause the program to
block if it's running in the background, and the primary reason for quiet
mode is to allow the program to run in the background without blocking, so
that you can do other work in the foreground.


C-KERMIT RESTRICTIONS AND KNOWN BUGS

1. Initialization of Transactions

C-Kermit running in local mode in conjunction with a remote server presently
does not send a configuration (I) packet prior to sending a generic command,
host command, or 'get' command.  This means that the user of C-Kermit cannot
directly control the block check type to be used, and that if the local
C-Kermit is restarted, the remote server might retain settings, such as
repeated character compression, that are not in effect in the new C-Kermit
based upon previous I- or S-packet exchanges.  This situation can be cleared
up by sending a short (even a null) file to the remote server, to get the
configuration parameters back in agreement.


ADAPTING C-KERMIT TO OTHER SYSTEMS:

C-Kermit is designed for portability.  The level of portability is indicated
in parentheses after the module name: "C" means any system that has a C
compiler that conforms to the description in "The C Programming Language" by
Kernighan & Ritchie (Prentice-Hall, 1978).  "Cf" is like "C", but also
requires "standard" features like printf and fprintf, argument passing via
argv/argc, and so on, as described in Kernighan & Ritchie.  "Unix" means the
module should be useful under any Unix implementation; it requires features
such as fork() and pipes.  Anything else means that the module is particular
to the indicated system.  The modules are:


ckmain.c, ckermi.h, ckdebu.h (Cf):

This is the main program.  It contains declarations for global variables and
a small amount of code to initialize some variables and invoke the command
parser.  In its distributed form, it assumes that command line arguments are
passed to it via argc and argv.  Since this portion of code is only several
lines long, it should be easy to replace for systems that have different
styles of user interaction.  The header files define symbols and macros used
by the various modules of C-Kermit.


wart.c (Cf), ckprot.w (C):

The ckprot module embodies the Kermit protocol state table and the code to
accomplish state switching.  It is written in "wart", a language which may be
regarded as a subset of the Unix "lex" lexical analyzer generator.  Wart
implements enough of lex to allow the ckprot module to function.  Lex itself
was not used because it is proprietary.  The protocol module ckprot.w is read
by wart, and a system-independent C program is produced.

The syntax of a Wart program is illustrated by ckprot.w:

1. C declarations, #includes, #defines, comments.

2. Wart state name declarations, introduced by "%state".

3. Perhaps more of (1).

4. The wart token "%%" to introduce the state table, which is composed
   of entries of the format  <state-list>input-character { action }

5. Another "%%" to mark the end of the state table.

6. C code which includes an invocation of the wart() function, whose
   definition is generated by the wart program.  The wart() function 
   calls upon the input() function (defined externally, in this case in
   ckfns.c) to return a single character, which it uses in conjunction
   with the current state to index into a big case statement which it
   generates from the state table (4).

The state-list is a list of one or more states defined in the %states
directive, enclosed in angle brackets, e.g. <send>, <ssinit,ssdata>.
The state-list may be omitted.  The input is a single character, supplied
by the input() function.  In Kermit's case, it is normally the packet
type.  A period indicates "any input".  The action is the C language
code to be executed when the specified input appears when in the state(s)
specified in the state-list.  The action normally consists of a function
invocation followed by a switch to a new state.  Examples:

<foo>X { bar(); state = Y }

This means "if in state foo the input 'X' appears, invoke the function
bar() and then switch to state Y.

<foo,bar>. { baz(); BEGIN Z; }

If in state foo or bar and any input appears, invoke baz(), then switch to
state Z.  BEGIN is a macro predeclared by wart to be "state = " (lex has
a similar macro).

A { }

This means that if an 'A' appears as input in any state, perform no action,
and remain in the same state.

The state table is scanned sequentially, so that constructions like

<foo>A { bar(); }
A      { baz(); BEGIN mumble; }

are possible -- this means "if in state foo and the input 'A' appears,
invoke bar() and remain in the current state.  If in any other state and
an 'A' appears, invoke baz() and switch to state mumble.

ckfns.c (C):

The module contains all the Kermit protocol support functions -- packet
formation, encoding, decoding, block check calculation, filename and data
conversion, protocol parameter negotiation, and high-level interaction
with the communication line and file system.


ckx???.c (specific to system ???):
ckxbsd.c (Berkeley Unix):

The ckx module contains the system-dependent primitives for communication
line i/o, timers, and interrupts.  Certain important variables are defined
in this module, which determine whether C-Kermit is by default remote or
local, what the default communication device is, and so forth.  The ckx
module maintains its own private database of file descriptors and modes for
the console terminal and the file transfer communication line so that other
modules (like ckfns or the terminal connect module) need not be concerned
with them.  This module will vary significantly among Unix implementations
since the sgtty/ioctl/termio functions and their arguments are among the
least compatible areas of Unix.


ckz???.c (specific to system ???):
ckzbsd.c (Berkeley Unix):

The ckz module contains system-dependent primitives for file i/o, wildcard
(meta character) expansion, file existence and access checking, and system
command execution.  It maintains an internal database of i/o "channels"
(file pointers in the case of Unix) for the files C-Kermit cares about --
the input file (the file which is being sent), the output file (the file
being received), the various logs, the screen, and so forth.  This module
will vary little among Unix implementations except for the wildcard
expansion code, which requires detailed knowledge of the directory
structure.  The ckz module also defines variables containing the system
command strings used to perform certain functions like directory listing,
file deletion, etc.


ckuser.c (Unix):

This is the "user interface" for C-Kermit.  It includes the command parser,
the screen output functions, and console input functions.  The command
parser comes in two pieces -- the traditional Unix command line decoder
(which is quite small and compact), and the interactive keyword parser
(which is rather large).  This module is fully replacable; its interface to
the other modules is very simple, and is explained at the beginning of the
source file.  The ckuser module also includes code to execute any commands
directly which don't require the Kermit protocol -- local file management,
etc.  The module is rated "Unix" because it makes occasional use of the
system() function.


ckcmd.c, ckcmd.h (Cf):

This is an interactive command parsing package developed for C-Kermit.
It is written portably enough to be usable on any system that has a C
compiler that supports functions like printf.  The file name parsing
functions depend upon primitives defined in the ckz module; if these
primitives cannot be supplied for a certain system, then the filename
parsing functions can be deleted, and the package will still be useful
for parsing keywords, numbers, arbitrary text strings, and so forth.
The style of interaction is the same as that found on the DECSYSTEM-20.


ckconu.c (Unix):

This is the connect module.  As supplied, it should operate in any Unix
environment, or any C-based environment that provides the fork() function.
The module requires access to global variables that specify line speed,
parity, duplex, flow control, etc, but invokes functions from the ckx module
to accomplish the desired settings and input/output, and functions from the
ckz module to perform session logging.  No terminal emulation is performed.
There is no code for controlling modems or dialers.  The ckconu function may
be entirely replaced, so long as the global settings are honored by its
replacement.  PC implementations of C-Kermit may require the ckcon module to
do screen control, escape sequence interpretation, etc, and may also wish to
write special code to get the best possible performance.


Moving C-Kermit to a new system entails:

1. Creating a new ckx module in C, assembler, or whatever language is
   most appropriate for system programming on the new system.

2. Creating a new ckz module, as above.

3. If the system is not Unix-like, then a new ckuser module may be required,
   as well as a different invocation of it from ckmain.

4. If the distributed connect module doesn't work or performs poorly, then
   it may be replaced.  For instance, interrupt-driven i/o may be required,
   especially if the system doesn't have forks.

Those who favor a different style of user/program interaction from that
provided in ckuser.c may replace the entire module, for instance with one
that provides a mouse/window/icon environment, a menu/function-key
environment, etc.


IMPORTANT -- This description of the C-Kermit modules is based on a
pre-release test version of the program for Berkeley Unix 4.2.  As of this
writing, the program has not yet been moved to any other system.  Some
reorganization may be required in order to bring this program up on other
systems.
@//E*O*F ckermi.doc//
chmod u=rw,g=r,o=r ckermi.doc
 
echo x - ckermi.h
sed 's/^@//' > "ckermi.h" <<'@//E*O*F ckermi.h//'
/* ckermit.h -- Symbol and macro definitions for C-Kermit */

#include <stdio.h>
#include <ctype.h>
#include "ckdebu.h"

/* Mnemonics for ASCII characters */

#define SOH	   001	    	/* ASCII Start of header */
#define BEL        007		/* ASCII Bell (Beep) */
#define BS         010		/* ASCII Backspace */
#define CR         015		/* ASCII Carriage Return */
#define XON	   021	    	/* ASCII XON */
#define SP	   040		/* ASCII Space */
#define DEL	   0177		/* ASCII Delete (Rubout) */

/* Kermit parameters and defaults */

#define MAXPACK	   94		/* Maximum packet size */
#define RBUFL	   200 	    	/* Receive buffer length */
#define CTLQ	   '#'		/* Control char prefix I will use */
#define MYEBQ	   '&'		/* 8th-Bit prefix char I will use */
#define MYRPTQ	   '~'		/* Repeat count prefix I will use */

#define MAXTRY	    10	  	/* Times to retry a packet */
#define MYPADN	    0	  	/* How many padding chars I need */
#define MYPADC	    '\0'  	/* Which padding character I need */

#define DMYTIM	    7	  	/* Default timeout interval to use. */
#define URTIME	    10	  	/* Timeout interval to be used on me. */

#define DEFTRN	    0           /* Default line turnaround handshake */
#define DEFPAR	    0           /* Default parity */
#define MYEOL	    CR          /* End-Of-Line character I need on packets. */

#define DRPSIZ	    90	        /* Default incoming packet size. */
#define DSPSIZ	    90	        /* Default outbound packet size. */

#define DDELAY      5		/* Default delay. */
#define DSPEED	    9600 	/* Default line speed. */

/* Files */

#define ZCTERM      0	    	/* Console terminal */
#define ZSTDIO      1		/* Standard input/output */
#define ZIFILE	    2		/* Current input file */
#define ZOFILE      3	    	/* Current output file */
#define ZDFILE      4	    	/* Current debugging log file */
#define ZTFILE      5	    	/* Current transaction log file */
#define ZPFILE      6	    	/* Current packet log file */
#define ZSFILE      7		/* Current session log file */
#define ZNFILS      8	    	/* How many defined file numbers */

/* Macros */

#define tochar(ch)  ((ch) + SP )	/* Number to character */
#define unchar(ch)  ((ch) - SP )	/* Character to number */
#define ctl(ch)     ((ch) ^ 64 )	/* Controllify/Uncontrollify */
#define unpar(ch)   ((ch) & 127)	/* Clear parity bit */
@//E*O*F ckermi.h//
chmod u=rw,g=r,o=r ckermi.h
 
echo x - ckermi.mak
sed 's/^@//' > "ckermi.mak" <<'@//E*O*F ckermi.mak//'
# Makefile to build C-Kermit for Berkeley Unix 4.2

kermit: ckmain.o ckcmd.o ckuser.o ckprot.o ckfns.o ckconu.o ckxbsd.o ckzbsd.o
	cc -o kermit ckmain.o ckcmd.o ckuser.o ckprot.o ckfns.o ckconu.o ckzbsd.o ckxbsd.o

ckmain.o: ckmain.c ckermi.h

ckuser.o: ckuser.c ckcmd.h ckermi.h

ckcmd.o: ckcmd.c ckcmd.h

ckprot.o: ckprot.c ckermi.h

ckprot.c: ckprot.w wart
	wart ckprot.w ckprot.c

ckfns.o: ckfns.c ckermi.h

ckzbsd.o: ckzbsd.c ckermi.h

ckxbsd.o: ckxbsd.c

ckconu.o: ckconu.c

wart: ckwart.o
	cc -o wart ckwart.o

ckwart.o: ckwart.c
@//E*O*F ckermi.mak//
chmod u=rw,g=r,o=r ckermi.mak
 
echo Inspecting for damage in transit...
temp=/tmp/shar$$; dtemp=/tmp/.shar$$
trap "rm -f $temp $dtemp; exit" 0 1 2 3 15
cat > $temp <<\!!!
    1159    8198   51955 ckermi.doc
      59     367    2307 ckermi.h
      28      66     537 ckermi.mak
    1246    8631   54799 total
!!!
wc  ckermi.doc ckermi.h ckermi.mak | sed 's=[^ ]*/==' | diff -b $temp - >$dtemp
if [ -s $dtemp ]
then echo "Ouch [diff of wc output]:" ; cat $dtemp
else echo "No problems found."
fi
exit 0

tsc2597@acf4.UUCP (Sam Chin) (02/28/85)

# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# Wrapped by acf4!tsc2597  on Wed Feb 27 22:20:15 EST 1985
# Contents:  ckfns.c ckmain.c ckprot.w
 
echo x - ckfns.c
sed 's/^@//' > "ckfns.c" <<'@//E*O*F ckfns.c//'
char *fnsv = "C-Kermit functions, 4.0(023) 1 Feb 85";

/*  C K F N S  --  System-independent Kermit protocol support functions  */
/*
 System-dependent primitives defined in:

   ckx???.c -- terminal i/o
   cxz???.c -- file i/o, directory structure

*/

#include "ckermi.h"			/* Symbol definitions for Kermit */

/* Externals from ckmain.c */

extern int spsiz, timint, npad, chklen, ebq, ebqflg, rpt, rptq, rptflg, capas;

extern int pktnum, prvpkt, numtry, oldtry, sndtyp, fsize, bctr, bctu,
  inpktl, size, osize, maxsize, spktl, nfils, stdouf, warn, timef;

extern int parity, speed, turn, turnch, ffc, flci, flco, tfc, tlci, tlco, 
 filcnt, delay, displa, pktlog, tralog, seslog, xflg, memstr, mypadn;

extern int deblog, hcflg, image, binary, fncnv, local, server, cxseen, czseen;

extern char padch, mypadc, eol, ctlq, myctlq, sstate, *hlptxt;

extern char filnam[], sndpkt[], recpkt[], data[], srvcmd[], *srvptr, stchr, 
 mystch;

extern char *cmarg, *cmarg2, **cmlist;
char *strcpy();

/* Variables local to this module */

static char *memptr;			/* Pointer for memory strings */

static char cmdstr[100];		/* Unix system command string */

static int  sndsrc;			/* Flag for where to send from: */
					/* -1: name in cmdata */
					/*  0: stdin          */
					/* >0: list in cmlist */

static int  memstr,			/* Flag for input from memory string */
     t,					/* Current character */
     next;				/* Next character */

/*  I N P U T  --  Attempt to read packet number 'pktnum'.  */

/*
 This is the function that feeds input to Kermit's finite state machine.

 If a special start state is in effect, that state is returned as if it were
 the type of an incoming packet.  Otherwise:

 . If the desired packet arrives within MAXTRY tries, return its type,
   with its data stored in the global 'data' array.

 . If the previous packet arrives again, resend the last packet and wait for
   another to come in.

 . If the desired packet does not arrive within MAXTRY tries, return indicating
   that an error packet should be sent.
*/

input() {
    int len, num, type, numtry;

    if (sstate != 0) {			/* If a start state is in effect, */
	type = sstate;			/* return it like a packet type, */
	sstate = 0;			/* and then nullify it. */
	*data = '\0';
	return(type);
    } else type = rpack(&len,&num,data); /* Else, try to read a packet. */

/* If it's the same packet we just sent, it's an echo.  Read another. */

    if (type == sndtyp) type = rpack(&len,&num,data);

    chkint();				/* Check for console interrupts. */
/*
 If previous packet again, a timeout pseudopacket, or a bad packet, try again.
*/
    for (numtry = 0; num == prvpkt || type == 'T' || type == 'Q' ; numtry++)
    {
	if (numtry > MAXTRY) {		/* If too many tries, give up */
	    strcpy(data,"Timed out.");	/* and send a timeout error packet. */
	    return('E');
	}
	resend();			/* Else, send last packet again, */
	type = rpack(&len,&num,data);	/* and try to read a new one. */
	chkint();			/* Look again for interruptions. */
    }
    return(type);			/* Success, return packet type. */
}

/*  S P A C K  --  Construct and send a packet  */

spack(type,num,len,dat) char type, *dat; int num, len; {
    int i,j;
    
    j = dopar(padch);
    for (i = 0; i < npad; sndpkt[i++] = j)  /* Do any requested padding */
    	;
    sndpkt[i++] = dopar(mystch);	/* Start packet with the start char */
    sndpkt[i++] = dopar(tochar(len+bctu+2));	/* Put in the length */
    sndpkt[i++] = dopar(tochar(num));		/* The packet number */
    sndpkt[i++] = sndtyp = dopar(type);		/* Packet type */

    for (j = len; j > 0; j-- ) sndpkt[i++] = dopar(*dat++); /* Data */

    sndpkt[i] = '\0';			/* Mark end for block check */
    switch(bctu) {
	case 1: 			/* Type 1 - 6 bit checksum */
	    sndpkt[i++] = dopar(tochar(chk1(sndpkt+1)));
	    break;
	case 2:				/* Type 2 - 12 bit checksum*/
	    j = chk2(sndpkt+1);
	    sndpkt[i++] = dopar(tochar((j & 07700) >> 6));
	    sndpkt[i++] = dopar(tochar(j & 077));
	    break;
        case 3:				/* Type 3 - 16 bit CRC-CCITT */
	    j = chk3(sndpkt+1);
	    sndpkt[i++] = dopar(tochar((j & 0170000) >> 12));
	    sndpkt[i++] = dopar(tochar((j & 07700) >> 6));
	    sndpkt[i++] = dopar(tochar(j & 077));
	    break;
	}
    for (j = npad; j > 0; j-- ) sndpkt[i++] = dopar(padch); /* Padding */

    sndpkt[i++] = dopar(eol);		/* EOL character */
    sndpkt[i] = '\0';			/* End of the packet */
    ttol(sndpkt,spktl=i);		/* Send the packet just built */
    flco += spktl;			/* Count the characters */
    tlco += spktl;
    if (pktlog) zsoutl(ZPFILE,sndpkt);	/* If logging packets, log it */
    screen(type,num,sndpkt);		/* Update screen */
}

/*  D O P A R  --  Add an appropriate parity bit to a character  */

dopar (ch) char ch; {
    int a;
    switch (parity) {
	case 'm':  return(ch | 128);		/* Mark */
	case 's':  return(ch & 127);		/* Space */
	case 'o':  ch |= 128;			/* Odd (fall thru) */
	case 'e':				/* Even */
	    a = (ch & 15) ^ ((ch >> 4) & 15);
	    a = (a & 3) ^ ((a >> 2) & 3);
	    a = (a & 1) ^ ((a >> 1) & 1);
	    return(ch | (a << 7));
	default:   return(ch);
    }
}

/*  C H K 1  --  Compute a type-1 Kermit 6-bit checksum.  */

chk1(pkt) char *pkt; {
    int chk;
    chk = chk2(pkt);
    return((((chk & 0300) >> 6) + chk) & 077);
}


/*  C H K 2  --  Compute the numeric sum of all the bytes in the packet.  */

chk2(pkt) char *pkt; {
    unsigned int chk;
    int p;
    for (chk = 0; *pkt != '\0'; *pkt++) {
    	p = (parity) ? *pkt & 0177 : *pkt;
	chk += p;
    }
    return(chk);
}


/*  C H K 3  --  Compute a type-3 Kermit block check.  */
/*
 Calculate the 16-bit CRC of a null-terminated string using a byte-oriented
 tableless algorithm invented by Andy Lowry (Columbia University).  The
 magic number 010201 is derived from the CRC-CCITT polynomial x^16+x^12+x^5+1.
 Note - this function could adapted for strings containing imbedded 0's
 by including a length argument.
*/
chk3(s) char *s; {
    int c, q;
    int crc = 0;

    while ((c = *s++) != '\0') {
	if (parity) c &= 0177;
	q = (crc ^ c) & 017;		/* Low-order nibble */
	crc = (crc >> 4) ^ (q * 010201);
	q = (crc ^ (c >> 4)) & 017;	/* High order nibble */
	crc = (crc >> 4) ^ (q * 010201);
    }
    return(crc);
}

/* Functions for sending various kinds of packets */

ack() {					/* Send an ordinary acknowledgment. */
    spack('Y',pktnum,0,"");		/* No data. */
    nxtpkt(&pktnum);			/* Increment the packet number. */
}					/* Note, only call this once! */

ack1(s) char *s; {			/* Send an ACK with data. */
    spack('Y',pktnum,strlen(s),s);	/* Send the packet. */
    nxtpkt(&pktnum);			/* Increment the packet number. */
}					/* Only call this once! */

nack() {				/* Negative acknowledgment. */
    spack('N',pktnum,0,"");		/* NAK's never have data. */
}

resend() {				/* Send the old packet again. */
    ttol(sndpkt,spktl);
    screen('%',pktnum,sndpkt);
    if (pktlog) zsoutl(ZPFILE,sndpkt);
}

errpkt(reason) char *reason; {		/* Send an error packet. */
    encstr(reason);
    spack('E',pktnum,size,data);
}

scmd(t,dat) char t, *dat; {		/* Send a packet of the given type */
    encstr(dat);			/* Encode the command string */
    ttflui();				/* Flush pending input. */
    spack(t,pktnum,size,data);
}

srinit() {				/* Send R (GET) packet */
    encstr(cmarg);			/* Encode the filename. */
    ttflui();				/* Flush pending input. */
    spack('R',pktnum,size,data);	/* Send the packet. */
}

nxtpkt(num) int *num; {
    prvpkt = *num;			/* Save previous */
    *num = (*num + 1) % 64;		/* Increment packet number mod 64 */
}

sigint() {				/* Terminal interrupt handler */
    errpkt("User typed ^C");
    doexit();
}

/* R P A C K  --  Read a Packet */

rpack(l,n,dat) int *l, *n; char *dat; {
    int i, j, x, done, pstart, pbl;
    char chk[4], xchk[4], t, type;

    chk[3] = xchk[3] = 0;
    i = inlin();			/* Read a line */
    if (i != 0) {
	debug(F101,"rpack: inlin","",i);
	screen('T',pktnum,"");
	return('T');
    }
    debug(F110,"rpack: inlin ok, recpkt",recpkt,0);

/* Look for start of packet */

    for (i = 0; ((t = recpkt[i]) != stchr) && (i < RBUFL) ; i++)
    	;
    if (++i >= RBUFL) return('Q');	/* Skip rest if not found */

/* now "parse" the packet */

    debug(F101,"entering rpack with i","",i);
    done = 0;
    while (!done) {
	debug(F101,"rpack starting at i","",i);
        pstart = i;			/* remember where packet started */

/* length */

	if ((t = recpkt[i++]) == stchr) continue; /* Resynch if SOH */

   /***	if (t == 2) doexit(); *** uncomment this to allow ^A^B cause exit ***/

	if (t == MYEOL) return('Q');
	*l = unchar(t);			/* Packet length */
	debug(F101," pkt len","",*l);

/* sequence number */

	if ((t = recpkt[i++]) == stchr) continue;
	if (t == MYEOL) return('Q');
	*n = unchar(t);
	debug(F101,"rpack: n","",*n);

/* cont'd... */

/* ...rpack(), cont'd */


/* type */

	if ((type = recpkt[i++]) == stchr) continue;
	if (type == MYEOL) return('Q');
	debug(F101,"rpack: type","",type);

	if ((type == 'S') || (type == 'I')) pbl = 1;	/* Heuristics for  */
	else if (type == 'N') pbl = *l - 2;    /* syncing block check type */
	else pbl = bctu;

	*l -= (pbl + 2);		/* Now compute data length */
	debug(F101,"rpack: bctu","",bctu);
	debug(F101," pbl","",pbl);
	debug(F101," data length","",*l);

/* data */

	dat[0] = '\0';			/* Return null string if no data */
	for (j=0; j<*l; i++,j++)
	    if ((dat[j] = recpkt[i]) == stchr) continue;
		else if (dat[j] == MYEOL) return('Q');
	dat[j] = '\0';

/* get the block check */

    	debug(F110," packet chk",recpkt+i,0);
    	for (j = 0; j < pbl; j++) {
	    chk[j] = recpkt[i];
	    debug(F101," chk[j]","",chk[j]);
	    if (chk[j] == stchr) break;
	    if (chk[j] == eol) return('Q');
	    recpkt[i++] = '\0';
	}
	chk[j] = 0;
	debug(F111," chk array, j",chk,j);
	if (j != pbl) continue;		/* Block check right length? */
	done = 1;			/* Yes, done. */
    }

/* cont'd... */

/* ...rpack(), cont'd */


/* Got packet, now check the block check */

    switch (pbl) {
	case 1:
	    xchk[0] = tochar(chk1(&recpkt[pstart]));
	    if (chk[0] != xchk[0]) {
		if (deblog) {
		    debug(F000,"rpack: chk","",chk[0]);
		    debug(F000," should be ","",xchk[0]);
		}
		screen('Q',n,recpkt);
		return('Q');
	    }
	    break;
	case 2:
	    x = chk2(&recpkt[pstart]);
	    xchk[0] = tochar((x & 07700) >> 6);
	    xchk[1] = tochar(x & 077);
	    if (deblog) {
		debug(F000," xchk[0]","=",xchk[0]);
		debug(F000," xchk[1]","=",xchk[1]);
	    }
	    if ((xchk[0] != chk[0]) || (xchk[1] != chk[1])) {
		debug(F100," bct2's don't compare","",0);
		screen('Q',n,recpkt);
		return('Q');
            }
	    break;
	case 3:
	    x = chk3(&recpkt[pstart]);
	    xchk[0] = tochar((x & 0170000) >> 12);
	    xchk[1] = tochar((x & 07700) >> 6);
	    xchk[2] = tochar(x & 077);
	    if (deblog) {
		debug(F000," xchk[0]","=",xchk[0]);
		debug(F000," xchk[1]","=",xchk[1]);
		debug(F000," xchk[2]","=",xchk[2]);
            }
	    if ((xchk[0] != chk[0]) || 
	    	(xchk[1] != chk[1]) || 
		(xchk[2] != chk[2])) {
		    debug(F100," bct3's don't compare","",0);
		    screen('Q',n,recpkt);
		    return('Q');
	    }
	    break;
        }

/* Good packet, return its type */

    ttflui();				/* Done, flush any remaining. */
    screen(type,*n,recpkt);		/* Update screen */
    return(type);
}

/*  I N C H R  --  Input character from communication line, with timeout  */
    	
inchr(timo) int timo; {
    int c;
    c = ttinc(timo);
    debug(F101,"inchr ttinc","",c);
    if (c < 0) return(c); 		/* Get a character */
    if (parity) c = c & 0177;		/* If parity on, discard parity bit. */
    debug(F101," after parity","",c);
    return(c);
}


/*  I N L I N  -- Input a line (up to break char) from communication line  */

/*  Returns 0 on success, nonzero on failure  */

inlin() {
    int e, i, j, k;

    e = (turn) ? turnch : MYEOL;
    i = j = k = 0;
    if (parity) {
    	while ((j != e) && (i < RBUFL) && (k < MAXTRY)) {
	    j = inchr(1);		/* Get char, 1 second timeout */
	    debug(F101,"inlin inchr","",j);
	    if (j < 0) k++;		/* Timed out. */
	    else {
		if (j) recpkt[i++] = j;	/* Save it */
		k = 0;			/* Reset timeout counter. */
	    }
	}
    } else {
    	i = ttinl(recpkt,RBUFL,timint,e);	/* Get them all at once */
	if (i < 0) k = 1;
    }
    debug(F111,"inlin",recpkt,i);
    debug(F101," timeouts","",k);
    if (i < 1) return(1);
    if (pktlog) zsoutl(ZPFILE,recpkt);
    if (k > MAXTRY) return(1);
    tlci += i;				/* Count the characters. */
    flci += i;
    recpkt[i+1] = '\0';			/* Terminate the input string. */
if (0) {    
    if (turn) {				/* If doing line turnaround, */
	debug(F101,"inlin: looking for turnaround","",turnch);
	for (i = 0 ; i < MAXTRY ; i++) {
	    j = inchr(1);
	    if (pktlog) zchout(ZPFILE,j);
	    if (j == turnch) break;     /* Wait for turnaround char */
        }
    }
}
    return(0);
}

/*  E N C S T R  --  Encode a string from memory. */

/*  Call this instead of getpkt() if source is a string, rather than a file. */

encstr(s) char* s; {
    int m; char *p;

    m = memstr; p = memptr;		/* Save these. */

    memptr = s;				/* Point to the string. */
    memstr = 1;				/* Flag memory string as source. */
    next = -1;				/* Initialize character lookahead. */
    getpkt(spsiz);			/* Fill a packet from the string. */
    memstr = m;				/* Restore memory string flag */
    memptr = p;				/* and pointer */
    next = -1;				/* Put this back as we found it. */
    debug(F111,"encstr",data,size);
}

/* E N C O D E - Kermit packet encoding procedure */

encode(a) int a; {			/* The current character */
    int a7;				/* Low order 7 bits of character */
    int b8;				/* 8th bit of character */

    if (rptflg)	{			/* Repeat processing? */
        if (a == next) {		/* Got a run... */
	    if (++rpt < 94)		/* Below max, just count */
                return;
	    else if (rpt == 94) {	/* Reached max, must dump */
                data[size++] = rptq;
                data[size++] = tochar(rpt);
                rpt = 0;
	    }
        } else if (rpt == 1) {		/* Run broken, only 2? */
            rpt = 0;			/* Yes, reset repeat flag & count. */
	    encode(a);			/* Do the character twice. */
	    if (size <= maxsize) osize = size;
	    rpt = 0;
	    encode(a);
	    return;
	} else if (rpt > 1) {		/* More than two */
            data[size++] = rptq;	/* Insert the repeat prefix */
            data[size++] = tochar(++rpt); /* and count. */
            rpt = 0;			/* Reset repeat counter. */
        }
    }
    a7 = a & 0177;			/* Isolate ASCII part */
    b8 = a & 0200;			/* and 8th (parity) bit. */

    if (ebqflg && b8) {			/* Do 8th bit prefix if necessary. */
        data[size++] = ebq;
        a = a7;
    }
    if ((a7 < SP) || (a7==DEL))	{	/* Do control prefix if necessary */
        data[size++] = myctlq;
	a = ctl(a);
    }
    if (a7 == myctlq)			/* Prefix the control prefix */
        data[size++] = myctlq;

    if ((rptflg) && (a7 == rptq))	/* If it's the repeat prefix, */
        data[size++] = myctlq;		/* quote it if doing repeat counts. */

    if ((ebqflg) && (a7 == ebq))	/* Prefix the 8th bit prefix */
        data[size++] = myctlq;		/* if doing 8th-bit prefixes */

    data[size++] = a;			/* Finally, insert the character */
    data[size] = '\0';			/* itself, and mark the end. */
}

/* D E C O D E  --  Kermit packet decoding procedure */

/* Call with string to be decoded and an output function. */

decode(buf,fn) char *buf; int (*fn)(); {
    unsigned int a, a7, b8;		/* Low order 7 bits, and the 8th bit */

    rpt = 0;

    while ((a = *buf++) != '\0') {
	if (rptflg) {			/* Repeat processing? */
	    if (a == rptq) {		/* Yes, got a repeat prefix? */
		rpt = unchar(*buf++);	/* Yes, get the repeat count, */
		a = *buf++;		/* and get the prefixed character. */
	    }
	}
	b8 = 0;				/* Check high order "8th" bit */
	if (ebqflg) {			/* 8th-bit prefixing? */
	    if (a == ebq) {		/* Yes, got an 8th-bit prefix? */
		b8 = 0200;		/* Yes, remember this, */
		a = *buf++;		/* and get the prefixed character. */
	    }
	}
	if (a == ctlq) {		/* If control prefix, */
	    a  = *buf++;		/* get its operand. */
	    a7 = a & 0177;		/* Only look at low 7 bits. */
	    if ((a7 >= 0100 && a7 <= 0137) || a7 == '?') /* Uncontrollify */
	    a = ctl(a);			/* if in control range. */
	}
	a |= b8;			/* OR in the 8th bit */
	if (rpt == 0) rpt = 1;		/* If no repeats, then one */
	for (; rpt > 0; rpt--) {	/* Output the char RPT times */
	    if (a == CR && !binary) break; /* But skip CR if binary. */
	    ffc++, tfc++;		/* Count the character */
	    (*fn)(a);			/* Send it to the output function. */
	}
    }
}


/*  Output functions passed to 'decode':  */

putsrv(c) char c; { 	/*  Put character in server command buffer  */
    *srvptr++ = c;
    *srvptr = '\0';	/* Make sure buffer is null-terminated */
}

puttrm(c) char c; {     /*  Output character to console.  */
    conoc(c);
}

putfil(c) char c; {	/*  Output char to file. */
    zchout(ZOFILE,c);
}

/*  G E T P K T -- Fill a packet data field  */

/*
 Gets characters from the current source -- file or memory string.
 Encodes the data into the packet, filling the packet optimally.

 Uses global variables:
 t     -- current character.
 next  -- next character.
 data  -- the packet data buffer.
 size  -- number of characters in the data buffer.

Returns the size as value of the function, and also sets global size,
and fills (and null-terminates) the global data array.

Before calling getpkt the first time for a given source (file or string),
set the variable 'next' to -1.
*/

getpkt(maxsize) int maxsize; {		/* Fill one packet buffer */
    int i;				/* Loop index. */

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

    if (next < 0) t = getch();		/* Get first character of file. */

    /* Do any leftovers */

    for (size = 0; (data[size] = leftover[size]) != '\0'; size++)
    	;
    *leftover = '\0';

    /* Now fill up the rest of the packet. */

    while(t >= 0) {			/* Until EOF... */
	next = getch();			/* Get next character for lookahead. */
	osize = size;			/* Remember current position. */
        encode(t);			/* Encode the current character. */
        t = next;			/* Next is now current. */

	if (size == maxsize) 		/* If the packet is exactly full, */
            return(size);		/* and return. */

	if (size > maxsize) {		/* If too big, save some for next. */
	    for (i = 0; (leftover[i] = data[osize+i]) != '\0'; i++)
	    	;
	    size = osize;		/* Return truncated packet. */
	    data[size] = '\0';
	    return(size);
	}
    }
    return(size);			/* Return any partial final buffer. */
}

/*  G E T C H  -- Get the next character from file (or pipe). */
 
/*  Convert newlines to CRLFs if newline/CRLF mapping is being done. */

getch()	{				/* Get next character */
    int a, x;				/* The character to return. */
    static int b = 0;			/* A character to remember. */
    
    if (b > 0) {			/* Do we have a newline saved? */
	b = 0;				/* Yes, return that. */
	return('\n');
    }

    if (memstr)				/* Try to get the next character */
    	x = ((a = *memptr++) == '\0');	/* from the appropriate source, */
    else				/* memory or the current file. */
    	x = ((a = zchin(ZIFILE)) < 0 );

    if (x)
    	return(-1);			/* No more, return -1 for EOF. */
    else {				/* Otherwise, read the next char. */
	ffc++, tfc++;			/* Count it. */
	if (a == '\n' && !binary) {	/* If nl and we must do nl-CRLF */
	    b = a;			/* mapping, save the nl, */
	    return(CR);			/* and return a CR. */
	} else return(a);		/* General case, return the char. */
    }
}


/*  C A N N E D  --  Check if current file transfer cancelled */

canned(buf) char *buf; {
    if (*buf == 'X') cxseen = 1;
    if (*buf == 'Z') czseen = 1;
    debug(F101,"canned: cxseen","",cxseen);
    debug(F101," czseen","",czseen);
    return((czseen || cxseen) ? 1 : 0);
}

/*  T I N I T  --  Initialize a transaction  */

tinit() {
    xflg = 0;				/* reset x-packet flag */
    memstr = 0;				/* reset memory-string flag */
    memptr = NULL;			/*  and pointer */
    bctu = 1;				/* reset block check type to 1 */
    filcnt = 0;				/* reset file counter */
    tfc = tlci = tlco = 0;		/* reset character counters */
    prvpkt = -1;			/* reset packet number */
    pktnum = 0;
    if (server) {			/* If acting as server, */
	timint = 30;			/* use 30 second timeout, */
	nack();				/* send a NAK */
    }
}


/*  R I N I T  --  Respond to S packet  */

rinit(d) char *d; {
    char *tp;
    ztime(&tp);
    tlog(F110,"Transaction begins",tp,0); /* Make transaction log entry */
    tfc = tlci = tlco = 0;
    spar(d);
    rpar(d);
    ack1(d);
}

/*  S I N I T  --  Make sure file exists, then send Send-Init packet */

sinit() {
    int x; char *tp;

    sndsrc = nfils;			/* Where to look for files to send */
    ztime(&tp);
    tlog(F110,"Transaction begins",tp,0); /* Make transaction log entry */
    debug(F101,"sinit: sndsrc","",sndsrc);
    if (sndsrc < 0) {			/* Must expand from 'send' command */
	nfils = zxpand(cmarg);		/* Look up literal name. */
	if (nfils < 0) {
	    screen(2,0,"?Too many files");
	    return(0);
        } else if (nfils == 0) {	/* If none found, */
	    char xname[100];		/* convert the name. */
	    zrtol(cmarg,xname);
	    nfils = zxpand(xname); 	/* Look it up again. */
	}
	if (nfils < 1) {		/* If no match, report error. */
	    if (server) 
	    	errpkt("File not found");
	    else
		screen(2,0,"?File not found");
	    return(0);
	}
	x = gnfile();			/* Position to first file. */
	if (x < 1) {
	    if (!server) 
	    	screen(2,0,"?No readable file to send");
            else
	    	errpkt("No readable file to send");
	    return(0);
    	} 
    } else if (sndsrc > 0) {		/* Command line arglist -- */
	x = gnfile();			/* Get the first file from it. */
	if (x < 1) return(0);		/* (if any) */
    } else if (sndsrc == 0) {		/* stdin or memory always exist... */
	cmarg2 = "";			/* No alternate name */
	strcpy(filnam,"stdin");		/* If F packet, filnam is used. */
	tlog(F110,"Sending from",cmdstr,0); /* If X packet, cmdstr is used. */
    }

    debug(F101,"sinit: nfils","",nfils);
    debug(F110," filnam",filnam,0);
    debug(F110," cmdstr",cmdstr,0);
    ttflui();				/* Flush input buffer. */
    x = rpar(data);			/* Send a Send-Init packet. */
    if (!local && !server) sleep(delay);
    spack('S',pktnum,x,data);
    return(1);
}

/*  R C V F I L -- Receive a file  */

rcvfil() {
    int x;

    ffc = flci = flco = 0;		/* Init per-file counters */
    srvptr = srvcmd;			/* Decode packet data. */
    decode(data,putsrv);
    screen(0,0,srvcmd);			/* Update screen */
    screen(1,0,"=> ");
    tlog(F110,"Receiving",srvcmd,0);	/* Transaction log entry */
    if (*cmarg2 != '\0') {		/* Check for alternate name */
	strcpy(srvcmd,cmarg2);		/* Got one, use it. */
	*cmarg2 = '\0';
    }
    x = openo(srvcmd,filnam);		/* Try to open it */
    if (x) {
	tlog(F110," as",filnam,0);
	screen(2,0,filnam);
	intmsg(++filcnt);
    } else {
        tlog(F110,"Failure to open",filnam,0);
	screen(2,0,"*** error");
    }
    return(x);				/* Pass on return code from openo */
}

/*  R E O F  --  Receive End Of File  */

reof() {

    if (cxseen == 0) cxseen = (*data == 'D');
    clsof();

    if (cxseen || czseen) {
	tlog(F100," *** Discarding","",0);
    } else {
	tlog(F100," end of file","",0);
	tlog(F101,"  file characters        ","",ffc);
	tlog(F101,"  communication line in  ","",flci);
	tlog(F101,"  communication line out ","",flco);
    }
}

/*  R E O T  --  Receive End Of Transaction  */

reot() {
    char *tp;
    cxseen = czseen = 0; 
    ztime(&tp);
    tlog(F110,"End of transaction",tp,0);
    if (filcnt > 1) {
	tlog(F101," files","",filcnt);
	tlog(F101," total file characters   ","",tfc);
	tlog(F101," communication line in   ","",tlci);
	tlog(F101," communication line out  ","",tlco);
    }
}

/*  S F I L E -- Send File header packet for global "filnam" */

sfile() {
    char pktnam[100];			/* Local copy of name */

    if (fncnv) {
	if (*cmarg2 != '\0') {		/* If we have a send-as name, */
	    zltor(cmarg2,pktnam);	/* convert it to common form, */
	    cmarg2 = "";		/* and blank it out for next time, */
	} else zltor(filnam,pktnam);	/* otherwise use the real file name. */
    } else {
	if (*cmarg2 != '\0')		/* Same as above, but without */
	    strcpy(pktnam,cmarg2);	/* name conversion */
        else strcpy(filnam,pktnam);
    }

    debug(F110,"sfile",filnam,0);
    if (openi(filnam) == 0) 		/* Try to open the file */
	return(0); 		

    rpt = flci = flco = ffc = 0;	/* OK, Init counters, etc. */
    encstr(pktnam);			/* Encode the name. */
    nxtpkt(&pktnum);			/* Increment the packet number */
    ttflui();				/* Clear pending input */
    spack('F',pktnum,size,data); 	/* Send the F packet */
    if (displa) {
	screen(0,pktnum,filnam);	/* Update screen */
	screen(1,0,"=> ");
	screen(1,0,pktnam);
	screen(3,fsize,", size");
	intmsg(++filcnt);		/* Count file, give interrupt msg */
    }
    tlog(F110,"Sending",filnam,0);	/* Transaction log entry */
    tlog(F110," as",pktnam,0);
    next = -1;				/* Init file character lookahead. */
    return(1);
}


/* Send an X Packet -- Like SFILE, but with Text rather than File header */

sxpack() {				/* Send an X packet */
    debug(F110,"sxpack",cmdstr,0);
    encstr(cmdstr);			/* Encode any data. */
    rpt = flci = flco = ffc = 0;	/* Init counters, etc. */
    next = -1;				/* Init file character lookahead. */
    nxtpkt(&pktnum);			/* Increment the packet number */
    spack('X',pktnum,size,data);	/* No incrementing pktnum */
    screen(0,pktnum,cmdstr);		/* Update screen. */
    intmsg(++filcnt);
    tlog(F110,"Sending from:",cmdstr,0);
    return(1);
}

/*  S D A T A -- Send a data packet */

sdata() {
    int len;
    if (cxseen || czseen) return(0);	/* If interrupted, done. */
    if ((len = getpkt(spsiz-chklen-3)) == 0) return(0); /* If no data, done. */
    nxtpkt(&pktnum);			/* Increment the packet number */
    spack('D',pktnum,len,data);		/* Send the packet */
    return(1);
}


/*  S E O F -- Send an End-Of-File packet */

seof() {
    nxtpkt(&pktnum);			/* Increment the packet number */
    if (czseen || cxseen) {
	spack('Z',pktnum,1,"D");
	tlog(F100," *** interrupted, sending discard request","",0);
    } else {
	spack('Z',pktnum,0,"");
	tlog(F100," end of file","",0);
	tlog(F101,"  file characters        ","",ffc);
	tlog(F101,"  communication line in  ","",flci);
	tlog(F101,"  communication line out ","",flco);
    }
}


/*  S E O T -- Send an End-Of-Transaction packet */

seot() {
    char *tp;
    nxtpkt(&pktnum);			/* Increment the packet number */
    spack('B',pktnum,0,"");
    cxseen = czseen = 0; 
    ztime(&tp);
    tlog(F110,"End of transaction",tp,0);
    if (filcnt > 1) {
	tlog(F101," files","",filcnt);
	tlog(F101," total file characters   ","",tfc);
	tlog(F101," communication line in   ","",tlci);
	tlog(F101," communication line out  ","",tlco);
    }
}

/*   R P A R -- Fill the data array with my send-init parameters  */

rpar(data) char data[]; {
    data[0] = tochar(spsiz-1);		/* Biggest packet I can receive */
    data[1] = tochar(URTIME);		/* When I want to be timed out */
    data[2] = tochar(mypadn);		/* How much padding I need (none) */
    data[3] = ctl(mypadc);		/* Padding character I want */
    data[4] = tochar(MYEOL);		/* End-Of-Line character I want */
    data[5] = CTLQ;			/* Control-Quote character I send */
    if (ebqflg) data[6] = ebq = '&';
    	else data[6] = 'Y';		/* 8-bit quoting */
    data[7] = bctr + '0';		/* Block check type */
    data[8] = MYRPTQ;			/* Do repeat counts */
    data[9] = '\0';
    return(9);				/* Return the length. */
}

/*   S P A R -- Get the other system's Send-Init parameters.  */

spar(data) char data[]; {
    int len, x;

    len = strlen(data);		    	/* Number of fields */

    spsiz = (len-- > 0) ? unchar(data[0]) : DSPSIZ; 	/* Packet size */
    if (spsiz < 10) spsiz = DSPSIZ;

    x = (len-- > 0) ? unchar(data[1]) : DMYTIM;	/* Timeout */
    if (!timef) {			/* Only use if not overridden */
	timint = x;
	if (timint < 0) timint = DMYTIM;
    }

    npad = 0; padch = '\0';		    	    	/* Padding */
    if (len-- > 0) {
	npad = unchar(data[2]);
	if (len-- > 0) padch = ctl(data[3]); else padch = 0;
    }

    eol = (len-- > 0) ? unchar(data[4]) : '\r';	    	/* Terminator  */
    if ((eol < 2) || (eol > 037)) eol = '\r';

    ctlq = (len-- > 0) ? data[5] : CTLQ;    	    	/* Control prefix */

    if (len-- > 0) {			    	    	/* 8th-bit prefix */
	ebq = data[6];
	if ((ebq > 040 && ebq < 0100) || (ebq > 0140 && ebq < 0177)) {
	    ebqflg = 1;
	} else if (parity && (ebq == 'Y')) {
	    ebqflg = 1;
	    ebq = '&';
	} else if (ebq == 'N') {
	    ebqflg = 0;
	} else ebqflg = 0;
    } else ebqflg = 0;

    chklen = 1;				    	    	/* Block check */
    if (len-- > 0) {
	chklen = data[7] - '0';
	if ((chklen < 1) || (chklen > 3)) chklen = 1;
    }
    bctr = chklen;

    if (len-- > 0) {			    	    	/* Repeat prefix */
	rptq = data[8]; 
	rptflg = ((rptq > 040 && rptq < 0100) || (rptq > 0140 && rptq < 0177));
    } else rptflg = 0;

    if (deblog) sdebu(len);
}

/*  S D E B U  -- Record spar results in debugging log  */

sdebu(len) int len; {
    debug(F111,"spar: data",data,len);
    debug(F101," spsiz ","",spsiz);
    debug(F101," timint","",timint);
    debug(F101," npad  ","",npad);
    debug(F101," padch ","",padch);
    debug(F101," eol   ","",eol);
    debug(F101," ctlq  ","",ctlq);
    debug(F101," ebq   ","",ebq);
    debug(F101," ebqflg","",ebqflg);
    debug(F101," chklen","",chklen);
    debug(F101," rptq  ","",rptq);
    debug(F101," rptflg","",rptflg);
}

/*  G N F I L E  --  Get the next file name from a file group.  */

/*  Returns 1 if there's a next file, 0 otherwise  */

gnfile() {
    int x, y;

/* If file group interruption (C-Z) occured, fail.  */

    debug(F101,"gnfile: czseen","",czseen);

    if (czseen) {
	tlog(F100,"Transaction cancelled","",0);
	return(0);
    }

/* If input was stdin or memory string, there is no next file.  */

    if (sndsrc == 0) return(0);

/* If file list comes from command line args, get the next list element. */

    y = -1;
    while (y < 0) {			/* Keep trying till we get one... */

	if (sndsrc > 0) {
	    if (nfils-- > 0) {
		strcpy(filnam,*cmlist++);
		debug(F111,"gnfile: cmlist filnam",filnam,nfils);
	    } else {
		*filnam = '\0';
		debug(F101,"gnfile cmlist: nfils","",nfils);
		return(0);
	    }
	}

/* Otherwise, step to next element of internal wildcard expansion list. */

	if (sndsrc < 0) {
	    x = znext(filnam);
	    debug(F111,"gnfile znext: filnam",filnam,x);
	    if (x == 0) return(0);
	}

/* Get here with a filename. */

	y = zchki(filnam);		/* Check if file readable */
	if (y < 0) {
	    debug(F110,"gnfile skipping:",filnam,0);
	    tlog(F111,filnam,"not sent, reason",y);
	    screen(0,0,"Skipping");
	    screen(2,0,filnam);
	} else fsize = y;
    }    	
    return(1);
}

/*  O P E N I  --  Open an existing file for input  */

openi(name) char *name; {
    int x, filno;
    if (memstr) return(1);		/* Just return if file is memory. */

    debug(F110,"openi",name,0);
    debug(F101," sndsrc","",sndsrc);

    filno = (sndsrc == 0) ? ZSTDIO : ZIFILE;    /* ... */

    debug(F101," file number","",filno);

    if (zopeni(filno,name)) {		/* Otherwise, try to open it. */
	debug(F110," ok",name,0);
    	return(1);
    } else {				/* If not found, */
	char xname[100];		/* convert the name */
	zrtol(name,xname);		/* to local form and then */
	debug(F110," zrtol:",xname,0);
	x = zopeni(filno,xname);	/* try opening it again. */
	debug(F101," zopeni","",x);
	if (x) {
	    debug(F110," ok",xname,0);
	    return(1);			/* It worked. */
        } else {
	    screen(2,0,"Can't open file");  /* It didn't work. */
	    tlog(F110,xname,"could not be opened",0);
	    debug(F110," openi failed",xname,0);
	    return(0);
        }
    }
}

/*  O P E N O  --  Open a new file for output.  */

/*  Returns actual name under which the file was opened in string 'name2'. */

openo(name,name2) char *name, *name2; {
    char xname[100], *xp;

    if (stdouf)				/* Receiving to stdout? */
	return(zopeno(ZSTDIO,""));

    debug(F110,"openo: name",name,0);

    xp = xname;
    if (fncnv)				/* If desired, */
    	zrtol(name,xp);			/* convert name to local form */
    else				/* otherwise, */
    	strcpy(xname,name);		/* use it literally */

    debug(F110,"openo: xname",xname,0);

    if (warn) {				/* File collision avoidance? */
	if (zchki(xname) != -1) {	/* Yes, file exists? */
	    znewn(xname,&xp);		/* Yes, make new name. */
	    strcpy(xname,xp);
	    debug(F110," exists, new name ",xname,0);
        }
    }
    if (zopeno(ZOFILE,xname) == 0) {	/* Try to open the file */
	debug(F110,"openo failed",xname,0);
	tlog(F110,"Failure to open",xname,0);
	return(0);
    } else {
	strcpy(name2,xname);
	debug(F110,"openo ok, name2",name2,0);
	return(1);
    }
}

/*  O P E N T  --  Open the terminal for output, in place of a file  */

opent() {
    ffc = tfc = 0;
    return(zopeno(ZCTERM,""));
}

/*  C L S I F  --  Close the current input file. */

clsif() {
    if (memstr) {			/* If input was memory string, */
	memstr = 0;			/* indicate no more. */
    } else if (hcflg) {
	zclosf();			/* If host cmd close fork, */
    } else zclose(ZIFILE);		/* else close input file. */

    screen(1,0," [OK]");
    hcflg = cxseen = 0;			/* Reset flags. */
}


/*  C L S O F  --  Close an output file.  */

clsof() {
    zclose(ZOFILE);			/* Close it. */
    if (czseen || cxseen) {
	zdelet(filnam);   		/* Delete it if interrupted. */
	debug(F100,"Discarded","",0);
	tlog(F100,"Discarded","",0);
	screen(1,0," [Discarded]");
    } else {
	debug(F100,"Closed","",0);
	screen(1,0," [OK]");
    }
    cxseen = 0;
}

/*  S N D H L P  --  Routine to send builtin help  */

sndhlp() {
    nfils = 0;				/* No files, no lists. */
    xflg = 1;				/* Flag we must send X packet. */
    strcpy(cmdstr,"help text");		/* Data for X packet. */
    next = -1;				/* Init getch lookahead */
    memstr = 1;				/* Just set the flag. */
    memptr = hlptxt;			/* And the pointer. */
    return(sinit());
}


/*  C W D  --  Change current working directory  */

/*
 String passed has first byte as length of directory name, rest of string
 is name.  Fails if can't connect, else ACKs (with name) and succeeds. 
*/

cwd(vdir) char *vdir; {
    vdir[unchar(*vdir) + 1] = '\0';	/* End with a null */
    if (zchdir(vdir+1)) {
	encstr(vdir+1);
	ack1(data);
	tlog(F110,"Changed directory to",vdir+1,0);
	return(1); 
    } else {
	tlog(F110,"Failed to change directory to",vdir+1,0);
	return(0);
    }
}


/*  S Y S C M D  --  Do a system command  */

/*  Command string is formed by concatenating the two arguments.  */

syscmd(prefix,suffix) char *prefix, *suffix; {
    char *cp;

    for (cp = cmdstr; *prefix != '\0'; *cp++ = *prefix++) ;
    while (*cp++ = *suffix++) ;

    debug(F110,"syscmd",cmdstr,0);
    if (zxcmd(cmdstr) > 0) {
	debug(F100,"zxcmd ok","",0);
	nfils = sndsrc = 0;		/* Flag that input from stdin */
	xflg = hcflg = 1;		/* And special flags for pipe */
	return (sinit());		/* Send S packet */
    } else {
	debug(F100,"zxcmd failed","",0);
	return(0);
    }
}
@//E*O*F ckfns.c//
chmod u=rw,g=r,o=r ckfns.c
 
echo x - ckmain.c
sed 's/^@//' > "ckmain.c" <<'@//E*O*F ckmain.c//'
char *versio = "C-Kermit 4.0(025) PRERELEASE TEST VERSION, 5 Feb 85";

/* C K M A I N  --  C-Kermit Main program */

/*
 Authors: Frank da Cruz, Bill Catchings, Jeff Damens;
 Columbia University Center for Computing Activities, 1984-85.
 Copyright (C) 1985, Trustees of Columbia University in the City of New York.
 Permission is granted to any individual or institution to copy or use this
 program except for explicitly commercial purposes, provided this copyright
 notice is retained.
*/
/*
 The Kermit file transfer protocol was developed at Columbia University.
 It is named after Kermit the Frog, star of the television series THE
 MUPPET SHOW; the name is used by permission of Henson Associates, Inc.
 "Kermit" is also Celtic for "free".
*/
/*
 Thanks to the following people for their contributions to this program:
   Bob Cattani & Chris Maio, Columbia CS Dept
   Alan Crosswell, CUCCA
   Herm Fischer, Litton Data Systems
   Carl Fongheiser, CWRU
   Jim Guyton, Rand Corp
   Stan Hanks, Rice U.
   Ken Harrenstein, SRI
   Ken Poulton, HP Labs
   Walter Underwood, Ford Aerospace
   Pieter Van Der Linden, Centre Mondial (Paris)
   Lauren Weinstein, Vortex
 and many others.
*/

#include "ckermi.h"

/* Text message definitions */

char *hlptxt = "C-Kermit Server\n\
\n\
Server Function    Customary Command to Invoke the Function\n\
 Send File(s)       GET filespec\n\
 Receive File(s)    SEND filespec\n\
 Directory Listing  REMOTE DIRECTORY [filespec]\n\
 Change Directory   REMOTE CWD [directory]\n\
 Type File(s)       REMOTE TYPE filespec\n\
 Delete File(s)     REMOTE DELETE filespec\n\
 Disk Usage Query   REMOTE SPACE [directory]\n\
 Unix Shell Command REMOTE HOST command-string\n\
 Who's Logged In?   REMOTE WHO [user]\n\
 Help               REMOTE HELP\n\
 Finish Serving     FINISH\n\
\n\0";

char *srvtxt = "\r\n\
C-Kermit server starting.  Return to your local machine by typing\r\n\
its escape sequence for closing the connection, and issue further\r\n\
commands from there.  To shut down the C-Kermit server, issue the\r\n\
FINISH command and then reconnect.\n\
\r\n\0";


/* Declarations for Send-Init Parameters */

int spsiz =  DSPSIZ,			/* maximum packet size we can send */
    timint = DMYTIM,			/* My timeout interval */
    timef = 0,				/* Flag for override packet timeout */
    npad = MYPADN,			/* How much padding to send */
    mypadn = MYPADN,			/* How much padding to ask for */
    chklen = 1,				/* Length of block check */
    bctr = 1,				/* Block check type requested */
    bctu = 1,				/* Block check type used */
    ebq =  MYEBQ,			/* 8th bit prefix */
    ebqflg = 0,				/* 8th-bit quoting flag */
    rpt = 0,				/* Repeat count */
    rptq = MYRPTQ,			/* Repeat prefix */
    rptflg = 0,				/* Repeat processing flag */
    capas = 0;				/* Capabilities */

char padch = MYPADC,			/* Padding character to send */
    mypadc = MYPADC,			/* Padding character to ask for */
    eol = MYEOL,			/* End-Of-Line character to send */
    ctlq = CTLQ,			/* Control prefix in incoming data */
    myctlq = CTLQ;			/* Outbound control character prefix */


/* Packet-related variables */

int pktnum = 0,				/* Current packet number */
    prvpkt = -1,			/* Previous packet number */
    sndtyp,				/* Type of packet just sent */
    size,				/* Current size of output pkt data */
    osize,				/* Previous output packet data size */
    maxsize,				/* Max size for building data field */
    spktl;				/* Length packet being sent */

char sndpkt[MAXPACK*2], 		/* Entire packet being sent */
    recpkt[RBUFL], 			/* Packet most recently received */
    data[MAXPACK+4],   			/* Packet data buffer */
    srvcmd[MAXPACK*2],			/* Where to decode server command */
    *srvptr,				/* Pointer to above */
    mystch = SOH,			/* Outbound packet-start character */
    stchr = SOH;			/* Incoming packet-start character */

/* File-related variables */

char filnam[50];			/* Name of current file. */

int nfils,				/* Number of files in file group */
    fsize;				/* Size of current file */

/* Communication line variables */

char ttname[50];			/* Name of communication line. */

int parity,				/* Parity specified, 0,'e','o',etc */
    flow,				/* Flow control, 1 = xon/xoff */
    speed = -1,				/* Line speed */
    turn = 0,				/* Line turnaround handshake flag */
    turnch = XON,			/* Line turnaround character */
    duplex = 0,				/* Duplex, full by default */
    escape = 034,			/* Escape character for connect */
    delay = DDELAY;			/* Initial delay before sending */


/* Statistics variables */

int filcnt,			/* Number of files in transaction */
    flci,			/* Characters from line, current file */
    flco,			/* Chars to line, current file  */
    tlci,			/* Chars from line in transaction */
    tlco,   	    	    	/* Chars to line in transaction */
    ffc,			/* Chars to/from current file */
    tfc;			/* Chars to/from files in transaction */

/* Flags */

int deblog = 0,				/* Flag for debug logging */
    pktlog = 0,				/* Flag for packet logging */
    seslog = 0,				/* Session logging */
    tralog = 0,				/* Transaction logging */
    displa = 0,				/* File transfer display on/off */
    stdouf = 0,				/* Flag for output to stdout */
    xflg   = 0,				/* Flag for X instead of F packet */
    hcflg  = 0,				/* Doing Host command */
    fncnv  = 1,				/* Flag for file name conversion */
    binary = 0,				/* Flag for binary file */
    warn   = 0,				/* Flag for file warning */
    quiet  = 0,				/* Be quiet during file transfer */
    local  = 0,				/* Flag for external tty vs stdout */
    server = 0,				/* Flag for being a server */
    cnflg  = 0,				/* Connect after transaction */
    cxseen = 0,				/* Flag for cancelling a file */
    czseen = 0; 	    	    	/* Flag for cancelling file group */

/* Variables passed from command parser to protocol module */

char sstate = 0;			/* Starting state for automaton */
char *cmarg;				/* Pointer to command data */
char *cmarg2;				/* Pointer to second data field */
char **cmlist;				/* Pointer to file list in argv */

/* Miscellaneous */

char **xargv;				/* Global copies of argv */
int  xargc;				/* and argc  */

extern char *dftty;			/* Default tty name from ckx???.c */
extern int dfloc;			/* Default location: remote/local */
extern int dfprty;			/* Default parity */
extern int dfflow;			/* Default flow control */


/*  M A I N  --  C-Kermit main program  */

main(argc,argv) int argc; char **argv; {

    char *strcpy();

/* Do some initialization */

    xargc = argc;			/* Make global copies of argc */
    xargv = argv;			/* ...and argv. */
    sstate = 0;				/* No default start state. */
    strcpy(ttname,dftty);		/* Set up default tty name. */
    local = dfloc;			/* And whether it's local or remote */
    parity = dfprty;			/* Set initial parity */
    flow = dfflow;			/*  and flow control */
    
/* Look for a UNIX-style command line... */

    if (argc > 1) {			/* Command line arguments? */
	sstate = cmdlin();		/* Yes, parse. */
	if (sstate) {
	    proto();			/* Take any requested action, */
	    if (!quiet) conoll("");	/* put cursor back at left margin, */
	    if (cnflg) conect();	/* connect if requested, */
	    doexit();			/* and then exit. */
    	}
    }	
    
/* If no action requested on command line, enter interactive parser */

    cmdini();				/* Initialize command parser */
    while(sstate = parser()) {		/* Loop getting commands. */
	if (sstate) proto();		/* Enter protocol if requested. */
    }
    exit(0);
}
@//E*O*F ckmain.c//
chmod u=rw,g=r,o=r ckmain.c
 
echo x - ckprot.w
sed 's/^@//' > "ckprot.w" <<'@//E*O*F ckprot.w//'
char *protv = "C-Kermit Protocol Module 4.0(014), 5 Feb 85"; /* -*-C-*- */

/* C K P R O T  -- C-Kermit Protocol Module, in Wart preprocessor notation. */

/* Authors: Jeff Damens, Bill Catchings, Frank da Cruz (Columbia University) */

#include "ckermi.h"

/* Define the states for Wart */

%states rfile rdata ssinit ssdata sseof sseot serve generic get rgen

/* Declare external C variables */

  extern char sstate, *versio, *srvtxt, *cmarg, *cmarg2;
  extern char data[], filnam[], srvcmd[], ttname[], *srvptr;
  extern int pktnum, timint, nfils, image, hcflg, xflg, speed, flow;
  extern int prvpkt, cxseen, czseen, server, local, displa, bctu, bctr, quiet;
  extern int putsrv(), puttrm(), putfil(), errpkt();
  extern char *DIRCMD, *DELCMD, *TYPCMD, *SPACMD, *SPACM2, *WHOCMD;

#define SERVE  tinit(); BEGIN serve
#define RESUME if (server) { SERVE; } else return

%%
/* Protocol entry points, one for each start state (sstate) */

s { tinit();	    	    	    	    	    /* Do Send command */
    if (sinit()) BEGIN ssinit;
       else RESUME; }
v { tinit(); BEGIN get; }		    	    /* Do Receive command */
r { tinit(); srinit(); BEGIN get; }  	    	    /* Do Get command */
c { tinit(); scmd('C',cmarg); BEGIN rgen; }   	    /* Do host Command */
g { tinit(); scmd('G',cmarg); BEGIN rgen; } 	    /* Do Generic command */
x { SERVE; }	    	    	    	    	    /* Be a Server */

/***
 Note -- Need to add states to allow for sending I-packet before
 generic or host commands, and to ignore error packets received in response.
***/

/* Dynamic states: <current-states>input-character { action } */

<rgen,get,serve>S { rinit(data); bctu = bctr; BEGIN rfile; }

<serve>R { srvptr = srvcmd; decode(data,putsrv); /* Get Receive-Init */
	   cmarg = srvcmd;
	   nfils = -1;
    	   if (sinit()) BEGIN ssinit; else { SERVE; } }

<serve>I { spar(data);			/* Get Init-Parameters */
	   rpar(data);
	   ack1(data);
	   pktnum = 0; prvpkt = -1; }

<serve>G { srvptr = srvcmd; decode(data,putsrv); /* Get & decode command. */
	   putsrv('\0'); putsrv('\0');
	   sstate = srvcmd[0]; BEGIN generic; }

<serve>C { srvptr = srvcmd;		/* Get command for shell */
	   decode(data,putsrv);
	   putsrv('\0');
	   if (syscmd("",srvcmd)) BEGIN ssinit;
	   else { errpkt("Can't do shell command"); SERVE; } }

<serve>. { errpkt("Unimplemented server function"); SERVE; }

<generic>C { if (!cwd(srvcmd+1)) errpkt("Can't change directory");
    	     SERVE; }

<generic>D { if (syscmd(DIRCMD,srvcmd+2)) BEGIN ssinit;
    	     else { errpkt("Can't list directory"); SERVE; } }

<generic>E { if (syscmd(DELCMD,srvcmd+2)) BEGIN ssinit;
    	     else { errpkt("Can't remove file"); SERVE; } }

<generic>F { ack(); return; }

<generic>H { if (sndhlp()) BEGIN ssinit;
    	     else { errpkt("Can't send help"); SERVE; } }

<generic>T { if (syscmd(TYPCMD,srvcmd+2)) BEGIN ssinit;
    	     else { errpkt("Can't type file"); SERVE; } }

<generic>U { int x;			/* Disk Usage query */
    	     x = *(srvcmd+1);
    	     x = ((x == '\0') || (x == unchar(0)));
	     x = (x ? syscmd(SPACMD,"") : syscmd(SPACM2,srvcmd+2));
    	     if (x) BEGIN ssinit; else { errpkt("Can't check space"); SERVE; }}

<generic>W { if (syscmd(WHOCMD,srvcmd+2)) BEGIN ssinit;
    	     else { errpkt("Can't do who command"); SERVE; } }

<generic>. { errpkt("Unimplemented generic server function"); SERVE; }

/* Dynamic states, cont'd */


<rgen>Y { decode(data,puttrm); RESUME; }

<rgen,rfile>F { if (rcvfil()) { ack(); BEGIN rdata; }
		 else { errpkt("Can't open file"); RESUME; } }

<rgen,rfile>X { opent(); ack(); BEGIN rdata; }

<rfile>B { ack(); reot(); RESUME; }

<rdata>D { if (cxseen) ack1("X");
    	   else if (czseen) ack1("Z");
	   else ack();
	   decode(data,putfil); }

<rdata>Z { ack(); reof(); BEGIN rfile; }

<ssinit,ssdata,sseof,sseot>N { resend(); }

<ssinit>Y {  int x; char *s;
    	     spar(data);
    	     bctu = bctr;
	     if (xflg) { x = sxpack(); s = "Can't execute command"; }
	    	  else { x = sfile(); s = "Can't open file"; }
	     if (x) BEGIN ssdata; else { errpkt(s); RESUME; }
          }	    

<ssdata>Y { if (canned(data) || !sdata()) {
		clsif();
		seof();
		BEGIN sseof; } }

<sseof>Y { if (gnfile() > 0) {
		if (sfile()) BEGIN ssdata;
		else { errpkt("Can't open file") ; RESUME; }
	   } else {
		seot();
		BEGIN sseot; } }

<sseot>Y { RESUME; }

E { int x;				/* Error packet */
    ermsg(data);			/* Issue message */
    x = quiet; quiet = 1;		/* Close files silently */
    clsif(); clsof();
    quiet = x; RESUME; }

@. { nack(); }				/* Anything else, nack */
%%

/*  P R O T O  --  Protocol entry function  */

proto() {

    extern int sigint();
    int x;

    conint(sigint);			/* Enable console interrupts */

/* Set up the communication line for file transfer. */

    if (local && (speed < 0)) {
	screen(2,0,"Sorry, you must 'set speed' first");
	return;
    }
    if (ttopen(ttname) < 0) {
	screen(2,0,"Can't open line");
	return;
    }
    x = (local) ? speed : -1;
    if (ttpkt(x,flow) < 0) {		/* Put line in packet mode, */
	screen(2,0,"Can't condition line"); /* setting speed, flow control */
	return;
    }
    if (sstate == 'x') {		/* If entering server mode, */
	server = 1;			/* set flag, */
	if (!quiet) {
	    if (!local)			/* and issue appropriate message. */
	    	conol(srvtxt);
	    else {
	    	conol("Entering server mode on ");
		conoll(ttname);
	    }
	}
    } else server = 0;
    sleep(1);

/*
 The 'wart()' function is generated by the wart program.  It gets a
 character from the input() routine and then based on that character and
 the current state, selects the appropriate action, according to the state
 table above, which is transformed by the wart program into a big case
 statement.  The function is active for one transaction.
*/

    wart();				/* Enter the state table switcher. */

/* Restore the communication line */
    
    ttclos();				/* Close the line. */
    if (server) {
	server = 0;
    	if (!quiet)  			/* Give appropriate message */
	    conoll("C-Kermit server done");
    } else
    	screen(BEL,0,"");		/* Or beep */
}
@//E*O*F ckprot.w//
chmod u=rw,g=r,o=r ckprot.w
 
echo Inspecting for damage in transit...
temp=/tmp/shar$$; dtemp=/tmp/.shar$$
trap "rm -f $temp $dtemp; exit" 0 1 2 3 15
cat > $temp <<\!!!
    1228    5662   35285 ckfns.c
     209    1201    7497 ckmain.c
     204     911    6080 ckprot.w
    1641    7774   48862 total
!!!
wc  ckfns.c ckmain.c ckprot.w | sed 's=[^ ]*/==' | diff -b $temp - >$dtemp
if [ -s $dtemp ]
then echo "Ouch [diff of wc output]:" ; cat $dtemp
else echo "No problems found."
fi
exit 0

tsc2597@acf4.UUCP (Sam Chin) (02/28/85)

# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# Wrapped by acf4!tsc2597  on Wed Feb 27 22:20:56 EST 1985
# Contents:  ckuser.c
 
echo x - ckuser.c
sed 's/^@//' > "ckuser.c" <<'@//E*O*F ckuser.c//'
char *userv = "User Interface V4.0(034), 5 Feb 85";

/*  C K U S E R --  "User Interface" for Unix Kermit  */

/*  Frank da Cruz, Columbia University Center for Computing Activities, 1985 */
/*
 This module contains the terminal input and output functions for Unix
 Kermit.  It includes a simple Unix-style command line parser as well as
 an interactive prompting keyword command parser.  It depends on the existence
 of Unix facilities like fopen, fgets, feof, (f)printf, argv/argc, etc.  Other
 functions that are likely to vary among Unix implementations -- like setting
 terminal modes or interrupts -- are invoked via calls to functions that are
 defined in the system-dependent modules, ckx??? and ckz???.

 The command line parser processes any arguments found on the command line,
 as passed to main() via argv/argc.  The interactive parser uses the facilities
 of the cmd package (developed for this program, but usable by any program).

 Any command parser may be substituted for this one.  The only requirements
 for the Kermit command parser are these:

 1. Set parameters via global variables like duplex, speed, ttname, etc.
    See ckmain.c for the declarations and descriptions of these variables.

 2. If a command can be executed without the use of Kermit protocol, then
    execute the command directly and set the variable sstate to 0. Examples
    include 'set' commands, local directory listings, the 'connect' command.

 3. If a command requires the Kermit protocol, set the following variables:

    sstate                             string data
      'x' (enter server mode)            (none)
      'r' (send a 'get' command)         cmarg, cmarg2
      'v' (enter receive mode)           cmarg2
      'g' (send a generic command)       cmarg
      's' (send files)                   nfils, cmarg & cmarg2 OR cmlist
      'c' (send a remote host command)   cmarg

    cmlist is an array of pointers to strings.
    cmarg, cmarg2 are pointers to strings.
    nfils is an integer.    

    cmarg can be a filename string (possibly wild), or
       a pointer to a prefabricated generic command string, or
       a pointer to a host command string.
    cmarg2 is the name to send a single file under, or
       the name under which to store an incoming file; must not be wild.
    cmlist is a list of nonwild filenames, such as passed via argv.
    nfils is an integer, interpreted as follows:
      -1: argument string is in cmarg, and should be expanded internally.
       0: stdin.
      >0: number of files to send, from cmlist.

 The screen() function is used to update the screen during file transfer.
 The tlog() function maintains a transaction log.
 The debug() function maintains a debugging log.
 The intmsg() and chkint() functions provide the user i/o for interrupting
   file transfers.
*/

/* Includes */

#include "ckermi.h"
#include "ckcmd.h"

/* External Kermit Variables, see ckmain.c for description. */

extern int size, rpsiz, spsiz, npad, timint, speed, local, server, image, flow,
  displa, binary, fncnv, delay, parity, deblog, escape, xargc,
  turn, duplex, cxseen, czseen, nfils, ckxech, pktlog, seslog, tralog, stdouf,
  filcnt, tfc, tlci, tlco, ffc, turnch, chklen, bctr, bctu, fsize, dfloc,
  rptflg, ebqflg, warn, quiet, cnflg, timef, mypadn;

extern char *versio, *protv, *ckxv, *ckzv, *fnsv, *connv, *dftty, *cmdv;
extern char *cmarg, *cmarg2, **xargv, **cmlist;
extern char mystch, sstate, mypadc, padch, eol, ctlq, filnam[], ttname[];
char *strcpy();

/* Declarations from cmd package */

extern char cmdbuf[];			/* Command buffer */

/* Declarations from ckz??? module */

extern char *SPACMD, *zhome();		/* Space command, home directory */

/* Variables and symbols local to this module */

char line[100], *lp;			/* Character buffer for anything */
char debfil[50];			/* Debugging log file name */
char pktfil[50];			/* Packet log file name */
char sesfil[50];			/* Session log file name */
char trafil[50];			/* Transaction log file name */

int n,					/* General purpose int */
    cflg,				/* Command-line connect cmd given */
    action,				/* Action selected on command line*/
    ncmd,				/* Number of commands */
    nprm,	    	    	    	/* Number of parameters */
    nrmt,				/* Number of remote commands */
    npar,				/* Number of kinds of parity */
    nlog,				/* Number of kinds of log files */
    nflo,				/* Kinds of flow control */
    nhsh,				/* Kinds of handshake */
    nfilp,				/* Number of file parameters */
    repars,				/* Reparse needed */
    tlevel,				/* Take command level */
    cwdf = 0;				/* CWD has been done */

#define MAXTAKE 20			/* Maximum nesting of TAKE files */
FILE *tfile[MAXTAKE];			/* File pointers for TAKE command */

char *homdir;				/* Pointer to home directory string */

/*
 Simple Unix-style command line parser, conforming with 'A Proposed Command
 Syntax Standard for Unix Systems', Hemenway & Armitage, Unix/World, Vol.1,
 No.3, 1984.
*/

char cmdstr[100];

char *hlp1 = "\
  Usage: kermit [-x arg [-x arg]...[-yyy]..]]\n\
   x is an option that requires an argument, y an option with no argument:\n\
     actions (* options also require -l and -b) --\n\
       -s file(s)   send (use '-s -' to send from stdin)\n\
       -r           receive\n\
       -k           receive to stdout\n\
     * -g file(s)   get remote file(s) from server (quote wildcards)\n\
       -a name      alternate name, used with -s, -r, -g\n\
       -x           enter server mode\n\
     * -f           finish remote server\n\
     * -c           connect before transaction\n\
     * -n           connect after transaction\n\
       -h           help - print this message\n";

char *hlp2 = "\
     settings --\n\
       -l line      communication line device\n\
       -b baud      line speed, e.g. 1200\n\
       -i           binary file or Unix-to-Unix\n\
       -p x         parity, x is one of e,o,m,s,n\n\
       -t           line turnaround handshake = xon, half duplex\n\
       -w           don't write over preexisting files\n\
       -q   	    be quiet during file transfer\n\
       -d   	    log debugging info to debug.log\n\
 If no action command is included, enter interactive dialog.\n";


/*  U S A G E  */

usage() {
    conol(hlp1);
    conol(hlp2);
}

/*  C M D L I N  --  Get arguments from command line  */

cmdlin() {
    char x;

    cmarg = "";				/* Initialize. */
    cmarg2 = "";
    action = cflg = 0;

    while (--xargc > 0) {		/* Go through command line words */
	*xargv++;
	debug(F111,"xargv",*xargv,xargc);
    	if (**xargv == '-') {		/* Got an option (begins with dash) */
	    x = *(*xargv+1);		/* Get the option letter */
	    x = doarg(x);		/* Go handle the option */
	    if (x < 0) exit(0);
    	} else {			/* No dash where expected */
	    usage();
	    exit(1);
	}
    }
    debug(F101,"action","",action);
    if (!local) {
	if ((action == 'g') || (action == 'r') ||
	    (action == 'c') || (cflg != 0))
	    fatal("-l and -b required");
    }
    if (*cmarg2 != 0) {
	if ((action != 's') && (action != 'r') &&
	    (action != 'v'))
	    fatal("-a without -s, -r, or -g");
    }
    if ((action == 'v') && (stdouf) && (!local)) {
    	if (isatty(1))
	    fatal("unredirected -k can only be used in local mode");
    }
    if ((action == 's') || (action == 'v') ||
    	(action == 'r') || (action == 'x')) {
	if (local) displa = 1;
	if (stdouf) displa = 0;
    }

    if (quiet) displa = 0;		/* No display if quiet requested */

    if (cflg) {
	conect();			/* Connect if requested */
	if (action == 0) {
	    if (cnflg) conect();	/* And again if requested */
	    doexit();
	}
    }
    if (displa) concb();		/* (for console "interrupts") */
    return(action);			/* Then do any requested protocol */
}

/*  D O A R G  --  Do a command-line argument.  */

doarg(x) char x; {
    int z; char *xp;

    xp = *xargv+1;			/* Pointer for bundled args */
    while (x) {
	switch (x) {

case 'x':				/* server */
    if (action) fatal("conflicting actions");
    action = 'x';
    break;

case 'f':
    if (action) fatal("conflicting actions");
    action = setgen('F',"","","");
    break;

case 'r':				/* receive */
    if (action) fatal("conflicting actions");
    action = 'v';
    break;

case 'k':				/* receive to stdout */
    if (action) fatal("conflicting actions");
    stdouf = 1;
    action = 'v';
    break;

case 's': 				/* send */
    if (action) fatal("conflicting actions");
    if (*(xp+1)) fatal("invalid argument bundling after -s");
    z = nfils = 0;			/* Initialize file counter, flag */
    cmlist = xargv+1;			/* Remember this pointer */
    while (--xargc > 0) {		/* Traverse the list */	
	*xargv++;
	if (**xargv == '-') {		/* Check for sending stdin */
	    if (strcmp(*xargv,"-") != 0) break;
	    z++;
        }
	nfils++;			/* Bump file counter */
    }
    xargc++, *xargv--;			/* Adjust argv/argc */
    if (nfils < 1) fatal("missing filename for -s");
    if (z > 1) fatal("-s: too many -'s");
    if (z == 1) {
	if (nfils == 1) nfils = 0;
	else fatal("invalid mixture of filenames and '-' in -s");
    }
    if (nfils == 0) {
	if (isatty(0)) fatal("sending from terminal not allowed");
    }
    debug(F101,*xargv,"",nfils);
    action = 's';
    break;

/* cont'd... */

/* ...doarg(), cont'd */

case 'g':				/* get */
    if (action) fatal("conflicting actions");
    if (*(xp+1)) fatal("invalid argument bundling after -g");
    *xargv++, xargc--;
    if ((xargc == 0) || (**xargv == '-'))
    	fatal("missing filename for -g");
    cmarg = *xargv;
    action = 'r';
    break;

case 'c':				/* connect before */
    cflg = 1;
    break;

case 'n':				/* connect after */
    cnflg = 1;
    break;

case 'h':				/* help */
    usage();
    return(-1);

case 'a':				/* "as" */
    if (*(xp+1)) fatal("invalid argument bundling after -a");
    *xargv++, xargc--;
    if ((xargc < 1) || (**xargv == '-'))
    	fatal("missing name in -a");
    cmarg2 = *xargv;
    break;

case 'l':				/* set line */
    if (*(xp+1)) fatal("invalid argument bundling after -l");
    *xargv++, xargc--;
    if ((xargc < 1) || (**xargv == '-'))
    	fatal("communication line device name missing");
    strcpy(ttname,*xargv);
    if (strcmp(ttname,dftty) == 0) local = dfloc; else local = 1;
    break;

case 'b':   	    			/* set baud */
    if (*(xp+1)) fatal("invalid argument bundling");
    *xargv++, xargc--;
    if ((xargc < 1) || (**xargv == '-'))
    	fatal("missing baud");
    z = atoi(*xargv);			/* Convert to number */
    if (chkspd(z) > -1) speed = z;	/* Check it */
    	else fatal("unsupported baud rate");
    break;

case 'i':				/* Treat files as binary */
    binary = 1;
    break;

/* cont'd... */

/* ...doarg(), cont'd */


case 'w':				/* File warning */
    warn = 1;
    break;

case 'q':				/* Quiet */
    quiet = 1;
    break;

case 'd':				/* debug */
    debopn("debug.log");
    break;

case 'p':				/* set parity */
    if (*(xp+1)) fatal("invalid argument bundling");
    *xargv++, xargc--;
    if ((xargc < 1) || (**xargv == '-'))
    	fatal("missing parity");
    switch(x = **xargv) {
	case 'e':
	case 'o':
	case 'm':
	case 's': parity = x; break;
	case 'n': parity = 0; break;
	default:  fatal("invalid parity");
        }
    break;

case 't':
    turn = 1;				/* Line turnaround handshake */
    turnch = XON;			/* XON is turnaround character */
    duplex = 1;				/* Half duplex */
    flow = 0;				/* No flow control */
    break;

default:
    fatal("invalid argument, type 'kermit -h' for help");
        }

    x = *++xp;				/* See if options are bundled */
    }
    return(0);
}

/* Misc */

fatal(msg) char *msg; {			/* Fatal error message */
    fprintf(stderr,"\r\nFatal: %s\n",msg);
    tlog(F110,"Fatal:",msg,0);
    doexit();
}


ermsg(msg) char *msg; {			/* Print error message */
    if (!quiet) fprintf(stderr,"\r\nError - %s\n",msg);
    tlog(F110,"Error -",msg,0);
}

/* Interactive command parser */ 


/* Values associated with top-level commands, must be 0 or greater. */

#define XXBYE   0	/* BYE */
#define XXCLE   1	/* CLEAR */
#define XXCLO   2	/* CLOSE */
#define XXCON   3	/* CONNECT */
#define XXCPY   4	/* COPY */
#define XXCWD   5	/* CWD (Change Working Directory) */
#define XXDEF	6	/* DEFINE (a command macro) */
#define XXDEL   7	/* (Local) DELETE */
#define XXDIR   8	/* (Local) DIRECTORY */
#define XXDIS   9	/* DISCONNECT */
#define XXECH  10	/* ECHO */
#define XXEXI  11	/* EXIT */
#define XXFIN  12	/* FINISH */
#define XXGET  13	/* GET */
#define XXHLP  14	/* HELP */
#define XXINP  15	/* INPUT */
#define XXLOC  16	/* LOCAL */
#define XXLOG  17	/* LOG */
#define XXMAI  18	/* MAIL */
#define XXMOU  19	/* (Local) MOUNT */
#define XXMSG  20	/* (Local) MESSAGE */
#define XXOUT  21	/* OUTPUT */
#define XXPAU  22	/* PAUSE */
#define XXPRI  23	/* (Local) PRINT */
#define XXQUI  24	/* QUIT */
#define XXREC  25	/* RECEIVE */
#define XXREM  26	/* REMOTE */
#define XXREN  27	/* (Local) RENAME */
#define XXSEN  28	/* SEND */
#define XXSER  29   	/* SERVER */
#define XXSET  30	/* SET */
#define XXSHE  31	/* Command for SHELL */
#define XXSHO  32	/* SHOW */
#define XXSPA  33	/* (Local) SPACE */
#define XXSTA  34	/* STATISTICS */
#define XXSUB  35	/* (Local) SUBMIT */
#define XXTAK  36	/* TAKE */
#define XXTRA  37	/* TRANSMIT */
#define XXTYP  38	/* (Local) TYPE */
#define XXWHO  39	/* (Local) WHO */

/* Top-Level Keyword Table */

struct keytab cmdtab[] = {
    "!",	   XXSHE, 0,
    "bye",         XXBYE, 0,
    "c",           XXCON, CM_INV,
    "close",	   XXCLO, 0,
    "connect",     XXCON, 0,
    "cwd",	   XXCWD, 0,
    "directory",   XXDIR, 0,
    "echo",        XXECH, 0,
    "exit",	   XXEXI, 0,
    "finish",	   XXFIN, 0,
    "get",	   XXGET, 0,
    "help",	   XXHLP, 0,
    "log",  	   XXLOG, 0,
    "quit",	   XXQUI, 0,
    "r",           XXREC, CM_INV,
    "receive",	   XXREC, 0,
    "remote",	   XXREM, 0,
    "s",           XXSEN, CM_INV,
    "send",	   XXSEN, 0,
    "server",	   XXSER, 0,
    "set",	   XXSET, 0,
    "show", 	   XXSHO, 0,
    "space",       XXSPA, 0,
    "statistics",  XXSTA, 0,
    "take",	   XXTAK, 0
};

/* Number of Top-Level Keywords */

#define NCMD (sizeof(cmdtab) / sizeof(struct keytab))

/* SET parameters */

#define XYBREA  0	/* BREAK simulation */
#define XYCHKT  1	/* Block check type */
#define XYDEBU  2	/* Debugging */
#define XYDELA  3	/* Delay */
#define XYDUPL  4	/* Duplex */
#define XYEOL   5	/* End-Of-Line (packet terminator) */
#define XYESC   6	/* Escape character */
#define XYFILE  7	/* File Parameters */

#define XYFLOW  9	/* Flow Control */
#define XYHAND 10	/* Handshake */
#define XYIFD  11	/* Incomplete File Disposition */
#define XYIMAG 12	/* "Image Mode" */
#define XYINPU 13	/* INPUT command parameters */
#define XYLEN  14	/* Maximum packet length to send */
#define XYLINE 15	/* Communication line to use */
#define XYLOG  16	/* Log file */
#define XYMARK 17	/* Start of Packet mark */
#define XYNPAD 18	/* Amount of padding */
#define XYPADC 19	/* Pad character */
#define XYPARI 20	/* Parity */
#define XYPAUS 21	/* Interpacket pause */
#define XYPROM 22	/* Program prompt string */
#define XYQBIN 23	/* 8th-bit prefix */
#define XYQCTL 24	/* Control character prefix */
#define XYREPT 25	/* Repeat count prefix */
#define XYRETR 26	/* Retry limit */
#define XYSPEE 27	/* Line speed (baud rate) */
#define XYTACH 28	/* Character to be doubled */
#define XYTIMO 29	/* Timeout interval */


/* Parameter keyword table */

struct keytab prmtab[] = {
    "baud",	        XYSPEE,  CM_INV,
    "block-check",  	XYCHKT,  0,
    "delay",	    	XYDELA,  0,
    "duplex",	    	XYDUPL,  0,
    "end-of-packet",    XYEOL,   0,
    "escape-character", XYESC,   0,
    "file", 	  	XYFILE,  0,
    "flow-control", 	XYFLOW,  0,
    "handshake",    	XYHAND,  0,
    "line",             XYLINE,  0,
    "packet-length",    XYLEN,   0,
    "pad-character",    XYPADC,  0,
    "padding",          XYNPAD,  0,
    "parity",	    	XYPARI,  0,
    "prompt",	    	XYPROM,  0,
    "speed",	        XYSPEE,  0,
    "start-of-packet",  XYMARK,  0,
    "timeout",	        XYTIMO,  0
};
#define NPRM (sizeof(prmtab) / sizeof(struct keytab)) /* How many parameters */

/* REMOTE command symbols */

#define XZCPY  0	/* Copy */
#define XZCWD  1	/* Change Working Directory */
#define XZDEL  2	/* Delete */
#define XZDIR  3	/* Directory */
#define XZHLP  4	/* Help */
#define XZHOS  5	/* Host */
#define XZKER  6	/* Kermit */
#define XZLGI  7	/* Login */
#define XZLGO  8	/* Logout */
#define XZMAI  9	/* Mail */
#define XZMOU 10	/* Mount */
#define XZMSG 11	/* Message */
#define XZPRI 12	/* Print */
#define XZREN 13	/* Rename */
#define XZSET 14	/* Set */
#define XZSPA 15	/* Space */
#define XZSUB 16	/* Submit */
#define XZTYP 17	/* Type */
#define XZWHO 18	/* Who */

/* Remote Command Table */

struct keytab remcmd[] = {
    "cwd",       XZCWD, 0,
    "delete",    XZDEL, 0,
    "directory", XZDIR, 0,
    "help",      XZHLP, 0,
    "host",      XZHOS, 0,
    "space",	 XZSPA, 0,
    "type", 	 XZTYP, 0,
    "who",  	 XZWHO, 0
};

/* Number of Elements */

#define NRMT (sizeof(remcmd) / sizeof(struct keytab))

/* Miscellaneous keyword tables */


/* On/Off table */

struct keytab onoff[] = {
    "off",       0, 0,
    "on",        1, 0
};


/* Duplex keyword table */

struct keytab dpxtab[] = {
    "full", 	 0, 0,
    "half",      1, 0
};


/* Parity keyword table */

struct keytab partab[] = {
    "even",    'e', 0,
    "mark",    'm', 0,
    "none",      0, 0,
    "odd",     'o', 0,
    "space",   's', 0
};


/* Show table */

#define SHPAR 0
#define SHVER 1

struct keytab shotab[] = {
    "parameters", SHPAR, 0,
    "versions",   SHVER, 0
};


/* Logging keyword table */

#define LOGD 0
#define LOGP 1
#define LOGS 2
#define LOGT 3

struct keytab logtab[] = {
    "debugging",    LOGD, 0,
    "packets",	    LOGP, 0,
    "session",      LOGS, 0,
    "transactions", LOGT, 0
};

/*  More keyword tables  */


/* Flow Control */

struct keytab flotab[] = {
    "none",     0, 0,
    "xon/xoff", 1, 0
};
#define NFLO (sizeof(flotab) / sizeof(struct keytab))


/*  Handshake characters  */

struct keytab hshtab[] = {
    "bell", 007, 0,
    "cr",   015, 0,
    "esc",  033, 0,
    "lf",   012, 0,
    "none", 999, 0,  /* (can't use negative numbers) */
    "xoff", 023, 0,
    "xon",  021, 0
};
#define NHSH (sizeof(hshtab) / sizeof(struct keytab))

/* File parameters */

#define XYFILN 0
#define XYFILT 1
#define XYFILW 2
#define XYFILD 3

struct keytab filtab[] = {
    "display", XYFILD, 0,
    "names",   XYFILN, 0,
    "type",    XYFILT, 0,
    "warning", XYFILW, 0
};
#define NFILP (sizeof(filtab) / sizeof(struct keytab))

struct keytab fntab[] = {   		/* File naming */
    "converted", 1, 0,
    "literal",   0, 0
};

struct keytab fttab[] = {		/* File types */
    "binary",    1, 0,
    "text",      0, 0
};


/* Block checks */

struct keytab blktab[] = {
    "1", 1, 0,
    "2", 2, 0,
    "3", 3, 0
};

/*  C M D I N I  --  Initialize the interactive command parser  */

cmdini() {

    printf("%s\nType ? for help\n",versio);
    cmsetp("C-Kermit>");		/* Set default prompt. */

    ncmd = NCMD;			/* Perform these calculations */
    nprm = NPRM;			/* only once... */
    nrmt = NRMT;
    nhsh = NHSH;
    nflo = NFLO;
    nfilp = NFILP;			/* Number of file parameters */
    npar = 5;				/* Five kinds of parity */
    nlog = 4;				/* Four kinds of logs */
    tlevel = -1;			/* Take file level */

/* Look for init file ".kermrc" in home or current directory. */

    homdir = zhome();
    lp = line;
    if (homdir)
	sprintf(lp,"%s/.kermrc",homdir);
    else
    	sprintf(lp,".kermrc");
    if ((tfile[0] = fopen(line,"r")) != NULL) {
	tlevel = 0;
	debug(F110,"init file",line,0);
    }
    if (homdir && (tlevel < 0)) {
    	sprintf(lp,".kermrc");
	if ((tfile[0] = fopen(line,"r")) != NULL) {
	    tlevel = 0;
	    debug(F110,"init file",line,0);
	} else {
	    debug(F100,"no init file","",0);
        }
    }

/* Set up tty for interactive command parsing */

     congm();				/* Get console modes */
     concb();				/* Put in cbreak/noecho mode */
}


/*  T R A P  --  Terminal interrupt handler */

trap() {
    debug(F100,"terminal interrupt...","",0);
    doexit();
}

/* P A R S E R  */

parser() {
    int xx;

    conint(trap);		/* Turn on console terminal interrupts. */

/*
 sstate becomes nonzero when a command has been parsed that requires
 some action from the protocol module.  Any non-protocol actions, such as
 local directory listing or terminal emulation, are invoked directly from
 below.
*/
    if (local) printf("\n");		/*** Temporary kludge **/
     sstate = 0;			/* Start with no start state. */
     while (sstate == 0) {		/* Parse cmds until action requested */

        while ((tlevel > -1) && feof(tfile[tlevel])) { /* If end of take */
		fclose(tfile[tlevel]);	/* file, close it */
		tlevel--;		/* and forget about it. */
		cmini(ckxech);		/* and clear the cmd buffer. */
 	}
	if (tlevel > -1) {
	    if (fgets(cmdbuf,CMDBL,tfile[tlevel]) == NULL) continue;
	} else {			/* Otherwise. */
	    prompt();			/* Issue interactive prompt. */
	    cmini(ckxech);
    	}
	repars = 1;
	displa = 0;

	while (repars) {
	    cmres();			/* Reset buffer pointers. */
	    xx = cmkey(cmdtab,ncmd,"Command","");
	    debug(F101,"top-level cmkey","",xx);
	    switch (docmd(xx)) {
		case -4:		/* EOF */
		    doexit();
	        case -1:		/* Reparse needed */
		    repars = 1;
		    continue;
	    	case -2:		/* Invalid command given */
		    cmini(ckxech);	/* (fall thru) */
 	    	case -3:		/* Empty command OK at top level */
		default:		/* Anything else (fall thru) */
		    repars = 0;		/* No reparse, get new command. */
		    continue;
            }
        }
    }

/* Got an action command; disable terminal interrupts and return start state */

    if (!local) connoi();		/* Interrupts off only if remote */
    return(sstate);
}

/*  D O E X I T  --  Exit from the program.  */

doexit() {
    if (local) {
	ttclos();			/* Close external line */
	strcpy(ttname,dftty);		/* Restore default tty */
	local = dfloc;			/* And default remote/local status */
	}
    if (!quiet) conres();		/* Restore console terminal. */
    if (!quiet) connoi();		/* Turn off console interrupt traps. */

    if (deblog) {			/* Close any open logs. */
	debug(F100,"Debug Log Closed","",0);
	*debfil = '\0';
	deblog = 0;
	zclose(ZDFILE);
    }
    if (pktlog) {
	*pktfil = '\0';
	pktlog = 0;
	zclose(ZPFILE);
    }
    if (seslog) {
    	*sesfil = '\0';
	seslog = 0;
	zclose(ZSFILE);
    }
    if (tralog) {
	tlog(F100,"Transaction Log Closed","",0);
	*trafil = '\0';
	tralog = 0;
	zclose(ZTFILE);
    }
    exit(0);				/* Exit from the program. */
}

/*  B L D L E N  --  Make length-encoded copy of string  */

char *
bldlen(str,dest) char *str, *dest; {
    int len;
    len = strlen(str);
    *dest = tochar(len);
    strcpy(dest+1,str);
    return(dest+len+1);
}


/*  S E T G E N  --  Construct a generic command  */

setgen(type,arg1,arg2,arg3) char type, *arg1, *arg2, *arg3; {
    char *upstr, *cp;

    cp = cmdstr;
    *cp++ = type;
    *cp = NUL;
    if (*arg1 != NUL) {
	upstr = bldlen(arg1,cp);
	if (*arg2 != NUL) {
	    upstr = bldlen(arg2,upstr);
	    if (*arg3 != NUL) bldlen(arg3,upstr);
	}
    }
    cmarg = cmdstr;
    debug(F110,"setgen",cmarg,0);

    return('g');
}

/*  D O C M D  --  Do a command  */

/*
 Returns:
   -2: user typed an illegal command
   -1: reparse needed
    0: parse was successful (even tho command may have failed).
*/ 

docmd(cx) int cx; {
    int x, y;
    char *s;

    switch (cx) {

case -4:				/* EOF */
    if (!quiet) printf("\r\n");
    doexit();
case -3:				/* Null command */
    return(0);
case -2:				/* Error */
case -1:				/* Reparse needed */
    return(cx);

case XXBYE:				/* bye */
    if ((x = cmcfm()) < 0) return(x);
    if (!local) {
	printf("You have to 'set line' first\n");
	return(0);
    }
    sstate = setgen('L',"","","");
    return(0);

case XXCON:                     	/* connect */
    if ((x = cmcfm()) < 0) return(x);
    conres();				/* restore tty to normal mode */
    conect();
    concb();				/* tty back in character mode */
    return(0);

case XXCWD:
    if (cmtxt("Name of local directory, or carriage return",homdir,&s) < 0)
    	return(-1);    
    if (chdir(s)) perror(s);
    cwdf = 1;
    system("pwd");
    return(0);

case XXCLO:
    x = cmkey(logtab,nlog,"Which log to close","");
    if (x == -3) {
	printf("?You must tell which log\n");
	return(-2);
    }
    if (x < 0) return(x);
    if ((y = cmcfm()) < 0) return(y);
    switch (x) {

	case LOGD:
	    if (deblog == 0) {
		printf("?Debugging log wasn't open\n");
		return(0);
	    }
	    *debfil = '\0';
	    deblog = 0;
	    return(zclose(ZDFILE));

	case LOGP:
	    if (pktlog == 0) {
		printf("?Packet log wasn't open\n");
		return(0);
	    }
	    *pktfil = '\0';
	    pktlog = 0;
	    return(zclose(ZPFILE));

	case LOGS:
	    if (seslog == 0) {
		printf("?Session log wasn't open\n");
		return(0);
	    }
	    *sesfil = '\0';
	    seslog = 0;
	    return(zclose(ZSFILE));

    	case LOGT:
	    if (tralog == 0) {
		printf("?Transaction log wasn't open\n");
		return(0);
	    }
	    *trafil = '\0';
	    tralog = 0;
	    return(zclose(ZTFILE));

	default:
	    printf("\n?Unexpected log designator - %d\n", x);
	    return(0);
    }

case XXDIR:				/* directory */
    if ((x = cmifi("Directory/file specification","*",&s,&y)) < 0) return(x);
    lp = line;
    sprintf(lp,"ls -l %s",s);
    system(line);
    return(0);

case XXECH:				/* echo */
    x = cmtxt("Material to be echoed","",&s);
    if (x < 0) return(x);
    printf("%s\n",s);
    return(0);

case XXQUI:				/* quit, exit */
case XXEXI:
    if ((x = cmcfm()) > -1) doexit();
    else return(x);

case XXFIN:				/* finish */
    if ((x = cmcfm()) < 0) return(x);
    if (!local) {
	printf("You have to 'set line' first\n");
	return(0);
    }
    sstate = setgen('F',"","","");
    return(0);

case XXGET:				/* Get */
    if (!local) {
	printf("\nYou have to 'set line' first\n");
	return(0);
    }
    x = cmtxt("Name of remote file(s), or carriage return","",&cmarg);
    if ((x == -2) || (x == -1)) return(x);

/* If foreign file name omitted, get foreign and local names separately */

    if (*cmarg == NUL) {

	if (tlevel > -1) {		/* Input is from take file */

	    if (fgets(line,100,tfile[tlevel]) == NULL)
	    	fatal("take file ends prematurely in 'get'");
	    cmarg = line;
	    if (fgets(cmdbuf,CMDBL,tfile[tlevel]) == NULL)
	    	fatal("take file ends prematurely in 'get'");
	    if (*cmdbuf == NUL) cmarg2 = line; else cmarg2 = cmdbuf;

        } else {			/* Input is from terminal */

	    char psave[40];		/* Save old prompt */
	    cmsavp(psave,40);
	    cmsetp(" Remote file specification: "); /* Make new one */
	    cmini(ckxech);
	    x = -1;
	    while (x < 0) {		/* Prompt till they answer */
	    	prompt();
	    	x = cmtxt("Name of remote file(s)","",&cmarg);
	    	if (*cmarg == NUL) x = -1;
	    }
	    strcpy(line,cmarg);		/* Make a safe copy */
	    cmarg = line;
	    cmsetp(" Local name to store it under: ");	/* New prompt */
	    cmini(ckxech);
	    x = -1;
	    while (x < 0) {		/* Again, prompt till answered */
	    	prompt();
	    	x = cmofi("Local file name",cmarg,&cmarg2);
            }
	    cmsetp(psave);		/* Restore old prompt. */
	    if ((x == cmcfm()) < 0) return(-2);
        }
    }
    sstate = 'r';			/* All ok, set start state. */
    if (local) displa = 1;
    return(0);

case XXHLP:				/* Help */
    x = cmkey(cmdtab,ncmd,"C-Kermit command","help");
    return(dohlp(x));

case XXLOG:				/* Log */
    x = cmkey(logtab,nlog,"What to log","");
    if (x == -3) {
	printf("?You must specify what is to be logged\n");
	return(-2);
    }
    if (x < 0) return(x);
    return(dolog(x));

case XXREC:				/* Receive */
    cmarg2 = "";
    x = cmofi("Name under which to store the file, or CR","",&cmarg2);
    if ((x == -1) || (x == -2)) return(x);
    debug(F111,"cmofi cmarg2",cmarg2,x);
    if ((x = cmcfm()) < 0) return(x);
    sstate = 'v';
    if (local) displa = 1;
    return(0);

case XXREM:				/* Remote */
    if (!local) {
	printf("\nYou have to 'set line' first\n");
	return(-2);
    }
    x = cmkey(remcmd,nrmt,"Remote Kermit server command","");
    if (x == -3) {
	printf("?You must specify a command for the remote server\n");
	return(-2);
    }
    return(dormt(x));

case XXSEN:				/* Send */
    cmarg = cmarg2 = "";
    if ((x = cmifi("File(s) to send","",&s,&y)) < 0) {
	if (x == -3) {
	    printf("?A file specification is required\n");
	    return(-2);
	}
	return(x);
    }
    nfils = -1;				/* Files come from internal list. */
    strcpy(line,s);			/* Save copy of string just parsed. */
    debug(F101,"Send: wild","",y);
    *cmarg2 = '\0';			/* Initialize send-as name */
    if (y == 0) {
	if ((x = cmfld("Name to send it with",line,&cmarg2)) < 0) return(x);
    }
    if ((x = cmcfm()) < 0) return(x);
    cmarg = line;			/* File to send */
    debug(F110,"Sending:",cmarg,0);
    debug(F110," as:",cmarg2,0);
    sstate = 's';			/* Set start state */
    if (local) displa = 1;
    return(0);

case XXSER:				/* Server */
    if (x = (cmcfm()) < 0) return(x);
    sstate = 'x';
    if (local) displa = 1;
    return(0);

case XXSET:				/* Set */
    x = cmkey(prmtab,nprm,"Parameter","");
    if (x == -3) {
	printf("?You must specify a parameter to set\n");
	return(-2);
    }
    if (x < 0) return(x);
    return(doprm(x));
    
case XXSHE:				/* Local shell command */
    if (cmtxt("Unix shell command to execute","",&s) < 0) return(-1);
    conres();				/* Make console normal  */
    system(s);				/* Execute the command */
    concb();				/* Console back in cbreak mode */
    return(0);

case XXSHO:				/* Show */
    x = cmkey(shotab,2,"","parameters");
    if (x < 0) return(x);
    if (y = (cmcfm()) < 0) return(y);
    switch (x) {
	case SHPAR:
	    printf("\nLine: %s, speed: %d, mode: ",ttname,speed);
	    if (local) printf("local"); else printf("remote");
	    printf("\n Parity: ");
	    switch (parity) {
		case 'e': printf("even");  break;
		case 'o': printf("odd");   break;
		case 'm': printf("mark");  break;
		case 's': printf("space"); break;
		case 0:   printf("none");  break;
		default:  printf("invalid - %d",parity); break;
    	    }		
	    printf(", duplex: ");
	    if (duplex) printf("half, "); else printf("full, ");
	    printf("flow: ");
	    if (flow == 1) printf("xon/xoff");
	    	else if (flow == 0) printf("none");
		else printf("%d",flow);
	    printf(", handshake: ");
	    if (turn) printf("%d\n",turnch); else printf("none\n");
	    printf(" Timeout: %d, delay: %d\n",timint,delay);
	    printf(" Padding: %d, pad character: %d\n",mypadn,mypadc);
            printf(" Packet start: %d, end: %d, length: %d",mystch,eol,spsiz);
	    printf(", block check: %d\n",bctr);
	    printf("\nFile parameters:\n");
	    printf(" Names:        ");
	    if (fncnv) printf("converted\n"); else printf("literal\n");
	    printf(" Type:         ");
	    if (binary) printf("binary\n"); else printf("text\n");
	    printf(" Warning:      ");
	    if (warn) printf("on\n"); else printf("off\n");
	    printf(" Display:      ");
	    if (quiet) printf("off\n"); else printf("on\n");
	    printf("\nLogs:");
	    printf("\n Debugging:    ");
	    if (deblog) printf("%s",debfil); else printf("none");
	    printf("\n Packets:      ");
	    if (pktlog) printf("%s",pktfil); else printf("none");
	    printf("\n Session:      ");
	    if (seslog) printf("%s",sesfil); else printf("none");
	    printf("\n Transactions: ");
	    if (tralog) printf("%s",trafil); else printf("none");
	    printf("\n\n");
	    break;
	case SHVER:
	    printf("\nVersions:\n %s\n %s\n %s\n",versio,protv,fnsv);
	    printf(" %s\n %s\n %s\n %s\n %s\n\n",cmdv,userv,ckxv,ckzv,connv);
	    break;
	default:
	    printf("\nNothing to show...\n");
	    break;
    }	    
    return(0);

case XXSPA:				/* space */
    if (x = (cmcfm()) < 0) return(x);
    system(SPACMD);
    return(0);

case XXSTA:				/* statistics */
    if (x = (cmcfm()) < 0) return(x);
    printf("\nMost recent transaction --\n");
    printf(" files: %d\n",filcnt);
    printf(" total file characters  : %d\n",tfc);
    printf(" communication line in  : %d\n",tlci);
    printf(" communication line out : %d\n\n",tlco);
    printf(" block check type used  : %d\n",bctu);
    printf(" compression            : ");
    if (rptflg) printf("yes\n"); else printf("no\n");
    printf(" 8th bit prefixing      : ");
    if (ebqflg) printf("yes\n"); else printf("no\n\n");
    return(0);

case XXTAK:				/* take */
    if (tlevel > MAXTAKE-1) {
	printf("?Take files nested too deeply\n");
	return(-2);
    }
    if ((y = cmifi("C-Kermit command file","",&s,&x)) < 0) { 
	if (y == -3) {
	    printf("?A file specification is required\n");
	    return(-2);
	} else return(y);
    }
    if (x != 0) {
	printf("?Wildcards not allowed in command file name\n");
	return(-2);
    }
    strcpy(line,s);			/* Make a safe copy of the string */
    if ((y = cmcfm()) < 0) return(y);
    if ((tfile[++tlevel] = fopen(line,"r")) == NULL) {
	perror("take");
	printf("Can't open command file - %s\n",line);
	debug(F110,"Failure to open",line,0);
	tlevel--;
	return(0);
    }
    return(0);

default:
    printf("Not available yet - %s\n",cmdbuf);
    return(-2);
    }
}

/*  D O L O G  --  */

dolog(x) int x; {
    int y; char *s;

    switch (x) {

	case LOGD:
	    y = cmofi("Name of debugging log file","debug.log",&s);
	    break;

	case LOGP:
	    y = cmofi("Name of packet log file","packet.log",&s);
	    break;

	case LOGS:
	    y = cmofi("Name of session log file","session.log",&s);
	    break;

	case LOGT:
	    y = cmofi("Name of transaction log file","transaction.log",&s);
	    break;

	default:
	    printf("\n?Unexpected log designator - %d\n",x);
	    return(-2);
    }
    if (y < 0) return(y);

    strcpy(line,s);
    s = line;
    if ((y = cmcfm()) < 0) return(y);

/* cont'd... */

/* ...dolog, cont'd */


    switch (x) {

	case LOGD:
	    return(deblog = debopn(s));

	case LOGP:
	    zclose(ZPFILE);
	    y = zopeno(ZPFILE,s);
	    if (y > 0) strcpy(pktfil,s); else *pktfil = '\0';
	    return(pktlog = y);

	case LOGS:
	    zclose(ZSFILE);
	    y = zopeno(ZSFILE,s);
	    if (y > 0) strcpy(sesfil,s); else *sesfil = '\0';
	    return(seslog = y);

	case LOGT:
	    zclose(ZTFILE);
	    tralog = zopeno(ZTFILE,s);
	    if (tralog > 0) {
		strcpy(trafil,s);
		tlog(F110,"Transaction Log:",versio,0);
		ztime(&s);
		tlog(F100,s,"",0);
    	    }
	    else *trafil = '\0';
	    return(tralog);

	default:
	    return(-2);
    }
}


/*  D E B O P N  --  Open a debugging file  */

debopn(s) char *s; {
    char *tp;
    zclose(ZDFILE);
    deblog = zopeno(ZDFILE,s);
    if (deblog > 0) {
	strcpy(debfil,s);
	debug(F110,"Debug Log ",versio,0);
	ztime(&tp);
	debug(F100,tp,"",0);
    } else *debfil = '\0';
    return(deblog);
}

/*  D O P R M  --  Set a parameter.  */
/*
 Returns:
  -2: illegal input
  -1: reparse needed
   0: success
*/
doprm(xx) int xx; {
    int x, y, z;
    char *s;

switch (xx) {

case XYLINE:
    if ((x = cmtxt("Device name",dftty,&s)) < 0) return(x);
    strcpy(ttname,s);
    if (strcmp(ttname,dftty) == 0) local = dfloc; else local = 1;
    return(0);

case XYCHKT:
    if ((y = cmkey(blktab,3,"","1")) < 0) return(y);
    if ((x = cmcfm()) < 0) return(x);
    bctr = y;
    return(0);

case XYDEBU:
    return(seton(&deblog));

case XYDELA:
    y = cmnum("Number of seconds before starting to send","5",10,&x);
    debug(F101,"XYDELA: y","",y);
    return(setnum(&delay,x,y));

case XYDUPL:
    if ((y = cmkey(dpxtab,2,"","full")) < 0) return(y);
    if ((x = cmcfm()) < 0) return(x);
    duplex = y;
    return(0);

case XYEOL:
    y = cmnum("Decimal ASCII code for packet terminator","0",10,&x);
    y = setcc(&z,x,y);
    eol = z;
    return(y);

case XYESC:
    y = cmnum("Decimal ASCII code for escape character","",10,&x);
    return(setcc(&escape,x,y));

case XYFILE:
    if ((y = cmkey(filtab,nfilp,"File parameter","")) < 0) return(y);
    switch (y) {
	int z;
	case XYFILD:			/* Display */
	    y = seton(&z);
	    if (y < 0) return(y);
	    quiet = !z;
	    return(0);

	case XYFILN:			/* Names */
	    if ((x = cmkey(fntab,2,"how to handle filenames","converted")) < 0)
	    	return(x);
	    if ((z = cmcfm()) < 0) return(z);
	    fncnv = x;
	    return(0);

	case XYFILT:			/* Type */
	    if ((x = cmkey(fttab,2,"type of file","text")) < 0)
	    	return(x);
	    if ((z = cmcfm()) < 0) return(z);
	    binary = x;
	    return(0);

	case XYFILW:			/* Warning/Write-Protect */
	    return(seton(&warn));
    }

case XYFLOW:				/* Flow control */
    if ((y = cmkey(flotab,nflo,"","xon/xoff")) < 0) return(y);
    if ((x = cmcfm()) < 0) return(x);
    flow = y;
    return(0);

case XYHAND:				/* Handshake */
    if ((y = cmkey(hshtab,nhsh,"","none")) < 0) return(y);
    if ((x = cmcfm()) < 0) return(x);
    turn = (y > 0127) ? 0 : 1 ;
    turnch == y;
    return(0);

case XYLEN:
    y = cmnum("Maximum number of characters in a packet","80",10,&x);
    return(setnum(&spsiz,x,y));

case XYMARK:
    y = cmnum("Decimal ASCII code for packet-start character","1",10,&x);
    y = setcc(&z,x,y);
    mystch = z;
    return(y);

case XYNPAD:
    y = cmnum("How many padding characters for inbound packets","0",10,&x);
    return(setnum(&mypadn,x,y));

case XYPADC:
    y = cmnum("Decimal ASCII code for inbound pad character","0",10,&x);
    y = setcc(&z,x,y);
    mypadc = z;
    return(y);

case XYPARI:
    if ((y = cmkey(partab,npar,"","none")) < 0) return(y);
    if ((x = cmcfm()) < 0) return(x);
    parity = y;
    ebqflg = 1;				/* Flag we want 8th-bit prefixing */
    return(0);

case XYPROM:
    if ((x = cmtxt("Program's command prompt","C-Kermit>",&s)) < 0) return(x);
    cmsetp(s);
    return(0);

case XYSPEE:
    if (!local) {
	printf("\nSpeed setting can only be done on an external line\n");
	printf("You must 'set line' before issuing this command\n");
	return(0);
    }	
    lp = line;
    sprintf(lp,"Baud rate for %s",ttname);
    if ((y = cmnum(line,"",10,&x)) < 0) return(y);
    if (y = (cmcfm()) < 0) return(y);
    y = chkspd(x);
    if (y < 0) 
    	printf("?Unsupported line speed - %d\n",x);
    else {
    	speed = y;
	printf("%s: %d baud\n",ttname,speed);
    }
    return(0);

case XYTIMO:
    y = cmnum("Interpacket timeout interval","5",10,&x);
    y = setnum(&timint,x,y);
    if (y > -1) timef = 1;
    return(y);

default:
    if (x = (cmcfm()) < 0) return(x);
    printf("Not working yet - %s\n",cmdbuf);
    return(0);
    }
}

/*  C H K S P D  --  Check if argument is a valid baud rate  */

chkspd(x) int x; {
    switch (x) {
	case 0:
	case 110:
	case 150:
	case 300:
	case 600:
	case 1200:
	case 1800:
	case 2400:
	case 4800:
	case 9600:
	    return(x);
	default: 
	    return(-1);
      }
}

/*  S E T O N  --  Parse on/off (default on), set parameter to result  */

seton(prm) int *prm; {
    int x, y;
    if ((y = cmkey(onoff,2,"","on")) < 0) return(y);
    if ((x = cmcfm()) < 0) return(x);
    *prm = y;
    return(0);
}

/*  S E T N U M  --  Set parameter to result of cmnum() parse.  */
/*
 Call with x - number from cnum parse, y - return code from cmnum
*/
setnum(prm,x,y) int x, y, *prm; {
    debug(F101,"setnum",y);
    if (y < 0) return(y);
    if (x > 94) {
	printf("\n?Sorry, 94 is the maximum\n");
	return(-2);
    }
    if ((y = cmcfm()) < 0) return(y);
    *prm = x;
    return(0);
}

/*  S E T C C  --  Set parameter to an ASCII control character value.  */

setcc(prm,x,y) int x, y, *prm; {
    if (y < 0) return(y);
    if ((x > 037) && (x != 0177)) {
	printf("\n?Not in ASCII control range - %d\n",x);
	return(-2);
    }
    if ((y = cmcfm()) < 0) return(y);
    *prm = x;
    return(0);
}

/*  D O H L P  --  Give a help message  */

dohlp(xx) int xx; {
    int x,y;

    char *tophlp = "\n\
Type ? for a list of commands, type 'help x' for any command x.\n\
While typing commands, use the following special characters:\n\n\
 DEL, RUBOUT, BACKSPACE, CTRL-H: Delete the most recent character typed.\n\
 CTRL-W: Delete the most recent word typed.\n\
 CTRL-U: Delete the current line.\n\
 CTRL-R: Redisplay the current line.\n\
 ?       (question mark) display help on the current command or field.\n\
 ESC     (Escape or Altmode) Attempt to complete the current field.\n\
 \\       (backslash) include the following character literally.\n\n\
@From Unix command level, type 'kermit -h' to get help about command line args.\
\n";

    if (xx < 0) return(xx);
    switch (xx) {

case XXBYE:
    return(hmsg("Shut down and log out a remote Kermit server"));

case XXCLO:
    return(hmsg("Close one of the following logs:\n\
 session, transaction, packet, debugging -- 'help log' for further info."));

case XXCON:
    return(hmsg("Connect to a remote system via the tty device given in the\n\
most recent 'set line' command"));

case XXCWD:
    return(hmsg("Change Working Directory, equivalent to Unix 'cd' command"));

case XXDEL:
    return(hmsg("Delete a local file or files"));

case XXDIR:
    return(hmsg("Display a directory of local files"));

case XXECH:
    return(hmsg("Display the rest of the command on the terminal,\n\
useful in command files."));

case XXEXI:
case XXQUI:
    return(hmsg("Exit from the Kermit program, closing any open logs."));

case XXFIN:
    return(hmsg("\
Tell the remote Kermit server to shut down without logging out."));

case XXGET:
    return(hmsg("\
Format: 'get filespec'.  Tell the remote Kermit server to send the named\n\
files.  If filespec is omitted, then you are prompted for the remote and\n\
local filenames separately."));

case XXHLP:
    return(hmsg(tophlp));

case XXLOG:
    return(hmsg("Record information in a log file:\n\n\
 debugging             Debugging information, to help track down\n\
  (default debug.log)  bugs in the C-Kermit program.\n\n\
 packets               Kermit packets, to help track down protocol problems.\n\
  (packet.log)\n\n\
 session               Terminal session, during CONNECT command.\n\
  (session.log)\n\n\
 transactions          Names and statistics about files transferred.\n\
  (transaction.log)\n"));

case XXREC:
    return(hmsg("\
Format: 'receive [filespec]'.  Wait for a file to arrive from the other\n\
Kermit, which must be given a 'send' command.  If the optional filespec is\n\
given, the (first) incoming file will be stored under that name, otherwise\n\
it will be stored under the name it arrives with."));

case XXREM:
    if ((y = cmkey(remcmd,nrmt,"Remote command","")) == -2) return(y);
    if (y == -1) return(y);
    if (x = (cmcfm()) < 0) return(x);
    return(dohrmt(y));

case XXSEN:
    return(hmsg("\
Format: 'send file1 [file2]'.  File1 may contain wildcard characters '*' or\n\
'?'.  If no wildcards, then file2 may be used to specify the name file1 is\n\
sent under; if file2 is omitted, file1 is sent under its own name."));

case XXSER:
    return(hmsg("\
Enter server mode on the currently selected line.  All further commands\n\
will be taken in packet form from the other Kermit program."));

case XXSET:
    if ((y = cmkey(prmtab,nprm,"Parameter","")) == -2) return(y);
    if (y == -2) return(y);
    if (x = (cmcfm()) < 0) return(x);
    return(dohset(y));

case XXSHE:
    return(hmsg("\
Issue a command to the Unix shell (space required after '!')"));

case XXSHO:
    return(hmsg("\
Display current values of 'set' parameters; 'show version' will display\n\
program version information for each of the C-Kermit modules."));

case XXSPA:
    return(hmsg("Display disk usage in current device, directory"));

case XXSTA:
    return(hmsg("Display statistics about most recent file transfer"));

case XXTAK:
    return(hmsg("\
Take Kermit commands from the named file.  Kermit command files may\n\
themselves contain 'take' commands, up to a reasonable depth of nesting."));

default:
    if (x = (cmcfm()) < 0) return(x);
    printf("Not available yet - %s\n",cmdbuf);
    break;
    }
    return(0);
}



/*  H M S G  --  Get confirmation, then print the given message  */

hmsg(s) char *s; {
    int x;
    if (x = (cmcfm()) < 0) return(x);
    printf("%s\n",s);
    return(0);
}

/*  D O H S E T  --  Give help for SET command  */

dohset(xx) int xx; {
    
    if (xx == -3) {
	printf("\
The 'set' command is used to establish various communication or file\n\
parameters.  The 'show' command can be used to display the values of\n\
'set' parameters.  Help is available for each individual parameter;\n\
type 'help set ?' to see what's available.\n");
    	return(0);
    }
    if (xx < 0) return(xx);
    switch (xx) {

case XYCHKT:
    printf("\
Type of packet block check to be used for error detection, 1, 2, or 3.\n\
Type 1 is standard, and catches most errors.  Types 2 and 3 specify more\n\
rigorous checking, at the cost of higher overhead.  Not all Kermit programs\n\
support types 2 and 3.\n");
    return(0);

case XYDELA: 
    printf("\
Number of seconds to wait before sending first packet after 'send' command\n");
    return(0);

case XYDUPL:
    printf("\
During 'connect': 'full' means remote host echoes, 'half' means this program\n\
does its own echoing.\n");
    return(0);

case XYEOL:
    printf("\
Decimal ASCII value for character to terminate outbound packets, normally\n\
13 (CR, carriage return).  Inbound packets are assumed to end with CR.\n");
    return(0);

case XYESC:
    printf("\
Decimal ASCII value for escape character during 'connect', normally 28\n\
(Control-\\)\n");
    return(0);

case XYFILE:
    printf("\
set file: names, type, warning, display.\n\n\
'names' are normally 'converted', which means file names are converted\n\
to 'common form' during transmission; 'literal' means use filenames\n\
literally (useful between like Unix systems).\n");
    printf("\n\
'type' is normally 'text', in which conversion is done between Unix newlines\n\
and CRLF line delimiters; 'binary' means to do no conversion.  Use 'binary'\n\
for executable programs or binary data.\n");
    printf("\n\
'warning' is 'on' or 'off', normally off.  When off, incoming files will\n\
overwrite existing files of the same name.  When on, new names will be\n\
given to incoming files whose names are the same as existing files.\n");
    printf("\n\
'display' is normally 'on', causing file transfer progress to be displayed\n\
on your screen when in local mode.  'set display off' is useful for allowing\n\
file transfers to proceed in the background.\n\n");
    return(0);

case XYFLOW:
    printf("\
Type of flow control to be used.  Choices are 'xon/xoff' and 'none'.\n\
normally xon/xoff.\n");
    return(0);

case XYHAND:
    printf("\
Decimal ASCII value for character to use for half duplex line turnaround\n\
handshake.  Normally, handshaking is not done.\n");
    return(0);

case XYLEN:
    printf("\
Packet length to use.  90 by default.  94 maximum.\n");
    return(0);

case XYLINE:
    printf("Device name of communication line to use.  Normally %s.\n",dftty);
    if (!dfloc) {
	printf("If you set the line to other than %s, then Kermit\n",dftty);
	printf("\
will be in 'local' mode; 'set line' will reset Kermit to remote mode.\n");
    }
    return(0);

case XYMARK:
    printf("\
Decimal ASCII value of character that marks the beginning of packets sent by\n\
this program (normally 1 = Control-A)\n");
    return(0);

case XYNPAD:
    printf("\
Number of padding characters to request for inbound packets, normally 0.\n");
    return(0);

case XYPADC:
    printf("Decimal ASCII value of inbound padding character, normally 0.\n");
    return(0);

case XYPARI:
    printf("\
Parity to use during terminal connection and file transfer:\n\
even, odd, mark, space, or none.  Normally none.\n");
    return(0);

case XYPROM:
    printf("Prompt string for this program, normally 'C-Kermit>'.\n");
    return(0);

case XYSPEE:
    printf("\
Communication line speed for external tty line specified in most recent\n\
'set line'.  Any of the common baud rates: 0, 110, 150, 300, 600, 1200,\n\
1800, 2400, 4800, 9600.\n");
    return(0);

case XYTIMO:
    printf("\
Timeout interval for this program to use during file transfer, seconds.\n");
    return(0);

default:
    printf("Not available yet - %s\n",cmdbuf);
    return(0);
    }
}

/*  D O R M T  --  Do a remote command  */

dormt(xx) int xx; {
    int x;
    char *s, sbuf[50], *s2;

    if (xx < 0) return(xx);
    switch (xx) {

case XZCWD:				/* CWD */
    if ((x = cmtxt("Remote directory name","",&s)) < 0) return(x);
    debug(F111,"XZCWD: ",s,x);
    *sbuf = NUL;
    s2 = sbuf;
    if (*s != NUL) {			/* If directory name given, */
					/* get password on separate line. */
        if (tlevel > -1) {		/* From take file... */

	    *line = NUL;
	    if (fgets(sbuf,50,tfile[tlevel]) == NULL)
	    	ermsg("take file ends prematurely in 'remote cwd'");
	    debug(F110," pswd from take file",s2,0);

        } else {			/* From terminal... */

	    printf(" Password: "); 		/* get a password */
	    while ((x = getchar()) != '\n') {   /* without echo. */
	    	if ((x &= 0177) == '?') {
	    	    printf("? Password of remote directory\n Password: ");
		    s2 = sbuf;
		    *sbuf = NUL;
	    	}
	    	else if (x == ESC)	/* Mini command line editor... */
	    	    putchar(BEL);
		else if (x == BS || x == 0177)
		    *s2--;
		else if (x == 025) {
		    s2 = sbuf;
		    *sbuf = NUL;
		}
	    	else
		    *s2++ = x;
            }
	    *s2 = NUL;
	    putchar('\n');
        }
        s2 = sbuf;
    } else s2 = "";
    debug(F110," password",s2,0);
    sstate = setgen('C',s,s2,"");
    return(0);

case XZDEL:				/* Delete */
    if ((x = cmtxt("Name of remote file(s) to delete","",&s)) < 0) return(x);
    return(sstate = rfilop(s,'E'));

case XZDIR:				/* Directory */
    if ((x = cmtxt("Remote directory or file specification","",&s)) < 0)
    	return(x);
    return(sstate = rfilop(s,'D'));

case XZHLP:				/* Help */
    if (x = (cmcfm()) < 0) return(x);
    sstate = setgen('H',"","","");
    return(0);

case XZHOS:				/* Host */
    if ((x = cmtxt("Command for remote system","",&cmarg)) < 0) return(x);
    return(sstate = 'c');

case XZPRI:				/* Print */
    if ((x = cmtxt("Remote file(s) to print on remote printer","",&s)) < 0)
    	return(x);
    return(sstate = rfilop(s,'S'));

case XZSPA:				/* Space */
    if ((x = cmtxt("Confirm, or remote directory name","",&s)) < 0) return(x);
    return(sstate = setgen('U',s,"",""));
    
case XZTYP:				/* Type */
    if ((x = cmtxt("Remote file specification","",&s)) < 0) return(x);
    return(sstate = rfilop(s,'T'));

case XZWHO:
    if ((x = cmtxt("Remote user name, or carriage return","",&s)) < 0)
    	return(x);
    return(sstate = setgen('W',s,"",""));

default:
    if (x = (cmcfm()) < 0) return(x);
    printf("not working yet - %s\n",cmdbuf);
    return(-2);
    }
}



/*  R F I L O P  --  Remote File Operation  */

rfilop(s,t) char *s, t; {
    if (*s == NUL) {
	printf("?File specification required\n");
	return(-2);
    }
    debug(F111,"rfilop",s,t);
    return(setgen(t,s,"",""));
}

/*  D O H R M T  --  Give help about REMOTE command  */

dohrmt(xx) int xx; {
    int x;
    if (xx == -3) {
	printf("\
The 'remote' command is used to send file management instructions to a\n\
remote Kermit server.  There should already be a Kermit running in server\n\
mode on the other end of the currently selected line.  Type 'remote ?' to\n\
see a list of available remote commands.  Type 'help remote x' to get\n\
further information about a particular remote command 'x'.\n");
    	return(0);
    }
    if (xx < 0) return(xx);
    switch (xx) {

case XZCWD:
    return(hmsg("\
Ask remote Kermit server to change its working directory."));

case XZDEL:
    return(hmsg("\
Ask remote Kermit server to delete the named file(s)."));

case XZDIR:
    return(hmsg("\
Ask remote Kermit server to provide directory listing of the named file(s)."));

case XZHLP:
    return(hmsg("\
Ask remote Kermit server to tell you what services it provides."));

case XZHOS:
    return(hmsg("\
Send a command to the remote system in its own command language\n\
through the remote Kermit server."));

case XZSPA:
    return(hmsg("\
Ask the remote Kermit server to tell you about its disk space."));

case XZTYP:
    return(hmsg("\
Ask the remote Kermit server to type the named file(s) on your screen."));

case XZWHO:
    return(hmsg("\
Ask the remote Kermit server to list who's logged in, or to give information\n\
about the specified user."));

default:
    if (x = (cmcfm()) < 0) return(x);
    printf("not working yet - %s\n",cmdbuf);
    return(-2);
    }
}

/*  S C R E E N  --  Screen display function  */
/*  
   c - a character or small integer
   n - an integet
   s - a string.
 Fill in this routine with the appropriate display update for the system.
 This version is for a dumb tty, and uses the arguments like this:
   c:     0 - print s on a new line, followed by a space.
          1 - print s at current screen position.
          2 - print s at current position, followed by newline.
	  3 - print "s: n" at current position, followed by newline.
      other - print c as a character.
   n: if c is 'other', used to decide whether or how to print c.
   s: as above.
 Horizontal screen position is kept current if screen is only updated by 
 calling this function.  Wraparound is done at column 78.
*/
screen(c,n,s) char c; int n; char *s; {
    static int p = 0;			/* Screen position */
    int len;  char buf[80];
    len = strlen(s);
    if (!displa || quiet) return;	/* No update if display flag off */
    switch (c) {
	case 0:				/* Print s on newline */
	    conoll("");			/* Start new line, */
	    conol(s);			/* Print string. */
	    conoc(SP);			/* Leave a space. */
	    p = len + 1;		/* Set position counter. */
	    return;
	case 1:
	    if (p + len > 78) { conoll(""); p = 0; }
	    conol(s);  if ((p += len) > 78) conoll("");
	    return;
	case 2:				/* Print s, then newline */
	    if (p + len > 78) conoll("");
	    conoll(s);  p = 0;
	    return;
	case 3:
	    sprintf(buf,"%s: %d",s,n);  conoll(buf);  p = 0;
	    return;
	case BS:			/* Backspace */
	    if (p > 0) p--;
	case BEL:			/* Beep */
	    conoc(c);
	    return;
	default:			/* Packet type display */
	    if (c == 'Y') return;	/* Don't bother with ACKs */
	    if (c == 'D') {		/* Only show every 4th data packet */
		c = '.';
		if (n % 4) return;
	    }
	    if (p++ > 78) {		/* If near left margin, */
		conoll("");		/* Start new line */
		p = 0;			/* and reset counter. */
	    }
	    conoc(c);			/* Display the character. */
	    return;
    }
}

/*  I N T M S G  --  Issue message about terminal interrupts  */

intmsg(n) int n; {
    if ((!displa) || (quiet)) return;
    if (n == 1) {
	screen(2,0,"");
	screen(2,0,"CTRL-F to cancel file,  CTRL-R to resend current packet");
	screen(2,0,"CTRL-B to cancel batch, CTRL-A for status report...");
    }
    else screen(1,0," ");
}


/*  C H K I N T  --  Check for console interrupts  */

/*** should rework not to destroy typeahead ***/

chkint() {
    int ch, cn;

    if ((!local) || (quiet)) return(0);	/* Only do this if local & not quiet */
    cn = conchk();			/* Any input waiting? */
    debug(F101,"conchk","",cn);

    while (cn > 0) {			/* Yes, read it. */
	cn--;
	if ((ch = coninc()) < 0) return(0);
	switch (ch & 0177) {
	    case 0001:			/* CTRL-A */
		screen(2,0,"^A  Status report:");
		screen(1,0,     " file type: ");
		if (binary) screen(2,0,"binary"); else screen(2,0,"text");
		screen(3,filcnt," file number");
		screen(3,ffc,   " characters ");
		screen(3,bctu,  " block check");
		screen(3,rptflg," compression");
		screen(3,ebqflg," 8th-bit prefixing");
		continue;
	    case 0002:			/* CTRL-B */
	    	screen(1,0,"^B - Cancelling Batch ");
	    	czseen = 1;
		continue;
	    case 0006:			/* CTRL-F */
	    	screen(1,0,"^F - Cancelling File ");
	    	cxseen = 1;
		continue;
	    case 0022:	    	    	/* CTRL-R */
	    	screen(1,0,"^R - Resending ");
	    	resend();
		return(1);
	    default:			/* Anything else, just beep */
	    	screen(BEL,0,"");
		continue;
    	}
    }
    return(0);
}

/*  D E B U G  --  Enter a record in the debugging log  */

/*
 Call with a format, two strings, and a number:
   f  - Format, a bit string in range 0-7.
        If bit x is on, then argument number x is printed.
   s1 - String, argument number 1.  If selected, printed as is.
   s2 - String, argument number 2.  If selected, printed in brackets.
   n  - Int, argument 3.  If selected, printed preceded by equals sign.

   f=0 is special: print s1,s2, and interpret n as a char.
*/
debug(f,s1,s2,n) int f, n; char *s1, *s2; {
    static char s[200];
    char *sp = s;

    if (!deblog) return;		/* If no debug log, don't */
    switch (f) {
    	case F000:			/* 0, print both strings, */
	    sprintf(sp,"%s%s%c\n",s1,s2,n); /*  and interpret n as a char */
	    zsout(ZDFILE,s);
	    break;
    	case F001:			/* 1, "=n" */
	    sprintf(sp,"=%d\n",n);
	    zsout(ZDFILE,s);
	    break;
    	case F010:			/* 2, "[s2]" */
	    sprintf(sp,"[%s]\n",s2);
	    zsout(ZDFILE,"");
	    break;
    	case F011:			/* 3, "[s2]=n" */
	    sprintf(sp,"[%s]=%d\n",s2,n);
	    zsout(ZDFILE,s);
	    break;
    	case F100:			/* 4, "s1" */
	    zsoutl(ZDFILE,s1);
	    break;
    	case F101:			/* 5, "s1=n" */
	    sprintf(sp,"%s=%d\n",s1,n);
	    zsout(ZDFILE,s);
	    break;
    	case F110:			/* 6, "s1[s2]" */
	    sprintf(sp,"%s[%s]\n",s1,s2);
	    zsout(ZDFILE,s);
	    break;
    	case F111:			/* 7, "s1[s2]=n" */
	    sprintf(sp,"%s[%s]=%d\n",s1,s2,n);
	    zsout(ZDFILE,s);
	    break;
	default:
	    sprintf(sp,"\n?Invalid format for debug() - %d\n",n);
	    zsout(ZDFILE,s);
    }
}

/*  T L O G  --  Log a record in the transaction file  */
/*
 Call with a format and 3 arguments: two strings and a number:
   f  - Format, a bit string in range 0-7, bit x is on, arg #x is printed.
   s1 - String, argument number 1.
   s2 - String, argument number 2.
   n  - Int, argument 3.
*/
tlog(f,s1,s2,n) int f, n; char *s1, *s2; {
    static char s[200];
    char *sp = s; int x;
    
    if (!tralog) return;		/* If no transaction log, don't */
    switch (f) {
    	case F000:			/* 0 (special) "s1 n s2"  */
	    sprintf(sp,"%s %d %s\n",s1,n,s2);
	    zsout(ZTFILE,s);
	    break;
    	case F001:			/* 1, " n" */
	    sprintf(sp," %d\n",n);
	    zsout(ZTFILE,s);
	    break;
    	case F010:			/* 2, "[s2]" */
	    x = strlen(s2);
	    if (s2[x] == '\n') s2[x] = '\0';
	    sprintf(sp,"[%s]\n",s2);
	    zsout(ZTFILE,"");
	    break;
    	case F011:			/* 3, "[s2] n" */
	    x = strlen(s2);
	    if (s2[x] == '\n') s2[x] = '\0';
	    sprintf(sp,"[%s] %d\n",s2,n);
	    zsout(ZTFILE,s);
	    break;
    	case F100:			/* 4, "s1" */
	    zsoutl(ZTFILE,s1);
	    break;
    	case F101:			/* 5, "s1: n" */
	    sprintf(sp,"%s: %d\n",s1,n);
	    zsout(ZTFILE,s);
	    break;
    	case F110:			/* 6, "s1 s2" */
	    x = strlen(s2);
	    if (s2[x] == '\n') s2[x] = '\0';
	    sprintf(sp,"%s %s\n",s1,s2);
	    zsout(ZTFILE,s);
	    break;
    	case F111:			/* 7, "s1 s2: n" */
	    x = strlen(s2);
	    if (s2[x] == '\n') s2[x] = '\0';
	    sprintf(sp,"%s %s: %d\n",s1,s2,n);
	    zsout(ZTFILE,s);
	    break;
	default:
	    sprintf(sp,"\n?Invalid format for tlog() - %d\n",n);
	    zsout(ZTFILE,s);
    }
}
@//E*O*F ckuser.c//
chmod u=rw,g=r,o=r ckuser.c
 
echo Inspecting for damage in transit...
temp=/tmp/shar$$; dtemp=/tmp/.shar$$
trap "rm -f $temp $dtemp; exit" 0 1 2 3 15
cat > $temp <<\!!!
    2199    8573   59389 ckuser.c
!!!
wc  ckuser.c | sed 's=[^ ]*/==' | diff -b $temp - >$dtemp
if [ -s $dtemp ]
then echo "Ouch [diff of wc output]:" ; cat $dtemp
else echo "No problems found."
fi
exit 0

tsc2597@acf4.UUCP (Sam Chin) (02/28/85)

# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# Wrapped by acf4!tsc2597  on Wed Feb 27 22:21:33 EST 1985
# Contents:  ckwart.c ckwart.doc ckwho.txt ckxbsd.bwr ckxbsd.c ckzbsd.c
 
echo x - ckwart.c
sed 's/^@//' > "ckwart.c" <<'@//E*O*F ckwart.c//'
/* W A R T
 *
 * pre-process a lex-like file into a C program.
 *
 * Jeff Damens, Columbia University Center for Computing Activites, 11/84.
 * (Reorganized by Frank da Cruz into a single source module for ease
 * of distribution).
 * Copyright (C) 1984, Trustees of Columbia University.
 * May be copied and used except for explicitly commercial purposes.
 *
 * input format is:
 *  lines to be copied | %state <state names...>
 *  %%
 * <state> | <state,state,...> CHAR  { actions }
 * ...
 *  %%
 */

#include <stdio.h>
#include <ctype.h>

/* token types */

#define SEP 1
#define LBRACK 2
#define RBRACK 3
#define WORD 4
#define COMMA 5

/* storage sizes */

#define MAXSTATES 50			/* max number of states */
#define MAXWORD 50			/* max # of chars/word */
#define SBYTES ((MAXSTATES+7)/8)	/* # of bytes for state bitmask */

/* name of wart function in generated program */

#ifndef FNAME
#define FNAME "wart"
#endif

/* data structure for state information */

typedef unsigned short CHAR;

struct trans { CHAR states[SBYTES];	/* included states */
    	       int anyst;		/* true if this good from any state */
    	       CHAR inchr;		/* input character */
	       int actno;		/* associated action */
	       struct trans *nxt; };	/* next transition */

typedef struct trans *Trans;

/* Variables and tables */

int lines,nstates,nacts;

char tokval[MAXWORD];

int tbl[MAXSTATES*128];



char *txt1 = "\n\
#define BEGIN state =\n\
\n\
int state = 0;\n\
\n";

char *fname = FNAME;		/* function name goes here */

/* rest of program... */

char *txt2 = "()\n\
{\n\
  int c,actno;\n\
  extern int tbl[];\n\
  while (1) {\n\
	c = input();\n\
	if ((actno = tbl[c + state*128]) != -1)\n\
	  switch(actno) {\n";

/* this program's output goes here, followed by final text... */

char *txt3 = "\n    }\n  }\n\}\n\n";

/*
 * turn on the bit associated with the given state
 *
 */
setstate(state,t)
int state;
Trans t;
{
  int idx,msk;
  idx = state/8;			/* byte associated with state */
  msk = 0x80 >> (state % 8);		/* bit mask for state */
  t->states[idx] |= msk;
}

/*
 * see if the state is involved in the transition
 *
 */

teststate(state,t)
int state;
Trans t;
{
  int idx,msk;
  idx = state/8;
  msk = 0x80 >> (state % 8);
  return(t->states[idx] & msk);
}


/*
 * read input from here...
 *
 */

Trans
rdinput(infp,outfp)
FILE *infp,*outfp;
{
  Trans x,rdrules();
  lines = 1;				/* line counter */
  nstates = 0;				/* no states */
  nacts = 0;				/* no actions yet */
  fprintf(outfp,"\n\
/* WARNING -- This C source program generated by Wart preprocessor. */\n");
  fprintf(outfp,"\
/* Do not edit this file; edit the Wart-format source file instead, */\n");
  fprintf(outfp,"\
/* and then run it through Wart to produce a new C source file.     */\n\n");
  initial(infp,outfp);			/* read state names, initial defs */
  prolog(outfp);			/* write out our initial code */
  x = rdrules(infp,outfp);		/* read rules */
  epilogue(outfp);			/* write out epilogue code */
  return(x);
}

/*
 * initial - read initial definitions and state names.  Returns
 * on EOF or %%.
 *
 */

initial(infp,outfp)
FILE *infp,*outfp;
{
  int c;
  char wordbuf[MAXWORD];
  while ((c = getc(infp)) != EOF) {
	if (c == '%') {
			rdword(infp,wordbuf);
			if (strcmp(wordbuf,"states") == 0)
			    rdstates(infp,outfp);
			else if (strcmp(wordbuf,"%") == 0) return;
			else fprintf(outfp,"%%%s",wordbuf);
		      }
	else putc(c,outfp);
	if (c == '\n') lines++;
     }
}

/*
 * boolean function to tell if the given character can be part of
 * a word.
 *
 */
isin(s,c) char *s; int c; {
   for (; *s != '\0'; s++)
      if (*s == c) return(1);
   return(0);
}
isword(c)
int c;
{
  static char special[] = ".%_-$@";	/* these are allowable */
  return(isalnum(c) || isin(special,c));
}

/*
 * read the next word into the given buffer.
 *
 */
rdword(fp,buf)
FILE *fp;
char *buf;
{
  int len = 0,c;
  while (isword(c = getc(fp)) && ++len < MAXWORD) *buf++ = c;
  *buf++ = '\0';			/* tie off word */
  ungetc(c,fp);				/* put break char back */
}

/*
 * read state names, up to a newline.
 *
 */

rdstates(fp,ofp)
FILE *fp,*ofp;
{
  int c;
  char wordbuf[MAXWORD];
  while ((c = getc(fp)) != EOF && c != '\n')
  {
	if (isspace(c)) continue;	/* skip whitespace */
	ungetc(c,fp);			/* put char back */
	rdword(fp,wordbuf);		/* read the whole word */
	enter(wordbuf,++nstates);	/* put into symbol tbl */
	fprintf(ofp,"#define %s %d\n",wordbuf,nstates);
  }
  lines++;
}
		
/*
 * allocate a new, empty transition node
 *
 */

Trans
newtrans()
{
  Trans new;
  int i;
  new = (Trans) malloc(sizeof (struct trans));
  for (i=0; i<SBYTES; i++) new->states[i] = 0;
  new->anyst = 0;
  new->nxt = NULL;
  return(new);
}

/*
 * read all the rules.
 *
 */

Trans
rdrules(fp,out)
FILE *fp,*out;
{
  Trans head,cur,prev;
  int curtok,i;
  head = cur = NULL;
  while ((curtok = gettoken(fp)) != SEP) 

	switch(curtok) {
		case LBRACK: if (cur == NULL) cur = newtrans();
		    	     else fatal("duplicate state list");
			     statelist(fp,cur);/* set states */
			     continue;	/* prepare to read char */

		case WORD:   if (strlen(tokval) != 1)
					fatal("multiple chars in state");
			     if (cur == NULL) {
				cur = newtrans();
				cur->anyst = 1;
				}
			     cur->actno = ++nacts;
			     cur->inchr = tokval[0];
			     if (head == NULL) head = cur;
			     else prev->nxt = cur;
			     prev = cur;
			     cur = NULL;
			     copyact(fp,out,nacts);
			     break; 
		 default: fatal("bad input format");
	     }
	
   return(head);
}

/*
 * read a list of (comma-separated) states, set them in the
 * given transition.
 *
 */
statelist(fp,t)
FILE *fp;
Trans t;
{
  int curtok,sval;
  curtok = COMMA;
  while (curtok != RBRACK) {
	if (curtok != COMMA) fatal("missing comma");
	if ((curtok = gettoken(fp)) != WORD) fatal("missing state name");
        if ((sval = lkup(tokval)) == -1) {
		fprintf(stderr,"state %s undefined\n",tokval);
		fatal("undefined state");
	   }
        setstate(sval,t);
	curtok = gettoken(fp);
   }
}

/*
 * copy an action from the input to the output file
 *
 */
copyact(inp,outp,actno)
FILE *inp,*outp;
int actno;
{
  int c,bcnt;
  fprintf(outp,"case %d:\n",actno);
  while ((c = getc(inp)) != '\n' && isspace(c));	/* skip whitespace */
  if (c == '{') {
     bcnt = 1;
     putc(c,outp);
     while (bcnt > 0 && (c = getc(inp)) != EOF) {
	if (c == '{') bcnt++;
	else if (c == '}') bcnt--;
	else if (c == '\n') lines++;
	putc(c,outp);
      }
     if (bcnt > 0) fatal("action doesn't end");
    }
   else {
	  while (c != '\n' && c != EOF) {
		putc(c,outp);
		c = getc(inp);
	    }
	  lines++;
	}
   fprintf(outp,"\nbreak;\n");
}

/*
 * find the action associated with a given character and state.
 * returns -1 if one can't be found.
 *
 */
faction(hd,state,chr)
Trans hd;
int state,chr;
{
  while (hd != NULL) {
    if (hd->anyst || teststate(state,hd))
      if (hd->inchr == '.' || hd->inchr == chr) return(hd->actno);
    hd = hd->nxt;
    }
  return(-1);
}


/*
 * empty the table...
 *
 */
emptytbl()
{
  int i;
  for (i=0; i<nstates*128; i++) tbl[i] = -1;
}

/*
 * add the specified action to the output for the given state and chr.
 *
 */

addaction(act,state,chr)
int act,state,chr;
{
 tbl[state*128 + chr] = act;
}

writetbl(fp)
FILE *fp;
{
  warray(fp,"tbl",tbl,128*(nstates+1));
}

/*
 * write an array to the output file, given its name and size.
 *
 */
warray(fp,nam,cont,siz)
FILE *fp;
char *nam;
int cont[],siz;
{
  int i;
  fprintf(fp,"int %s[] = {\n",nam);
  for (i = 0; i < siz; i++) {
	fprintf(fp,"%d, ",cont[i]);
	if ((i % 20) == 0) putc('\n',fp);
	}
  fprintf(fp,"};\n");
}

main(argc,argv)
int argc;
char *argv[];
{
  Trans head;
  int state,c;
  FILE *infile,*outfile;

  if (argc > 1) {
    if ((infile = fopen(argv[1],"r")) == NULL) {
    	fprintf(stderr,"Can't open %s\n",argv[1]);
	fatal("unreadable input file"); } }
  else infile = stdin;

  if (argc > 2) {
    if ((outfile = fopen(argv[2],"w")) == NULL) {
    	fprintf(stderr,"Can't write to %s\n",argv[2]);
	fatal("bad output file"); } }
  else outfile = stdout;

  clrhash();				/* empty hash table */
  head = rdinput(infile,outfile);	/* read input file */
  emptytbl();				/* empty our tables */
  for (state = 0; state <= nstates; state++)
    for (c = 1; c < 128; c++)
     addaction(faction(head,state,c),state,c);	/* find actions, add to tbl */
  writetbl(outfile);
  copyrest(infile,outfile);
  printf("%d states, %d actions\n",nstates,nacts);
#ifdef undef
  for (state = 1; state <= nstates; state ++)
    for (c = 1; c < 128; c++)
       if (tbl[state*128 + c] != -1) printf("state %d, chr %d, act %d\n",
       	state,c,tbl[state*128 + c]);
#endif
  exit(0);
}

/*
 * fatal error handler
 *
 */

fatal(msg)
char *msg;
{
  fprintf(stderr,"error in line %d: %s\n",lines,msg);
  exit(1);
}

prolog(outfp)
FILE *outfp;
{
  int c;
  while ((c = *txt1++) != '\0')  putc(c,outfp);
  while ((c = *fname++) != '\0') putc(c,outfp);
  while ((c = *txt2++) != '\0')  putc(c,outfp);
}

epilogue(outfp)
FILE *outfp;
{
  int c;
  while ((c = *txt3++) != '\0') putc(c,outfp);
}

copyrest(in,out)
FILE *in,*out;
{
  int c;
  while ((c = getc(in)) != EOF) putc(c,out);
}

/*
 * gettoken - returns token type of next token, sets tokval
 * to the string value of the token if appropriate.
 *
 */

gettoken(fp)
FILE *fp;
{
  int c;
  while (1) {				/* loop if reading comments... */
    do {
	  c = getc(fp);
	  if (c == '\n') lines++;
       } while (isspace(c));		/* skip whitespace */
    switch(c) {
	  case EOF: return(SEP);
	  case '%': if ((c = getc(fp)) == '%') return(SEP);
		    tokval[0] = '%';
		    tokval[1] = c;
		    rdword(fp,tokval+2);
		    return(WORD);
	  case '<': return(LBRACK);
	  case '>': return(RBRACK);
	  case ',': return(COMMA);
	  case '/': if ((c = getc(fp)) == '*') {
	    	      rdcmnt(fp);	/* skip over the comment */
		      continue; }	/* and keep looping */
		    else {
			ungetc(c);	/* put this back into input */
			c = '/'; }	/* put character back, fall thru */

	  default: if (isword(c)) {
			  ungetc(c,fp);
			  rdword(fp,tokval);
			  return(WORD);
		      	}
		   else fatal("Invalid character in input");
	     }
  }
}

/*
 * skip over a comment
 *
 */

rdcmnt(fp)
FILE *fp;
{
  int c,star,prcnt;
  prcnt = star = 0;			/* no star seen yet */
  while (!((c = getc(fp)) == '/' && star)) {
    if (c == EOF || (prcnt && c == '%')) fatal("Unterminated comment");
    prcnt = (c == '%');
    star = (c == '*');
    if (c == '\n') lines++; }
}


/*
 * symbol table management for wart
 *
 * entry points:
 *   clrhash - empty hash table.
 *   enter - enter a name into the symbol table
 *   lkup - find a name's value in the symbol table.
 *
 */

#define HASHSIZE 101			/* # of entries in hash table */

struct sym { char *name;		/* symbol name */
	     int val;			/* value */
	     struct sym *hnxt; }	/* next on collision chain */
    *htab[HASHSIZE];			/* the hash table */


/*
 * empty the hash table before using it...
 *
 */
clrhash()
{
  int i;
  for (i=0; i<HASHSIZE; i++) htab[i] = NULL;
}

/*
 * compute the value of the hash for a symbol
 *
 */
hash(name)
char *name;
{
  int sum;
  for (sum = 0; *name != '\0'; name++) sum += (sum + *name);
  sum %= HASHSIZE;			/* take sum mod hashsize */
  if (sum < 0) sum += HASHSIZE;		/* disallow negative hash value */
  return(sum);
}

/*
 * make a private copy of a string...
 *
 */
char *
copy(s)
char *s;
{
  char *new;
  new = (char *) malloc(strlen(s) + 1);
  strcpy(new,s);
  return(new);
}

/*
 * enter state name into the hash table
 *
 */
enter(name,svalue)
char *name;
int svalue;
{
  int h;
  struct sym *cur;
  if (lkup(name) != -1) {
	fprintf(stderr,"state %s appears twice...\n");
	exit(1); }
  h = hash(name);
  cur = (struct sym *)malloc(sizeof (struct sym));
  cur->name = copy(name);
  cur->val = svalue;
  cur->hnxt = htab[h];
  htab[h] = cur;
}

/*
 * find name in the symbol table, return its value.  Returns -1
 * if not found.
 *
 */
lkup(name)
char *name;
{
  struct sym *cur;
  for (cur = htab[hash(name)]; cur != NULL; cur = cur->hnxt)
	if (strcmp(cur->name,name) == 0) return(cur->val);
  return(-1);
}
@//E*O*F ckwart.c//
chmod u=rw,g=r,o=r ckwart.c
 
echo x - ckwart.doc
sed 's/^@//' > "ckwart.doc" <<'@//E*O*F ckwart.doc//'
WART

Wart is a program that implements a small subset of the Unix 'lex' lexical
analyzer generator.  Unlike lex, wart may be distributed without requirement
for a Unix license.  Wart was written by Jeff Damens at the Columbia University
Center of Computing Activities to facilitate development of Unix Kermit.

Wart is intended for production of state table switchers.  It allows a set of
states to be defined, along with a function for getting input, and a table of
state transitions.  A C program is generated which performs actions and
switches states based on the current state and the input.

The following short program demonstrates some of the capabilities and
limitations of Wart.  The program accepts from the command line a binary
number, preceded by an optional minus sign, and optionally containing a
fractional part.  It prints the decimal equivalent.

#include <stdio.h>

int state, s = 1, m = 0, d;
float f;
char *b;

%states sign mantissa fraction		    /* Declare wart states */

%%					    /* Begin state table */
<sign>-      { s = -1; BEGIN mantissa; }    /* Look for sign */
<sign>0      { m = 0;  BEGIN mantissa; }    /* Got digit, start mantissa */
<sign>1      { m = 1;  BEGIN mantissa; }
<sign>.      { fatal("bad input"); }	    /* Detect bad format */
<mantissa>0  { m *= 2; }		    /* Accumulate mantissa */
<mantissa>1  { m = 2 * m + 1; }
<mantissa>$  { printf("%d\n", s * m); return; }
<mantissa>.  { f = 0.0; d = 1; BEGIN fraction; }    /* Start fraction */
<fraction>0  { d *= 2; }		    	    /* Accumulate fraction */
<fraction>1  { d *= 2; f += 1.0 / d; }
<fraction>$  { printf("%f\n", s * (m + f) ); return; }
<fraction>.  { fatal("bad input"); }
%%

input() {				    /* Define input() function */
    int x;
    return(((x = *b++) == '\0') ? '$' : x );
}

fatal(s) char *s; {			    /* Error exit */
    fprintf(stderr,"fatal - %s\n",s);
    exit(1);
}

main(argc,argv) int argc; char **argv; {    /* Main program */
    if (argc < 1) exit(1);
    b = *++argv;
    state = sign;			    /* Initialize state */
    wart();				    /* Invoke state switcher */
    exit(0);				    /* Done */
}

The wart program accepts as input a C program containing lines that start
with "%" or sections delimited by "%%".  The directive "%states" declares
the program's states.  The section enclosed by "%%" markers is the state
table, with entries of the form

  <state>X { action }

which is read as "if in state <state> with input X perform { action }"

The optional <state> field tells the current state or states the program must
be in to perform the indicated action.  If no state is specified, then it
means the action will be performed regardless of the current state.  If more
than one state is specifed, then the action will be performed in any of the
listed states.  Multiple states are separated by commas.

The required input field consists of a single literal character.  When in
the indicated state, if the input is the specified character, then the
associated action will be performed.  The character '.' matches any input
character.  No pattern matching or range notation is provided.  The input
character is obtained from the input() function, which you must define.  It
should be alphanumeric, or else one of the characters ".% -$@" (quotes not
included).  Note that the program above recognize the binary point '.'
through a ruse.

The action is a series of zero or more C language statements, enclosed in
curly braces.

The BEGIN macro is defined simply to be "state = ", as in lex.

The wart() function is generated by the wart program based on the state
declarations and the state transition table.  It loops through calls to
input(), using the result to index into a big case statement it has created
from the state table.

Wart is invoked as follows:

	wart          (Input from stdin, output to stdout)

	wart fn1      (Input from fn1, output to stdout)

	wart fn1 fn2  (Input from fn1, output to fn2.  Example:  wart a.w a.c)

Wart programs have the conventional filetype '.w'.
@//E*O*F ckwart.doc//
chmod u=rw,g=r,o=r ckwart.doc
 
echo x - ckwho.txt
sed 's/^@//' > "ckwho.txt" <<'@//E*O*F ckwho.txt//'
(18 Feb 85)

The following people have tentatively volunteered to supply support in
C-Kermit 4.0 for the following systems:


What                             Who

DEC Pro-350/380, Venix           SY.FDC@CU20B (Frank da Cruz)

IBM 370-series, Ahmdah UTS       SY.FDC@CU20B (Frank da Cruz)

Apple Macintosh                  SY.WBC3@CU20B (Bill Catchings)

Masscomp RTU 2.2                 sob@RICE (Stan Barber)

Coherent                         vortex!lauren@RAND-UNIX (Lauren Weinstein) 

Callan UniStar 300 with
 Unisoft 68000 System V Unix     EBM@MIT-XX (Eliot Moss)

ATT 3Bx, System V                Chris@COLUMBIA-20 (Chris Maio)

IBM PC, etc, PC/IX               HFISCHER@USC-ECLB (Herm Fischer)

IBM PC, etc, Xenix               HFISCHER@USC-ECLB (Herm Fischer)

VAX,PDP-11 with IS3, Interactive
 Systems version of System III   HFISCHER@USC-ECLB (Herm Fischer)

Os9                              BLARSON@USC-ECL (Bob Larson)

Version 7                        vasoll%okstate.csnet@CSNET-RELAY (Mark Vasoll)

4.2 UUCP Line Locking            hipl!tony@NYU-CMCL2 (Tony Movshon)

HP9000 Series 200 (HP9836)
 with HP-UX System III           b-davis@utah-cs (Brad Davis)

CP/M (Small C or BDS C)          bdale@cmu-cs-g (Bdale Garbee)

Honeywell GCOS3/8                Carlin%HIS-PHOENIX-MULTICS@MIT-MULTICS

68000 Xenix                      ED@MIT-MC (Ed Schwalenberg)

VAX, 2.0 BSD                     nsc!jon@DECWRL (Jon Ryshpan)

CP/M-86, De Smet C               nsc!jon@DECWRL (Jon Ryshpan)

Login scripts, raw upload        poulton%hplabs.csnet@CSNET-RELAY (Ken Poulton)

Apple II, Aztec C                Saline@MIT-MULTICS (Steven Saline)
@//E*O*F ckwho.txt//
chmod u=rw,g=r,o=r ckwho.txt
 
echo x - ckxbsd.bwr
sed 's/^@//' > "ckxbsd.bwr" <<'@//E*O*F ckxbsd.bwr//'
ttxin()

1.  11 Feb 85: changed
      x = read(ttyfd,buf,&n);
    to
      x = read(ttyfd,buf,n);

2.  11 Feb 85: in for loop, check & pass along return code from read():
      if ((y = read(ttyfd,&c,1)) < 1) return(y);


3.  11 Feb 85: declare c to be unsigned char, rather than int, to allow
    read() to work on machines that store chars in MSB rather LSB.
    Make sure char is not sign-extended upon return.

ttinc() -

1. 11 Feb 85: declare ch to be unsigned char, rather than int, as in ttinl().
@//E*O*F ckxbsd.bwr//
chmod u=rw,g=r,o=r ckxbsd.bwr
 
echo x - ckxbsd.c
sed 's/^@//' > "ckxbsd.c" <<'@//E*O*F ckxbsd.c//'
char *ckxv = "4.2BSD Terminal I/O, 4.0(013), 12 Feb 85";

/* Interrupt, terminal control & i/o functions for 4.2BSD */

/*
 Note - KERLD is the Berknet line driver, modified to pass through all 8
 bits, and to allow an arbitrary break character to be set.  Don't define
 this symbol unless you have made this modification to your 4.2BSD kernel!
*/ 
#define KERLD 0

/*
 Variables:

   dftty  -- Pointer to default tty name string, like "/dev/tty".
   dfloc  -- 0 if dftty is console, 1 if external line.
   dfprty -- Default parity
   dfflow -- Default flow control
   ckxech -- Flag for who echoes console typein:
     1 - The program (system echo is turned off)
     0 - The system (or front end, or terminal).
   functions that want to do their own echoing should check this flag
   before doing so.

 Functions for assigned communication line (either external or console tty):

   ttopen(ttname)          -- Open the named tty for exclusive access.
   ttclos()                -- Close & reset the tty, releasing any access lock.
   ttpkt(speed,flow)       -- Put the tty in packet mode and set the speed.
   ttvt(speed,flow)        -- Put the tty in virtual terminal mode.
   ttinl(dest,max,timo)    -- Timed read line from the tty.
   ttinc(timo)             -- Timed read character from tty.
   ttchk()                 -- See how many characters in tty input buffer.
   ttxin(n,buf)            -- Read n characters from tty (untimed).
   ttol(string,length)     -- Write a string to the tty.
   ttoc(c)                 -- Write a character to the tty.
   ttflui()                -- Flush tty input buffer.

Functions for console terminal:

   congm()   -- Get console terminal modes.
   concb()   -- Put the console in single-character wakeup mode with no echo.
   conbin()  -- Put the console in binary (raw) mode.
   conres()  -- Restore the console to mode obtained by congm().
   conoc(c)  -- Unbuffered output, one character to console.
   conol(s)  -- Unbuffered output, null-terminated string to the console.
   conxo(n,s) -- Unbuffered output, n characters to the console.
   conchk()  -- Check if characters available at console.
   coninc()  -- Get a character from the console.
   conint()  -- Enable terminal interrupts on the console.
   connoi()  -- Disable terminal interrupts on the console.

Time functions

   msleep(m) -- Millisecond sleep
   ztime(&s) -- Return pointer to date/time string
*/

#include <stdio.h>			/* Unix Standard i/o */
#include <sgtty.h>			/* Set/Get tty modes */
#include <signal.h>			/* Interrupts */
#include <setjmp.h>			/* Longjumps */
#include <sys/file.h>			/* File information */
#include <sys/time.h>			/* Clock info */
#include "ckdebu.h"			/* Formats for debug() */


/* dftty is the device name of the default device for file transfer */

char *dftty = "/dev/tty";


/* dfloc is 0 if dftty is the user's console terminal, 1 if an external line */

int dfloc = 0;

/* Other defaults */

int dfprty = 0;				/* No parity */
int dfflow = 1;				/* Xon/Xoff flow control */

/* ckxech is 0 if system normally echoes console characters, 1 otherwise */

int ckxech = 0;


/* Declarations of variables global to this module */

jmp_buf sjbuf;				/* Longjump buffer */

static int lkf = 0,			/* Line lock flag */
    conif = 0,				/* Console interrupts on/off flag */
    cgmf = 0,				/* Flag that console modes saved */
    kerld = KERLD,			/* Flag for using line discipline */
    ttyfd = -1;				/* TTY file descriptor */

static struct timeval tv;		/* For getting time, from sys/time.h */
static struct timezone tz;

static struct sgttyb 			/* sgtty info... */
    ttold, ttraw, tttvt,		/* for communication line */
    ccold, ccraw, cccbrk;		/* and for console */

#if KERLD
  struct tchars oldc, newc;		/* Special characters */
  int ld = NETLDISC;			/* Special Kermit line discipline */
  int oldld;				/* Old discipline */
#endif

/*  T T O P E N  --  Open a tty for exclusive access.  */

/*  Returns 0 on success, -1 on failure.  */

/***
 Do something better about exclusive access later, like the uucp
 lock file hair...
***/

ttopen(ttname) char *ttname; {

    if (ttyfd > -1) ttclos();		/* If old one open, close it. */

    ttyfd = open(ttname,2);		/* Open a tty for read/write */
    if (ttyfd < 0) return(-1);
    lkf = 0;

    if (flock(ttyfd,(LOCK_EX|LOCK_NB)) < 0) {
    	fprintf(stderr,"Warning - Access to %s not exclusive\n",ttname);
    } else lkf = 1;

    gtty(ttyfd,&ttold);			/* Get sgtty info */
    gtty(ttyfd,&ttraw);			/* And a copy of it for packets*/
    gtty(ttyfd,&tttvt);			/* And one for virtual tty service */
    return(0);
}


/*  T T C L O S  --  Close the TTY, releasing any lock.  */

ttclos() {
    if (ttyfd < 0) return(0);		/* Wasn't open. */
    if (lkf) flock(ttyfd,LOCK_UN);	/* Unlock it first. */
    ttres();				/* Reset modes. */
    close(ttyfd);			/* Close it. */
    ttyfd = -1;				/* Mark it as closed. */
    return(0);
}

/*  T T P K T  --  Condition the communication line for packets. */

/*  If called with speed > -1, also set the speed.  */

/*  Returns 0 on success, -1 on failure.  */

ttpkt(speed,flow) int speed, flow; {
    int s;
    if (ttyfd < 0) return(-1);		/* Not open. */

#if KERLD
    if (kerld) {
	ioctl(ttyfd,TIOCGETD,&oldld);	/* Get line discipline */
	ioctl(ttyfd,TIOCGETC,&oldc);	/* Get special chars */
	newc = oldc;			/* Copy special chars */
	newc.t_brkc = '\r';		/* Set CR to be break character */
	if(ioctl(ttyfd,TIOCSETC,&newc) < 0) return(-1);
    }
#endif

/* Note, KERLD ignores the TANDEM, ECHO, and CRMOD bits */

    if (flow == 1) ttraw.sg_flags |= TANDEM; /* XON/XOFF if selected */
    if (flow == 0) ttraw.sg_flags &= ~TANDEM;
    ttraw.sg_flags |= RAW;		/* Go into raw mode */
    ttraw.sg_flags &= ~(ECHO|CRMOD);	/* Use CR for break character */
    
    s = ttsspd(speed);			/* Check the speed */
    if (s > -1) ttraw.sg_ispeed = ttraw.sg_ospeed = s; /* Do the speed */

    if (stty(ttyfd,&ttraw) < 0) return(-1);	/* Set the new modes. */

#if KERLD
    if (kerld) {
	if (ioctl(ttyfd,TIOCSETD,&ld) < 0)
	    return(-1); /* Set line discpline. */
    }
#endif

    ttflui();				/* Flush any pending input */
    return(0);
}

/*  T T V T -- Condition communication line for use as virtual terminal  */

ttvt(speed,flow) int speed, flow; {
    int s;
    if (ttyfd < 0) return(-1);		/* Not open. */

    if (flow == 1) tttvt.sg_flags |= TANDEM; /* XON/XOFF if selected */
    if (flow == 0) tttvt.sg_flags &= ~TANDEM;
    tttvt.sg_flags |= RAW;		/* Raw mode */
    tttvt.sg_flags &= ~ECHO;		/* No echo */
    
    s = ttsspd(speed);			/* Check the speed */
    if (s > -1) tttvt.sg_ispeed = tttvt.sg_ospeed = s; /* Do the speed */

    return(stty(ttyfd,&tttvt));
}


/*  T T S S P D  --  Return the internal baud rate code for 'speed'.  */

ttsspd(speed) {
    int s, spdok;

    if (speed < 0) return(-1);
	spdok = 1;			/* Assume arg ok */
	switch (speed) {
	    case 0:    s = B0;    break;	/* Just the common ones. */
	    case 110:  s = B110;  break;	/* The others from ttydev.h */
	    case 150:  s = B150;  break;	/* could also be included if */
	    case 300:  s = B300;  break;	/* necessary... */
	    case 600:  s = B600;  break;
	    case 1200: s = B1200; break;
	    case 1800: s = B1800; break;
	    case 2400: s = B2400; break;
	    case 4800: s = B4800; break;
	    case 9600: s = B9600; break;
	    default:
	    	spdok = 0;
		fprintf(stderr,"Unsupported line speed - %d\n",speed);
		fprintf(stderr,"Current speed not changed\n");
		break;
	}	    
	if (spdok) return(s); else return(-1);
 }

/*  T T F L U I  --  Flush tty input buffer */

ttflui() {
    long int n;
    if (ttyfd < 0) return(-1);		/* Not open. */
    n = FREAD;				/* Specify read queue. */
    if (ioctl(ttyfd,TIOCFLUSH,&n) < 0) perror("flush failed");
    return(0);
}



/*  T T R E S  --  Restore terminal to "normal" mode.  */

ttres() {				/* Restore the tty to normal. */
    if (ttyfd < 0) return(-1);		/* Not open. */
    sleep(1);				/* Wait for pending i/o to finish. */
#if KERLD
    if (kerld) ioctl(ttyfd,TIOCSETD,&oldld); /* Restore old line discipline. */
#endif
    if (stty(ttyfd,&ttold) < 0) return(-1); /* Restore sgtty stuff */
#if KERLD
    if (kerld) ioctl(ttyfd,TIOCSETC,&oldc); /* Restore old special chars. */
#endif
    return(0);
}

/* Interrupt Functions */


/* Timeout handler for communication line input functions */

timerh() {
    longjmp(sjbuf,1);
}

 
/* Set up terminal interrupts on console terminal */


conint(f) int (*f)(); {			/* Set an interrupt trap. */
    if (conif) return;			/* Nothing to do if already on. */
    signal(SIGINT,f);			/* Function to trap to. */
    conif = 1;
}


/* Reset console terminal interrupts */

connoi() {
    signal(SIGINT,SIG_DFL);
    conif = 0;
}


/*  T T C H K  --  Tell how many characters are waiting in tty input buffer  */

ttchk() {
    int n, x;
    x = ioctl(ttyfd, FIONREAD, &n);
    return((x < 0) ? 0 : n);
}


/*  T T X I N  --  Get n characters from tty input buffer  */

ttxin(n,buf) int n; char *buf; {
    int x;
    x = read(ttyfd,buf,n);
    if (x > 0) buf[x] = '\0';
    return(x);
}

/*  T T O L  --  Similar to "ttinl", but for writing.  */

ttol(s,n) int n; char *s; {
    int x;
    if (ttyfd < 0) return(-1);		/* Not open. */
    x = write(ttyfd,s,n);
    debug(F111,"ttol",s,n);
    if (x < 0) debug(F101,"ttol failed","",x);
    return(x);
}


/*  T T O C  --  Output a character to the communication line  */

ttoc(c) char c; {
    if (ttyfd < 0) return(-1);		/* Not open. */
    return(write(ttyfd,&c,1));
}


/*  T T I N L  --  Read a record (up to break character) from comm line.  */
/*
  If no break character encountered within "max", return "max" characters,
  with disposition of any remaining characters undefined.  Otherwise, return
  the characters that were read, including the break character, in "dest" and
  the number of characters read as the value of function, or 0 upon end of
  file, or -1 if an error occurred.  Times out & returns error if not completed
  within "timo" seconds.
*/

ttinl(dest,max,timo,eol) int max,timo; char *dest; {
    int x, y, z;
    unsigned char c;
    if (ttyfd < 0) return(-1);		/* Not open. */
    if (timo <= 0) {			/* Untimed. */
	x = read(ttyfd,dest,max);	/* Try to read. */
	return(x);			/* Return the count. */
    }
    signal(SIGALRM,timerh);		/* Timed, set up timeout action. */
    alarm(timo);			/* Set the timer. */
    if (setjmp(sjbuf)) return(-1);	/* Do this if timer went off. */
    if (kerld) {
	x = read(ttyfd,dest,max);	/* Try to read. */
    } else {
	for (x = c = 0; (x < max) && (c != eol); x++) {
	     if ((y = read(ttyfd,&c,1)) < 1) return(y);
	     dest[x] = c;
	}
	x++;
    }
    alarm(0);				/* Success, turn off timer, */
    signal(SIGALRM,SIG_DFL);		/* and associated interrupt. */
    return(x);				/* Return the count. */
}

/*  T T I N C --  Read a character from the communication line  */

ttinc(timo) int timo; {
    int n;
    unsigned char ch;		       

    if (ttyfd < 0) return(-1);		/* Not open. */
    if (timo <= 0) {			/* Untimed. */
	n = read(ttyfd,&ch,1);		/* Wait for a character. */
	return( (n > 0) ? (ch & 0377) : n );
    }

    signal(SIGALRM,timerh);		/* Timed, set up timer. */
    alarm(timo);
    if (setjmp(sjbuf)) return(-1);
    n = read(ttyfd,&ch,1);		/* Read a character. */
    alarm(0);				/* Turn off timer, */
    signal(SIGALRM,SIG_DFL);		/* and interrupt. */
    if (n > 0) return(ch & 0377); else return(n); /* Return char or -1. */
}

/*  T T S N D B  --  Send a BREAK signal  */

ttsndb() {
    int x;

    if (ttyfd < 0) return(-1);		/* Not open. */
    if (ioctl(ttyfd,TIOCSBRK,(char *)0) < 0) {	/* Turn on BREAK */
    	conol("Can't send BREAK");
	return(-1);
    }
    x = msleep(275);			/* Sleep for so many milliseconds */
    if (ioctl(ttyfd,TIOCCBRK,(char *)0) < 0) {	/* Turn off BREAK */
	conol("BREAK stuck!!!");
	doexit();			/* Get out, closing the line. */
    }
    return(x);
}


/*  M S L E E P  --  Millisecond version of sleep().  */

/*
 Intended only for small intervals.  For big ones, just use sleep().
 This is a very expensive way to do interval timing.  It just loops,
 looking at the system clock.  If anyone can figure out a better way,
 e.g. with setitimer(), then do it that way.
*/

msleep(m) int m; {
    int t1, t3, t4;

    if (gettimeofday(&tv, &tz) < 0) return(-1); /* Get current time. */
    t1 = tv.tv_sec;			/* Seconds */

    while (1) {
	gettimeofday(&tv, &tz);
	t3 = tv.tv_sec - t1;
	t4 = (tv.tv_usec + 1000000 * t3) / 1000;
	if (t4 > m) return(t4);
    }
}


/*  Z T I M E  --  Return date/time string  */

ztime(s) char **s; {
    char *asctime();
    struct tm *localtime();
    struct tm *tp;

    gettimeofday(&tv, &tz);
    time(&tv.tv_sec);
    tp = localtime(&tv.tv_sec);
    *s = asctime(tp);
}

/*  C O N G M  --  Get console terminal modes.  */

/*
 Saves current console mode, and establishes variables for switching between 
 current (presumably normal) mode and other modes.
*/

congm() {
     gtty(0,&ccold);			/* Structure for restoring */
     gtty(0,&cccbrk);			/* For setting CBREAK mode */
     gtty(0,&ccraw);			/* For setting RAW mode */
     cgmf = 1;				/* Flag that we got them. */
}


/*  C O N C B --  Put console in cbreak mode.  */

/*  Returns 0 if ok, -1 if not  */

concb() {
    if (cgmf == 0) congm();		/* Get modes if necessary. */
    cccbrk.sg_flags |= CBREAK;		/* Set to character wakeup, */
    cccbrk.sg_flags &= ~ECHO;		/* no echo. */
    ckxech = 1;				/* Program can echo characters */
    return(stty(0,&cccbrk));
}


/*  C O N B I N  --  Put console in binary mode  */

/*  Returns 0 if ok, -1 if not  */

conbin() {
    if (cgmf == 0) congm();		/* Get modes if necessary. */
    ccraw.sg_flags |= (RAW|TANDEM);   	/* Set rawmode, XON/XOFF */
    ccraw.sg_flags &= ~(ECHO|CRMOD);  	/* Set char wakeup, no echo */
    ckxech = 1;				/* Program can echo characters */
    return(stty(0,&ccraw));
}


/*  C O N R E S  --  Restore the console terminal  */

conres() {
    if (cgmf == 0) return(0);		/* Don't do anything if modes */
    sleep(1);				/*  not known! */
    ckxech = 0;				/* System should echo chars */
    return(stty(0,&ccold));		/* Restore controlling tty */
}

/*  C O N O C  --  Output a character to the console terminal  */

conoc(c) char c; {
    write(1,&c,1);
}

/*  C O N X O  --  Write x characters to the console terminal  */

conxo(x,s) char *s; int x; {
    write(1,s,x);
}

/*  C O N O L  --  Write a line to the console terminal  */

conol(s) char *s; {
    int len;
    len = strlen(s);
    write(1,s,len);
}

/*  C O N O L L  --  Output a string followed by CRLF  */

conoll(s) char *s; {
    conol(s);
    write(1,"\r\n",2);
}


/*  C O N C H K  --  Check if characters available at console  */

conchk() {
    int n, x;
    x = ioctl(0, FIONREAD, &n);
    return((x < 0) ? 0 : n);
}


/*  C O N I N C  --  Get a character from the console  */

coninc() {
    int n; char ch;
    n = read(0, &ch, 1);		/* Read a character. */
    ch &= 0377;
    if (n > 0) return(ch); else return(-1);  /* Return the char, or -1. */
}
@//E*O*F ckxbsd.c//
chmod u=rw,g=r,o=r ckxbsd.c
 
echo x - ckzbsd.c
sed 's/^@//' > "ckzbsd.c" <<'@//E*O*F ckzbsd.c//'
char *ckzv = "4.2BSD file support, 4.0(011) 30 Jan 85";

/* C K Z B S D  --  Kermit file system support for 4.2BSD */

/* Definitions of some Unix system commands */

char *DIRCMD = "ls -l ";		/* For directory listing */
char *DELCMD = "rm -f ";		/* For file deletion */
char *TYPCMD = "cat ";			/* For typing a file */
char *SPACMD = "pwd ; quota ; df .";	/* Space/quota of current directory */
char *SPACM2 = "df ";			/* For space in specified directory */
char *WHOCMD = "finger ";		/* For seeing who's logged in */
/*
  Functions (n is one of the predefined file numbers from ckermi.h):

   zopeni(n,name)   -- Opens an existing file for input.
   zopeno(n,name)   -- Opens a new file for output.
   zclose(n)        -- Closes a file.
   zchin(n)         -- Gets the next character from an input file.
   zsout(n,s)       -- Write a null-terminated string to output file, buffered.
   zsoutl(n,s)      -- Like zsout, but appends a line terminator.
   zsoutx(n,s,x)    -- Write x characters to output file, unbuffered.
   zchout(n,c)      -- Add a character to an output file, unbuffered.
   zchki(name)      -- Check if named file exists and is readable, return size.
   zchko(name)      -- Check if named file can be created.
   znewn(name,s)    -- Make a new unique file name based on the given name.
   zdelet(name)     -- Delete the named file.
   zxpand(string)   -- Expands the given wildcard string into a list of files.
   znext(string)    -- Returns the next file from the list in "string".
   zxcmd(cmd)       -- Execute the command in a lower fork.
   zclosf()         -- Close input file associated with zxcmd()'s lower fork.
   zrtol(n1,n2)     -- Convert remote filename into local form.
   zltor(n1,n2)     -- Convert local filename into remote form.
   zchdir(dirnam)   -- Change working directory.
   zhome()          -- Return pointer to home directory name string.
 */
#include <stdio.h>			/* Standard Unix i/o */
#include <ctype.h>			/* Character types */
#include <sys/types.h>			/* Data types */
#include <sys/dir.h>			/* Directory structure */
#include <sys/stat.h>			/* File status */
#include <sys/file.h>			/* File access */
#include <sys/time.h>
#include "ckermi.h"			/* Kermit definitions */

FILE *fp[ZNFILS] = { 			/* File pointers */
    NULL, NULL, NULL, NULL, NULL, NULL, NULL };

#define MAXWLD 500			/* Maximum wildcard filenames */

static int pid;	    			/* pid of child fork */
static int fcount;			/* Number of files in wild group */
char *getenv(), *strcpy();		/* For finding home directory */
extern errno;				/* System error code */

static char *mtchs[MAXWLD],		/* Matches found for filename */
     **mtchptr;				/* Pointer to current match */

/*  Z O P E N I  --  Open an existing file for input. */

zopeni(n,name) int n; char *name; {
    debug(F111," zopeni",name,n);
    debug(F101,"  fp","",(int) fp[n]);
    if (chkfn(n) != 0) return(0);
    if (n == ZSTDIO) {			/* Standard input? */
	if (isatty(0)) {
	    fprintf(stderr,"?Terminal input not allowed\n");
	    debug(F110,"zopeni: attempts input from unredirected stdin","",0);
	    return(0);
	}
	fp[ZIFILE] = stdin;
	return(1);
    }
    fp[n] = fopen(name,"r");		/* Real file. */
    debug(F111," zopeni", name, (int) fp[n]);
    if (fp[n] == NULL) perror("zopeni");
    return((fp[n] != NULL) ? 1 : 0);
}

/*  Z O P E N O  --  Open a new file for output.  */

zopeno(n,name) int n; char *name; {
    debug(F111," zopeno",name,n);
    if (chkfn(n) != 0) return(0);
    if ((n == ZCTERM) || (n == ZSTDIO)) {   /* Terminal or standard output */
	fp[ZOFILE] = stdout;
	debug(F101," fp[]=stdout", "", (int) fp[n]);
	return(1);
    }
    fp[n] = fopen(name,"w");		/* A real file */
    if (fp[n] == NULL) perror("zopeno");
    if (n == ZDFILE) setbuf(fp[n],NULL); /* Make debugging file unbuffered */
    debug(F101, " fp[n]", "", (int) fp[n]);
    return((fp[n] != NULL) ? 1 : 0);
}

/*  Z C L O S E  --  Close the given file.  */

zclose(n) int n; {
    if (chkfn(n) < 1) return(0);
    if ((fp[n] != stdout) && (fp[n] != stdin)) fclose(fp[n]);
    fp[n] = NULL;
    return(1);
}

/*  Z C H I N  --  Get a character from the input file.  */

zchin(n) int n; {
    int a;
    if (chkfn(n) < 1) return(-1);
    a = getc(fp[n]);
    return((a == EOF) ? -1 : a & 0377);
}

/*  Z S O U T  --  Write a string to the given file, buffered.  */

zsout(n,s) int n; char *s; {
    if (chkfn(n) < 1) return(-1);
    fprintf(fp[n],s);
    return(0);
}

/*  Z S O U T L  --  Write string to file, with line terminator, buffered  */

zsoutl(n,s) int n; char *s; {
    if (chkfn(n) < 1) return(-1);
    fprintf(fp[n],"%s\n",s);
    return(0);
}

/*  Z S O U T X  --  Write x characters to file, unbuffered.  */

zsoutx(n,s,x) int n, x; char *s; {
    if (chkfn(n) < 1) return(-1);
    return(write(fp[n]->_file,s,x));
}


/*  Z C H O U T  --  Add a character to the given file.  */

zchout(n,c) int n; char c; {
    if (chkfn(n) < 1) return(-1);
    if (n == ZSFILE)
    	return(write(fp[n]->_file,&c,1)); /* Use unbuffered for session log */
    else {
    	putc(c,fp[n]);			/* Buffered for everything else */
	return(0);
    }
}

/*  C H K F N  --  Internal function to verify file number is ok  */

/*
 Returns:
  -1: File number n is out of range
   0: n is in range, but file is not open
   1: n in range and file is open
*/
chkfn(n) int n; {
    switch (n) {
	case ZCTERM:
	case ZSTDIO:
	case ZIFILE:
	case ZOFILE:
	case ZDFILE:
	case ZTFILE:
	case ZPFILE:
	case ZSFILE: break;
	default:
	    debug(F101,"chkfn: file number out of range","",n);
	    fprintf(stderr,"?File number out of range - %d\n",n);
	    return(-1);
    }
    return( (fp[n] == NULL) ? 0 : 1 );
}

/*  Z C H K I  --  Check if input file exists and is readable  */

/*
  Returns:
   >= 0 if the file can be read (returns the size).
     -1 if file doesn't exist or can't be accessed,
     -2 if file exists but is not readable (e.g. a directory file).
     -3 if file exists but protected against read access.
*/
/*
 For Berkeley Unix, a file must be of type "regular" to be readable.
 Directory files, special files, and symbolic links are not readable.
*/
zchki(name) char *name; {
    struct stat buf;
    int x;

    x = stat(name,&buf);
    if (x < 0) {
	debug(F111,"zchki stat fails",name,errno);
	return(-1);
    }
    x = buf.st_mode & S_IFMT;		/* Isolate file format field */
    if (x != S_IFREG) {
	debug(F111,"zchki skipping:",name,x);
	return(-2);
    }
    debug(F111,"zchki stat ok:",name,x);

    if ((x = access(name,R_OK)) < 0) { 	/* Is the file accessible? */
	debug(F111," access failed:",name,x); /* No */
    	return(-3);			
    } else {
	x = buf.st_size;
	debug(F111," access ok:",name,x); /* Yes */
	return( (x > -1) ? x : 0 );
    }
}

/*  Z C H K O  --  Check if output file can be created  */

/*
 Returns -1 if write permission for the file would be denied, 0 otherwise.
*/
zchko(name) char *name; {
    int i, x;
    char s[50], *sp;	

    sp = s;				/* Make a copy, get length */
    x = 0;
    while ((*sp++ = *name++) != '\0')
    	x++;
    if (x == 0) return(-1);		/* If no filename, fail. */

    debug(F101," length","",x);
    for (i = x; i > 0; i--)		/* Strip filename. */
	if (s[i-1] == '/') break;
    
    debug(F101," i","",i);
    if (i == 0)				/* If no path, use current directory */
    	strcpy(s,"./");			
    else				/* Otherwise, use given one. */
        s[i] = '\0';

    x = access(s,W_OK);			/* Check access of path. */
    if (x < 0) {
	debug(F111,"zchko access failed:",s,errno);
	return(-1);
    } else {
	debug(F111,"zchko access ok:",s,x);
	return(0);
    }
}

/*  Z D E L E T  --  Delete the named file.  */

zdelet(name) char *name; {
    unlink(name);
}


/*  Z R T O L  --  Convert remote filename into local form  */

/*  For UNIX, this means changing uppercase letters to lowercase.  */

zrtol(name,name2) char *name, *name2; {
    for ( ; *name != '\0'; name++) {
    	*name2++ = isupper(*name) ? tolower(*name) : *name;
    }
    *name2 = '\0';
}


/*  Z L T O R  --  Convert filename from local format to common form.   */

zltor(name,name2) char *name, *name2; {
    char work[100], *cp, *pp;
    int dc = 0;

    strcpy(work,name);
    for (cp = pp = work; *cp != '\0'; cp++) {	/* strip path name */
    	if (*cp == '/') {
	    pp = cp;
	    pp++;
	}
	else if (islower(*cp)) *cp = toupper(*cp); /* Uppercase letters */
	else if (*cp == '~') *cp = 'X';	/* Change tilde to 'X' */
	else if ((*cp == '.') && (++dc > 1)) *cp = 'X'; /* & extra dots */
    }
    cp = name2;				/* If nothing before dot, */
    if (*pp == '.') *cp++ = 'X';	/* insert 'X' */
    strcpy(cp,pp);
}    


/*  Z C H D I R  --  Change directory  */

zchdir(dirnam) char *dirnam; {
    char *hd;
    if (*dirnam == '\0') hd = getenv("HOME");
    else hd = dirnam;
    return((chdir(hd) == 0) ? 1 : 0);
}


/*  Z H O M E  --  Return pointer to user's home directory  */

char *
zhome() {
    char *getenv();
    return(getenv("HOME"));
}

/*  Z X C M D -- Run a system command so its output can be read like a file */

zxcmd(comand) char *comand; {
    int pipes[2];
    if (pipe(pipes) != 0) return(0);	/* can't make pipe, fail */
    if ((pid = fork()) == 0) {		/* child */
	close(pipes[0]);		/* close input side of pipe */
	close(0);			/* close stdin */
	if (open("/dev/null",0) < 0) return(0);	/* replace input by null */
	dup2(pipes[1],1);		/* replace stdout & stderr */
	dup2(pipes[1],2);		/* by the pipe */
	close(pipes[1]);		/* get rid of this copy of the pipe */
	execl("/bin/sh","sh","-c",comand,0); /* use shell to do it */
	exit(0); }			/* just punt if it didn't work */
    close(pipes[1]);			/* don't need the output side */
    fp[ZIFILE] = fdopen(pipes[0],"r");	/* open a stream for it */
    return(1);
}


/*  Z C L O S F  - wait for the child fork to terminate and close the pipe. */

zclosf() {
    int wstat;
    fclose(fp[ZIFILE]);
    fp[ZIFILE] = NULL;
    while ((wstat = wait(0)) != pid && wstat != -1) ;
}

/*  Z X P A N D  --  Expand a wildcard string into an array of strings  */
/*
  Returns the number of files that match fn1, with data structures set up
  so that first file (if any) will be returned by the next znext() call.
*/
zxpand(fn) char *fn; {
    fcount = fgen(fn,mtchs,MAXWLD);	/* Look up the file. */
    if (fcount > 0) {
	mtchptr = mtchs;		/* Save pointer for next. */
    }
    debug(F111,"zxpand",mtchs[0],fcount);
    return(fcount);
}


/*  Z N E X T  --  Get name of next file from list created by zxpand(). */
/*
 Returns >0 if there's another file, with its name copied into the arg string,
 or 0 if no more files in list.
*/
znext(fn) char *fn; {
    if (fcount-- > 0) strcpy(fn,*mtchptr++);
    else *fn = '\0';
    debug(F111,"znext",fn,fcount+1);
    return(fcount+1);
}


/*  Z N E W N  --  Make a new name for the given file  */

znewn(fn,s) char *fn, **s; {
    static char buf[100];
    char *bp, *xp;
    int len = 0, n = 0, d = 0, t;

    bp = buf;
    while (*fn) {
	*bp++ = *fn++;
	len++;
    }
    *bp++ = '*';			/* Put a star on the end */
    *bp-- = '\0';

    n = zxpand(buf);			/* Expand the resulting wild name */
    
    while (n-- > 0) {			/* Find any existing name~d files */
	xp = *mtchptr++;
	xp += len;
	if (*xp == '~') {
	    t = atoi(xp+1);
	    if (t > d) d = t;		/* Get maximum d */
	}
    }
    sprintf(bp,"~%d",d+1);		/* Make and return name~(d+1) */
    *s = buf;
}

/* Directory Functions for 4.2BSD, written by Jeff Damens, CUCCA, 1984. */


/*
 * The path structure is used to represent the name to match.
 * Each slash-separated segment of the name is kept in one
 * such structure, and they are linked together, to make
 * traversing the name easier.
 */

struct path {
              char npart[MAXNAMLEN];	/* name part of path segment */
              struct path *fwd;		/* forward ptr */
            };

#define SSPACE 2000			/* size of string-generating buffer */
static char sspace[SSPACE];             /* buffer to generate names in */
static char *freeptr,**resptr;         	/* copies of caller's arguments */
static int remlen;                      /* remaining length in caller's array*/
static int numfnd;                      /* number of matches found */

/*
 * splitpath:
 *  takes a string and splits the slash-separated portions into
 *  a list of path structures.  Returns the head of the list.  The
 *  structures are allocated by malloc, so they must be freed.
 *  Splitpath is used internally by the filename generator.
 *
 * Input: A string.
 * Returns: A linked list of the slash-separated segments of the input.
 */

struct path *
splitpath(p)
char *p;
{
 struct path *head,*cur,*prv;
 int i;
 head = prv = NULL;
 if (*p == '/') p++;            /* skip leading slash */
 while (*p != '\0')
 {
   cur = (struct path *) malloc(sizeof (struct path));
   cur -> fwd = NULL;
   if (head == NULL) head = cur;
   else prv -> fwd = cur;       /* link into chain */
   prv = cur;
   for (i=0; i < MAXNAMLEN && *p != '/' && *p != '\0'; i++)
     cur -> npart[i] = *p++;
   cur -> npart[i] = '\0';      /* end this segment */
   if (i >= MAXNAMLEN) while (*p != '/' && *p != '\0') p++;
   if (*p == '/') p++;
 }
 return(head);
}

/*
 * fgen:
 *  This is the actual name generator.  It is passed a string,
 *  possibly containing wildcards, and an array of character pointers.
 *  It finds all the matching filenames and stores them into the array.
 *  The returned strings are allocated from a static buffer local to
 *  this module (so the caller doesn't have to worry about deallocating
 *  them); this means that successive calls to fgen will wipe out
 *  the results of previous calls.  This isn't a problem here
 *  because we process one wildcard string at a time.
 *
 * Input: a wildcard string, an array to write names to, the
 *        length of the array.
 * Returns: the number of matches.  The array is filled with filenames
 *          that matched the pattern.  If there wasn't enough room in the
 *	    array, -1 is returned.
 * By: Jeff Damens, CUCCA, 1984.
 */

fgen(pat,resarry,len)
char *pat,*resarry[];
int len;
{
 struct path *head;
 char scratch[100],*sptr;
 head = splitpath(pat);
 if (*pat == '/')
 {
  scratch[0] = '/';
  sptr = scratch+1;
 }
 else
 {
  strcpy(scratch,"./");
  sptr = scratch+2;
 }					/* init buffer correctly */
 numfnd = 0;                            /* none found yet */
 freeptr = sspace;			/* this is where matches are copied */
 resptr = resarry;			/* static copies of these so*/
 remlen = len;				/* recursive calls can alter them */
 traverse(head,scratch,sptr);		/* go walk the directory tree */
 for (; head != NULL; head = head -> fwd)
   free(head);				/* return the path segments */
 return(numfnd);			/* and return the number of matches */
}

/*
 * traverse:
 *  Walks the directory tree looking for matches to its arguments.
 *  The algorithm is, briefly:
 *   If the current pattern segment contains no wildcards, that
 *   segment is added to what we already have.  If the name so far
 *   exists, we call ourselves recursively with the next segment
 *   in the pattern string; otherwise, we just return.
 *
 *   If the current pattern segment contains wildcards, we
 *   open the name we've accumulated so far (assuming it is
 *   really a directory), then read each filename in it, and, if
 *   it matches the wildcard pattern segment, add that filename
 *   to what we have so far and call ourselves recursively on the
 *   next segment.
 *
 *   Finally, when no more pattern segments remain, we add what
 *   we've accumulated so far to the result array and increment
 *   the number of matches.
 *
 * Input: a pattern path list (as generated by splitpath), a string
 *	  pointer that points to what we've traversed so far (this
 *	  can be initialized to "/" to start the search at the root
 *	  directory, or to "./" to start the search at the current
 *	  directory), and a string pointer to the end of the string
 *	  in the previous argument.
 * Returns: nothing.
 */

traverse(pl,sofar,endcur)
struct path *pl;
char *sofar,*endcur;
{
 DIR *fd;
 struct direct *dirbuf;
 struct stat statbuf;
 if (pl == NULL)
 {
  *--endcur = '\0';                    /* end string, overwrite trailing / */
  addresult(sofar);
  return;
 }
 if (!iswild(pl -> npart))
 {
  strcpy(endcur,pl -> npart);
  endcur += strlen(pl -> npart);
  *endcur = '\0';                     	/* end current string */
  if (stat(sofar,&statbuf) == 0)	/* if current piece exists */
  {
      *endcur++ = '/';                  /* add slash to end */
      *endcur = '\0';			/* and end the string */
      traverse(pl -> fwd,sofar,endcur);
  }
  return;
 }
/* cont'd... */

/*...traverse, cont'd */

/* segment contains wildcards, have to search directory */
 *endcur = '\0';                        	/* end current string */
 if (stat(sofar,&statbuf) == -1) return;   	/* doesn't exist, forget it */
 if ((statbuf.st_mode & S_IFDIR) == 0) return;  /* not a directory, skip */
 if ((fd = opendir(sofar)) == NULL) return;  	/* can't open, forget it */
 while (dirbuf = readdir(fd))
  if (dirbuf->d_ino != 0 && match(pl -> npart,dirbuf->d_name)) {
    char *eos;
    strcpy(endcur,dirbuf->d_name);
    eos = endcur + strlen(dirbuf->d_name);
    *eos = '/';                    /* end this segment */
    traverse(pl -> fwd,sofar,eos+1);
  }
 closedir(fd);
}

/*
 * addresult:
 *  Adds a result string to the result array.  Increments the number
 *  of matches found, copies the found string into our string
 *  buffer, and puts a pointer to the buffer into the caller's result
 *  array.  Our free buffer pointer is updated.  If there is no
 *  more room in the caller's array, the number of matches is set to -1.
 * Input: a result string.
 * Returns: nothing.
 */

addresult(str)
char *str;
{
 int l;
 if (strncmp(str,"./",2) == 0) str += 2;
 if (--remlen < 0) {
  numfnd = -1;
  return;
 }
 l = strlen(str) + 1;			/* size this will take up */
 if ((freeptr + l) > &sspace[SSPACE]) {
    numfnd = -1;			/* do not record if not enough space */
    return;
  }
 strcpy(freeptr,str);
 *resptr++ = freeptr;
 freeptr += l;
 numfnd++;
}

iswild(str)
char *str;
{
 char c;
 while ((c = *str++) != '\0')
   if (c == '*' || c == '?') return(1);
 return(0);
}

/*
 * match:
 *  pattern matcher.  Takes a string and a pattern possibly containing
 *  the wildcard characters '*' and '?'.  Returns true if the pattern
 *  matches the string, false otherwise.
 * by: Jeff Damens, CUCCA
 *
 * Input: a string and a wildcard pattern.
 * Returns: 1 if match, 0 if no match.
 */

match(pattern,string) char *pattern,*string; {
    char *psave,*ssave;			/* back up pointers for failure */
    psave = ssave = NULL;
    while (1) {
	for (; *pattern == *string; pattern++,string++)  /* skip first */
	    if (*string == '\0') return(1);	/* end of strings, succeed */
	if (*string != '\0' && *pattern == '?') {
	    pattern++;			/* '?', let it match */
	    string++;
	} else if (*pattern == '*') {	/* '*' ... */
	    psave = ++pattern;		/* remember where we saw it */
	    ssave = string;		/* let it match 0 chars */
	} else if (ssave != NULL && *ssave != '\0') {	/* if not at end  */
  					/* ...have seen a star */
	    string = ++ssave;		/* skip 1 char from string */
	    pattern = psave;		/* and back up pattern */
	} else return(0);		/* otherwise just fail */
    }
}
@//E*O*F ckzbsd.c//
chmod u=rw,g=r,o=r ckzbsd.c
 
echo Inspecting for damage in transit...
temp=/tmp/shar$$; dtemp=/tmp/.shar$$
trap "rm -f $temp $dtemp; exit" 0 1 2 3 15
cat > $temp <<\!!!
     616    1914   12117 ckwart.c
     101     685    4018 ckwart.doc
      52     167    1648 ckwho.txt
      18      88     504 ckxbsd.bwr
     536    2494   15108 ckxbsd.c
     642    3225   19296 ckzbsd.c
    1965    8573   52691 total
!!!
wc  ckwart.c ckwart.doc ckwho.txt ckxbsd.bwr ckxbsd.c ckzbsd.c | sed 's=[^ ]*/==' | diff -b $temp - >$dtemp
if [ -s $dtemp ]
then echo "Ouch [diff of wc output]:" ; cat $dtemp
else echo "No problems found."
fi
exit 0