char *protv =                                                     /* -*-C-*- */
"C-Kermit Protocol Module 11.0.500, 22 Jul 2026";

int kactive = 0;			/* Kermit protocol is active */
/*
  This file, ckcpro.w, is the source file for the Kermit protocol state table
  switcher: ckcpro.c, which is created by running ckcpro.w through the "wart"
  program via "make ckcpro.c", which converts the part enclosed in double
  percent signs from Lex format to the C module called ckcpro.c.

  In November 2022 this file was retired on the unwise assumption that the
  Kermit protocol would never change again.

  Starting with edit 10.0.168 of 29 January 2024, ckcpro.w is reinstated
  because it proved impossible to make protocol changes or additions by
  editing ckcpro.c directly when a few simple client/server fuctions were
  needed: REMOTE CDUP and REMOTE STATUS.  The makefile has not changed
  however, except for putting back "make wart" and "make ckcpro.c", so
  ckcpro.w is not automatically converted by the 'wart' program as it was
  before.

  In short, if you want to change the protocol module (this file), you have to
  build it by hand with:

    make wart
    make ckcpro.c
*/
#define PKTZEROHACK

/* C K C P R O  -- C-Kermit Protocol Module, in Wart preprocessor notation. */
/*
  Author: Frank da Cruz <fdc@columbia.edu>,
  Columbia University Academic Information Systems, New York City.

  Copyright (C) 1985, 2024
    Trustees of Columbia University in the City of New York.
    All rights reserved.  See the C-Kermit COPYING.TXT file or the
    copyright text in the ckcmai.c module for disclaimer and permissions.
*/
#ifndef NOXFER
#include "ckcsym.h"
#include "ckcdeb.h"
#include "ckcasc.h"
#include "ckcker.h"
#ifdef OS2
#ifndef NT
#define INCL_NOPM
#define INCL_VIO			/* Needed for ckocon.h */
#include <os2.h>
#undef COMMENT
#endif /* NT */
#include "ckocon.h"
#endif /* OS2 */

#ifdef CK_AUTHENTICATION
#include "ckuath.h"                     /* fdc 2021-12-17 */
#endif /* CK_AUTHENTICATION */
#include "ckcfnp.h"                     /* Prototypes (must be last) */

/*
 Note -- This file may also be preprocessed by the UNIX Lex program, but
 you must indent the above #include statements before using Lex, and then
 restore them to the left margin in the resulting C program before compilation.
 Also, the invocation of the "wart()" function below must be replaced by an
 invocation  of the "yylex()" function.  It might also be necessary to remove
 comments in the (%)(%)...(%)(%) section.
*/

/* State definitions for Wart (or Lex) */
%states ipkt rfile rattr rdpkt ssinit ssfile ssattr ssdata sseof sseot
%states serve generic get rgen ssopkt ropkt

_PROTOTYP(static VOID xxproto,(void));
_PROTOTYP(static VOID wheremsg,(void));
_PROTOTYP(int wart,(void));
_PROTOTYP(static int sgetinit,(int,int));
_PROTOTYP(int sndspace,(int));

/* External C-Kermit variable declarations */
  extern char *versio, *srvtxt, *cmarg, *cmarg2, **cmlist, *rf_err;
  extern char * rfspec, * sfspec, * srfspec, * rrfspec;
  extern char * prfspec, * psfspec, * psrfspec, * prrfspec;
  extern char *cdmsgfile[];
  extern char * snd_move, * snd_rename, * srimsg;
  extern char filnam[], ofilnam[], fspec[], ttname[], ofn1[];
  extern CHAR sstate, *srvptr, *data;
  extern int timint, rtimo, nfils, hcflg, xflg, flow, mdmtyp, network;
  extern int oopts, omode, oname, opath, nopush, isguest, xcmdsrc, rcdactive;
  extern int rejection, moving, fncact, bye_active, urserver, fatalio;
  extern int protocol, prefixing, carrier, fnspath, interrupted;
  extern long filcnt;
  extern int recursive, inserver, nzxopts, idletmo, srvidl, xfrint;
  extern struct ck_p ptab[];
  extern int remfile, rempipe, xferstat, filestatus, wearealike, fackpath;
  extern int patterns, filepeek, gnferror;
  extern char * remdest;

#ifdef PKTZEROHACK
#define PKTZEROLEN 32
static char ipktack[PKTZEROLEN];
static int ipktlen = 0;
#endif /* PKTZEROHACK */

static int s_timint = -1;		/* For saving timeout value */
static int myjob = 0;
static int havefs = 0;
#ifdef CK_LOGIN
static int logtries = 0;
#endif /* CK_LOGIN */

static int cancel = 0;
int fackbug = 0;

#ifdef STREAMING
extern int streaming, streamok;

static VOID
streamon() {
    if (streamok) {
	debug(F100,"streamon","",0);
	streaming = 1;
	timint = 0;			/* No timeouts while streaming. */
    }
}

#else /* STREAMING */
#define streamon()
#define streamoff()
#endif /* STREAMING */

#ifndef NOSPL
_PROTOTYP( int addmac, (char *, char *) );
_PROTOTYP( int zzstring, (char *, char **, int *) );
#endif /* NOSPL */
#ifndef NOICP
_PROTOTYP( int cmdsrc, (void) );
#endif /* NOICP */

#ifndef NOSERVER
  extern char * x_user, * x_passwd, * x_acct;
  extern int x_login, x_logged;
#endif /* NOSERVER */

#include "ckcnet.h"

#ifdef TNCODE
  extern int ttnproto;			/* Network protocol */
#endif /* TNCODE */

#ifdef CK_SPEED
  extern short ctlp[];			/* Control-character prefix table */
#endif /* CK_SPEED */

#ifdef TNCODE
  extern int tn_b_nlm, tn_b_xfer, tn_nlm;
#ifdef CK_ENCRYPTION
  extern int tn_no_encrypt_xfer;
#endif /* CK_ENCRYPTION */
#endif /* TNCODE */

#ifdef TCPSOCKET
#ifndef NOLISTEN
  extern int tcpsrfd;
#endif /* NOLISTEN */
#endif /* TCPSOCKET */

  extern int cxseen, czseen, server, srvdis, local, displa, bctu, bctr, bctl;
  extern int bctf;
  extern int quiet, tsecs, parity, backgrd, nakstate, atcapu, wslotn, winlo;
  extern int wslots, success, xitsta, rprintf, discard, cdtimo, keep, fdispla;
  extern int timef, stdinf, rscapu, sendmode, epktflg, epktrcvd, epktsent;
  extern int binary, fncnv, dest;
  extern long speed, crc16;
  extern CK_OFF_T calibrate, ffc;
#ifndef OS2
  extern char *SPACMD, *SPACM2, *WHOCMD;
#endif /* OS2 */
  extern CHAR *rdatap;
  extern struct zattr iattr;

#ifdef VMS
  extern int batch;
#endif /* VMS */

#ifdef GFTIMER
  extern CKFLOAT fptsecs;
#endif /* GFTIMER */

  extern CHAR *srvcmd;
  extern CHAR *epktmsg;

#ifdef CK_TMPDIR
extern int f_tmpdir;			/* Directory changed temporarily */
extern char savdir[];			/* For saving current directory */
extern char * dldir;
#endif /* CK_TMPDIR */

  extern int query;			/* Query-active flag */
#ifndef NOSPL
  extern int cmdlvl;
  char querybuf[QBUFL+1] = { NUL, NUL }; /* QUERY response buffer */
  char *qbufp = querybuf;		/* Pointer to it */
  int qbufn = 0;			/* Length of data in it */
#else
  extern int tlevel;
#endif /* NOSPL */

#ifndef NOICP
  extern int escape;
#endif /* NOICP */
/*
  If the following flag is nonzero when the protocol module is entered,
  then server mode persists for exactly one transaction, rather than
  looping until BYE or FINISH is received.
*/
extern int justone;

static int r_save = -1;
static int p_save = -1;

/* Function to let remote-mode user know where their file(s) went */

int whereflg = 1;			/* Unset with SET XFER REPORT */

static VOID
wheremsg() {
    extern int quiet;
    extern long filrej;
    long n;
    n = filcnt - filrej;
    debug(F101,"wheremsg n","",n);

    debug(F110,"wheremsg prfspec",prfspec,0);
    debug(F110,"wheremsg rfspec",rfspec,0);
    debug(F110,"wheremsg psfspec",psfspec,0);
    debug(F110,"wheremsg sfspec",sfspec,0);

    debug(F110,"wheremsg prrfspec",prrfspec,0);
    debug(F110,"wheremsg rrfspec",rrfspec,0);
    debug(F110,"wheremsg psrfspec",psrfspec,0);
    debug(F110,"wheremsg srfspec",srfspec,0);

    if (!quiet && !local) {
	if (n == 1) {
	    switch (myjob) {
	      case 's':
		if (sfspec) {
		    printf(" SENT: [%s]",sfspec);
		    if (srfspec)
		      printf(" To: [%s]",srfspec);
		    printf(" (%s)\r\n", success ? "OK" : "FAILED");
		}
		break;
	      case 'r':
	      case 'v':
		if (rrfspec) {
		    printf(" RCVD: [%s]",rrfspec);
		    if (rfspec)
		      printf(" To: [%s]",rfspec);
		    printf(" (%s)\r\n", success ? "OK" : "FAILED");
		}
	    }
	} else if (n > 1) {
	    switch (myjob) {
	      case 's':
		if (sfspec) {
		    printf(" SENT: (%ld files)",n);
		    if (srfspec)
		      printf(" Last: [%s]",srfspec);
		    printf(" (%s)\r\n", success ? "OK" : "FAILED");
		}
		break;
	      case 'r':
	      case 'v':
		if (rrfspec) {
		    printf(" RCVD: (%ld files)",n);
		    if (rfspec)
		      printf(" Last: [%s]",rfspec);
		    printf(" (%s)\r\n", success ? "OK" : "FAILED");
		}
	    }
	} else if (n == 0) {
	    if (myjob == 's')
	      printf(" SENT: (0 files)          \r\n");
	    else if (myjob == 'r' || myjob == 'v')
	      printf(" RCVD: (0 files)          \r\n");
	}
    }
}

static VOID
rdebug() {
    if (server)
      debug(F111,"RESUME","server=1",justone);
    else
      debug(F111,"RESUME","server=0",justone);
}

/* Flags for the ENABLE and DISABLE commands */
extern int
  en_cpy, en_cwd, en_del, en_dir, en_fin, en_get, en_bye, en_mai, en_pri,
  en_hos, en_ren, en_sen, en_spa, en_set, en_typ, en_who, en_ret, en_xit,
  en_mkd, en_rmd;
#ifndef NOSPL
extern int en_asg, en_que;
#endif /* NOSPL */
extern int what, lastxfer;
extern int protostartup;		/* SET PROTOCOL STARTUP-STRING */

/* Global variables declared here */

  int whatru = 0;			/* What are you. */
  int whatru2 = 0;			/* What are you, cont'd. */

/* Local variables */

  static char vstate = 0;  		/* Saved State   */
  static char vcmd = 0;    		/* Saved Command */
  static int reget = 0;			/* Flag for executing REGET */
  static int retrieve = 0;		/* Flag for executing RETRIEVE */
  static int opkt = 0;			/* Send Extended GET packet */

  static int x;				/* General-purpose integer */
  static char *s;			/* General-purpose string pointer */

/* Macros - Note, BEGIN is predefined by Wart (and Lex) as "state = ", */
/* BEGIN is NOT a GOTO! */
#define TINIT if (tinit(1) < 0) return(-9)
#define SERVE { TINIT; resetc(); nakstate=1; what=W_NOTHING; cmarg2=""; \
sendmode=SM_SEND; havefs=0; recursive=r_save; fnspath=p_save; BEGIN serve; }
#define RESUME { rdebug(); if (!server) { wheremsg(); return(0); } else \
if (justone) { justone=0; wheremsg(); return(0); } else { SERVE; } }

#ifdef GFTIMER
#define QUIT x=quiet; quiet=1; clsif(); clsof(1); tsecs=gtimer(); \
 fptsecs=gftimer(); quiet=x; return(success)
#else
#define QUIT x=quiet; quiet=1; clsif(); clsof(1); tsecs=gtimer(); quiet=x; \
 return(success)
#endif /* GFTIMER */

/*
  By late 1999, the big switch() statement generated from the following state
  table began choking even gcc, so here we extract the code from the larger
  states into static routines to reduce the size of the cases and the
  switch() overall.  The routines follow the state table; the prototypes are
  here.  Each of these routines simply contains the text from the
  corresponding case, but with return(-1) added in appropriate places; see
  instructions after the state table switcher.
*/
static int rc;				/* Return code for these routines */
static int rcv_s_pkt();			/* Received an S packet */
static int rcv_firstdata();		/* Received first Data packet */
static int rcv_shortreply();		/* Short reply to a REMOTE command  */
static int srv_query();			/* Server answers an query */
static int srv_copy();			/* Server executes REMOTE COPY */
static int srv_rename();		/* Server executes REMOTE RENAME */
static int srv_login();			/* Server executes REMOTE LOGIN */
static int srv_timeout();		/* Server times out */

%%

/*
  Protocol entry points, one for each start state (sstate).
  The lowercase letters are internal "inputs" from the user interface.
  NOTE: The start state letters that appear on the left margin immediately
  below can NOT be used as packet types OR as G-packet subcodes.
*/

s { TINIT;				/* Send file(s) */
    if (sinit() > 0) BEGIN ssinit;
       else RESUME; }

v { TINIT; nakstate = 1; BEGIN get; }	/* Receive file(s) */

r {					/* Client sends a GET command */
    TINIT;
    vstate = get;
    reget = 0;
    retrieve = 0;
    opkt = 0;
    vcmd = 0;
#ifdef PKTZEROHACK
    ipktack[0] = NUL;
#endif /* PKTZEROHACK */
    if (sipkt('I') >= 0)
      BEGIN ipkt;
    else
      RESUME;
}

h {					/* Client sends a RETRIEVE command */
    TINIT;
    vstate = get;
    reget = 0;
    retrieve = 1;
    opkt = 0;
    vcmd = 0;
    if (sipkt('I') >= 0)
      BEGIN ipkt;
    else
      RESUME;
}
j {					/* Client sends a REGET command */
    TINIT;
    vstate = get;
    reget = 1;
    retrieve = 0;
    opkt = 0;
    vcmd = 0;
    if (sipkt('I') >= 0)
      BEGIN ipkt;
    else
      RESUME;
}
o {					/* Client sends Extended GET Packet */
    TINIT;
    vstate = get;
    reget = oopts & GOPT_RES;
    retrieve = oopts & GOPT_DEL;
    opkt = 1;
    vcmd = 0;
    if (sipkt('I') >= 0)
      BEGIN ipkt;
    else
      RESUME;
}
c {					/* Client sends a Host command */
    TINIT;
    vstate = rgen;
    vcmd = 'C';
    if (sipkt('I') >= 0)
      BEGIN ipkt;
    else
      RESUME;
}
k { TINIT;				/* Client sends a Kermit command */
    vstate = rgen;
    vcmd = 'K';
    if (sipkt('I') >= 0)
      BEGIN ipkt;
    else
      RESUME;
}
g {					/* Client sends a REMOTE command */
    TINIT;
    vstate = rgen;
    vcmd = 'G';
    if (sipkt('I') >= 0)
      BEGIN ipkt;
    else
      RESUME;
}
x {					/* Enter server mode */
    int x;
    x = justone;
    if (!ENABLED(en_del)) {		/* If DELETE is disabled */
	if (fncact == XYFX_B ||		/* undo any file collision action */
	    fncact == XYFX_U ||		/* that could result in deletion or */
	    fncact == XYFX_A ||		/* modification of existing files. */
	    fncact == XYFX_X) {
#ifndef NOICP
	    extern int g_fncact;
	    g_fncact = fncact;		/* Save current setting */
#endif /* NOICP */
	    fncact = XYFX_R;		/* Change to RENAME */
	    debug(F101,"server DELETE disabled so fncact RENAME","",fncact);
	}
    }
    SERVE;				/* tinit() clears justone... */
    justone = x;
#ifdef IKSDB
    if (ikdbopen) slotstate(what, "SERVER", "", "");
#endif /* IKSDB */
}

a {
    int b1 = 0, b2 = 0;
    if (!data) TINIT;			/* "ABEND" -- Tell other side. */

    if (!bctf) {		     /* Block check 3 forced on all packets */
#ifndef pdp11
	if (epktflg) {			/* If because of E-PACKET command */
	    b1 = bctl; b2 = bctu;	/* Save block check type */
	    bctl = bctu = 1;		/* set it to 1 */
	}
#endif /* pdp11 */
    }
    errpkt((CHAR *)"User cancelled");	/* Send the packet */
    if (!bctf) {		     /* Block check 3 forced on all packets */
#ifndef pdp11
	if (epktflg) {			/* Restore the block check */
	    epktflg = 0;
	    bctl = b1; bctu = b2;
	}
    }
#endif /* pdp11 */
    success = 0;
    return(0);				/* Return from protocol. */
}

/*
  Dynamic states: <current-states>input-character { action }
  nakstate != 0 means we're in a receiving state, in which we send ACKs & NAKs.
*/

<rgen,get,serve,ropkt>S {		/* Receive Send-Init packet. */
    rc = rcv_s_pkt();
    cancel = 0;				/* Reset cancellation counter */
    debug(F101,"rcv_s_pkt","",rc);
    if (rc > -1) return(rc);		/* (see below) */
}

/* States in which we get replies back from commands sent to a server. */
/* Complicated because direction of protocol changes, packet number    */
/* stays at zero through I-G-S sequence, and complicated even more by  */
/* sliding windows buffer allocation. */

<ipkt>Y {				/* Get ack for I-packet */
    int x = 0;
#ifdef PKTZEROHACK
    ckstrncpy(ipktack,(char *)rdatap,PKTZEROLEN); /* Save a copy of the ACK */
    ipktlen = strlen(ipktack);
#endif /* PKTZEROHACK */
    spar(rdatap);			/* Set parameters */
    cancel = 0;
    winlo = 0;				/* Set window-low back to zero */
    debug(F101,"<ipkt>Y winlo","",winlo);
    urserver = 1;			/* So I know I'm talking to a server */
    if (vcmd) {				/* If sending a generic command */
	if (tinit(0) < 0) return(-9);	/* Initialize many things */
	x = scmd(vcmd,(CHAR *)cmarg);	/* Do that */
	if (x >= 0) x = 0;		/* (because of O-Packet) */
	debug(F101,"proto G packet scmd","",x);
	vcmd = 0;			/* and then un-remember it. */
    } else if (vstate == get) {
	debug(F101,"REGET sstate","",sstate);
	x = srinit(reget, retrieve, opkt); /* GET or REGET, etc */
    }
    if (x < 0) {			/* If command was too long */
	if (!srimsg)
	  srimsg = "Error sending string";
	errpkt((CHAR *)srimsg);		/* cancel both sides. */
	success = 0;
	RESUME;
    } else if (x > 0) {			/* Need to send more O-Packets */
	BEGIN ssopkt;
    } else {
	rtimer();			/* Reset the elapsed seconds timer. */
#ifdef GFTIMER
	rftimer();
#endif /* GFTIMER */
	winlo = 0;			/* Window back to 0, again. */
	debug(F101,"<ipkt>Y vstate","",vstate);
	nakstate = 1;			/* Can send NAKs from here. */
	BEGIN vstate;			/* Switch to desired state */
    }
}

<ssopkt>Y {				/* Got ACK to O-Packet */
    debug(F100,"CPCPRO <ssopkt>Y","",0);
    x = sopkt();
    debug(F101,"CPCPRO <ssopkt>Y x","",x);
    if (x < 0) {			/* If error */
	errpkt((CHAR *)srimsg);		/* cancel both sides. */
	success = 0;
	RESUME;
    } else if (x == 0) {		/* This was the last O-Packet */
	rtimer();			/* Reset the elapsed seconds timer. */
#ifdef GFTIMER
	rftimer();
#endif /* GFTIMER */
	winlo = 0;			/* Window back to 0, again. */
	debug(F101,"<ssopkt>Y winlo","",winlo);
	nakstate = 1;			/* Can send NAKs from here. */
	BEGIN vstate;			/* Switch to desired state */
    }
    debug(F101,"CPCPRO <ssopkt>Y not changing state","",x);
}

<ipkt>E {				/* Ignore Error reply to I packet */
    int x = 0;
    winlo = 0;				/* Set window-low back to zero */
    debug(F101,"<ipkt>E winlo","",winlo);
    if (vcmd) {				/* In case other Kermit doesn't */
	if (tinit(0) < 0) return(-9);
	x = scmd(vcmd,(CHAR *)cmarg);	/* understand I-packets. */
	if (x >= 0) x = 0;		/* (because of O-Packet) */
	vcmd = 0;			/* Otherwise act as above... */
    } else if (vstate == get) x = srinit(reget, retrieve, opkt);
    if (x < 0) {			/* If command was too long */
	errpkt((CHAR *)srimsg);		/* cancel both sides. */
	success = 0;
	RESUME;
    } else if (x > 0) {			/* Need to send more O-Packets */
	BEGIN ssopkt;
    } else {
	freerpkt(winlo);		/* Discard the Error packet. */
	debug(F101,"<ipkt>E winlo","",winlo);
	winlo = 0;			/* Back to packet 0 again. */
	nakstate = 1;			/* Can send NAKs from here. */
	BEGIN vstate;
    }
}

<get>Y {		/* Resend of previous I-pkt ACK, same seq number */
    freerpkt(0);			/* Free the ACK's receive buffer */
    resend(0);				/* Send the GET packet again. */
}

/* States in which we're being a server */

<serve,get>I {				/* Get I-packet */
#ifndef NOSERVER
    spar(rdatap);			/* Set parameters from it */
    ack1(rpar());			/* Respond with our own parameters */
    tinit(0);				/* Initialize most things */
#endif /* NOSERVER */
    cancel = 0;				/* Reset cancellation counter */
}

<serve>R {				/* GET */
#ifndef NOSERVER
    if (x_login && !x_logged) {
	errpkt((CHAR *)"Login required");
	SERVE;
    } else if (sgetinit(0,0) < 0) {
	RESUME;
    } else {
#ifdef CKSYSLOG
	if (ckxsyslog >= SYSLG_PR && ckxlogging)
	  cksyslog(SYSLG_PR, 1, "server", "GET", (char *)srvcmd);
#endif /* CKSYSLOG */
	BEGIN ssinit;
    }
#endif /* NOSERVER */
}

<serve>H {				/* GET /DELETE (RETRIEVE) */
#ifndef NOSERVER
    if (x_login && !x_logged) {
	errpkt((CHAR *)"Login required");
	RESUME;
    } else if (!ENABLED(en_del)) {
	errpkt((CHAR *)"Deleting files is disabled");
	RESUME;
    } else if (sgetinit(0,0) < 0) {
	RESUME;
    } else {
	moving = 1;
#ifdef CKSYSLOG
	if (ckxsyslog >= SYSLG_PR && ckxlogging)
	  cksyslog(SYSLG_PR, 1, "server", "GET /DELETE", (char *)srvcmd);
#endif /* CKSYSLOG */
	BEGIN ssinit;
    }
#endif /* NOSERVER */
}

<serve>V {				/* GET /RECURSIVE */
#ifndef NOSERVER
    recursive = 1;			/* Set these before sgetinit() */
    if (fnspath == PATH_OFF)
      fnspath = PATH_REL;		/* Don't worry, they will be */
    if (x_login && !x_logged) {		/* reset next time through. */
	errpkt((CHAR *)"Login required");
	RESUME;
    } else if (sgetinit(0,0) < 0) {
	RESUME;
    } else {
#ifdef CKSYSLOG
	if (ckxsyslog >= SYSLG_PR && ckxlogging)
	  cksyslog(SYSLG_PR, 1, "server", "GET /RECURSIVE", (char *)srvcmd);
#endif /* CKSYSLOG */
	BEGIN ssinit;
    }
#endif /* NOSERVER */
}

<serve>W {				/* GET /RECURSIVE /DELETE */
#ifndef NOSERVER
    recursive = 1;			/* Set these before sgetinit() */
    if (fnspath == PATH_OFF)
      fnspath = PATH_REL;		/* Don't worry, they will be */
    moving = 1;				/* reset next time through. */
    if (x_login && !x_logged) {
	errpkt((CHAR *)"Login required");
	RESUME;
    } else if (!ENABLED(en_del)) {
	errpkt((CHAR *)"Deleting files is disabled");
	RESUME;
    } else if (sgetinit(0,0) < 0) {
	RESUME;
    } else {
#ifdef CKSYSLOG
	if (ckxsyslog >= SYSLG_PR && ckxlogging)
	  cksyslog(SYSLG_PR,1,"server",
		   "GET /RECURSIVE /DELETE",(char *)srvcmd);
#endif /* CKSYSLOG */
	BEGIN ssinit;
    }
#endif /* NOSERVER */
}

<serve>J {				/* GET /RECOVER (REGET) */
#ifndef NOSERVER
    if (x_login && !x_logged) {
	errpkt((CHAR *)"Login required");
	SERVE;
    } else if (sgetinit(1,0) < 0) {
	RESUME;
    } else {
#ifdef CKSYSLOG
	if (ckxsyslog >= SYSLG_PR && ckxlogging)
	  cksyslog(SYSLG_PR, 1, "server", "GET /RECOVER", (char *)srvcmd);
#endif /* CKSYSLOG */
	BEGIN ssinit;
    }
#endif /* NOSERVER */
}

<serve>O {				/* Extended GET */
#ifndef NOSERVER
    if (x_login && !x_logged) {		/* (any combination of options) */
	errpkt((CHAR *)"Login required");
	SERVE;
    } else if ((x = sgetinit(0,1)) < 0) {
	debug(F101,"CKCPRO <serve>O sgetinit fail","",x);
	RESUME;
    } else if (x == 0) {
	debug(F101,"CKCPRO <serve>O sgetinit done","",x);
#ifdef CKSYSLOG
	if (ckxsyslog >= SYSLG_PR && ckxlogging)
	  cksyslog(SYSLG_PR, 1, "server", "EXTENDED GET", (char *)srvcmd);
#endif /* CKSYSLOG */
	BEGIN ssinit;
    } else {				/* Otherwise stay in this state */
	debug(F101,"CKCPRO <serve>O sgetinit TBC","",x);
	ack();
	BEGIN ropkt;
    }
#endif /* NOSERVER */
}

<ropkt>O {
#ifndef NOSERVER
    if (x_login && !x_logged) {		/* (any combination of options) */
	errpkt((CHAR *)"Login required");
	SERVE;
    } else if ((x = sgetinit(0,1)) < 0) {
	debug(F101,"CKCPRO <ropkt>O sgetinit fail","",x);
	RESUME;
    } else if (x == 0) {
	debug(F101,"CKCPRO <ropkt>O sgetinit done","",x);
#ifdef CKSYSLOG
	if (ckxsyslog >= SYSLG_PR && ckxlogging)
	  cksyslog(SYSLG_PR, 1, "server", "EXTENDED GET", (char *)srvcmd);
#endif /* CKSYSLOG */
	BEGIN ssinit;
    } else {				/* Otherwise stay in this state */
	debug(F101,"CKCPRO <ropkt>O sgetinit TBC","",x);
	ack();
    }
#endif /* NOSERVER */
}

<serve>G {				/* Generic server command */
#ifndef NOSERVER
    srvptr = srvcmd;			/* Point to command buffer */
    decode(rdatap,putsrv,0);		/* Decode packet data into it */
    putsrv(NUL);			/* Insert a couple nulls */
    putsrv(NUL);			/* for termination */
    if (srvcmd[0]) {
	sstate = srvcmd[0];		/* Set requested start state */
	if (x_login && !x_logged &&	/* Login required? */
	    /* Login, Logout, and Help are allowed when not logged in */
	    sstate != 'I' && sstate != 'L' && sstate != 'H') {
	    errpkt((CHAR *)"Login required");
	    SERVE;
	} else {
	    nakstate = 0;		/* Now I'm the sender. */
	    what = W_REMO;		/* Doing a REMOTE command. */
#ifdef STREAMING
	    if (!streaming)
#endif /* STREAMING */
	      if (timint < 1)
		timint = chktimo(rtimo,timef); /* Switch to per-packet timer */
	    binary = XYFT_T;		/* Switch to text mode */
	    BEGIN generic;		/* Switch to generic command state */
	}
    } else {
	errpkt((CHAR *)"Badly formed server command"); /* report error */
	RESUME;			/* & go back to server command wait */
    }
#endif /* NOSERVER */
}

<serve>C {				/* Receive Host command */
#ifndef NOSERVER
    if (x_login && !x_logged) {
	errpkt((CHAR *)"Login required");
	SERVE;
    } else if (!ENABLED(en_hos)) {
	errpkt((CHAR *)"REMOTE HOST disabled");
	RESUME;
    } else if (nopush) {
	errpkt((CHAR *)"HOST commands not available");
	RESUME;
    } else {
	srvptr = srvcmd;		/* Point to command buffer */
	decode(rdatap,putsrv,0);	/* Decode command packet into it */
	putsrv(NUL);			/* Null-terminate */
	nakstate = 0;			/* Now sending, not receiving */
	binary = XYFT_T;		/* Switch to text mode */
	if (syscmd((char *)srvcmd,"")) { /* Try to execute the command */
	    what = W_REMO;		/* Doing a REMOTE command. */
#ifdef STREAMING
	    if (!streaming)
#endif /* STREAMING */
	      if (timint < 1)
		timint = chktimo(rtimo,timef); /* Switch to per-packet timer */
#ifdef CKSYSLOG
	    if (ckxsyslog >= SYSLG_PR && ckxlogging)
	      cksyslog(SYSLG_PR, 1, "server", "REMOTE HOST", (char *)srvcmd);
#endif /* CKSYSLOG */
	    BEGIN ssinit;		/* If OK, send back its output */
	} else {			/* Otherwise */
	    errpkt((CHAR *)"Can't do system command"); /* report error */
	    RESUME;			/* & go back to server command wait */
	}
    }
#endif /* NOSERVER */
}

<serve>q {				/* Interrupted or connection lost */
    rc = srv_timeout();
    debug(F101,"srv_timeout","",rc);
    if (rc > -1) return(rc);		/* (see below) */
}

<serve>N {				/* Server got a NAK in command-wait */
#ifndef NOSERVER
    errpkt((CHAR *)"Did you say RECEIVE instead of GET?");
    RESUME;
#endif /* NOSERVER */
}

<serve>. {				/* Any other command in this state */
#ifndef NOSERVER
    if (c != ('E' - SP) && c != ('Y' - SP)) /* except E and Y packets. */
      errpkt((CHAR *)"Unimplemented server function");
    /* If we answer an E with an E, we get an infinite loop. */
    /* A Y (ACK) can show up here if we sent back a short-form reply to */
    /* a G packet and it was echoed.  ACKs can be safely ignored here. */
    RESUME;				/* Go back to server command wait. */
#endif /* NOSERVER */
}

<generic>I {				/* Login/Out */
    rc = srv_login();
    debug(F101,"<generic>I srv_login","",rc);
    if (rc > -1) return(rc);		/* (see below) */
}

<generic>C {				/* Got REMOTE CD command */
#ifndef NOSERVER
#ifdef CKSYSLOG
    if (ckxsyslog >= SYSLG_PR && ckxlogging)
      cksyslog(SYSLG_PR, 1, "server", "REMOTE CD", (char *)srvcmd);
#endif /* CKSYSLOG */
    if (!ENABLED(en_cwd)) {
	errpkt((CHAR *)"REMOTE CD disabled");
	RESUME;
    } else {
	char * p = NULL;
	x = cwd((char *)(srvcmd+1));	/* Try to change directory */
#ifdef IKSDB
	if (ikdbopen) slotstate(what,"REMOTE CD", (char *)(srvcmd+2), "");
#endif /* IKSDB */
	if (!x) {			/* Failed */
	    errpkt((CHAR *)"Can't change directory");
	    RESUME;			/* Back to server command wait */
	} else if (x == 2) {		/* User wants message */
	    if (!ENABLED(en_typ)) {	/* Messages (REMOTE TYPE) disabled? */
		errpkt((CHAR *)"REMOTE TYPE disabled");
		RESUME;
	    } else {			/* TYPE is enabled */
		int i;
		for (i = 0; i < 8; i++) {
		    if (zchki(cdmsgfile[i]) > -1) {
			break;
		    }
		}
		binary = XYFT_T;	/* Use text mode for this. */
		if (i < 8 && sndtype(cdmsgfile[i])) { /* Have readme file? */
		    BEGIN ssinit;	/* OK */
		} else {		/* not OK */
		    p = zgtdir();
		    if (!p) p = "";
		    success = (*p) ? 1 : 0;
		    ack1((CHAR *)p);	/* ACK with new directory name */
		    success = 1;
		    RESUME;		/* wait for next server command */
		}
	    }
	} else {			/* User doesn't want message */
	    p = zgtdir();
	    if (!p) p = "";
	    success = (*p) ? 1 : 0;
	    ack1((CHAR *)p);
	    success = 1;
	    RESUME;			/* Wait for next server command */
	}
    }
#endif /* NOSERVER */
}

<generic>u {				/* Got REMOTE CDUP command */
#ifndef NOSERVER
#ifdef CKSYSLOG
    if (ckxsyslog >= SYSLG_PR && ckxlogging)
      cksyslog(SYSLG_PR, 1, "server", "REMOTE CDUP", (char *)srvcmd);
#endif /* CKSYSLOG */
    if (!ENABLED(en_cwd)) {
	errpkt((CHAR *)"REMOTE CD disabled");
	RESUME;
    } else {
	char * p = NULL;
	char * s = NULL;
#ifdef VMS
        s = "[-]";
#else
#ifdef datageneral
        s = "^";
#else
        s = "..";
#endif /* datageneral */
#endif /* VMS */
	x = cwd(s);                     /* Try to change directory */
#ifdef IKSDB
	if (ikdbopen) slotstate(what,"REMOTE CD", (char *)(srvcmd+2), "");
#endif /* IKSDB */
	if (!x) {			/* Failed */
	    errpkt((CHAR *)"Can't change directory");
	    RESUME;			/* Back to server command wait */
	} else if (x == 2) {		/* User wants message */
	    if (!ENABLED(en_typ)) {	/* Messages (REMOTE TYPE) disabled? */
		errpkt((CHAR *)"REMOTE TYPE disabled");
		RESUME;
	    } else {			/* TYPE is enabled */
		int i;
		for (i = 0; i < 8; i++) {
		    if (zchki(cdmsgfile[i]) > -1) {
			break;
		    }
		}
		binary = XYFT_T;	/* Use text mode for this. */
		if (i < 8 && sndtype(cdmsgfile[i])) { /* Have readme file? */
		    BEGIN ssinit;	/* OK */
		} else {		/* not OK */
		    p = zgtdir();
		    if (!p) p = "";
		    success = (*p) ? 1 : 0;
		    ack1((CHAR *)p);	/* ACK with new directory name */
		    success = 1;
		    RESUME;		/* wait for next server command */
		}
	    }
	} else {			/* User doesn't want message */
	    p = zgtdir();
	    if (!p) p = "";
	    success = (*p) ? 1 : 0;
	    ack1((CHAR *)p);
	    success = 1;
	    RESUME;			/* Wait for next server command */
	}
    }
#endif /* NOSERVER */
}


<generic>A {				/* Got REMOTE PWD command */
#ifndef NOSERVER
#ifdef CKSYSLOG
    if (ckxsyslog >= SYSLG_PR && ckxlogging)
      cksyslog(SYSLG_PR, 1, "server", "REMOTE PWD", NULL);
#endif /* CKSYSLOG */
    if (!ENABLED(en_cwd)) {
	errpkt((CHAR *)"REMOTE CD disabled");
	RESUME;
    } else {
	if (encstr((CHAR *)zgtdir()) > -1) { /* Encode current directory */
	    ack1(data);			/* If it fits, send it back in ACK */
	    success = 1;
	} else {			/* Failed */
	    ack();			/* Send empty ACK */
	    success = 0;		/* and indicate failure locally */
	}
	RESUME;				/* Back to server command wait */
    }
#endif /* NOSERVER */
}

<generic>D {				/* REMOTE DIRECTORY command */
#ifndef NOSERVER
    char *n2;
#ifdef CKSYSLOG
    if (ckxsyslog >= SYSLG_PR && ckxlogging)
      cksyslog(SYSLG_PR, 1, "server", "REMOTE DIRECTORY", (char *)srvcmd);
#endif /* CKSYSLOG */
    if (!ENABLED(en_dir)) {		/* If DIR is disabled, */
	errpkt((CHAR *)"REMOTE DIRECTORY disabled"); /* refuse. */
	RESUME;
    } else {				/* DIR is enabled. */
#ifdef IKSDB
	if (ikdbopen) slotstate(what,"REMOTE DIR", (char *)(srvcmd+2), "");
#endif /* IKSDB */
	if (!ENABLED(en_cwd)) {		/* But CWD is disabled */
	    zstrip((char *)(srvcmd+2),&n2); /* and they included a pathname, */
	    if (strcmp((char *)(srvcmd+2),n2)) { /* so refuse. */
		errpkt((CHAR *)"Access denied");
		RESUME;			/* Remember, this is not a goto! */
	    }
	}
	if (state == generic) {			/* It's OK to go ahead. */
	    int x;
	    if ((x = snddir((char*)(srvcmd+2))) > 0)
	    {
		BEGIN ssinit;		/* send the results back; */
	    } else {			/* otherwise */
		if (x < 0)
		  errpkt((CHAR *)"No files match");
		else
		  errpkt((CHAR *)"Can't list directory");
		RESUME;			/* return to server command wait */
	    }
	}
    }
#endif /* NOSERVER */
}

<generic>E {				/* REMOTE DELETE (Erase) */
#ifndef NOSERVER
    char *n2;
#ifdef CKSYSLOG
    if (ckxsyslog >= SYSLG_PR && ckxlogging)
      cksyslog(SYSLG_PR, 1, "server", "REMOTE DELETE", (char *)srvcmd);
#endif /* CKSYSLOG */
    if (!ENABLED(en_del)) {
	errpkt((CHAR *)"REMOTE DELETE disabled");
	RESUME;
    } else {				/* DELETE is enabled */
#ifdef IKSDB
	if (ikdbopen) slotstate(what,"REMOTE DELETE", (char *)(srvcmd+2), "");
#endif /* IKSDB */
	if (!ENABLED(en_cwd)) {		/* but CWD is disabled */
	    zstrip((char *)(srvcmd+2),&n2); /* and they included a pathname, */
	    if (strcmp((char *)(srvcmd+2),n2)) { /* so refuse. */
		errpkt((CHAR *)"Access denied");
		RESUME;			/* Remember, this is not a goto! */
	    }
	} else if (isdir((char *)(srvcmd+2))) { /* A directory name? */
	    errpkt((CHAR *)"It's a directory");
	    RESUME;
	}
	if (state == generic) {		/* It's OK to go ahead. */
	    int x;
	    if ((x = snddel((char*)(srvcmd+2))) > 0) {
		BEGIN ssinit;		/* If OK send results back */
	    } else {			/* otherwise */
		if (x < 0)
		  errpkt((CHAR *)"File not found"); /* report failure */
		else
		  errpkt((CHAR *)"DELETE failed");
		RESUME;			/* & return to server command wait */
	    }
	}
    }
#endif /* NOSERVER */
}

<generic>F {				/* FINISH */
#ifndef NOSERVER
#ifdef CKSYSLOG
    if (ckxsyslog >= SYSLG_PR && ckxlogging)
      cksyslog(SYSLG_PR, 1, "server", "FINISH", NULL);
#endif /* CKSYSLOG */
#ifdef IKSDB
    if (ikdbopen) slotstate(what,"SERVER FINISH", "", "");
#endif /* IKSDB */
    if (!ENABLED(en_fin)) {
	errpkt((CHAR *)"FINISH disabled");
	RESUME;
    } else {
	ack();				/* Acknowledge */
	xxscreen(SCR_TC,0,0L,"");	/* Display */
	success = 1;
	return(0);			/* Done */
    }
#endif /* NOSERVER */
}

<generic>X {				/* EXIT */
#ifndef NOSERVER
#ifdef CKSYSLOG
    if (ckxsyslog >= SYSLG_PR && ckxlogging)
      cksyslog(SYSLG_PR, 1, "server", "REMOTE EXIT", NULL);
#endif /* CKSYSLOG */
#ifdef IKSDB
    if (ikdbopen) slotstate(what,"REMOTE EXIT", "", "");
#endif /* IKSDB */
    if (!ENABLED(en_xit)) {
	errpkt((CHAR *)"EXIT disabled");
	RESUME;
    } else {
	ack();				/* Acknowledge */
	xxscreen(SCR_TC,0,0L,"");	/* Display */
	doexit(GOOD_EXIT,xitsta);
    }
#endif /* NOSERVER */
}

<generic>L {				/* BYE (Logout) */
#ifndef NOSERVER
#ifdef CKSYSLOG
    if (ckxsyslog >= SYSLG_PR && ckxlogging)
      cksyslog(SYSLG_PR, 1, "server", "BYE", NULL);
#endif /* CKSYSLOG */
#ifdef IKSDB
    if (ikdbopen) slotstate(what,"SERVER BYE", "", "");
#endif /* IKSDB */
    if (!ENABLED(en_bye)) {
	errpkt((CHAR *)"BYE disabled");
	RESUME;
    } else {
	ack();				/* Acknowledge */
	success = 1;
	msleep(750);			/* Give the ACK time to get out */
	if (local)
	  ttres();			/* Reset the terminal */
	xxscreen(SCR_TC,0,0L,"");	/* Display */
	doclean(1);			/* Clean up files, etc */
#ifdef DEBUG
	debug(F100,"C-Kermit BYE - Logging out...","",0);
	zclose(ZDFILE);
#endif /* DEBUG */
#ifdef IKSD
#ifdef CK_LOGIN
	if (inserver)
	  ckxlogout();
	else
#endif /* CK_LOGIN */
#endif /* IKSD */
#ifdef TCPSOCKET
#ifndef NOLISTEN
	  if (network && tcpsrfd > 0 && !inserver)
	    doexit(GOOD_EXIT,xitsta);
	else
#endif /* NOLISTEN */
#endif /* TCPSOCKET */
	  return(zkself());		/* Try to log self out */
    }
#endif /* NOSERVER */
}

<generic>H {				/* REMOTE HELP */
#ifdef CKSYSLOG
    if (ckxsyslog >= SYSLG_PR && ckxlogging)
      cksyslog(SYSLG_PR, 1, "server", "REMOTE HELP", NULL);
#endif /* CKSYSLOG */
#ifdef IKSDB
    if (ikdbopen) slotstate(what,"REMOTE HELP", "", "");
#endif /* IKSDB */
#ifndef NOSERVER
    if (sndhlp()) {
	BEGIN ssinit;			/* try to send it */
    } else {				/* If not ok, */
	errpkt((CHAR *)"Can't send help"); /* send error message instead */
	RESUME;				/* and return to server command wait */
    }
#endif /* NOSERVER */
}

<generic>Q {				/* REMOTE STATUS */
#ifdef CKSYSLOG
    if (ckxsyslog >= SYSLG_PR && ckxlogging)
      cksyslog(SYSLG_PR, 1, "server", "REMOTE STATUS", NULL);
#endif /* CKSYSLOG */
#ifdef IKSDB
    if (ikdbopen) slotstate(what,"REMOTE STATUS", "", "");
#endif /* IKSDB */
#ifndef NOSERVER
    if (sndstatus()) {
	BEGIN ssinit;			/* try to send it */
    } else {				/* If not ok, */
	errpkt((CHAR *)"Can't send status"); /* send error message instead */
	RESUME;				/* and return to server command wait */
    }
#endif /* NOSERVER */
}

<generic>R {                            /* REMOTE RENAME */
    rc = srv_rename();
    debug(F101,"srv_rename","",rc);
    if (rc > -1) return(rc);		/* (see below) */
}

<generic>K {                            /* REMOTE COPY */
    rc = srv_copy();
    debug(F101,"srv_copy","",rc);
    if (rc > -1) return(rc);		/* (see below) */
}

<generic>S {				/* REMOTE SET */
#ifdef CKSYSLOG
    if (ckxsyslog >= SYSLG_PR && ckxlogging)
      cksyslog(SYSLG_PR, 1, "server", "REMOTE SET", (char *)srvcmd);
#endif /* CKSYSLOG */
#ifndef NOSERVER
#ifdef IKSDB
    if (ikdbopen) slotstate(what,"REMOTE SET", (char *)(srvcmd+1), "");
#endif /* IKSDB */
    if (!ENABLED(en_set)) {
	errpkt((CHAR *)"REMOTE SET disabled");
	RESUME;
    } else {
	if (remset((char *)(srvcmd+1))) { /* Try to do what they ask */
	    success = 1;
	    ack();			/* If OK, then acknowledge */
	} else				/* Otherwise */
	  errpkt((CHAR *)"Unknown REMOTE SET parameter"); /* give error msg */
	RESUME;				/* Return to server command wait */
    }
#endif /* NOSERVER */
}

<generic>T {				/* REMOTE TYPE */
#ifndef NOSERVER
    char *n2;
#ifdef CKSYSLOG
    if (ckxsyslog >= SYSLG_PR && ckxlogging)
      cksyslog(SYSLG_PR, 1, "server", "REMOTE TYPE", (char *)srvcmd);
#endif /* CKSYSLOG */
    if (!ENABLED(en_typ)) {
	errpkt((CHAR *)"REMOTE TYPE disabled");
	RESUME;
    } else {
#ifdef IKSDB
	if (ikdbopen) slotstate(what,"REMOTE TYPE", (char *)(srvcmd+2), "");
#endif /* IKSDB */
	if (!ENABLED(en_cwd)) {		/* If CWD disabled */
	    zstrip((char *)(srvcmd+2),&n2); /* and they included a pathname, */
	    if (strcmp((char *)(srvcmd+2),n2)) { /* refuse. */
		errpkt((CHAR *)"Access denied");
		RESUME;			/* Remember, this is not a goto! */
	    }
	}
	if (state == generic) {		/* It's OK to go ahead. */
	    binary = XYFT_T;		/* Use text mode for this. */
	    if (			/* (RESUME didn't change state) */
	      sndtype((char *)(srvcmd+2)) /* New way */
		)
	      BEGIN ssinit;		/* OK */
	    else {			/* not OK */
		errpkt((CHAR *)"Can't type file"); /* give error message */
		RESUME;			/* wait for next server command */
	    }
	}
    }
#endif /* NOSERVER */
}

<generic>m {				/* REMOTE MKDIR */
#ifndef NOSERVER
#ifdef CK_MKDIR
#ifdef CKSYSLOG
    if (ckxsyslog >= SYSLG_PR && ckxlogging)
      cksyslog(SYSLG_PR, 1, "server", "REMOTE MKDIR", (char *)srvcmd);
#endif /* CKSYSLOG */
#ifdef IKSDB
    if (ikdbopen) slotstate(what,"REMOTE MKDIR", (char *)(srvcmd+2), "");
#endif /* IKSDB */
    if (!ENABLED(en_mkd)) {
	errpkt((CHAR *)"REMOTE MKDIR disabled");
	RESUME;
    } else if (!ENABLED(en_cwd)) {	/* If CWD disabled */
	errpkt((CHAR *)"Directory access restricted");
	RESUME;				/* Remember, this is not a goto! */
    }
    if (state == generic) {		/* OK to go ahead. */
	char *p = NULL;
	x = ckmkdir(0,(char *)(srvcmd+2),&p,0,1); /* Make the directory */
	if (!p) p = "";
	if (x > -1) {
	    encstr((CHAR *)p);		/* OK - encode the name */
	    ack1(data);			/* Send short-form response */
	    success = 1;
	    RESUME;
	} else {			/* not OK */
	    if (!*p) p = "Directory creation failure";
	    errpkt((CHAR *)p);		/* give error message */
	    RESUME;			/* Wait for next server command */
	}
    }
#else
    errpkt((CHAR *)"REMOTE MKDIR not available");
    RESUME;
#endif /* CK_MKDIR */
#endif /* NOSERVER */
}

<generic>d {				/* REMOTE RMDIR */
#ifndef NOSERVER
#ifdef CK_MKDIR
#ifdef CKSYSLOG
    if (ckxsyslog >= SYSLG_PR && ckxlogging)
      cksyslog(SYSLG_PR, 1, "server", "REMOTE RMDIR", (char *)srvcmd);
#endif /* CKSYSLOG */
#ifdef IKSDB
    if (ikdbopen) slotstate(what,"REMOTE RMDIR", (char *)(srvcmd+2), "");
#endif /* IKSDB */
    if (!ENABLED(en_rmd)) {
	errpkt((CHAR *)"REMOTE RMDIR disabled");
	RESUME;
    } else if (!ENABLED(en_cwd)) {	/* If CWD disabled */
	errpkt((CHAR *)"Directory access restricted");
	RESUME;				/* Remember, this is not a goto! */
    }
    if (state == generic) {		/* OK to go ahead. */
	char *p = NULL;
	x = ckmkdir(1,(char *)(srvcmd+2),&p,0,1);
	if (!p) p = "";
	if (x > -1) {
	    encstr((CHAR *)p);		/* OK - encode the name */
	    ack1(data);			/* Send short-form response */
	    success = 1;
	    RESUME;
	} else {			/* not OK */
	    if (!*p) p = "Directory removal failure";
	    errpkt((CHAR *)p);		/* give error message */
	    RESUME;			/* Wait for next server command */
	}
    }
#else
    errpkt((CHAR *)"REMOTE RMDIR not available");
    RESUME;
#endif /* CK_MKDIR */
#endif /* NOSERVER */
}

<generic>U {				/* REMOTE SPACE */
#ifndef NOSERVER
#ifdef CKSYSLOG
    if (ckxsyslog >= SYSLG_PR && ckxlogging)
      cksyslog(SYSLG_PR, 1, "server", "REMOTE SPACE", (char *)srvcmd);
#endif /* CKSYSLOG */
    if (!ENABLED(en_spa)) {
	errpkt((CHAR *)"REMOTE SPACE disabled");
	RESUME;
    } else {
	x = srvcmd[1];			/* Get area to check */
	x = ((x == NUL) || (x == SP)
#ifdef OS2
	     || (x == '!') || (srvcmd[3] == ':')
#endif /* OS2 */
	     );
#ifdef IKSDB
	if (ikdbopen) slotstate(what,
			      "REMOTE SPACE",
			      (x ? "" : (char *)srvcmd),
			      ""
			      );
#endif /* IKSDB */
	if (!x && !ENABLED(en_cwd)) {	/* CWD disabled */
	    errpkt((CHAR *)"Access denied"); /* and non-default area given, */
	    RESUME;			/* refuse. */
	} else {
#ifdef OS2
_PROTOTYP(int sndspace,(int));
	    if (sndspace(x ? toupper(srvcmd[2]) : 0)) {
		BEGIN ssinit;		/* send the report. */
	    } else {			/* If not ok, */
		errpkt((CHAR *)"Can't send space"); /* send error message */
		RESUME;			/* and return to server command wait */
	    }
#else
            if (nopush)
              x = 0;
            else
              x = (x ? syscmd(SPACMD,"") : syscmd(SPACM2,(char *)(srvcmd+2)));
	    if (x) {			/* If we got the info */
		BEGIN ssinit;		/* send it */
	    } else {			/* otherwise */
		errpkt((CHAR *)"Can't check space"); /* send error message */
		RESUME;			/* and await next server command */
	    }
#endif /* OS2 */
	}
    }
#endif /* NOSERVER */
}

<generic>W {				/* REMOTE WHO */
#ifndef NOSERVER
#ifdef CKSYSLOG
    if (ckxsyslog >= SYSLG_PR && ckxlogging)
      cksyslog(SYSLG_PR, 1, "server", "REMOTE WHO", (char *)srvcmd);
#endif /* CKSYSLOG */
#ifdef IKSDB
    if (ikdbopen) slotstate(what,"REMOTE WHO", (char *)(srvcmd+2), "");
#endif /* IKSDB */
    if (!ENABLED(en_who)) {
	errpkt((CHAR *)"REMOTE WHO disabled");
	RESUME;
    } else {
#ifdef OS2
_PROTOTYP(int sndwho,(char *));
	    if (sndwho((char *)(srvcmd+2))) {
		BEGIN ssinit;		/* try to send it */
	    } else {			/* If not ok, */
		errpkt((CHAR *)"Can't do who command"); /* send error msg */
		RESUME;			/* and return to server command wait */
	    }
#else
	if (syscmd(WHOCMD,(char *)(srvcmd+2))) {
	    BEGIN ssinit;
	} else {
	    errpkt((CHAR *)"Can't do who command");
	    RESUME;
	}
#endif /* OS2 */
    }
#endif /* NOSERVER */
}

<generic>V {				/* Variable query or set */
    rc = srv_query();
    debug(F101,"srv_query","",rc);
    if (rc > -1) return(rc);
}

<generic>M {				/* REMOTE MESSAGE command */
#ifndef NOSERVER
    debug(F110,"RMSG",(char *)srvcmd+2,0);
    xxscreen(SCR_MS,0,0L,(char *)(srvcmd+2));
    ack();
    RESUME;
#endif	/* NOSERVER */
}

<generic>q {				/* Interrupted or connection lost */
#ifndef NOSERVER
    if (fatalio) {			/* Connection lost */
#ifdef CKSYSLOG
	if (ckxsyslog >= SYSLG_PR && ckxlogging)
	  cksyslog(SYSLG_PR, 1, "server", "Interrupted", NULL);
#endif /* CKSYSLOG */
	success = 0;
	xitsta |= (what & W_KERMIT);
	QUIT;
    } else if (interrupted) {
	if (!ENABLED(en_fin)) {		/* Ctrl-C typed */
	    errpkt((CHAR *)"QUIT disabled");
	    RESUME;
	} else {
#ifdef CKSYSLOG
	    if (ckxsyslog >= SYSLG_PR && ckxlogging)
	      cksyslog(SYSLG_PR, 1, "server", "Interrupted", NULL);
#endif /* CKSYSLOG */
	    success = 0;
	    xitsta |= (what & W_KERMIT);
	    QUIT;
	}
    } else {				/* Shouldn't happen */
	debug(F100,"SERVER (generic) GOT UNEXPECTED 'q'","",0);
	QUIT;
    }
#endif /* NOSERVER */
}

<generic>. {				/* Anything else in this state... */
#ifndef NOSERVER
    errpkt((CHAR *)"Unimplemented REMOTE command"); /* Complain */
    RESUME;				/* and return to server command wait */
#endif /* NOSERVER */
}

<rgen>q {				/* Sent BYE and connection broken */
    if (bye_active && ttchk() < 0) {
	msleep(500);
	bye_active = 0;
	ttclos(0);			/* Close our end of the connection */
	clsof(0);
	return(success = 1);
    } else {				/* Other generic command */
	return(success = 0);		/* or connection not broken */
    }
}

<rgen>Y {				/* Short-Form reply */
    rc = rcv_shortreply();
    debug(F101,"<rgen>Y rcv_shortreply","",rc);
    if (rc > -1) return(rc);
}

<rgen,rfile>F {				/* File header */
    /* char *n2; */
    extern int rsn;
    debug(F101,"<rfile>F winlo 1","",winlo);
    xflg = 0;				/* Not screen data */
    if (!czseen)
      cancel = 0;			/* Reset cancellation counter */
#ifdef CALIBRATE
    if (dest == DEST_N)
      calibrate = 1;
#endif /* CALIBRATE */
    if (!rcvfil(filnam)) {		/* Figure out local filename */
	errpkt((CHAR *)rf_err);		/* Trouble */
	RESUME;
    } else {				/* Real file, OK to receive */
	char * fnp;
	debug(F111,"<rfile>F winlo 2",fspec,winlo);
	if (filcnt == 1)		/* rcvfil set this to 1 for 1st file */
	  crc16 = 0L;			/* Clear file CRC */
	fnp = fspec;			/* This is the full path */
	if (server && !ENABLED(en_cwd) || /* if DISABLE CD */
	    !fackpath			  /* or F-ACK-PATH OFF */
	    ) {
	    zstrip(fspec,&fnp);		/* don't send back full path */
#ifdef UNIX
/*
  fdc, November 2012.  Unix pathnames are getting longer, causing the full
  pathname that remote C-Kermit, when receiving a file, sends back to a local
  Kermit (e.g. K95) to overflow the file transfer display, so the user can't
  see which file is being transferred.  Here we try to shorten the pathname
  that is sent from the remote receiver back to the local sender.
*/
	} else if (!local) {		/* Try to shorten by using '~' */
	    extern char homedirpath[];	/* Filled in at startup time */
	    char *p;
	    int len = 0, ok = 0;
/*
  fdc, March 2013: If the file is being received to Kermit's current
  directory, don't send the current-directory path.
*/
	    p = zgtdir();		/* Get current directory */
	    if (p) if (*p) {		/* If we got one... */
		len = strlen(p); /* and it matches the filespec path */
		if (ckindex(p,fspec,0,0,1) == 1 && len > 3) {
		    fnp = fspec + len;
		    ok = 1;
		}
	    }
	    if (!ok) {
/*
  Nov 2012: If not the current directory then if it is being sent
  from somewhere in the user's home directory tree, it can be shortened
  using ~ notation.
*/
		p = homedirpath;		/* Get home directory path */
		if (p) if (*p) {		/* If we got one... */
		    len = strlen(p);	/* and it matches the filespec path */
		    if (ckindex(p,fspec,0,0,1) == 1 && len > 3) {
			int i = 0;
			char * s;
			fspec[i++] = '~';	/* ...replace it with "~/" */
			fspec[i++] = '/';
			s = (char *)fspec + len;
			while (*s) {
			    fspec[i++] = *s++;
			}
			fspec[i] = '\0';
			fnp = fspec;
		    }
		}
	    }
#endif /* UNIX */
	}
	encstr((CHAR *)fnp);
	if (fackbug)
	  ack();
	else
	  ack1(data);			/* Send it back in ACK */
	initattr(&iattr);		/* Clear file attribute structure */
	streamon();
	if (window(wslotn) < 0) {	/* Allocate negotiated window slots */
	    errpkt((CHAR *)"Can't open window");
	    RESUME;
	}
#ifdef IKSDB
	if (ikdbopen) slotstate(what,
			      server ? "SERVER" : "",
			      "RECEIVE",
			      fspec
			      );
#endif /* IKSDB */
	BEGIN rattr;			/* Now expect Attribute packets */
    }
}

<rgen,rfile>X {				/* X-packet instead of file header */
    xflg = 1;				/* Screen data */
    if (!czseen)
      cancel = 0;			/* Reset cancellation counter */
    ack();				/* Acknowledge the X-packet */
    initattr(&iattr);			/* Initialize attribute structure */
    streamon();
    if (window(wslotn) < 0) {		/* allocate negotiated window slots */
	errpkt((CHAR *)"Can't open window");
	RESUME;
    }
#ifndef NOSPL
    if (query) {			/* If this is the response to */
	qbufp = querybuf;		/* a query that we sent, initialize */
	qbufn = 0;			/* the response buffer */
	querybuf[0] = NUL;
    }
#endif /* NOSPL */
    what = W_REMO;			/* we're doing a REMOTE command */
#ifdef IKSDB
    if (ikdbopen) slotstate(what,
			  server ? "SERVER" : "",
			  "RECEIVE",
			  fspec
			  );
#endif /* IKSDB */
    BEGIN rattr;			/* Expect Attribute packets */
}

<rattr>A {				/* Attribute packet */
    if (gattr(rdatap,&iattr) == 0) {	/* Read into attribute structure */
#ifdef CK_RESEND
	ack1((CHAR *)iattr.reply.val);	/* Reply with data */
#else
	ack();				/* If OK, acknowledge */
#endif /* CK_RESEND */
    } else {				/* Otherwise */
	extern CK_OFF_T fsize;
	char *r;
	r = getreason(iattr.reply.val);
	ack1((CHAR *)iattr.reply.val);	/* refuse to accept the file */
	xxscreen(SCR_ST,ST_REFU,0L,r);	/* reason */
#ifdef TLOG
	if (tralog && !tlogfmt)
	  doxlog(what,filnam,fsize,binary,1,r);
#endif /* TLOG */
    }
}

<rattr>D {				/* First data packet */
    debug(F100,"<rattr> D firstdata","",0);
    rc = rcv_firstdata();
    debug(F101,"rcv_firstdata rc","",rc);
    if (rc > -1) return(rc);		/* (see below) */
}

<rfile>B {				/* EOT, no more files */
    ack();				/* Acknowledge the B packet */
    reot();				/* Do EOT things */
#ifdef CK_TMPDIR
/* If we were cd'd temporarily to another device or directory ... */
    if (f_tmpdir) {
	int x;
	x = zchdir((char *) savdir);	/* ... restore previous directory */
	f_tmpdir = 0;			/* and remember we did it. */
	debug(F111,"ckcpro.w B tmpdir restoring",savdir,x);
    }
#endif /* CK_TMPDIR */
    RESUME;				/* and quit */
}

<rdpkt>D {				/* Got Data packet */
    debug(F101,"<rdpkt>D cxseen","",cxseen);
    debug(F101,"<rdpkt>D czseen","",czseen);
    if (cxseen || czseen || discard) {	/* If file or group interruption */
	CHAR * msg;
	msg = czseen ? (CHAR *)"Z" : (CHAR *)"X";
#ifdef STREAMING
	if (streaming) {		/* Need to cancel */
	    debug(F111,"<rdpkt>D streaming cancel",msg,cancel);
	    if (cancel++ == 0) {	/* Only do this once */
		ack1(msg);		/* Put "X" or "Z" in ACK */
	    } else if (czseen) {
		errpkt((CHAR *)"User canceled");
		RESUME;
	    } else {
		fastack();
	    }
	} else
#endif /* STREAMING */
	  ack1(msg);
    } else {				/* No interruption */
	int rc, qf;
#ifndef NOSPL
	qf = query;
#else
	qf = 0;
#endif /* NOSPL */
#ifdef CKTUNING
	rc = (binary && !parity) ?
	  bdecode(rdatap,putfil):
	    decode(rdatap, qf ? puttrm : putfil, 1);
#else
	rc = decode(rdatap, qf ? puttrm : putfil, 1);
#endif /* CKTUNING */
	if (rc < 0) {
	    discard = (keep == 0 || (keep == SET_AUTO && binary != XYFT_T));
	    errpkt((CHAR *)"Error writing data"); /* If failure, */
	    RESUME;
	} else				/* Data written OK, send ACK */
#ifdef STREAMING
	  if (streaming)
	    fastack();
	else
#endif /* STREAMING */
	  ack();
    }
}

<rattr>Z {				/* EOF immediately after A-Packet. */
    rf_err = "Can't create file";
    timint = s_timint;
    if (discard) {			/* Discarding a real file... */
	x = 1;
    } else if (xflg) {			/* If screen data */
	if (remfile) {			/* redirected to file */
	    if (rempipe)		/* or pipe */
	      x = openc(ZOFILE,remdest); /* Pipe: start command */
	    else
	      x = opena(remdest,&iattr); /* File: open with attributes */
	} else {			/* otherwise */
	    x = opent(&iattr);		/* "open" the screen */
	}
#ifdef CALIBRATE
    } else if (calibrate) {		/* If calibration run */
	x = ckopenx(&iattr);		/* do this */
#endif /* CALIBRATE */
    } else {				/* otherwise */
	x = opena(filnam,&iattr);	/* open the file, with attributes */
	if (x == -17) {			/* REGET skipped because same size */
	    discard = 1;
	    rejection = 1;
	}
    }
    if (!x || reof(filnam, &iattr) < 0) { /* Close output file */
	errpkt((CHAR *) rf_err);	/* If problem, send error msg */
	RESUME;				/* and quit */
    } else {				/* otherwise */
	if (x == -17)
	  xxscreen(SCR_ST,ST_SKIP,SKP_RES,"");
	ack();				/* acknowledge the EOF packet */
	BEGIN rfile;			/* and await another file */
    }
}

<rdpkt>q {  				/* Ctrl-C or connection loss. */
    timint = s_timint;
    window(1);				/* Set window size back to 1... */
    cxseen = 1;
    x = clsof(1);			/* Close file */
    return(success = 0);		/* Failed */
}

<rdpkt>Z {				/* End Of File (EOF) Packet */
/*  wslots = 1;	*/			/* (don't set) Window size back to 1 */
#ifndef COHERENT /* Coherent compiler blows up on this switch() statement. */
    x = reof(filnam, &iattr);		/* Handle the EOF packet */
    switch (x) {			/* reof() sets the success flag */
      case -5:				/* Handle problems */
	errpkt((CHAR *)"RENAME failed"); /* Fatal */
	RESUME;
	break;
      case -4:
	errpkt((CHAR *)"MOVE failed");	/* Fatal */
	RESUME;
	break;
      case -3:				/* If problem, send error msg */
	errpkt((CHAR *)"Can't print file"); /* Fatal */
	RESUME;
	break;
      case -2:
	errpkt((CHAR *)"Can't mail file"); /* Fatal */
	RESUME;
	break;
      case 2:				/* Not fatal */
      case 3:
	xxscreen(SCR_EM,0,0L,"Receiver can't delete temp file");
	RESUME;
	break;
      default:
	if (x < 0) {			/* Fatal */
	    errpkt((CHAR *)"Can't close file");
	    RESUME;
	} else {			/* Success */
#ifndef NOSPL
	    if (query)			/* Query reponses generally */
	      conoll("");		/* don't have line terminators */
#endif /* NOSPL */
	    if (czseen) {		/* Batch canceled? */
		if (cancel++ == 0) {	/* If we haven't tried this yet */
		    ack1((CHAR *)"Z");	/* Try it once */
		} else {		/* Otherwise */
		    errpkt((CHAR *)"User canceled"); /* quite with Error */
		    RESUME;
		}
	    } else
	      ack();			/* Acknowledge the EOF packet */
	    BEGIN rfile;		/* and await another file */
	}
    }
#else
    if (reof(filnam, &iattr) < 0) {	/* Close the file */
	errpkt((CHAR *)"Error at end of file");
	RESUME;
    } else {				/* reof() sets success flag */
	ack();
	BEGIN rfile;
    }
#endif /* COHERENT */
}

<ssinit>Y {				/* ACK for Send-Init */
    spar(rdatap);			/* set parameters from it */
    cancel = 0;
    if (bctf) {
	bctu = 3;
	bctl = 3;
    } else {
	bctu = bctr;			/* switch to agreed-upon block check */
	bctl = (bctu == 4) ? 2 : bctu;	/* Set block-check length */
    }

#ifdef CK_RESEND
    if ((sendmode == SM_RESEND) && (!atcapu || !rscapu)) { /* RESEND */
	errpkt((CHAR *) "RESEND capabilities not negotiated");
	RESUME;
    } else {
#endif /* CK_RESEND */
	what = W_SEND;			/* Remember we're sending */
	lastxfer = W_SEND;
	x = sfile(xflg);		/* Send X or F header packet */
	cancel = 0;			/* Reset cancellation counter */
	if (x) {			/* If the packet was sent OK */
	    if (!xflg && filcnt == 1)	/* and it's a real file */
	      crc16 = 0L;		/* Clear the file CRC */
	    resetc();			/* reset per-transaction counters */
	    rtimer();			/* reset timers */
#ifdef GFTIMER
	    rftimer();
#endif /* GFTIMER */
	    streamon();			/* turn on streaming */
#ifdef IKSDB
	    if (ikdbopen) slotstate(what,
				  (server ? "SERVER" : ""),
				  "SEND",
				  filnam
				  );
#endif /* IKSDB */
	    BEGIN ssfile;		/* and switch to receive-file state */
	} else {			/* otherwise send error msg & quit */
	    s = xflg ? "Can't execute command" : (char *)epktmsg;
	    if (!*s) s = "Can't open file";
	    errpkt((CHAR *)s);
	    RESUME;
	}
#ifdef CK_RESEND
    }
#endif /* CK_RESEND */
}

/*
  These states are necessary to handle the case where we get a server command
  packet (R, G, or C) reply with an S packet, but the client retransmits the
  command packet.  The input() function doesn't catch this because the packet
  number is still zero.
*/
<ssinit>R {				/* R packet was retransmitted. */
    xsinit();				/* Resend packet 0 */
}

<ssinit>G {				/* Same deal if G packet comes again */
    xsinit();
}

/* should probably add cases for O, W, V, H, J, ... */

<ssinit>C {				/* Same deal if C packet comes again */
    xsinit();
}

<ssfile>Y {				/* ACK for F or X packet */
    srvptr = srvcmd;			/* Point to string buffer */
    decode(rdatap,putsrv,0);		/* Decode data field, if any */
    putsrv(NUL);			/* Terminate with null */
    ffc = 0L;				/* Reset file byte counter */
    debug(F101,"<ssfile>Y cxseen","",cxseen);
    if (*srvcmd) {			/* If remote name was recorded */
        if (sendmode != SM_RESEND) {
	    if (fdispla == XYFD_C || fdispla == XYFD_S)
	      xxscreen(SCR_AN,0,0L,(char *)srvcmd);
	    tlog(F110," remote name:",(char *) srvcmd,0L);
	    makestr(&psrfspec,(char *)srvcmd);
        }
    }
    if (cxseen||czseen) {		/* Interrupted? */
	debug(F101,"<ssfile>Y canceling","",0);
	x = clsif();			/* Close input file */
	sxeof(1);			/* Send EOF(D) */
	BEGIN sseof;			/* and switch to EOF state. */
    } else if (atcapu) {		/* If attributes are to be used */
	if (sattr(xflg | stdinf, 1) < 0) { /* send them */
	    errpkt((CHAR *)"Can't send attributes"); /* if problem, say so */
	    RESUME;			/* and quit */
	} else BEGIN ssattr;		/* if ok, switch to attribute state */
    } else {				/* Attributes not negotiated */
	if (window(wslotn) < 0) {	/* Open window */
	    errpkt((CHAR *)"Can't open window");
	    RESUME;
	} else if ((x = sdata()) == -2) { /* Send first data packet data */
	    window(1);			/* Connection lost, reset window */
	    x = clsif();		/* Close input file */
	    return(success = 0);	/* Return failure */
	} else if (x == -9) {		/* User interrupted */
	    errpkt((CHAR *)"User cancelled"); /* Send Error packet */
	    window(1);			/* Set window size back to 1... */
	    timint = s_timint;		/* Restore timeout */
	    return(success = 0);	/* Failed */
	} else if (x < 0) {		/* EOF (empty file) or interrupted */
	    window(1);			/* put window size back to 1, */
	    debug(F101,"<ssfile>Y cxseen","",cxseen);
	    x = clsif();		/* If not ok, close input file, */
	    if (x < 0)			/* treating failure as interruption */
	      cxseen = 1;		/* Send EOF packet */
	    seof(cxseen||czseen);
	    BEGIN sseof;		/* and switch to EOF state. */
	} else {			/* First data sent OK */
	    BEGIN ssdata;		/* All ok, switch to send-data state */
	}
    }
}

<ssattr>Y {				/* Got ACK to A packet */
    ffc = 0L;				/* Reset file byte counter */
    debug(F101,"<ssattr>Y cxseen","",cxseen);
    if (cxseen||czseen) {		/* Interrupted? */
	debug(F101,"<sattr>Y canceling","",0);
	x = clsif();			/* Close input file */
	sxeof(1);			/* Send EOF(D) */
	BEGIN sseof;			/* and switch to EOF state. */
    } else if (rsattr(rdatap) < 0) {	/* Was the file refused? */
	discard = 1;			/* Set the discard flag */
	clsif();			/* Close the file */
	sxeof(1);			/* send EOF with "discard" code */
	BEGIN sseof;			/* switch to send-EOF state */
    } else if ((x = sattr(xflg | stdinf, 0)) < 0) { /* Send more? */
	errpkt((CHAR *)"Can't send attributes"); /* Trouble... */
	RESUME;
    } else if (x == 0) {		/* No more to send so now the data */
	if (window(wslotn) < 0) {	/* Allocate negotiated window slots */
	    errpkt((CHAR *)"Can't open window");
	    RESUME;
	}
	if ((x = sdata()) == -2) {	/* File accepted, send first data */
	    window(1);			/* Connection broken */
	    x = clsif();		/* Close file */
	    return(success = 0);	/* Return failure */
	} else if (x == -9) {		/* User interrupted */
	    errpkt((CHAR *)"User cancelled"); /* Send Error packet */
	    window(1);			/* Set window size back to 1... */
	    timint = s_timint;		/* Restore timeout */
	    return(success = 0);	/* Failed */
	} else if (x < 0) {		/* If data was not sent */
	    window(1);			/* put window size back to 1, */
	    debug(F101,"<ssattr>Y cxseen","",cxseen);
	    if (clsif() < 0)		/* Close input file */
	      cxseen = 1;		/* Send EOF packet */
	    seof(cxseen||czseen);
	    BEGIN sseof;		/* and switch to EOF state. */
	} else {
	    BEGIN ssdata;		/* All ok, switch to send-data state */
	}
    }
}

<ssdata>q {  				/* Ctrl-C or connection loss. */
    window(1);				/* Set window size back to 1... */
    cxseen = 1;				/* To indicate interruption */
    x = clsif();			/* Close file */
    return(success = 0);		/* Failed */
}

<ssdata>Y {				/* Got ACK to Data packet */
    canned(rdatap);			/* Check if file transfer cancelled */
    debug(F111,"<ssdata>Y cxseen",rdatap,cxseen);
    debug(F111,"<ssdata>Y czseen",rdatap,czseen);
    if ((x = sdata()) == -2) {		/* Try to send next data */
	window(1);			/* Connection lost, reset window */
	x = clsif();			/* Close file */
	return(success = 0);		/* Failed */
    } else if (x == -9) {		/* User interrupted */
	errpkt((CHAR *)"User cancelled"); /* Send Error packet */
	window(1);			/* Set window size back to 1... */
	timint = s_timint;		/* Restore original timeout */
	return(success = 0);		/* Failed */
    } else if (x < 0) {			/* EOF - finished sending data */
	debug(F101,"<ssdata>Y cxseen","",cxseen);
	window(1);			/* Set window size back to 1... */
	if (clsif() < 0)		/* Close input file */
	  cxseen = 1;			/* Send EOF packet */
	debug(F101,"<ssdata>Y CALLING SEOF()","",cxseen);
	seof(cxseen||czseen);
	BEGIN sseof;			/* and enter send-eof state */
    }
    /* NOTE: If x == 0 it means we're draining: see sdata()! */
}

<sseof>Y {				/* Got ACK to EOF */
    int g, xdiscard;
    canned(rdatap);			/* Check if file transfer cancelled */
    debug(F111,"<sseof>Y cxseen",rdatap,cxseen);
    debug(F111,"<sseof>Y czseen",rdatap,czseen);
    debug(F111,"<sseof>Y discard",rdatap,discard);
    xdiscard = discard;
    discard = 0;
    success = (cxseen == 0 && czseen == 0); /* Transfer status... */
    debug(F101,"<sseof>Y success","",success);
    if (success && rejection > 0)	    /* If rejected, succeed if */
      if (rejection != '#' &&		    /* reason was date */
	  rejection != 1 && rejection != '?') /* or name; */
	success = 0;			    /* fail otherwise. */
    cxseen = 0;				/* This goes back to zero. */
    if (success) {			/* Only if transfer succeeded... */
	xxscreen(SCR_ST,ST_OK,0L,"");
	if (!xdiscard) {
	    makestr(&sfspec,psfspec);	/* Record filenames for WHERE */
	    makestr(&srfspec,psrfspec);
	}
	if (moving) {			/* If MOVE'ing */
	    x = zdelet(filnam);		/* Try to delete the source file */
#ifdef TLOG
	    if (tralog) {
		if (x > -1) {
		    tlog(F110," deleted",filnam,0);
		} else {
		    tlog(F110," delete failed:",ck_errstr(),0);
		}
	    }
#endif /* TLOG */
	} else if (snd_move) {		/* Or move it */
	    int x;
	    x = zrename(filnam,snd_move);
#ifdef TLOG
	    if (tralog) {
		if (x > -1) {
		    tlog(F110," moved to ",snd_move,0);
		} else {
		    tlog(F110," move failed:",ck_errstr(),0);
		}
	    }
#endif /* TLOG */
	} else if (snd_rename) {	/* Or rename it */
	    char *s = snd_rename;	/* Renaming string */
#ifndef NOSPL
	    int y;			/* Pass it thru the evaluator */
	    extern int cmd_quoting;	/* for \v(filename) */
	    if (cmd_quoting) {		/* But only if cmd_quoting is on */
		y = MAXRP;
		s = (char *)srvcmd;
		zzstring(snd_rename,&s,&y);
		s = (char *)srvcmd;
	    }
#endif /* NOSPL */
	    if (s) if (*s) {
		int x;
		x = zrename(filnam,s);
#ifdef TLOG
	    if (tralog) {
		if (x > -1) {
		    tlog(F110," renamed to",s,0);
		} else {
		    tlog(F110," rename failed:",ck_errstr(),0);
		}
	    }
#endif /* TLOG */
	    }
	}
    }
    if (czseen) {			/* Check group interruption flag */
	g = 0;				/* No more files if interrupted */
    } else {				/* Otherwise... */
	while (1) {			/* Keep trying... */
	    g = gnfile();		/* Get next file */
	    debug(F111,"<sseof>Y gnfile",filnam,g);
	    if (g == 0 && gnferror == 0) /* No more, stop trying */
	      break;
	    if (g > 0) {		/* Have one */
		if (sfile(xflg)) {	/* Try to open and send F packet */
		    BEGIN ssfile;	/* If OK, enter send-file state */
		    break;		/* and break out of loop. */
		}
	    } /* Otherwise keep trying to get one we can send... */
	}
    }
    if (g == 0) {
	debug(F101,"<sseof>Y no more files","",czseen);
	tsecs = gtimer();		/* Get statistics timers */
#ifdef GFTIMER
	fptsecs = gftimer();
#endif /* GFTIMER */
	seot();				/* Send EOT packet */
	BEGIN sseot;			/* Enter send-eot state */
    }
}

<sseot>Y {				/* Got ACK to EOT */
    debug(F101,"sseot justone","",justone);
    RESUME;				/* All done, just quit */
}

E {					/* Got Error packet, in any state */
    char *s = "";
    window(1);				/* Close window */
    timint = s_timint;			/* Restore original timeout */
    if (*epktmsg)			/* Message from Error packet */
      s = (char *)epktmsg;
    if (!*s) {				/* If not there then maybe here */
	s = (char *)rdatap;
	ckstrncpy((char *)epktmsg,(char *)rdatap,PKTMSGLEN);
    }
    if (!*s)				/* Hopefully we'll never see this. */
      s = "Unknown error";
    success = 0;			/* For IF SUCCESS/FAIL. */
    debug(F101,"ckcpro.w justone at E pkt","",justone);

    success = 0;			/* Transfer failed */
    xferstat = success;			/* Remember transfer status */
    if (!epktsent) {
	x = quiet; quiet = 1;		/* Close files silently, */
	epktrcvd = 1;			/* Prevent messages from clsof() */
	clsif();
	clsof(1); 			/* discarding any output file. */
	ermsg(s);			/* Issue the message (calls screen). */
	quiet = x;			/* Restore quiet state */
    }
    tstats();				/* Get stats */
/*
  If we are executing commands from a command file or macro, let the command
  file or macro decide whether to exit, based on SET { TAKE, MACRO } ERROR.
*/
    if (
#ifndef NOICP
	!xcmdsrc &&
#endif /* NOICP */
	backgrd && !server)
      fatal("Protocol error");
    xitsta |= (what & W_KERMIT);	/* Save this for doexit(). */
#ifdef CK_TMPDIR
/* If we were cd'd temporarily to another device or directory ... */
    if (f_tmpdir) {
	int x;
	x = zchdir((char *) savdir);	/* ... restore previous directory */
	f_tmpdir = 0;			/* and remember we did it. */
	debug(F111,"ckcpro.w E tmpdir restored",savdir,x);
    }
#endif /* CK_TMPDIR */
#ifdef IKSDB
    if (ikdbopen) slotstate(what,"ERROR", (char *)epktmsg, "");
#endif /* IKSDB */
    RESUME;
}

q { success = 0; QUIT; }		/* Ctrl-C or connection loss. */

. {					/* Anything not accounted for above */
    errpkt((CHAR *)"Unexpected packet type"); /* Give error message */
    window(1);
    xitsta |= (what & W_KERMIT);	/* Save this for doexit(). */
    RESUME;				/* and quit */
}

%%

/*
  From here down to proto() are routines that were moved out of the state
  table switcher because the resulting switch() had become too large.
  To move the contents of a state-table case to a routine:
    1. Add a prototype to the list above the state table switcher.
    2. Make a routine with an appropriate name, returning int.
    3. Move the code into it.
    4. Put a call to the new routine in the former spot:
         rc = name_of_routine();
         if (rc > -1) return(rc);
    5. Add "return(-1);" after every RESUME, SERVE, or BEGIN macro and
       at the end if the code is open-ended.
*/

#ifdef UNIX
/*
  M P S A F E  --  Determines if it's safe to place the argumet "s" on
  a shell-parsed command line, unquoted.  Intended for use in MAIL and PRINT.

  s is either the remote side's MAIL address / PRINT options text from the
  Disposition attribute, or the as-name it chose for the received file (used as
  the MAIL subject).   All of these are untrusted, and end up in a command
  string handed to sh -c.

  Returns 1 if every character in s is on the allowed list (letters, digits, and
  a small set of punctuation that real addresses and print options actually
  use), 0 if s contains anything else, notably any shell metacharacter.

  The parameter first, if nonzero, causes mpsafe() to reject a '-' at the start
  of s or following any space in s.  Both the MAIL address and the MAIL subject
  are treated as unsafe if they start with '-'; callers pass first=1 for both.
  PRINT options are the exception: starting with '-' is what the feature is for
  (they're command-line flags to the print command), so callers pass first=0
  there.

  Checking every word, not just s[0], matters because a space is on the allowed
  character list.  A space can't introduce a new command, so allowing it is safe
  with respect to shell injection.  It only lets the shell split s  which is
  necessary behavior for multiple space-separated MAIL recipients.
*/
int
#ifdef CK_ANSIC
mpsafe( char * s, int first )
#else
mpsafe(s,first) char * s; int first;
#endif /* CK_ANSIC */
{
    int i;
    int atword = 1;			/* At start of s or after a space */
    for (i = 0; s[i]; i++) {
	char c = s[i];
	if (c == ' ') {
	    atword = 1;
	    continue;
	}
	if (isalnum((unsigned char)c)) {
	    atword = 0;
	    continue;
	}
	switch (c) {
	  case '.': case '_': case '+': case '@':
	  case '/': case ':': case ',': case '%': case '=':
	    atword = 0;
	    break;
	  case '-':
	    if (first && atword)
	      return(0);
	    atword = 0;
	    break;
	  default:
	    return(0);
	}
    }
    return(1);
}
#endif /* UNIX */

static int
rcv_firstdata() {
    extern int dispos;
    debug(F101,"rcv_firstdata","",dispos);

    if (discard) {			/* if we're discarding the file */
	ack1((CHAR *)"X");		/* just ack the data like this. */
	cancel++;			/* and count it */
	BEGIN rdpkt;			/* and wait for more data packets. */
	return(-1);
    } else {				/* Not discarding. */
	rf_err = "Can't open file";
	if (xflg) {			/* If screen data */
	    if (remfile) {		/* redirected to file */
		if (rempipe)		/* or pipe */
		  x = openc(ZOFILE,remdest); /* Pipe: start command */
		else
		  x = opena(remdest,&iattr); /* File: open with attributes */
	    } else {			/* otherwise */
		x = opent(&iattr);	/* "open" the screen */
	    }
	} else {			/* otherwise */
#ifdef CALIBRATE
	    if (calibrate) {		/* If calibration run */
		x = ckopenx(&iattr);	/* open nothing */
#ifdef STREAMING
		if (streaming)		/* Streaming */
		  fastack();		/* ACK without ACKing. */
		else
#endif /* STREAMING */
		  ack();		/* Send real ACK */
		BEGIN rdpkt;		/* Proceed to next state */
		return(-1);
	    } else
#endif /* CALIBRATE */
#ifdef UNIX
/*
  In UNIX we can pipe the file data into the mail program, which is to be
  preferred to writing it out to a temp file and then mailing it afterwards.
  This depends rather heavily on all UNIXes having a mail command that
  accepts '-s "subject"' on the command line.  MAILCMD (e.g. mail, Mail, mailx)
  is defined in ckufio.c.

  The address (for MAIL) or options (for PRINT) text comes from the remote
  side's Disposition attribute, and the subject (ofilnam) can be influenced by
  the remote side's as-name.  Either way, they're both untrusted and both end up
  in a command string bassed to sh -c, so mpsafe() screens them first.
*/
	    if (dispos == 'M') {	/* Mail... */
		char *s;
		char * subj;
		char * tmp = NULL;
		int n = 0;
		extern char *MAILCMD;
		s = iattr.disp.val + 1;
		subj = mpsafe(ofilnam,1) ? ofilnam : "Kermit";
		if (!mpsafe(s,1)) {
		    debug(F110,"rcv_firstdata mail refused address",s,0);
		    x = 0;
		} else {
		    n = (int)strlen(MAILCMD) +    /* Mail command */
		      (int)strlen(s) +	      /* address */
		      (int)strlen(subj) + 32;  /* subject */
		    if ((tmp = (char *)malloc(n))) {
			ckmakxmsg(tmp,n,
				  MAILCMD," -s \"",subj,"\" ",s,
				  NULL,NULL,NULL,NULL,NULL,NULL,NULL);
			debug(F111,"rcv_firsdata mail",tmp,(int)strlen(tmp));
			x = openc(ZOFILE,(char *)tmp);
			free(tmp);
		    } else
		      x = 0;
		}
	    } else if (dispos == 'P') { /* Ditto for print */
		char * tmp = NULL;
		int n;
		extern char *PRINTCMD;
		if (!mpsafe(iattr.disp.val+1,0)) {
		    debug(F110,"rcv_firstdata print refused options",
			  iattr.disp.val+1,0);
		    x = 0;
		} else {
		    n = (int)strlen(PRINTCMD) +
		      (int)strlen(iattr.disp.val+1) + 4;
		    if ((tmp = (char *)malloc(n))) {
			sprintf(tmp,	/* safe (prechecked) */
				"%s %s", PRINTCMD, iattr.disp.val + 1);
			x = openc(ZOFILE,(char *)tmp);
			free(tmp);
		    } else
		      x = 0;
		}
	    } else
#endif /* UNIX */
	      x = opena(filnam,&iattr);	/* open the file, with attributes */
	}
	if (x) {			/* If file was opened ok */
	    int rc, qf;
#ifndef NOSPL
	    qf = query;
#else
	    qf = 0;
#endif /* NOSPL */

#ifdef CKTUNING
	    rc = (binary && !parity) ?
	      bdecode(rdatap,putfil):
	       decode(rdatap, qf ? puttrm : putfil, 1);
#else
	    rc = decode(rdatap, qf ? puttrm : putfil, 1);
#endif /* CKTUNING */
	    if (rc < 0) {
		errpkt((CHAR *)"Error writing data");
		RESUME;
		return(-1);
	    }
#ifdef STREAMING
	    if (streaming)		/* Streaming was negotiated */
	      fastack();		/* ACK without ACKing. */
	    else
#endif /* STREAMING */
	      ack();			/* acknowledge it */
	    BEGIN rdpkt;		/* and switch to receive-data state */
	    return(-1);
	} else {			/* otherwise */
	    errpkt((CHAR *) rf_err);	/* send error packet */
	    RESUME;			/* and quit. */
	    return(-1);
	}
    }
}

static int
rcv_shortreply() {
#ifdef PKTZEROHACK
    success = 0;
    debug(F111,"rcv_shortreply",rdatap,ipktlen);
    if (ipktack[0] && !strncmp(ipktack,(char *)rdatap,ipktlen)) {
	/* No it's the ACK to the I packet again */
	x = scmd(vcmd,(CHAR *)cmarg);	/* So send the REMOTE command again */
	/* Maybe this should be resend() */
	debug(F110,"IPKTZEROHACK",ipktack,x);
	if (x < 0) {
	    errpkt((CHAR *)srimsg);
	    RESUME;
	    return(-1);
	}
    } else {
	ipktack[0] = NUL;
#endif /* PKTZEROHACK */
	urserver = 1;
#ifndef NOSERVER
#ifndef NOSPL
	if (query) {			/* If to query, */
	    qbufp = querybuf;		/*  initialize query response buffer */
	    qbufn = 0;
	    querybuf[0] = NUL;
	}
#endif /* NOSPL */
	x = 1;
	if (remfile) {			/* Response redirected to file */
	    rf_err = "Can't open file";
	    if (rempipe)		/* or pipe */
	      x =
#ifndef NOPUSH
		zxcmd(ZOFILE,remdest)	/* Pipe: Start command */
#else
		0
#endif /* NOPUSH */
		;
	    else
	      x = opena(remdest,&iattr); /* File: Open with attributes */
	    debug(F111,"rcv_shortreply remfile",remdest,x);
	} else {
	    x = opent(&iattr);		/* "open" the screen */
	}
	if (x) {			/* If file was opened ok */
	    if (decode(rdatap,
#ifndef NOSPL
		       (query || !remfile) ? puttrm :
#else
		       !remfile ? puttrm :
#endif /* NOSPL */
		       zputfil, 1) < 0) { /* Note: zputfil, not putfil. */
		errpkt((CHAR *)"Error writing data");
		RESUME;
		return(-1);
	    } else {
		if (rdatap) {		/* If we had data */
                    if (*rdatap) {      /* add a line terminator */
                        if (remfile) {  /* to file */
                            zsoutl(ZOFILE,"");
                        } else {        /* or to screen. */
#ifndef NOICP
                            if (!query || !xcmdsrc)
#endif /* NOICP */
                              if (!(quiet && rcdactive))
                                conoll("");
                        }
                    }
                }
		if (bye_active && network) { /* I sent BYE or REMOTE LOGOUT */
		    msleep(500);	/* command and got the ACK... */
		    bye_active = 0;
		    ttclos(0);
		}
		clsof(0);
		if (!epktsent && !epktrcvd) /* If no error packet... */
		  success = 1;		/* success. */
		RESUME;
		return(-1);
	    }
	} else {			/* File not opened OK */
	    errpkt((CHAR *) rf_err);	/* send error message */
	    RESUME;			/* and quit. */
	    return(-1);
	}
#endif /* NOSERVER */
#ifdef PKTZEROHACK
    }
#endif /* PKTZEROHACK */
    debug(F101,"rcv_shortreply fallthru","",success);
    return(-1);
}


static int
srv_query() {
#ifndef NOSERVER
#ifndef NOSPL
    char c;
#ifdef CKSYSLOG
    if (ckxsyslog >= SYSLG_PR && ckxlogging)
      cksyslog(SYSLG_PR, 1, "server", "REMOTE QUERY", (char *)srvcmd);
#endif /* CKSYSLOG */
#ifdef IKSDB
    if (ikdbopen) slotstate(what,"REMOTE QUERY", (char *)(srvcmd+2), "");
#endif /* IKSDB */
    c = *(srvcmd+2);			/* Q = Query, S = Set */
    if (c == 'Q') {			/* Query */
	if (!ENABLED(en_que)) { /* Security */
	    errpkt((CHAR *)"REMOTE QUERY disabled");
	    RESUME;
	    return(-1);
	} else {			/* Query allowed */
	    int n; char *p, *q;
	    qbufp = querybuf;		/* Wipe out old stuff */
	    qbufn = 0;
	    querybuf[0] = NUL;
	    p = (char *) srvcmd + 3;	/* Pointer for making wrapper */
	    n = strlen((char *)srvcmd);	/* Position of end */
	    c = *(srvcmd+4);		/* Which type of variable */

	    if (*(srvcmd+6) == CMDQ) {	/* Starts with command quote? */
		p = (char *) srvcmd + 6; /* Take it literally */
		if (*p == CMDQ) p++;
	    } else {			/* They played by the rules */
		if (c == 'K') {		/* Kermit variable */
		    int k;
		    k = (int) strlen(p);
		    if (k > 0 && p[k-1] == ')') {
			p = (char *)(srvcmd + 4);
			*(srvcmd+4) = CMDQ;
			*(srvcmd+5) = 'f'; /* Function, so make it \f...() */
		    } else {
			*(srvcmd+3) = CMDQ; /* Stuff wrapping into buffer */
			*(srvcmd+4) = 'v';  /* Variable, so make it \v(...) */
			*(srvcmd+5) = '(';  /* around variable name */
			*(srvcmd+n) = ')';
			*(srvcmd+n+1) = NUL;
		    }
		} else {
		    *(srvcmd+3) = CMDQ; /* Stuff wrapping into buffer */
		    *(srvcmd+4) = 'v'; /*  Variable, so make it \v(...) */
		    *(srvcmd+5) = '(';	/* around variable name */
		    *(srvcmd+n) = ')';
		    *(srvcmd+n+1) = NUL;
		    if (c == 'S') {	/* System variable */
			*(srvcmd+4) = '$'; /*  so it's \$(...) */
		    } else if (c == 'G') { /* Non-\ Global variable */
			*(srvcmd+4) = 'm'; /*  so wrap it in \m(...) */
		    }
		}
	    }				/* Now evaluate it */
	    n = QBUFL;			/* Max length */
	    q = querybuf;		/* Where to put it */
	    if (zzstring(p,&q,&n) < 0) {
		errpkt((n > 0) ? (CHAR *)"Can't get value"
		               : (CHAR *)"Value too long"
		       );
		RESUME;
		return(-1);
	    } else {
		if (encstr((CHAR *)querybuf) > -1) { /* Encode it */
		    ack1(data);		/* If it fits, send it back in ACK */
		    success = 1;
		    RESUME;
		    return(-1);
		} else if (sndstring(querybuf)) { /* Long form response */
		    BEGIN ssinit;
		    return(-1);
		} else {		/* sndhlp() fails */
		    errpkt((CHAR *)"Can't send value");
		    RESUME;
		    return(-1);
		}
	    }
	}
    } else if (c == 'S') {		/* Set (assign) */
	if (!ENABLED(en_asg)) {		/* Security */
	    errpkt((CHAR *)"REMOTE ASSIGN disabled");
	    RESUME;
	    return(-1);
	} else {			/* OK */
	    int n;
	    n = xunchar(*(srvcmd+3));	/* Length of name */
	    n = 3 + n + 1;		/* Position of length of value */
	    *(srvcmd+n) = NUL;		/* Don't need it */
	    if (addmac((char *)(srvcmd+4),(char *)(srvcmd+n+1)) < 0)
	      errpkt((CHAR *)"REMOTE ASSIGN failed");
	    else {
		ack();
		success = 1;
	    }
	    RESUME;
	    return(-1);
	}
    } else {
	errpkt((CHAR *)"Badly formed server command");
	RESUME;
	return(-1);
    }
#else
    errpkt((CHAR *)"Variable query/set not available");
    RESUME;
    return(-1);
#endif /* NOSPL */
#endif /* NOSERVER */
}

static int
srv_copy() {
#ifndef NOSERVER
#ifdef CKSYSLOG
    if (ckxsyslog >= SYSLG_PR && ckxlogging)
      cksyslog(SYSLG_PR, 1, "server", "REMOTE COPY", (char *)srvcmd);
#endif /* CKSYSLOG */
#ifdef ZCOPY
    if (!ENABLED(en_cpy)) {
	errpkt((CHAR *)"REMOTE COPY disabled");
	RESUME;
	return(-1);
    } else {
	char *str1, *str2, f1[256], f2[256];
	int  len1, len2;
        len1 = xunchar(srvcmd[1]);	/* Separate the parameters */
        len2 = xunchar(srvcmd[2+len1]);
        strncpy(f1,(char *)(srvcmd+2),len1);
        f1[len1] = NUL;
        strncpy(f2,(char *)(srvcmd+3+len1),len2);
        f2[len2] = NUL;
#ifdef IKSDB
	if (ikdbopen) slotstate(what,"REMOTE COPY", f1, f2);
#endif /* IKSDB */
	if (!ENABLED(en_cwd)) {		/* If CWD is disabled */
	    zstrip(f1,&str1);		/* and they included a pathname, */
            zstrip(f2,&str2);
	    if (strcmp(f1,str1) || strcmp(f2,str2)) { /* Refuse. */
		errpkt((CHAR *)"Access denied");
		RESUME;			/* Remember, this is not a goto! */
		return(-1);
	    }
	}
	if (state == generic) {		/* It's OK to go ahead. */
            if (zcopy(f1,f2)) {		/* Try */
		errpkt((CHAR *)"Can't copy file"); /* give error message */
	    } else {
		success = 1;
		ack();
	    }
            RESUME;			/* wait for next server command */
	    return(-1);
	}
    }
    return(-1);
#else /* no ZCOPY */
    errpkt((CHAR *)"REMOTE COPY not available"); /* give error message */
    RESUME;				/* wait for next server command */
    return(-1);
#endif /* ZCOPY */
#endif /* NOSERVER */
}

static int
srv_rename() {
#ifndef NOSERVER
#ifdef CKSYSLOG
    if (ckxsyslog >= SYSLG_PR && ckxlogging)
      cksyslog(SYSLG_PR, 1, "server", "REMOTE RENAME", (char *)srvcmd);
#endif /* CKSYSLOG */
#ifdef ZRENAME
    if (!ENABLED(en_ren)) {
	errpkt((CHAR *)"REMOTE RENAME disabled");
	RESUME;
	return(-1);
    } else {				/* RENAME is enabled */
	char *str1, *str2, f1[256], f2[256];
	int len1, len2;
	len1 = xunchar(srvcmd[1]);	/* Separate the parameters */
	len2 = xunchar(srvcmd[2+len1]);
	strncpy(f1,(char *)(srvcmd+2),len1);
	f1[len1] = NUL;
	strncpy(f2,(char *)(srvcmd+3+len1),len2);
	f2[len2] = NUL;
	len2 = xunchar(srvcmd[2+len1]);
	strncpy(f1,(char *)(srvcmd+2),len1);
	f1[len1] = NUL;
	strncpy(f2,(char *)(srvcmd+3+len1),len2);
	f2[len2] = NUL;
#ifdef IKSDB
	if (ikdbopen) slotstate(what,"REMOTE RENAME", f1, f2);
#endif /* IKSDB */
	if (!ENABLED(en_cwd)) {		/* If CWD is disabled */
	    zstrip(f1,&str1);		/* and they included a pathname, */
	    zstrip(f2,&str2);
	    if ( strcmp(f1,str1) || strcmp(f2,str2) ) { /* refuse. */
		errpkt((CHAR *)"Access denied");
		RESUME;			/* Remember, this is not a goto! */
		return(-1);
	    }
	}
	if (state == generic) {		/* It's OK to go ahead. */
	    if (zrename(f1,f2)) {	/* Try */
		errpkt((CHAR *)"Can't rename file"); /* Give error msg */
	    } else {
		success = 1;
		ack();
	    }
	    RESUME;			/* Wait for next server command */
	    return(-1);
	}
    }
    return(-1);
#else /* no ZRENAME */
    /* Give error message */
    errpkt((CHAR *)"REMOTE RENAME not available");
    RESUME;				/* Wait for next server command */
    return(-1);
#endif /* ZRENAME */
#endif /* NOSERVER */
}

static int
srv_login() {
#ifndef NOSERVER
    char f1[LOGINLEN+1], f2[LOGINLEN+1], f3[LOGINLEN+1];
    CHAR *p;
    int len, i;

    debug(F101,"REMOTE LOGIN x_login","",x_login);
    debug(F101,"REMOTE LOGIN x_logged","",x_logged);

    f1[0] = NUL; f2[0] = NUL; f3[0] = NUL;
    len = 0;
    if (srvcmd[1])			/* First length field */
      len = xunchar(srvcmd[1]);		/* Separate the parameters */

    if (x_login) {			/* Login required */
	if (x_logged) {			/* And already logged in */
	    if (len > 0) {		/* Logging in again */
		errpkt((CHAR *)"Already logged in.");
	    } else {			/* Logging out */
		debug(F101,"REMOTE LOGOUT","",x_logged);
#ifdef CKSYSLOG
		if (ckxsyslog >= SYSLG_PR && ckxlogging)
		  cksyslog(SYSLG_PR, 1, "server", "REMOTE LOGOUT", NULL);
#endif /* CKSYSLOG */
#ifdef IKSDB
		if (ikdbopen) slotstate(what,"REMOTE LOGOUT", "", "");
#endif /* IKSDB */
		tlog(F110,"Logged out",x_user,0);
		ack1((CHAR *)"Logged out");
		success = 1;
		msleep(500);
#ifdef CK_LOGIN
		x_logged = 0;
#ifdef IKSD
		if (inserver)
		  ckxlogout();
#endif /* IKSD */
#endif /* CK_LOGIN */
	    }
	} else {			/* Not logged in yet */
	    debug(F101,"REMOTE LOGIN len","",len);
	    if (len > 0) {		/* Have username */
#ifdef CKSYSLOG
		if (ckxsyslog >= SYSLG_PR && ckxlogging)
		  cksyslog(SYSLG_PR, 1, "server", "REMOTE LOGIN", NULL);
#endif /* CKSYSLOG */
		if (len > LOGINLEN) {
		    errpkt((CHAR *)"Username too long");
		}
		p = srvcmd + 2;		/* Point to it */
		for (i = 0; i < len; i++) /* Copy it */
		  f1[i] = p[i];
		f1[len] = NUL;		/* Terminate it */
		p += len;		/* Point to next length field */
		if (*p) {		/* If we have one */
		    len = xunchar(*p++); /* decode it */
		    if (len > 0 && len <= LOGINLEN) {
			for (i = 0; i < len; i++) /* Same deal for password */
			  f2[i] = p[i];
			f2[len] = NUL;
			p += len;	/* And account */
			if (*p) {
			    len = xunchar(*p++);
			    if (len > 0 && len <= LOGINLEN) {
				for (i = 0; i < len; i++)
				  f3[i] = p[i];	/* Set but never used */
				f3[len] = NUL; /* (because account not used) */
			    }
			}
		    }
		}
		debug(F101,"REMOTE LOGIN 1","",x_logged);
#ifdef IKSD
#ifdef CK_LOGIN
		if (inserver) {		/* Log in to system for real */
		    x_logged = ckxlogin((CHAR *)f1,(CHAR *)f2,NULL,0);
		    debug(F101,"REMOTE LOGIN 2","",x_logged);
		    if (x_logged) {	/* Count attempts */
			logtries = 0;
			justone = 1;
		    } else {
			logtries++;
			sleep(logtries);
		    }
		} else
#endif /* CK_LOGIN */
#endif /* IKSD */
		  if (x_user && x_passwd) { /* User and password must match */
		      if (!strcmp(x_user,f1)) /* SET SERVER LOGIN */
			if (!strcmp(x_passwd,f2))
			  x_logged = 1;
		      debug(F101,"REMOTE LOGIN 3","",x_logged);
		  } else if (x_user) {	/* Only username given, no password */
		      if (!strcmp(x_user,f1)) /* so only username must match */
			x_logged = 1;
		      debug(F101,"REMOTE LOGIN 4","",x_logged);
		  }
#ifdef CK_LOGIN
                else {
		    x_logged = ckxlogin((CHAR *)f1,(CHAR *)f2,NULL,0);
		    debug(F101,"REMOTE LOGIN 5","",x_logged);
                }
#endif /* CK_LOGIN */
		if (x_logged) {		/* Logged in? */
		    tlog(F110,"Logged in", x_user, 0);
		    if (isguest)
		      ack1((CHAR *)"Logged in as guest - restrictions apply");
		    else
		      ack1((CHAR *)"Logged in");
		    success = 1;
		} else {
		    tlog(F110,"Login failed", f1, 0);
		    errpkt((CHAR *)"Access denied.");
#ifdef IKSD
#ifdef CK_LOGIN
		    if (inserver && logtries > 2)
		      ckxlogout();
#endif /* CK_LOGIN */
#endif /* IKSD */
		}
	    } else {			/* LOGOUT */
		errpkt((CHAR *)"Logout ignored");
	    }
	}
    } else {				/* Login not required */
	if (len > 0)
	  errpkt((CHAR *)"Login ignored.");
	else
	  errpkt((CHAR *)"Logout ignored.");
    }
#endif /* NOSERVER */
    RESUME;
    return(-1);
}

static int
srv_timeout() {
    /* K95 does this its own way */
    if (idletmo) {
#ifdef IKSD
        if (inserver) {
           printf("\r\nIKSD IDLE TIMEOUT: %d sec\r\n", srvidl);
           doexit(GOOD_EXIT,xitsta);
        }
#endif /* IKSD */
	idletmo = 0;
	printf("\r\nSERVER IDLE TIMEOUT: %d sec\r\n", srvidl);
	xitsta |= (what & W_KERMIT);
	QUIT;
    }
#ifndef NOSERVER
    else if (fatalio) {			/* Connection lost */
#ifdef CKSYSLOG
	  if (ckxsyslog >= SYSLG_PR && ckxlogging)
	    cksyslog(SYSLG_PR, 1, "server", "Connection lost", NULL);
#endif /* CKSYSLOG */
#ifdef IKSDB
	  if (ikdbopen) slotstate(what,"SERVER DISCONNECT",(char *)srvcmd, "");
#endif /* IKSDB */
	xitsta |= what;
	QUIT;
    } else if (interrupted) {		/* Interrupted by hand */
	if (!ENABLED(en_fin)) {
	    errpkt((CHAR *)"QUIT disabled");
	    RESUME;
	    return(-1);
	} else {
	    if (what == W_SEND || what == W_RECV || what == W_REMO) {
		success = 0;
#ifdef CKSYSLOG
		if (ckxsyslog >= SYSLG_PR && ckxlogging)
		  cksyslog(SYSLG_PR, 1, "server", "Interrupted", NULL);
#endif /* CKSYSLOG */
	    } else if (what == W_NOTHING && filcnt == 0) {
		success = 1;
	    } /* Otherwise leave success alone */
	    xitsta |= (what & W_KERMIT);
	    QUIT;
	}
    } else {				/* Shouldn't happen */
	debug(F100,"SERVER (top) GOT UNEXPECTED 'q'","",0);
	QUIT;
    }
#endif /* NOSERVER */
}

static int
rcv_s_pkt() {
#ifndef NOSERVER
    if (state == rgen)
      urserver = 1;
    if (/* state == serve && */ x_login && !x_logged) {
	errpkt((CHAR *)"Login required");
	SERVE;
    } else
#endif /* NOSERVER */
      if (state == serve && !ENABLED(en_sen)) { /* Not in server mode */
	errpkt((CHAR *)"SEND disabled"); /* when SEND is disabled. */
	RESUME;
	return(-1);
    } else {				/* OK to go ahead. */
#ifdef CK_TMPDIR
	if (dldir && !f_tmpdir) {	/* If they have a download directory */
	    debug(F110,"receive download dir",dldir,0);
	    if ((s = zgtdir())) {		/* Get current directory */
		debug(F110,"receive current dir",s,0);
		if (zchdir(dldir)) {	/* Change to download directory */
		    debug(F100,"receive zchdir ok","",0);
		    ckstrncpy(savdir,s,TMPDIRLEN);
		    f_tmpdir = 1;	/* Remember that we did this */
		} else
		  debug(F100,"receive zchdir failed","",0);
	    }
	}
#endif /* CK_TMPDIR */
	nakstate = 1;			/* Can send NAKs from here. */
	rinit(rdatap);			/* Set parameters */
	if (bctf) {
	    bctu = 3;
	    bctl = 3;
	} else {
	    bctu = bctr;	       /* switch to agreed-upon block check */
	    bctl = (bctu == 4) ? 2 : bctu; /* Set block-check length */
	}
	what = W_RECV;			/* Remember we're receiving */
	lastxfer = W_RECV;
	resetc();			/* Reset counters */
	rtimer();			/* Reset timer */
#ifdef GFTIMER
	rftimer();
#endif /* GFTIMER */
	streamon();
	BEGIN rfile;			/* Go into receive-file state */
    }
    return(-1);
}


/* END OF ROUTINES MOVED OUT OF STATE MACHINE */


/*  P R O T O  --  Protocol entry function  */

static int is_tn = 0;			/* It's a Telnet connection */

#ifdef CK_SPEED
int f_ctlp = 0;				/* Control-character prefix table */
#endif /* CK_SPEED */

/*
  This is simply a wrapper for the real protocol function just below,
  that saves any items that might be changed automatically by protocol
  negotiations and then restores them upon exit from protocol mode.
*/
VOID
proto() {
    extern int b_save, f_save, c_save, ss_save, slostart, reliable, urclear;
    extern int local, quiet, inserver, fnc_lastdiscard;
    extern long filcnt, filrej;
#ifndef NOCSETS
    extern int fcharset, fcs_save, tcharset, tcs_save;
#endif /* NOCSETS */

#ifdef PIPESEND
    extern int pipesend;
#endif /* PIPESEND */
#ifndef NOLOCAL
#ifdef OS2
    extern int cursorena[], cursor_save, term_io;
    extern BYTE vmode;
    int term_io_save;
#endif /* OS2 */
#endif /* NOLOCAL */
#ifdef TNCODE
    int _u_bin=0, _me_bin = 0;
#ifdef IKS_OPTION
    int /* _u_start=0, */ _me_start = 0;
#endif /* IKS_OPTION */
#endif /* TNCODE */
#ifdef PATTERNS
    int pa_save;
    int i;
#endif /* PATTERNS */
    int scan_save;

#ifdef PATTERNS
    pa_save = patterns;
#endif /* PATTERNS */
    scan_save = filepeek;

    myjob = sstate;

#ifdef CK_LOGIN
    if (isguest) {			/* If user is anonymous */
	en_pri = 0;			/* disable printing */
	en_mai = 0;			/* and disable email */
	en_del = 0;			/* and file deletion */
    }
#endif /* CK_LOGIN */

#ifndef NOLOCAL
#ifdef OS2
    cursor_save = cursorena[vmode];
    cursorena[vmode] = 0;
    term_io_save = term_io;
    term_io = 0;
#endif /* OS2 */
#endif /* NOLOCAL */
    b_save = binary;			/* SET FILE TYPE */
    f_save = fncnv;			/* SET FILE NAMES */
    c_save = bctr;
    p_save = fnspath;
    r_save = recursive;
    s_timint = timint;
    ss_save = slostart;
#ifndef NOCSETS
    fcs_save = fcharset;
    tcs_save = tcharset;
#endif /* NOCSETS */

    if (reliable == SET_ON)
      slostart = 0;
    is_tn = (!local && sstelnet)
#ifdef TNCODE
      || (local && network && ttnproto == NP_TELNET)
#endif /* TNCODE */
	;
#ifdef TNCODE
    if (is_tn) {
        if (tn_b_xfer && !(sstelnet || inserver)) {
	    /* Save the current state of Telnet Binary */
	    _u_bin = TELOPT_U(TELOPT_BINARY);
	    _me_bin = TELOPT_ME(TELOPT_BINARY);

	    /* If either direction is not Binary attempt to negotiate it */
	    if (!_u_bin && TELOPT_U_MODE(TELOPT_BINARY) != TN_NG_RF) {
		tn_sopt(DO,TELOPT_BINARY);
		TELOPT_UNANSWERED_DO(TELOPT_BINARY) = 1;
	    }
	    if (!_me_bin && TELOPT_ME_MODE(TELOPT_BINARY) != TN_NG_RF) {
		tn_sopt(WILL,TELOPT_BINARY);
		TELOPT_UNANSWERED_WILL(TELOPT_BINARY) = 1;
	    }
	    if (!(_me_bin && _u_bin))
	      tn_wait("proto set binary mode");
        }
#ifdef IKS_OPTION
#ifdef CK_XYZ
        if (protocol != PROTO_K) {	/* Non-Kermit protocol selected */
            if (TELOPT_U(TELOPT_KERMIT) &&
                TELOPT_SB(TELOPT_KERMIT).kermit.u_start) {
                iks_wait(KERMIT_REQ_STOP,0); /* Stop the other Server */
		/* _u_start = 1; */
            }
            if (TELOPT_ME(TELOPT_KERMIT) &&
                TELOPT_SB(TELOPT_KERMIT).kermit.me_start) {
                tn_siks(KERMIT_STOP);	/* I'm not servering */
		TELOPT_SB(TELOPT_KERMIT).kermit.me_start = 0;
		_me_start = 1;
            }
        } else
#endif /* CK_XYZ */
        if (sstate == 'x' || sstate == 'v') { /* Responding to a request */
            if (!inserver && TELOPT_U(TELOPT_KERMIT) &&
                TELOPT_SB(TELOPT_KERMIT).kermit.u_start) {
                iks_wait(KERMIT_REQ_STOP,0); /* Stop the other Server */
		/* _u_start = 1; */
            }
            if (TELOPT_ME(TELOPT_KERMIT) &&
                !TELOPT_SB(TELOPT_KERMIT).kermit.me_start) {
                tn_siks(KERMIT_START);	/* Send Kermit-Server Start */
		TELOPT_SB(TELOPT_KERMIT).kermit.me_start = 1;
            }
        } else {			/* Initiating a request */
            if (TELOPT_ME(TELOPT_KERMIT) &&
                TELOPT_SB(TELOPT_KERMIT).kermit.me_start) {
                tn_siks(KERMIT_STOP);	/* I'm not servering */
		TELOPT_SB(TELOPT_KERMIT).kermit.me_start = 0;
		_me_start = 1;
            }
            if (TELOPT_U(TELOPT_KERMIT) &&
	        !TELOPT_SB(TELOPT_KERMIT).kermit.u_start) {
		/* Send Req-Server-Start */
                if (!iks_wait(KERMIT_REQ_START,0)) {
                    if (sstate != 's') {
			success = 0;	/* Other Kermit refused to serve */
			if (local)
			  printf("A Kermit Server is not available\r\n");
			debug(F110,"proto()",
                             "A Kermit Server is not available",0);
			tlog(F110,"IKS client/server failure",
                             "A Kermit Server is not available",0);
			goto xxprotox;
                    }
		}
            }
        }
#endif /* IKS_OPTION */
#ifdef CK_ENCRYPTION
        if (tn_no_encrypt_xfer && !(sstelnet || inserver)) {
            ck_tn_enc_stop(); /* fdc 2021-12-17 */
        }
#endif /* CK_ENCRYPTION */
    }
#endif /* TNCODE */

    if (!xfrint) connoi();
    xxproto();				/* Call the real protocol function */

#ifdef IKS_OPTION
  xxprotox:
#endif /* IKS_OPTION */
    xferstat = success;			/* Remember transfer status */
    kactive = 0;

#ifdef TNCODE
#ifdef CK_ENCRYPTION
        if (tn_no_encrypt_xfer && !(sstelnet || inserver)) {
            ck_tn_enc_start(); /* fdc 2021-12-17 */
        }
#endif /* CK_ENCRYPTION */
#ifdef IKS_OPTION
    if (TELOPT_ME(TELOPT_KERMIT) &&
        TELOPT_SB(TELOPT_KERMIT).kermit.me_start && !_me_start) {
        tn_siks(KERMIT_STOP);		/* Server is stopped */
	TELOPT_SB(TELOPT_KERMIT).kermit.me_start = 0;
    }
#endif /* IKS_OPTION */
    if (is_tn && tn_b_xfer && !(sstelnet || inserver)) {
        /* if we negotiated Binary mode try to reset it */
        if (!_u_bin) {
            /* Check to see if the state changed during the transfer */
	    if (TELOPT_U(TELOPT_BINARY)) {
		tn_sopt(DONT,TELOPT_BINARY);
		TELOPT_UNANSWERED_DONT(TELOPT_BINARY) = 1;
	    } else
	      _u_bin = 1;		/* So we don't call tn_wait() */
        }
        if (!_me_bin) {
            /* Check to see if the state changed during the transfer */
	    if (TELOPT_ME(TELOPT_BINARY)) {
		tn_sopt(WONT,TELOPT_BINARY);
		TELOPT_UNANSWERED_WONT(TELOPT_BINARY) = 1;
	    } else
	      _me_bin = 1;		/* So we don't call tn_wait() */
	}
	if (!(_me_bin && _u_bin))
	  tn_wait("proto reset binary mode");
    }
#endif /* TNCODE */

#ifdef PATTERNS
    patterns = pa_save;
#endif /* PATTERNS */
    filepeek = scan_save;

#ifdef STREAMING
    streaming = 0;
    /* streamok = 0; */
#endif /* STREAMING */
    urclear = 0;
    if (!success) {
	xitsta |= (what & W_KERMIT);
	tlog(F110," failed:",(char *)epktmsg,0);
    }
/*
  Report when a RECEIVE/GET/SEND-class operation delivered no files, so
  this isn't silent regardless of who called proto().  This
  also covers the case where the protocol itself calls it a success
  (eg, a file was discarded because its name collided with an existing
  file), which the more detailed "RECEIVE- or GET-class command failed"
  hints elsewhere don't.
*/
    if (local && !quiet && !inserver && (!xferstat || filcnt <= filrej)) {
	printf("\r\n No files were transferred");
	if (!xferstat && *epktmsg)
	  printf(": %s", epktmsg);
	else if (xferstat && fnc_lastdiscard)
	  printf(" (refused: destination file already exists)");
	printf(".\r\n");
	fflush(stdout);
    }
    debug(F111,"proto xferstat",epktmsg,xferstat);
    slostart = ss_save;
    if (s_timint > -1) {		/* Because of REMOTE SET */
	timint = s_timint;
	s_timint = -1;
    }
    recursive = r_save;
    fnspath = p_save;
    if (c_save > -1) {			/* Because of REMOTE SET */
	bctr = c_save;
	c_save = -1;
    }
    fncnv   = f_save;
    binary  = b_save;
#ifdef PIPESEND
    pipesend = 0;    			/* Next time might not be pipesend */
#endif /* PIPESEND */
#ifndef NOLOCAL
#ifdef OS2
    cursorena[vmode] = cursor_save;
    term_io = term_io_save;
#endif /* OS2 */
#endif /* NOLOCAL */
}

static VOID
xxproto() {
    int x;
    long lx;
#ifdef CK_XYZ
#ifdef XYZ_INTERNAL
_PROTOTYP( int pxyz, (int) );
#endif /* XYZ_INTERNAL */
#endif /* CK_XYZ */

    char xss[2];			/* String representation of sstate */
    xss[0] = sstate;
    xss[1] = NUL;
    s_timint = timint;

    debug(F101,"xxproto entry justone","",justone);
    success = 0;

    retrieve = 0;			/* Reset these ... */
    reget = 0;
    opkt = 0;

    if (local && ttchk() < 0) {		/* Giving BYE or FIN */
	if (bye_active) {		/* but there is no connection */
	    ttclos(0);
	    success = 1;
	    return;
	}
	/* Ditto for any REMOTE command */
	if (sstate == 'g' && cmarg ) {
	    if (*cmarg == 'L' || *cmarg == 'F' || *cmarg == 'X')
	      success = 1;
	    else
	      printf("?No connection\r\n");
	    return;
	}
    }

/* Set up the communication line for file transfer. */
/* NOTE: All of the xxscreen() calls prior to the wart() invocation */
/* could just as easily be printf's or, for that matter, hints. */

    if (local && (speed < 0L) && (network == 0)) {
	xxscreen(SCR_EM,0,0L,"Sorry, you must 'set speed' first");
	return;
    }
    x = -1;
    if (ttopen(ttname,&x,mdmtyp,cdtimo) < 0) {
	debug(F111,"failed: proto ttopen local",ttname,local);
	xxscreen(SCR_EM,0,0L,"Can't open line");
	return;
    }
    if (x > -1) local = x;
    debug(F111,"proto ttopen local",ttname,local);

    lx = (local && !network) ? speed : -1;
#ifdef NETCONN
#ifdef CK_SPEED
    if (is_tn) {
	ctlp[(unsigned)255] = ctlp[CK_CR] = 1;
	if (parity == 'e' || parity == 'm') ctlp[127] = 1;
	if (flow == FLO_XONX) {		/* Also watch out for Xon/Xoff */
	    ctlp[17] = ctlp[19] = 1;
	    ctlp[17+128] = ctlp[19+128] = 1;
	}
    }
#endif /* CK_SPEED */
#endif /* NETCONN */
    if (ttpkt(lx,flow,parity) < 0) {	/* Put line in packet mode, */
	xxscreen(SCR_EM,0,0L,"Can't condition line");
	return;
    }
    if (local && !network && carrier != CAR_OFF) {
	int x;				/* Serial connection */
	x = ttgmdm();			/* with carrier checking */
	if (x > -1) {
	    if (!(x & BM_DCD)) {
		debug(F101,"proto ttgmdm","",0);
		xxscreen(SCR_EM,0,0L,"Carrier required but not detected");
		return;
	    }
	}
    }
    /*
      Send remote side's "receive" or "server" startup string, if any,
      subject to SET PROTOCOL STARTUP-STRING. */
    if (local && protostartup && ckindex((char *)xss,"srgcjhk",0,0,1)) {
	char *s = NULL;
        if (
#ifdef IKS_OPTION
	    /* Don't send auto-blah string if we know other side is serving */
	    !TELOPT_U(TELOPT_KERMIT) ||
	    !TELOPT_SB(TELOPT_KERMIT).kermit.u_start
#else
	    1
#endif /* IKS_OPTION */
	    ) {
	    if (sstate == 's') {	/* Sending file(s) */
		s = binary ? ptab[protocol].h_b_init : ptab[protocol].h_t_init;
	    } else if (protocol == PROTO_K) { /* Command for server */
		s = ptab[protocol].h_x_init;
	    }
	}
#ifdef CK_SPEED
#ifndef UNPREFIXZERO
	if (protocol == PROTO_K)	/* Because of C-strings... */
	  ctlp[0] = 1;
#endif /* UNPREFIXZERO */
#endif /* CK_SPEED */
	if (s) if (*s) {		/* If we have a command to send... */
	    char tmpbuf[356];
	    int tmpbufsiz = 356;
	    int stuff = -1, stuff2 = -1, len = 0;
	    extern int tnlm;
	    if (sstate == 's') {	/* Sending file(s) */
#ifdef CK_XYZ
		if (protocol == PROTO_X) {
		    char * s2;
		    s2 = cmarg2[0] ? cmarg2 : cmarg;
		    if ((int)strlen(s) + (int)strlen(s2) + 4 < 356)
		      sprintf(tmpbuf, s, s2);
		    else
		      tmpbuf[0] = NUL;
		} else {
#endif /* CK_XYZ */
		    ckmakmsg(tmpbuf, 356, s, NULL, NULL, NULL);
#ifdef CK_XYZ
		}
#endif /* CK_XYZ */
	    } else {			/* Command for server */
		ckstrncpy(tmpbuf,s,356);
	    }
	    ckstrncat(tmpbuf, "\015",sizeof(tmpbuf));
	    if (tnlm)			/* TERMINAL NEWLINE ON */
	      stuff = LF;		/* Stuff LF */
#ifdef TNCODE
	    /* TELNET NEWLINE MODE */
	    if (is_tn) {
		switch (TELOPT_ME(TELOPT_BINARY) ? tn_b_nlm : tn_nlm) {
		  case TNL_CR:
		    break;
		  case TNL_CRNUL:
		    break;
		  case TNL_CRLF:
		    stuff2 = stuff;
		    stuff = LF;
		    break;
		}
	    }
#endif /* TNCODE */

#ifdef NETCONN
#ifdef TCPSOCKET
#ifdef RLOGCODE
	    if (network && ttnproto == NP_RLOGIN) {
		switch (tn_b_nlm) { /* Always BINARY */
		  case TNL_CR:
		    break;
		  case TNL_CRNUL:
		    stuff2 = stuff;
		    stuff  = NUL;
		    break;
		  case TNL_CRLF:
		    stuff2 = stuff;
		    stuff = LF;
		    break;
		}
	    }
#endif /* RLOGCODE */
#endif /* TCPSOCKET */
#endif /* NETCONN */

	    len = strlen(tmpbuf);
	    if (stuff >= 0 && len < tmpbufsiz - 1) {
		tmpbuf[len++] = stuff;
		if (stuff2 >= 0 && len < tmpbufsiz - 1)
		  tmpbuf[len++] = stuff2;
		tmpbuf[len] = NUL;
	    }
	    ttol((CHAR *)tmpbuf,len);
	    if (protocol == PROTO_K)	/* Give remote Kermit time to start */
	      msleep(400);
	}
    }

#ifdef CK_XYZ
    if (protocol != PROTO_K) {		/* Non-Kermit protocol selected */
	char tmpbuf[356];
	int tmpbufsiz = 356;
	char * s = "";

#ifdef CK_TMPDIR
	if (sstate == 'v') {		/* If receiving and... */
	    if (dldir && !f_tmpdir) {	/* if they have a download directory */
                if ((s = zgtdir())) {   /* Get current directory */
		    if (zchdir(dldir)) { /* Change to download directory */
			ckstrncpy(savdir,s,TMPDIRLEN);
			f_tmpdir = 1;	/* Remember that we did this */
		    }
		}
	    }
	}
#endif /* CK_TMPDIR */

#ifdef XYZ_INTERNAL			/* Internal */
	success = !pxyz(sstate);
#else
#ifdef CK_REDIR				/* External */
	switch (sstate) {
	  case 's':			/* 'Tis better to SEND... */
	    s = binary ? ptab[protocol].p_b_scmd : ptab[protocol].p_t_scmd;
	    break;
	  case 'v':			/* ... than RECEIVE */
	    s = binary ? ptab[protocol].p_b_rcmd : ptab[protocol].p_t_rcmd;
	    break;
	}
	if (!s) s = "";
	if (*s) {
	    if (sstate == 's') {	/* Sending */
		extern int xfermode;
		int k = 0, x = 0, b = binary;
		/*
		  If just one file we can scan it to set the xfer mode.
		  Otherwise it's up to the external protocol program.
		*/
		if (patterns && xfermode == XMODE_A && !iswild(fspec)) {
		    extern int nscanfile;
		    k = scanfile(fspec,&x,nscanfile);
		    if (k > -1) {
			b = (k == FT_BIN) ? XYFT_B : XYFT_T;
			s = b ?
			    ptab[protocol].p_b_scmd :
			    ptab[protocol].p_t_scmd;
		    }
		}
		if ((int)strlen(s) + (int)strlen(fspec) < tmpbufsiz) {
		    sprintf(tmpbuf,s,fspec); /* safe (prechecked) */
		    tlog(F110,"Sending",fspec,0L);
		}
	    } else {			/* Receiving */
		if ((int)strlen(s) + (int)strlen(cmarg2) < tmpbufsiz) {
		    sprintf(tmpbuf,s,cmarg2); /* safe (prechecked) */
		    tlog(F110,"Receiving",cmarg2,0L);
		}
	    }
	    tlog(F110," via external protocol:",tmpbuf,0);
	    debug(F110,"ckcpro ttruncmd",tmpbuf,0);
	    success = ttruncmd(tmpbuf);
	    tlog(F110," status:",success ? "OK" : "FAILED", 0);
	} else {
	    printf("?Sorry, no external protocol defined for %s\r\n",
		   ptab[protocol].p_name
		   );
	}
#else
	printf(
"Sorry, only Kermit protocol is supported in this version of Kermit\n"
	       );
#endif /* CK_REDIR */
#endif /* XYZ_INTERNAL */
	return;
    }
#endif /* CK_XYZ */

#ifdef NTSIGX
    conraw();
    connoi();
#else
    if (!local)
      connoi();				/* No console interrupts if remote */
#endif /* NTSIG */

    kactive = 1;
    if (sstate == 'x') {		/* If entering server mode, */
	extern int howcalled;
	server = 1;			/* set flag, */
	debug(F101,"server backgrd","",backgrd);
	debug(F101,"server quiet","",quiet);
	debug(F100,"SHOULD NOT SEE THIS IF IN BACKGROUND!","",0);
	if (howcalled == I_AM_SSHSUB) {	/* and issue appropriate message. */
	    ttol((CHAR *)"KERMIT READY TO SERVE...\015\012",26);
	} else if (!local) {
	    if (!quiet && !backgrd
#ifdef IKS_OPTION
                && !TELOPT_ME(TELOPT_KERMIT) /* User was told by negotiation */
#endif /* IKS_OPTION */
		) {
		conoll(srvtxt);
		conoll("KERMIT READY TO SERVE...");
	    }
	} else {
	    conol("Entering server mode on ");
	    conoll(ttname);
	    conoll("Type Ctrl-C to quit.");
	    if (srvdis) intmsg(-1L);
#ifdef TCPSOCKET
#ifndef NOLISTEN
	    if (network && tcpsrfd > 0)
	      ttol((CHAR *)"KERMIT READY TO SERVE...\015\012",26);
#endif /* NOLISTEN */
#endif /* TCPSOCKET */
	}
    } else
      server = 0;
#ifdef VMS
    if (!quiet && !backgrd)    /* So message doesn't overwrite prompt */
      conoll("");
    if (local) conres();       /* So Ctrl-C will work */
#endif /* VMS */
/*
  If in remote mode, not shushed, not in background, and at top command level,
  issue a helpful message telling what to do...
*/
    if (!local && !quiet && !backgrd) {
	if (sstate == 'v') {
	    conoll("Return to your local Kermit and give a SEND command.");
	    conoll("");
	    conoll("KERMIT READY TO RECEIVE...");
	} else if (sstate == 's') {
	    conoll("Return to your local Kermit and give a RECEIVE command.");
	    conoll("");
	    conoll("KERMIT READY TO SEND...");
	} else if ( sstate == 'g' || sstate == 'r' || sstate == 'h' ||
		    sstate == 'j' || sstate == 'c' ) {
	    conoll("Return to your local Kermit and give a SERVER command.");
	    conoll("");
	    conoll((sstate == 'r' || sstate == 'j' || sstate == 'h') ?
		   "KERMIT READY TO GET..." :
		   "KERMIT READY TO SEND SERVER COMMAND...");
	}
    }
/*
  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.
*/
    rtimer();				/* Reset elapsed-time timer */
#ifdef GFTIMER
    rftimer();
#endif /* GFTIMER */
    resetc();				/* & other per-transaction counters. */

    debug(F101,"proto calling wart, justone","",justone);

    wart();				/* Enter the state table switcher. */
/*
  Note: the following is necessary in case we have just done a remote-mode
  file transfer, in which case the controlling terminal modes have been
  changed by ttpkt().  In particular, special characters like Ctrl-C and
  Ctrl-\ might have been turned off (see ttpkt).  So this call to ttres() is
  essential.  IMPORTANT: restore interrupt handlers first, otherwise any
  terminal interrupts that occur before this is done in the normal place
  later will cause a crash.
*/
#ifdef OS2
    ttres();				/* Reset the communication device */
#else
    if (!local) {
	setint();			/* Arm interrupt handlers FIRST */
	msleep(500);
	ttres();			/* Then restore terminal. */
    }
#endif /* OS2 */
    xxscreen(SCR_TC,0,0L,"");		/* Transaction complete */
    x = quiet;
    quiet=1;
    clsif();				/* Failsafe in case we missed */
    clsof(1);				/* a case in the state machine. */
    quiet = x;

    if (server) {			/* Back from packet protocol. */
	if (!quiet && !backgrd
#ifdef IKSD
	    && !inserver
#endif /* IKSD */
	    ) {				/* Give appropriate message */
	    conoll("");
	    conoll("C-Kermit server done");
        }
        server = 0;			/* Not a server any more */
    }
}

/*  S G E T I N I T  --  Handle incoming GET-Class packets  */

/*
  Returns:
   -1: On error
    0: GET packet processed OK - ready to Send.
    1: Extended GET processed OK - wait for another.
*/
static int
#ifdef CK_ANSIC
sgetinit(int reget, int xget)
#else
sgetinit(reget,xget) int reget, xget;
#endif /* CK_ANSIC */
{	/* Server end of GET command */
    char * fs = NULL;			/* Pointer to filespec */
    int i, n, done = 0;
#ifdef PIPESEND
    extern int usepipes, pipesend;
#endif /* PIPESEND */
    extern int nolinks;

    if (!ENABLED(en_get)) {		/* Only if not disabled!  */
	errpkt((CHAR *)"GET disabled");
	return(-1);
    }

    /* OK to proceed */

    nolinks = recursive;
    filcnt = 0;

#ifdef WHATAMI
    /* If they are alike this was already done in whoarewe() */
    debug(F101,"sgetinit whatru","",whatru);
    if (whatru & WMI_FLAG) {		/* Did we get WHATAMI info? */
	debug(F101,"sgetinit binary (1)","",binary);
#ifdef VMS
	if (binary != XYFT_I && binary != XYFT_L)
#else
#ifdef OS2
	  if (binary != XYFT_L)
#endif /* OS2 */
#endif /* VMS */
	    binary = (whatru & WMI_FMODE) ? /* Yes, set file type */
	      XYFT_B : XYFT_T;	/* automatically */
	debug(F101,"sgetinit binary (2)","",binary);
	if (!wearealike)
	  fncnv = (whatru & WMI_FNAME) ? 1 : 0; /* And name conversion */
    }
#endif /* WHATAMI */

    fs = (char *)srvcmd;
    srvptr = srvcmd;			/* Point to server command buffer */
    decode(rdatap,putsrv,0);		/* Decode the GET command into it */
    /* Accept multiple filespecs */
    cmarg2 = "";			/* Don't use cmarg2 */
    cmarg = "";				/* Don't use cmarg */

    done = 1;				/* Only 1 packet needed... */
    if (xget) {				/* Special decoding for Extended GET */
	char L, next, c;		/* PLV items */
	int len, val;			/* More PLV items */
	char * p = (char *)srvcmd;	/* String to decode */

	done = 0;			/* Maybe more packets needed */
	fs = NULL;			/* We don't know the filespec yet */
	c = *p++;			/* Get first parameter */

	while (c) {			/* For all parameters... */
	    debug(F000,"sgetinit c","",c);
	    L = *p++;			/* Get length */
	    if (L >= SP)		/* Decode length */
	      len = xunchar(L);
	    else if (c == '@') {	/* Allow missing EOP length field */
		len = 0;
	    } else {
		len = (xunchar(*p++) * 95);
		len += xunchar(*p++);
	    }
	    debug(F101,"sgetinit len","",len);
	    next = *(p+len);		/* Get next parameter */
	    *(p+len) = NUL;		/* Zero it out to terminal value */
	    debug(F110,"sgetinit p",p,0);
	    switch (c) {		/* Do the parameter */
	      case 'O':			/* GET Options */
		val = atoi(p);		/* Convert to int */
		debug(F101,"sgetinit O val","",val);
		if (val & GOPT_DEL) moving = 1;
		if (val & GOPT_RES) reget = 1;
		if (val & GOPT_REC) {
		    recursive = 1;
		    nolinks = 2;
		    if (fnspath == PATH_OFF)
		      fnspath = PATH_REL;
		}
		break;
	      case 'M':			/* Transfer Mode */
		val = atoi(p);
		debug(F101,"sgetinit M val","",val);
		if (val < 1)
		  break;
		patterns = 0;		/* Takes precedence over patterns */
		filepeek = 0;		/* and FILE SCAN */
		if (val == GMOD_TXT) binary = XYFT_T; /* Text */
		if (val == GMOD_BIN) binary = XYFT_B; /* Binary */
		if (val == GMOD_LBL) binary = XYFT_L; /* Labeled */
		break;
	      case 'F':			/* Filename */
		fs = p;
		debug(F110,"sgetinit filename",fs,0);
		break;
	      case '@':			/* End Of Parameters */
		done = 1;
		debug(F100,"sgetinit EOP","",0);
		break;
	      default:
		errpkt((CHAR *)"Unknown GET Parameter");
		debug(F100,"sgetinit unknown parameter","",0);
		return(-1);
	    }
	    p += (len + 1);
	    c = next;
	}
    }
    if (!fs) fs = "";			/* A filename is required */
    if (*fs) {
	havefs = 1;
	n = 0;				/* Check for quoted name */
	if ((n = strlen(fs)) > 1) {
	    /* Note: this does not allow for multiple quoted names */
	    if ((fs[0] == '{' && fs[n-1] == '}') ||
		(fs[0] == '"' && fs[n-1] == '"')) {
		fs[n-1] = '\0';
		fs++;
		debug(F111,"sgetinit unquoted filename",fs,n);
	    } else
	      n = 0;			/* This means no quoting */
	}

#ifdef PIPESEND
	debug(F111,"sgetinit",fs,usepipes);
	if (usepipes && ENABLED(en_hos) && *fs == '!') {
	    cmarg = fs + 1;		/* Point past the bang */
	    *fs = NUL;
	    nfils = -1;
	    pipesend = 1;
	    debug(F111,"sgetinit pipesend",cmarg,pipesend);
	}
	if (!pipesend) {		/* If it's not a pipe */
#endif /* PIPESEND */
	    if (n == 0) {		/* If the name was not quoted */
#ifndef NOMSEND
		nfils = fnparse(fs);	/* Allow it to be a list of names */
		debug(F111,"sgetinit A",fs,nfils);
	    } else {			/* If it was quoted */
#endif /* NOMSEND */
		nzxopts = 0;
#ifdef UNIXOROSK
		if (matchdot)  nzxopts |= ZX_MATCHDOT;
#endif /* UNIXOROSK */
		if (recursive) nzxopts |= ZX_RECURSE;
		/* Treat as a single filespec */
		nfils = 0 - nzxpand(fs,nzxopts);
		debug(F111,"sgetinit B",fs,nfils);
		cmarg = fs;
	    }
#ifdef PIPESEND
	}
#endif /* PIPESEND */
    }
    if (!done) {			/* Need more O packets... */
	debug(F100,"sgetinit O-Packet TBC","",0); /* To Be Continued */
	return(1);
    }
    debug(F100,"sgetinit O-Packet done - havefs","",havefs);
    if (!havefs) {			/* Done - make sure we have filename */
	errpkt((CHAR *)"GET without filename");
	return(-1);
    }
    freerpkt(winlo);
    winlo = 0;				/* Back to packet 0 again. */
    debug(F101,"sgetinit winlo","",winlo);
    nakstate = 0;			/* Now I'm the sender! */
    if (reget) sendmode = SM_RESEND;
    if (sinit() > 0) {			/* Send Send-Init */
#ifdef STREAMING
	if (!streaming)
#endif /* STREAMING */
	  timint = chktimo(rtimo,timef); /* Switch to per-packet timer */
	return(0);			/* If successful, switch state */
    } else return(-1);			/* Else back to server command wait */
}

#else  /* NOXFER */

#include "ckcdeb.h"

VOID
proto() {
    extern int success;
    success = 0;
}
#endif /* NOXFER */
