#ifdef NT
char *ckzv = "Win32 File support, 8.0.187, 18 July 2004";
#else /* NT */
char *ckzv = "OS/2 File support, 8.0.187, 18 July 2004";
#endif /* NT */

/* C K O F I O  --  Kermit file system support for OS/2 */


/*
  NOTE TO CONTRIBUTORS: This file, and all the other C-Kermit files, must be
  compatible with C preprocessors that support only #ifdef, #else, #endif,
  #define, and #undef.  Please do not use #if, logical operators, or other
  preprocessor features in any of the portable C-Kermit modules.  You can,
  of course, use these constructions in system-specific modules when you they
  are supported.
*/

/*
  Authors: Frank da Cruz (fdc@columbia.edu),
             Columbia University Academic Information Systems, New York City,
           Jeffrey E Altman <jaltman@secure-endpoints.com>
             Secure Endpoints Inc., New York City
  and others noted below.  Note: CUCCA = Previous name of Columbia University
  Academic Information Systems.

  Copyright (C) 1985, 2004, Trustees of Columbia University in the City of New
  York.  All rights reserved.
*/

/* Include Files */

#include "ckcsym.h"
#include "ckcdeb.h"
#include "ckcasc.h"
#ifndef NOCSETS
#include "ckcxla.h"
#endif /* NOCSETS */
#include "ckuusr.h"
#ifdef CK_LOGIN                         /* For AUTH_VALID, AUTHTYPE_xxx, ... */
#include "ckctel.h"
#ifdef CK_SSL
#include "ck_ssl.h"
#endif /* CK_SSL */
#endif /* CK_LOGIN */
#include <stdio.h>
#include <errno.h>
_PROTOTYP( int os2settitle, (char *, int) );
extern int priority;
#include <signal.h>

/*

  C-Kermit's OS/2 support originally by Chris Adie <C.Adie@uk.ac.edinburgh>
  Edinburgh University Computing Service, Scotland, for C-Kermit 4F.  Adapted
  to C-Kermit 5A and integrated into the UNIX support module by Kai Uwe Rommel
  <rommel@informatik.tu-muenchen.de>, Muenchen, Germany, December 1991.

  And then split away from the Unix modules for C-Kermit 6.1 because the
  amount of disparate code was just too great.
*/

/*
  Directory Separator macros, to allow this module to work with both UNIX and
  OS/2: Because of ambiguity with the command line editor escape \ character,
  the directory separator is currently left as / for OS/2 too, because the
  OS/2 kernel also accepts / as directory separator.  But this is subject to
  change in future versions to conform to the normal OS/2 style.

  Win32 also allows the Directory Separator to be / when it is used for
  local disk operations.  However, if a UNC is in use, then \ must be used.
  So we have two choices, we can return UNC names with \ quoted which might
  break if the command quoting is turned off, or we can performed the reverse
  translation in all file i/o functions if the first two characters are "//"
  indicating a UNC.
*/
#ifndef DIRSEP
#define DIRSEP       '/'
#endif /* DIRSEP */
#ifndef ISDIRSEP
#define ISDIRSEP(c)  ((c)=='/'||(c)=='\\')
#endif /* ISDIRSEP */

#ifdef SDIRENT
#define DIRENT
#endif /* SDIRENT */

#include "ckodir.h"

extern int binary;                      /* We need to know this for open() */
#ifdef CK_CTRLZ
extern int eofmethod;
#endif /* CK_CTRLZ */
extern int k95stdin,k95stdout;

#include <sys/utime.h>
#include <stdlib.h>
#include <process.h>
#include <share.h>
extern int fncact;                      /* Need this for zchkspa()         */
extern int tlevel, cmdlvl;              /* Need this for external commands */
#ifdef __IBMC__
extern FILE *popen(char *, char *);
extern int pclose(FILE *);
#else
#define popen    _popen
#define pclose   _pclose
#define fopen(n, m)  _fsopen(n, m, _SH_DENYWR)
#endif /* __IBMC__ */
#ifdef NT
#include <io.h>
#endif /* NT */

#define TIMESTAMP                       /* Can do file dates */
#include <time.h>                       /* Need this */
#include <sys/timeb.h>                  /* Need this too */
#include <sys/stat.h>
#include <sys/types.h>

#ifdef __IBMC__
#ifdef system
#undef system
#endif
#ifdef COMMENT
#ifdef stat
#undef stat
#endif
#define stat _stat
#endif /* COMMENT */
#endif

#ifdef NT
#ifndef __WATCOMC__
#define timezone _timezone
#define fileno _fileno
#endif /* __WATCOMC__ */
#define write _write
#define stricmp _stricmp
#define setmode _setmode
#define access _access
#define unlink _unlink
#define chdir  _chdir
#define getcwd _getcwd
#define utime  _utime
#define rmdir  _rmdir
#define utimbuf _utimbuf
#define stat    _stat
#ifndef SEM_INDEFINITE_WAIT
#define SEM_INDEFINITE_WAIT INFINITE
#endif /* SEM_INDEFINITE_WAIT */
#endif /* NT */

/* Because standard stat has trouble with trailing /'s we have to wrap it */
int os2stat(char *, struct stat *);

/* Is `y' a leap year? */
#define leap(y) (((y) % 4 == 0 && (y) % 100 != 0) || (y) % 400 == 0)

/* Number of leap years from 1970 to `y' (not including `y' itself). */
#define nleap(y) (((y) - 1969) / 4 - ((y) - 1901) / 100 + ((y) - 1601) / 400)

#ifdef DEBUG
extern int deblog;
#else
#define deblog 0
#endif /* DEBUG */
extern int xferlog;
extern char * xferfile;
int iklogopen = 0;
static time_t timenow;
static int logged_in = 0;
static char iksdmsg[CKMAXPATH+512];
extern char exedir[CKMAXPATH];
#ifndef XFERFILE
#define XFERFILE "iksd.log"
#endif /* XFERFILE */

/*
  Functions (n is one of the predefined file numbers from ckcker.h):

   zopeni(n,name)   -- Opens an existing file for input.
   zopeno(n,name,attr,fcb) -- Opens a new file for output.
   zclose(n)        -- Closes a file.
   zchin(n,&c)      -- Gets the next character from an input file.
   zsinl(n,&s,x)    -- Read a line from file n, max len x, into address s.
   zsout(n,s)       -- Write a null-terminated string to output file, buffered.
   zsoutl(n,s)      -- Like zsout, but appends a line terminator.
   zsoutx(n,s,x)    -- Write x characters to output file, unbuffered.
   zchout(n,c)      -- Add a character to an output file, unbuffered.
   zchki(name)      -- Check if named file exists and is readable, return size.
   zchko(name)      -- Check if named file can be created.
   zchkspa(name,n)  -- Check if n bytes available to create new file, name.
   znewn(name,s)    -- Make a new unique file name based on the given name.
   zdelet(name)     -- Delete the named file.
   nzxpand(string,flags) -- Expands the given wildcard string into a list of files.
   znext(string)    -- Returns the next file from the list in "string".
   zxcmd(n,cmd)     -- Execute the command in a lower fork on file number n.
   zclosf()         -- Close input file associated with zxcmd()'s lower fork.
   zrtol(n1,n2)     -- Convert remote filename into local form.
   zltor(n1,n2)     -- Convert local filename into remote form.
   zchdir(dirnam)   -- Change working directory.
   zhome()          -- Return pointer to home directory name string.
   zkself()         -- Kill self, log out own job.
   zsattr(struct zattr *) -- Return attributes for file which is being sent.
   zstime(f, struct zattr *, x) - Set file creation date from attribute packet.
   zrename(old, new) -- Rename a file.
   zlink(source,destination) -- Link a file.
   zcopy(source,destination) -- Copy a file.
   zmkdir(path)       -- Create the directory path if possible
   zfnqfp(fname,len,fullpath) - Determine full path for file name.
*/

/* Kermit-specific includes */
/*
  Definitions here supersede those from system include files.
  ckcdeb.h is included above.
*/
#include "ckcker.h"                     /* Kermit definitions */
#include "ckucmd.h"                     /* For sys-dependent keyword tables */
#include "ckuver.h"                     /* Version herald */

char *ckzsys = HERALD;

#include <fcntl.h>

/* Define macros for getting file type */

#ifdef ISDIRBUG                         /* Also allow this from command line */
#ifdef S_ISREG
#undef S_ISREG
#endif /* S_ISREG */
#ifdef S_ISDIR
#undef S_ISDIR
#endif /*  S_ISDIR */
#endif /* ISDIRBUG */

#ifndef S_IFREG
#ifdef  _S_IFREG
#define S_IFREG _S_IFREG
#endif
#endif

#ifndef S_IFDIR
#ifdef  _S_IFDIR
#define S_IFDIR _S_IFDIR
#endif
#endif

#ifndef S_IFMT
#ifdef _S_IFMT
#define S_IFMT _S_IFMT
#endif
#endif

#ifndef S_ISREG
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#endif /* S_ISREG */
#ifndef S_ISDIR
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#endif /* S_ISDIR */

/* More macros we might need */
#ifndef _O_APPEND
#define _O_APPEND  O_APPEND
#endif
#ifndef _O_WRONLY
#define _O_WRONLY  O_WRONLY
#endif
#ifndef _O_CREAT
#define _O_CREAT   O_CREAT
#endif
#ifndef _O_TEXT
#define _O_TEXT    O_TEXT
#endif
#ifndef _SH_DENYNO
#define _SH_DENYNO SH_DENYNO
#endif
#ifndef _O_BINARY
#define _O_BINARY  O_BINARY
#endif

/* Longest pathname ... */
#ifdef MAXPATHLEN
#ifdef MAXPATH
#undef MAXPATH
#endif /* MAXPATH */
#define MAXPATH MAXPATHLEN
#else
#ifdef PATH_MAX
#define MAXPATH PATH_MAX
#else
#define MAXPATH 255
#endif /* PATH_MAX */
#endif /* MAXPATHLEN */

#ifndef NOPUSH
extern int nopush;
#endif /* NOPUSH */
_PROTOTYP( char * zdtstr, (time_t) );
_PROTOTYP( time_t zstrdt, (char *, int) );

/* Some systems define these symbols in include files, others don't... */

#ifndef R_OK
#define R_OK 4                          /* For access */
#endif /* R_OK */

#ifndef W_OK
#define W_OK 2
#endif /* W_OK */

#ifndef O_RDONLY
#define O_RDONLY 000
#endif /* O_RDONLY */

#ifdef CKROOT
static char ckroot[CKMAXPATH+1] = { NUL, NUL };
static int ckrootset = 0;
int ckrooterr = 0;
#endif /* CKROOT */

    /* syslog and wtmp items for Internet Kermit Service */
extern char * clienthost;               /* From ckcmai.c. */
extern int ckxlogging;
static char fullname[CKMAXPATH+1];
static char tmp2[CKMAXPATH+1];

#ifdef CK_LOGIN
int ckxanon = 0;                        /* Anonymous login not ok */
char * anonacct = NULL;
#ifdef NT
char * iks_domain = NULL;               /* Default domain to use for logins */
#endif /* NT */
#ifdef CK_PERMS
int ckxperms = 0040;
#endif /* CK_PERMS */
#ifdef UNIX
int ckxpriv = 1;                        /* Allow Root logins? */
#endif /* UNIX */
#endif /* CK_LOGIN */

#ifndef CKWTMP
int ckxwtmp = 0;
#else
int ckxwtmp = 1;
#include <utmp.h>
#define WTMPFILE "/usr/adm/wtmp"        /* I hope this is portable */
char * wtmpfile = NULL;

static int wtmpfd = 0;
static char cksysline[32] = { NUL, NUL };

VOID
logwtmp(line, name, host) char *line, *name, *host; {
    struct utmp ut;
    struct stat buf;
    time_t time();

    if (!ckxwtmp)
      return;

    if (!wtmpfile)
      makestr(&wtmpfile,WTMPFILE);

    if (!line) line = "";
    if (!name) name = "";
    if (!host) host = "";

    if (!wtmpfd && (wtmpfd = open(wtmpfile, O_WRONLY|O_APPEND, 0)) < 0) {
        ckxwtmp = 0;
        debug(F110,"WTMP open failed",line,0);
        return;
    }
    if (!fstat(wtmpfd, &buf)) {
        ckstrncpy(ut.ut_line, line, sizeof(ut.ut_line));
        ckstrncpy(ut.ut_name, name, sizeof(ut.ut_name));
        ckstrncpy(ut.ut_host, host, sizeof(ut.ut_host));
        time(&ut.ut_time);
        if (write(wtmpfd, (char *)&ut, sizeof(struct utmp)) !=
            sizeof(struct utmp)) {
            ftruncate(wtmpfd, buf.st_size); /* Error, undo any partial write */
            debug(F110,"WTMP write error",line,0);
        } else {
            debug(F110,"WTMP record OK",line,0);
            return;
        }
    }
}
#endif /* CKWTMP */

/* Declarations */

int mskrename = 0;                      /* MS-Kermit file collision renaming off */
int maxnam = MAXNAMLEN;                 /* Available to the outside */
int maxpath = MAXPATH;
int ck_znewn = -1;
int pexitstat = -2;                     /* Process exit status */

FILE *fp[ZNFILS] = {                    /* File pointers */
    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };

/* Flags for each file indicating whether it was opened with popen() */
int ispipe[ZNFILS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

/* Buffers and pointers used in buffered file input and output. */
#ifdef DYNAMIC
extern char *zinbuffer, *zoutbuffer;
#else
extern char zinbuffer[], zoutbuffer[];
#endif /* DYNAMIC */
extern char *zinptr, *zoutptr;
extern int zincnt, zoutcnt;

static long iflen = -1L;                /* Input file length */

static PID_T pid = 0;                   /* pid of child fork */
static int fcount[2] = {0,0};           /* Number of files left in search */
static int fcntsav[2] = {0,0};
static int fcountstream[2] = {0,0};
static int fcntstreamsav[2] = {0,0};
static char nambuf[MAXNAMLEN+4];        /* Buffer for a filename */
static char UNCnam[MAXNAMLEN+1];        /* Scratch buffer for UNC names */
#ifndef NOFRILLS
static char zmbuf[200];                 /* For mail, remote print strings */
#endif /* NOFRILLS */

#ifdef  OS2ONLY
#define INCL_KBD
#define INCL_DOSERRORS
#define INCL_DOSFILEMGR
#define INCL_DOSPROCESS
#define INCL_DOSSEMAPHORES
#include <os2.h>
typedef unsigned short WORD;
#undef COMMENT

/* Get/Set All Extended Attributes support */

#define FreeMem(p) DosFreeMem(p)

#define MAX_GEA            500L  /* Max size for a GEA List */
#define MAXEACOUNT          128  /* Max number of EA's supported */
#define Ref_ASCIIZ            1  /* Reference type for DosEnumAttribute */

/* Definition of level specifiers, required for File Info */

#define GetInfoLevel1         1            /* Get info from SFT */
#define GetInfoLevel2         2            /* Get size of FEAlist */
#define GetInfoLevel3         3            /* Get FEAlist given the GEAlist */
#define GetInfoLevel4         4            /* Get whole FEAlist */
#define GetInfoLevel5         5            /* Get FSDname */

#define SetInfoLevel1         1            /* Set info in SFT */
#define SetInfoLevel2         2            /* Set FEAlist */

FEA2LIST *pFEAList = 0;  /* Pointer to buffer containing all EA information */
ULONG os2attrs = FILE_NORMAL;
extern unsigned int lf_opts;
#endif /* OS2ONLY */

#ifdef NT
#include <windows.h>
#ifdef CK_LOGIN
#define SECURITY_WIN32
#include <security.h>
#include <ntsecapi.h>
#include <lmaccess.h>
#include <lmapibuf.h>
#include <lmerr.h>
#endif /* CK_LOGIN */
#endif /* NT */
char os2filename[MAXPATH];

#ifdef IKSD
extern int inserver, local;
#endif /* IKSD */

extern int server, en_mkd, en_cwd, en_del;

extern char uidbuf[];           /* from ckcmai.c... */
#ifdef CK_LOGIN
#ifdef CKROOT
extern char * anonroot;
#endif /* CKROOT */
extern int isguest;
#define GUESTPASS_LEN 256
static char guestpass[GUESTPASS_LEN] = "";
#endif /* CK_LOGIN */

_PROTOTYP(char * whoami, (void));

/*  Z K S E L F  --  Kill Self: log out own job, if possible.  */

int
zkself() {                              /* For "bye", but no guarantee! */
    _exit(3);
    return(0);
}

/* Z C H K P I D -- Check to see if a PID is valid */
/* returns 1 if pid is valid and active, 0 if not  */

int
zchkpid(unsigned long pid) {
#ifdef NT
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,0,pid);
    if ( hProcess ) {
        CloseHandle(hProcess);
        return(1);
    }
    return(0);
#else /* NT */
#endif /* NT */
    return(1);
}

/*  D O I K L O G  --  Open Kermit-specific ftp-like transfer log. */

VOID                                    /* called in ckcmai.c */
doiklog() {
    if (iklogopen)                      /* Already open? */
      return;
    debug(F111,"doiklog",xferfile,xferlog);
    if (xferlog) {                      /* Open iksd log if requested */
        if (!xferfile) {                /* If no pathname given */
          char buf[MAXPATH];
          ckstrncpy(buf,exedir,MAXPATH);
          ckstrncat(buf,XFERFILE,MAXPATH);
          makestr(&xferfile,buf);       /* use this default */
        }
        if (*xferfile) {
            xferlog = sopen(xferfile, _O_WRONLY | _O_APPEND | _O_CREAT | _O_TEXT,
                             _SH_DENYNO, 666 );
            debug(F111,"doiklog open()",xferfile,xferlog);
            if (xferlog < 0) {
#ifdef CKSYSLOG
                cksyslog(SYSLG_FC, 0, "xferlog open failure",xferfile,NULL);
#endif /* CKSYSLOG */
                debug(F101,"doiklog open errno","",errno);
                xferlog = 0;
            } else
              iklogopen = 1;
        } else
          xferlog = 0;
#ifdef CKSYSLOG
        if (ckxlogging) {
            if ( xferlog )
                cksyslog(SYSLG_FC, 1, "xferlog: open ok", xferfile, NULL);
            else
                cksyslog(SYSLG_FC, 0, "xferlog: open failed", xferfile, NULL);
        }
#endif /* CKSYSLOG */
    }
}

static VOID
getfullname(name) char * name; {
    char *p = (char *)fullname;
    int len = 0;
    fullname[0] = '\0';
    /* If necessary we could also chase down symlinks here... */
#ifdef COMMENT
    /* This works but is incompatible with wuftpd */
    if (isguest && anonroot) {
        ckstrncpy(fullname,anonroot,CKMAXPATH);
        len = strlen(fullname);
        if (len > 0)
          if (fullname[len-1] == '/')
            len--;
    }
    p += len;
#endif /* COMMENT */
    zfnqfp(name, CKMAXPATH - len, p);
    while (*p) {
        if (*p < '!') *p = '_';
        p++;
    }
}

/*  Z O P E N I  --  Open an existing file for input. */

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

int
zopeni(n,name) int n; char *name; {
    int x, y;

    debug(F111,"zopeni name",name,n);
    debug(F101,"zopeni fp","", (unsigned) fp[n]);
    if (chkfn(n) != 0) {
        debug(F110,"zopeni chkfn()","file is open",0);
        return(0);
    }
    zincnt = 0;                         /* Reset input buffer */
    if (n == ZSYSFN) {                  /* Input from a system function? */
/*** Note, this function should not be called with ZSYSFN ***/
/*** Always call zxcmd() directly, and give it the real file number ***/
/*** you want to use.  ***/
        debug(F110,"zopeni called with ZSYSFN, failing!",name,0);
        *nambuf = '\0';                 /* No filename. */
        return(0);                      /* fail. */
#ifdef COMMENT
        return(zxcmd(n,name));          /* Try to fork the command */
#endif
    }
    if (n == ZSTDIO) {                  /* Standard input? */
        if (is_a_tty(0)) {
            fprintf(stderr,"Terminal input not allowed");
            debug(F110,"zopeni: attempts input from unredirected stdin","",0);
            return(0);
        }
        fp[ZIFILE] = stdin;
        ispipe[ZIFILE] = 0;
        setmode(fileno(stdin),_O_BINARY);
        return(1);
    }

#ifdef CKROOT
    debug(F111,"zopeni setroot",ckroot,ckrootset);
    if (ckrootset)
        if (!zinroot(name)) {
            debug(F110,"zopeni setroot violation",name,0);
            return(0);
        }
#endif /* CKROOT */

    if (n == ZIFILE || n == ZRFILE) {
        ckstrncpy( os2filename, name, MAXPATH ) ;
        errno = 0;
#ifdef NT
        fp[n] = _fsopen(name,"rbS",_SH_DENYWR);          /* Binary mode */
        if (!fp[n]) {
            debug(F111,"zopeni ZI/ZR _fsopen failed","GetLastError",GetLastError());
        }
#else
        fp[n] = fopen(name,"rb");/* Binary mode */
#endif /* NT */
        ispipe[ZIFILE] = 0;
        if (fp[ZIFILE]) {
            x = ferror(fp[ZIFILE]);
#ifdef ZDEBUG
            printf("ZOPENI errno=%d\n",errno);
            printf("ZOPENI ferror=%d\n",x);
#endif /* ZDEBUG */
        }
#ifdef CK_LABELED
        if (binary == XYFT_L) {
            os2getattr(name);
            os2geteas(name);
        }
#endif /* CK_LABELED */
    } else {
#ifdef NT
        fp[n] = _fsopen(name,"rbS",_SH_DENYWR); /* Real file, open it. */
        if (!fp[n]) {
            debug(F111,"zopeni _fsopen failed","GetLastError",GetLastError());
        }
#else
        fp[n] = fopen(name,"rb");/* Real file, open it. */
#endif /* NT */
    }
    debug(F111,"zopeni fopen", name, fp[n]);
#ifdef ZDEBUG
    printf("ZOPENI fp[%d]=%ld\n",n,fp[n]);
#endif /* ZDEBUG */
    ispipe[n] = 0;

    if (xferlog
#ifdef CKSYSLOG
        || ckxsyslog >= SYSLG_FA && ckxlogging
#endif /* CKSYSLOG */
        ) {
        getfullname(name);
        debug(F110,"zopeni fullname",fullname,0);
    }

    if (fp[n] == NULL) {
#ifdef CKSYSLOG
        if (ckxsyslog >= SYSLG_FA && ckxlogging) {
            char buf[1024];
            sprintf(buf,"file[%d] %s: open failed",n,fullname);
            cksyslog(SYSLG_FA, 0, buf,NULL,NULL);
        }
        perror(fullname);
#else
        perror(name);
#endif /* CKSYSLOG */
        return(0);
    } else {
#ifdef CKSYSLOG
        if (ckxsyslog >= SYSLG_FA && ckxlogging) {
          char buf[1024];
          sprintf(buf,"file[%d] %s: open read ok", n, fullname);
          cksyslog(SYSLG_FA, 1, buf, NULL, NULL);
        }
#endif /* CKSYSLOG */
        clearerr(fp[n]);
        return(1);
    }
}

/*  Z O P E N O  --  Open a new file for output.  */

int
zopeno(n,name,zz,fcb)
/* zopeno */  int n; char *name; struct zattr *zz; struct filinfo *fcb; {

    char p[8];                          /* (===OS2 change===) */
    int append = 0;

/* As of Version 5A, the attribute structure and the file information */
/* structure are included in the arglist. */

#ifdef DEBUG
    debug(F111,"zopeno",name,n);
    if (fcb) {
        debug(F101,"zopeno fcb disp","",fcb->dsp);
        debug(F101,"zopeno fcb type","",fcb->typ);
        debug(F101,"zopeno fcb char","",fcb->cs);
    } else {
        debug(F100,"zopeno fcb is NULL","",0);
    }
#endif /* DEBUG */

    if (chkfn(n) != 0)                  /* Already open? */
      return(0);                        /* Nothing to do. */

    if ((n == ZCTERM) || (n == ZSTDIO)) {   /* Terminal or standard output */
        fp[ZOFILE] = stdout;
        ispipe[ZOFILE] = 0;
#ifdef DEBUG
        if (n != ZDFILE)
          debug(F101,"zopeno fp[n]=stdout","",fp[n]);
#endif /* DEBUG */
        zoutcnt = 0;
        zoutptr = zoutbuffer;
        return(1);
    }

/* A real file.  Open it in desired mode (create or append). */
#ifdef CKROOT
    debug(F111,"zopeno setroot",ckroot,ckrootset);
    if (ckrootset)
        if (!zinroot(name)) {
            debug(F110,"zopeno setroot violation",name,0);
            return(0);
        }
#endif /* CKROOT */

    ckstrncpy(p,"w",8);                      /* Assume write/create mode */
    if (fcb) {                          /* If called with an FCB... */
        if (fcb->dsp == XYFZ_A) {       /* Does it say Append? */
            ckstrncpy(p,"a",8);              /* Yes. */
            debug(F100,"zopeno append","",0);
            append = 1;
        }
    }
    if (n == ZOFILE || n == ZSFILE) {   /* OS/2 binary mode */
        ckstrncpy( os2filename, name, MAXPATH ) ;
        ckstrncat(p,"b",8);
    }

#ifdef NT
    if ( n == ZOFILE )      /* optimise caching for sequential access */
        ckstrncat(p,"S",8); /* S is also known as _O_SEQUENTIAL */
#endif /* NT */

    if (xferlog
#ifdef CKSYSLOG
        || ckxsyslog >= SYSLG_FC && ckxlogging
#endif /* CKSYSLOG */
        ) {
        getfullname(name);
        debug(F110,"zopeno fullname",fullname,0);
    }
    debug(F110,"zopeno fopen arg",p,0);

#ifdef NT_COMMENT
    /*
    There is a good reason I never did this before.  And that is
    because Windows 95 does not support Overlapped I/O for disk
    files nor does the C Run Time Library.
    */
    if ( !isWin95() )
    {
        SECURITY_ATTRIBUTES security ;
        HANDLE hFile = NULL;

        memset(&security, 0, sizeof(SECURITY_ATTRIBUTES));
        security.nLength = sizeof(SECURITY_ATTRIBUTES);
        security.lpSecurityDescriptor = NULL ;
        security.bInheritHandle = FALSE ;

        hFile = CreateFile(name,
                            GENERIC_WRITE | GENERIC_READ,
                            FALSE, /* do not share */
                            &security,
                            OPEN_ALWAYS,
                            FILE_ATTRIBUTE_NORMAL |
                            /* FILE_FLAG_OVERLAPPED |*/
                            FILE_FLAG_SEQUENTIAL_SCAN,
                            NULL);
        if (hFile == INVALID_HANDLE_VALUE) {
            fp[n] = NULL;
        }
        else {
            int fd = _open_osfhandle( (int) hFile,
                                      (n == ZOFILE || n == ZSFILE) ?
                                      _O_BINARY : _O_TEXT );
            if ( fd == -1 )
                fp[n] = NULL;
            else
                fp[n] = _fdopen(fd, p);
        }
    } else
#endif /* NT_COMMENT */
    {
        fp[n] = fopen(name,p);          /* Try to open the file */
    }
    ispipe[ZIFILE] = 0;

#ifdef ZDEBUG
    printf("ZOPENO fp[%d]=%ld\n",n,fp[n]);
#endif /* ZDEBUG */

    if (fp[n] == NULL) {                /* Failed */
        debug(F101,"zopeno failed errno","",errno);
#ifdef CKSYSLOG
        if (ckxsyslog >= SYSLG_FC && ckxlogging) {
            char buf[1024];
            sprintf(buf, "file[%d] %s: %s failed (%m)",
                 n,
                 fullname,
                 append ? "append" : "create"
                 );
            cksyslog(SYSLG_FC, 0, buf, NULL, NULL);
        }
#endif /* CKSYSLOG */
    } else {                            /* Succeeded */
        if (n == ZDFILE ||              /* If it's the debug log */
            n == ZTFILE )               /* or the transaction log */
          setbuf(fp[n],NULL);           /* make it unbuffered */
#ifdef CKSYSLOG
        if (ckxsyslog >= SYSLG_FC && ckxlogging) {
            char buf[1024];
            sprintf(buf, "file[%d] %s: %s ok",
                     n, fullname, append ? "append" : "create");
            cksyslog(SYSLG_FC, 1, buf, NULL, NULL);
        }
#endif /* CKSYSLOG */
        debug(F100, "zopeno ok", "", 0);
    }
    zoutcnt = 0;                        /* (PWP) reset output buffer */
    zoutptr = zoutbuffer;
    return((fp[n] != NULL) ? 1 : 0);
}

/*  Z C L O S E  --  Close the given file.  */

/*  Returns 0 if arg out of range, 1 if successful, -1 if close failed.  */

int
zclose(n) int n; {
    int x, x2;
    extern long ffc;

    debug(F101,"zclose","",n);
    if (chkfn(n) < 1) return(0);        /* Check range of n */
    if ((n == ZOFILE) && (zoutcnt > 0)) /* (PWP) output leftovers */
      x2 = zoutdump();
    else
      x2 = 0;

    x = 0;                              /* Initialize return code */
    if (fp[ZSYSFN] || ispipe[n]) {      /* If file is really pipe */
        x = zclosf(n);                  /* do it specially */
        debug(F101,"zclose zclosf","",x);
        debug(F101,"zclose zclosf fp[n]","",fp[n]);
    } else {
        if ((fp[n] != stdout) && (fp[n] != stdin))
          x = fclose(fp[n]);
        fp[n] = NULL;
#ifdef CK_LABELED
        if (binary == XYFT_L) {
            debug(F111,"zclose LABELED","file number",n);
            if (n == ZOFILE) {
                debug(F111,"zclose LABELED",
                      "lf_opts && LBL_EXT",
                      lf_opts && LBL_EXT
                      );
                if (lf_opts && LBL_EXT)
                  os2seteas(os2filename);
                os2setattr(os2filename);
            } else if (n == ZIFILE && pFEAList) {
                FreeMem(pFEAList);
                pFEAList = 0;
            }
        }
#endif /* CK_LABELED */
    }
    iflen = -1L;                        /* Invalidate file length */
    if (x == EOF) {                     /* if we got a close error */
        debug(F101,"zclose fclose fails","",x);
        return(-1);
    } else if (x2 < 0) {                /* or error flushing last buffer */
        debug(F101,"zclose error flushing last buffer","",x2);
        return(-1);                     /* then return an error */
    } else {
        /* Print log record compatible with wu-ftpd */
        if (xferlog && (n == ZIFILE || n == ZOFILE)) {
            char * s, *p;
            extern char ttname[];
            if (!iklogopen) (VOID) doiklog(); /* Open log if necessary */
            debug(F101,"zclose iklogopen","",iklogopen);
            if (iklogopen) {
                int len;
                timenow = time(NULL);
#ifdef CK_LOGIN
                if (logged_in)
                  s = clienthost;
                else
#endif /* CK_LOGIN */
                  s = (char *)ttname;
                if (!s) s = "";
                if (!*s) s = "*";
#ifdef CK_LOGIN
                if (logged_in) {
                    p = guestpass;
                    if (!*p) p = "*";
                } else
#endif /* CK_LOGIN */
                  p = whoami();

                len = 24 + 12 + strlen(s) + 16
                    + strlen(fullname) + 1 + 1 + 1 + 1
                    + strlen(p) + 6 + 2 + 12;
                if ( len > sizeof(iksdmsg) )
                    sprintf(iksdmsg, /* SAFE */
                            "%.24s [BUFFER WOULD OVERFLOW]\n",
                             ctime(&timenow));
                else
                    sprintf(iksdmsg, /* SAFE */
                        "%.24s %d %s %ld %s %c %s %c %c %s %s %d %s\n",
                        ctime(&timenow),        /* date/time */
                        gtimer(),               /* elapsed secs */
                        s,                      /* peer name */
                        ffc,                    /* byte count */
                        fullname,               /* full pathname of file */
                        (binary ? 'b' : 'a'),   /* binary or ascii */
                        "_",                    /* options = none */
                        n == ZIFILE ? 'o' : 'i', /* in/out */
#ifdef CK_LOGIN
                        (isguest ? 'a' : 'r'),  /* User type */
#else
                        'r',
#endif /* CK_LOGIN */
                        p,                      /* Username or guest passwd */
#ifdef CK_LOGIN
                        logged_in ? "iks" : "kermit", /* Record ID */
#else
                        "kermit",
#endif /* CK_LOGIN */
                        0,              /* User ID on client system unknown */
                        "*"             /* Ditto */
                        );
                debug(F110,"zclose iksdmsg",iksdmsg,0);
                write(xferlog, iksdmsg, (int)strlen(iksdmsg));
            }
        }
        debug(F101,"zclose returns","",1);
        return(1);
    }
}

/*  Z C H I N  --  Get a character from the input file.  */

/*  Returns -1 if EOF, 0 otherwise with character returned in argument  */

int
zchin(n,c) int n; int *c; {
    int a;
#ifdef IKSD
    if (inserver && !local && (n == ZCTERM || n == ZSTDIO)) {
        a = coninc(0);
        if (a < 0)
            return(-1);
    } else
#endif /* IKSD */
    /* (PWP) Just in case this gets called when it shouldn't. */
    if (n == ZIFILE) {
        a = zminchar();
        if (a < 0)
            return(-1);
    } else {
        a = getc(fp[n]);
        if (a == EOF)
            return(-1);
    }
#ifdef CK_CTRLZ
    /* If SET FILE EOF CTRL-Z, first Ctrl-Z marks EOF */
    if (!binary && a == 0x1A && eofmethod == XYEOF_Z) {
        debug(F100,"zchin found Ctrl-Z","",0);
        return(-1);
    }
#endif /* CK_CTRLZ */
    *c = (CHAR) a & 0377;
    return(0);
}

/*  Z S I N L  --  Read a line from a file  */

/*
  Writes the line into the address provided by the caller.
  n is the Kermit "channel number".
  Writing terminates when newline is encountered, newline is not copied.
  Writing also terminates upon EOF or if length x is exhausted.
  Returns 0 on success, -1 on EOF or error.
*/
int
zsinl(n,s,x) int n, x; char *s; {
    int a, z = 0;                       /* z is return code. */
    int count = 0;
    extern CHAR feol;                   /* Line terminator */

    if (!s || chkfn(n) < 1)             /* Make sure file is open, etc */
        return(-1);
    s[0] = '\0';                        /* Don't return junk */

    a = -1;                             /* Current character, none yet. */
    while (x--) {                       /* Up to given length */
        int old=0;
        if (feol)                       /* Previous character */
             old = a;

        if (zchin(n,&a) < 0) {          /* Read a character from the file */
            debug(F101,"zsinl zchin fail","",count);
            if (count == 0)
                z = -1;                 /* EOF or other error */
            break;
        } else
            count++;

        if (feol) {                     /* Single-character line terminator */
            if (a == feol)
              break;
        } else {                        /* CRLF line terminator */
            if (a == '\015')            /* CR, get next character */
              continue;
            if (old == '\015') {        /* Previous character was CR */
                if (a == '\012')        /* This one is LF, so we have a line */
                  break;
                else                    /* Not LF, deposit CR */
                  *s++ = '\015';
            }
            if (a == '\012') break;     /* Here break on single LF too */
        }
        *s = a;                         /* Deposit character */
        s++;
    }
    *s = '\0';                          /* Terminate the string */
    return(z);
}

/*  Z X I N  --  Read x bytes from a file  */

/*
  Reads x bytes (or less) from channel n and writes them
  to the address provided by the caller.
  Returns number of bytes read on success, 0 on EOF or error.
*/
int
zxin(n,s,x) int n, x; char *s; {
#ifdef IKSD
    if (inserver && !local && (n == ZCTERM || n == ZSTDIO)) {
        int a, i;
        a = ttchk();
        for (i = 0 ;i < a && i < x; i++) {
            s[i] = coninc(0);
        }
        return(i);
    }
#endif /* IKSD */
    return(fread(s, sizeof (char), x, fp[n]));
}

int
ttwait(int fd, int secs) {
#ifdef NT
    DWORD  bytesAvail = 0;
    HANDLE hFile = (HANDLE)_get_osfhandle(fd);
    DWORD  type  = GetFileType(hFile);
    if (type != FILE_TYPE_PIPE)
        return(-1);

    do {
        if ( PeekNamedPipe(hFile,NULL,0,NULL,&bytesAvail,NULL) ) {
            if ( bytesAvail > 0 )
                return(1);
            else
                if ( secs-- > 0 )
                    sleep(1);
        }
        else {
            return(-1);
        }
    } while ( secs > 0 );

    return(0);
#else /* NT */
    return(-1);
#endif /* NT */
}

/*
  Z I N F I L L  --  Buffered file input.

  (re)fill the file input buffer with data.  All file input
  should go through this routine, usually by calling the zminchar()
  macro defined in ckcker.h.  Returns:

  Value 0..255 on success, the character that was read.
  -1 on end of file.
  -2 on any kind of error other than end of file.
  -3 timeout when reading from pipe (Kermit packet mode only).
*/
int
zinfill() {
    int x;
    extern int kactive;

    errno = 0;

#ifdef ZDEBUG
    printf("ZINFILL fp[%d]=%ld\n",ZIFILE,fp[ZIFILE]);
#endif /* ZDEBUG */

#ifdef CK_LABELED
    debug(F101,"zinfill: binary","",binary);
    debug(F101,"zinfill: pFEAList","",pFEAList);
    if ( binary == XYFT_L && pFEAList ) {
        zinptr = zinbuffer ;
        do_label_send(os2filename) ;
        if (feof(fp[ZIFILE]))
          return(-1);
        clearerr(fp[ZIFILE]);
        zincnt += fread(zinptr, sizeof (char), INBUFSIZE - zincnt, fp[ZIFILE]);
    } else {
#endif /* CK_LABELED */

#ifdef IKSD
        if (inserver && !local && fp[ZIFILE] == stdin) {
            int a, i;
            a = ttchk();
            for (i = 0 ;i < a && i < INBUFSIZE; i++) {
                zinbuffer[i] = coninc(0);
            }
            zincnt = i;
            zinptr = zinbuffer;    /* set pointer to beginning, (== &zinbuffer[0]) */
            if (zincnt == 0) return(-1);
            zincnt--;                           /* One less char in buffer */
            return((int)(*zinptr++) & 0377);    /* because we return the first */
        }
#endif /* IKSD */

        if (feof(fp[ZIFILE])) {
            debug(F100,"ZINFILL feof","",0);
#ifdef ZDEBUG
            printf("ZINFILL EOF\n");
#endif /* ZDEBUG */
            return(-1);
        }
        clearerr(fp[ZIFILE]);
        if (kactive && ispipe[ZIFILE]) {
            int secs, z = 0;
            secs = 1;
            debug(F101,"zinfill calling ttwait","",secs);
            if ((z = ttwait(fileno(fp[ZIFILE]),secs)) == 0) {
                debug(F101,"zinfill ttwait","",z);
                return(-3);
            }
        }

        zincnt = fread(zinbuffer, sizeof (char), INBUFSIZE, fp[ZIFILE]);
#ifdef CK_CTRLZ
        /* If SET FILE EOF CTRL-Z, first Ctrl-Z marks EOF */
        if (!binary && eofmethod == XYEOF_Z) {
            int i;
            for ( i=0 ; i<zincnt ; i++ ) {
                if (zinbuffer[i] == SUB) {
                    zincnt = i;         /* do not count Ctrl-Z */

                    if (i == 0) {
                        debug(F100,"ZINFILL Ctrl-Z first char in read","",0);
                        return(-1);
                    }
                    break;
                }
            }
        }
#endif /* CK_CTRLZ */
#ifdef DEBUG
        if (deblog)
            debug(F101,"ZINFILL fread","",zincnt);
#endif /* DEBUG */
#ifdef ZDEBUG
        printf("FREAD=%d\n",zincnt);
#endif /* ZDEBUG */
#ifdef OS2
#ifdef CK_LABELED
    }
#endif /* CK_LABELED */
#endif /* OS2 */

    if (ferror(fp[ZIFILE])) {
        debug(F100,"ZINFILL ferror","",0);
        debug(F100,"ZINFILL errno","",errno);
#ifdef ZDEBUG
        printf("ZINFILL errno=%d\n",errno);
#endif /* ZDEBUG */
        return(-2);
    }
    /* In case feof() didn't work just above -- sometimes it doesn't... */
    if (zincnt == 0) {
       if (feof(fp[ZIFILE]) ) {
           debug(F100,"ZINFILL count 0 EOF return -1","",0);
           return (-1);
       } else {
           debug(F100,"ZINFILL count 0 not EOF return -2","",0);
           return(-2);
       }
    }
    zinptr = zinbuffer;    /* set pointer to beginning, (== &zinbuffer[0]) */
    zincnt--;                           /* One less char in buffer */
    return((int)(*zinptr++) & 0377);    /* because we return the first */
}

/*  Z S O U T  --  Write a string out to the given file, buffered.  */

int
zsout(n,s) int n; char *s; {
    int rc = 0;
    if (chkfn(n) < 1) return(-1);       /* Keep this, prevents memory faults */
    if (!s) return(0);                  /* Null pointer, do nothing, succeed */
    if (!*s) return(0);                 /* empty string, ditto */
#ifdef IKSD
    if (inserver && !local && (n == ZCTERM || n == ZSTDIO)) {
        return(ttol(s,strlen(s))<0?-1:0);
    }
#endif /* IKSD */

    if (n == ZCTERM)
      return(Vscrnprintf("%s",s));
    if (n == ZSFILE)
      return(write(fileno(fp[n]),s,(int)strlen(s)));
    rc = fputs(s,fp[n]) == EOF ? -1 : 0;
#ifdef COMMENT
    /* Frank believes this can be commented out safely.
     * Doing so will prevent the file from being up to date
     * until the file is closed.  That is probably just fine
     * since the file is locked anyway while open.  5/11/2004
     */
    if (n == ZWFILE)
      fflush(fp[n]);
#endif /* COMMENT */
    return(rc);
}

/*  Z S O U T L  --  Write string to file, with line terminator, buffered  */

int
zsoutl(n,s) int n; char *s; {
    if (zsout(n,s) < 0)
        return(-1);
#ifdef IKSD
    if (inserver && !local && (n == ZCTERM || n == ZSTDIO)) {
        return(ttoc(LF));
    }
#endif /* IKSD */

    if (n == ZCTERM)
      return(Vscrnprintf("\n"));
    if (n == ZSFILE)                    /* But session log is unbuffered */
      return(write(fileno(fp[n]),"\n",1));
    if (fputs("\n",fp[n]) == EOF)
        return(-1); /* (===OS2 ? \r\n) */
    return(0);
}

/*  Z S O U T X  --  Write x characters to file, unbuffered.  */

int
zsoutx(n,s,x) int n, x; char *s; {
#ifdef IKSD
    if (inserver && !local && (n == ZCTERM || n == ZSTDIO)) {
        return(ttol(s,x));
    }
#endif /* IKSD */

    if (n == ZCTERM) {
        int i;
        for (i = 0; i < x; i++)
          if (!Vscrnprintf("%c",s[i]))
            return(-1);
        return(x);
    }
    return(write(fileno(fp[n]),s,x) == x ? x : -1);
}

/*  Z C H O U T  --  Add a character to the given file.  */

/*  Should return 0 or greater on success, -1 on failure (e.g. disk full)  */

int
#ifdef CK_ANSIC
zchout(register int n, char c)
#else
zchout(n,c) register int n; char c;
#endif /* CK_ANSIC */
/* zchout() */ {
#ifdef IKSD
    if (inserver && !local && (n == ZCTERM || n == ZSTDIO)) {
        return(ttoc(c));
    }
#endif /* IKSD */

    if ( n==ZCTERM )
        return(Vscrnprintf("%c",c));
    if (n == ZSFILE)                    /* Use unbuffered for session log */
        return(write(fileno(fp[n]),&c,1) == 1 ? 0 : -1);
                                /* Buffered for everything else */
        if (putc(c,fp[n]) == EOF)       /* If true, maybe there was an error */
        return(ferror(fp[n])?-1:0);     /* Check to make sure */
        else                                    /* Otherwise... */
        return(0);                      /* There was no error. */
}

/* (PWP) buffered character output routine to speed up file IO */

int
zoutdump() {
    int x;
    zoutptr = zoutbuffer;               /* Reset buffer pointer in all cases */
#ifdef DEBUG
    if (deblog)
      debug(F101,"zoutdump chars","",zoutcnt);
#endif /* DEBUG */
    if (zoutcnt == 0) {                 /* Nothing to output */
        return(0);
    } else if (zoutcnt < 0) {           /* Unexpected negative argument */
        zoutcnt = 0;                    /* Reset output buffer count */
        return(-1);                     /* and fail. */
    }

#ifdef CK_LABELED
    if (binary == XYFT_L ) {
        x = do_label_recv() ;
        if ( x > 0 ) { /* more room in the buffer */
            debug(F101,"zoutdump do_label_recv unfilled buffer","",zoutcnt);
            return 0 ;
        } else if ( x < 0 ) {
            debug(F101,"zoutdump do_label_recv error","",x);
            return -1 ;
        }
    }
#endif /* CK_LABELED */

#ifdef IKSD
    if (inserver && !local && fp[ZOFILE] == stdout) {
        x = ttol(zoutbuffer,zoutcnt);
        zoutcnt = 0;
        return( x > 0 ? 0 : -1);
    }
#endif /* IKSD */

#ifndef COMMENT
    /*
      Frank Prindle suggested that replacing this fwrite() by an fflush()
      followed by a write() would improve the efficiency, especially when
      writing to stdout.  Subsequent tests showed a 5-fold improvement.

      This appears to slow down NT.  So I changed it back.
    */
    x = fwrite(zoutbuffer, 1, zoutcnt, fp[ZOFILE]);
#else /* COMMENT */
    fflush(fp[ZOFILE]);
    x = write(fileno(fp[ZOFILE]),zoutbuffer,zoutcnt)
#endif /* COMMENT */
    if (x == zoutcnt) {
#ifdef DEBUG
        if (deblog)                     /* Save a function call... */
          debug(F101,"zoutdump write ok","",zoutcnt);
#endif /* DEBUG */
        zoutcnt = 0;                    /* Reset output buffer count */
        return(0);                      /* write() worked OK */
    } else {
#ifdef DEBUG
        if (deblog) {
            debug(F101,"zoutdump write error","",errno);
            debug(F101,"zoutdump write returns","",x);
        }
#endif /* DEBUG */
        zoutcnt = 0;                    /* Reset output buffer count */
        return(-1);                     /* write() failed */
    }
}

/*  C H K F N  --  Internal function to verify file number is ok  */

/*
 Returns:
  -1: File number n is out of range
   0: n is in range, but file is not open
   1: n in range and file is open
*/
int
chkfn(n) int n; {
    if (n != ZDFILE) debug(F101,"chkfn","",n);
    if (n < 0 || n >= ZNFILS) {
        if (n != ZDFILE) debug(F101,"chkfn out of range","",n);
        return(-1);
    } else {
        if (n != ZDFILE) debug(F101,"chkfn fp[n]","",fp[n]);
        return((fp[n] == NULL) ? 0 : 1);
    }
}

/*  Z G E T F S -- Return file size regardless of accessibility */
/*
  Returns:
    >= 0 if the file size can be determined
      -1 if the file size is unknown
      -2 if the file is a directory
*/

CK_OFF_T
zgetfs(name) char *name;
{
    struct stat buf;
    int x;

    if (!name)
        return(-1);
    if (!*name)
        return(-1);

    x = os2stat(name,&buf);
    if (x < 0) {
        return(-1);                             /* stat failed */
    }
    else if (!S_ISREG (buf.st_mode)             /* Must be regular file */
#ifdef S_ISFIFO
              && !S_ISFIFO (buf.st_mode)        /* or FIFO */
#endif /* S_ISFIFO */
              ) {
        return(-2);
    }
    return(buf.st_size);                        /* return size */
}


/*  Z C H K I  --  Check if input file exists and is readable  */

/*
  Returns:
   >= 0 if the file can be read (returns the size).
     -1 if file doesn't exist or can't be accessed,
     -2 if file exists but is not readable (e.g. a directory file).
     -3 if file exists but protected against read access.

  For Berkeley Unix, a file must be of type "regular" to be readable.
  Directory files, special files, and symbolic links are not readable.
*/

CK_OFF_T
zchki(name) char *name; {
    struct stat buf;
    int x, itsadir = 0;
    extern int zchkid, diractive, matchfifo;

    if (!name) return(-1);

#ifdef CKROOT
    debug(F111,"zchki setroot",ckroot,ckrootset);
    if (ckrootset)
        if (!zinroot(name)) {
            debug(F110,"zchki setroot violation",name,0);
            return(-1);
        }
#endif /* CKROOT */

    x = os2stat(name,&buf);
    if (x < 0) {
        debug(F111,"zchki stat fails",name,errno);
        return(-1);
    }

    if (S_ISDIR (buf.st_mode))
      itsadir = 1;

    if (!(itsadir && zchkid)) {         /* Unless this... */
        if (!S_ISREG (buf.st_mode)      /* Must be regular file */
#ifdef S_ISFIFO
            && (!matchfifo || !S_ISFIFO (buf.st_mode))  /* or FIFO */
#endif /* S_ISFIFO */
            ) {
            debug(F111,"zchki not regular file (or fifo)",name,matchfifo);
            return(-2);
        }
    }
    debug(F111,"zchki stat ok:",name,x);

    if (diractive) {                    /* If listing don't check access */
        x = 1;
    } else {
#ifdef SW_ACC_ID
        debug(F100,"zchki swapping ids for access()","",0);
        priv_on();
#endif /* SW_ACC_ID */
        x = access(name,R_OK);
#ifdef SW_ACC_ID
        priv_off();
        debug(F100,"zchki swapped ids restored","",0);
#endif /* SW_ACC_ID */
    }
    if (x < 0) {        /* Is the file accessible? */
        debug(F111,"zchki access failed:",name,x); /* No */
        return(-3);
    } else {
        iflen = buf.st_size;            /* Yes, remember size */
        ckstrncpy(nambuf,name,MAXNAMLEN); /* and name globally. */
        debug(F111,"zchki access ok:",name,iflen);
        return( (iflen > -1L) ? iflen : 0L );
    }
}

/*  Z C H K O  --  Check if output file can be created  */

/*
 Returns -1 if write permission for the file would be denied, 0 otherwise.
*/
int
zchko(name) char *name; {
    int i, x, itsadir=0;
    char *s;
    extern int zchkod;                  /* Used by IF WRITEABLE */

    if (!name) return(-1);              /* Watch out for null pointer. */

#ifdef CKROOT
    debug(F111,"zchko setroot",ckroot,ckrootset);
    if (ckrootset)
        if (!zinroot(name)) {
            debug(F110,"zchko setroot violation",name,0);
            errno = EACCES;
            return(-1);
        }
#endif /* CKROOT */

    debug(F111,"zchkod",name,zchkod);

    /*
    zchkod is a global flag meaning we're checking not to see if the directory
    file is writeable, but if it's OK to create files IN the directory.
    */
    if (!zchkod && isdir(name))         /* Directories are not writeable */
      return(-1);

    x = (int)strlen(name);              /* Get length of filename */
    s = malloc(x+3);                    /* Must copy because we can't */
    if (!s) {                           /* write into our argument. */
        fprintf(stderr,"zchko: Malloc error 46\n");
        return(-1);
    }
    ckstrncpy(s,name,x+3);

    for (i = x; i > 0; i--) {           /* Strip filename from right. */
        if (ISDIRSEP(s[i-1])) {
            itsadir = 1;
            break;
        }
    }
    debug(F101,"zchko i","",i);

#ifdef COMMENT
/*
  The following does not work for "foo/bar" where the foo directory does
  not exist even though we could create it: access("foo/.") fails, but
  access("foo") works OK.
*/
/* So now we use "path/." if path given, or "." if no path given. */
    s[i++] = '.';                       /* Append "." to path. */
    s[i] = '\0';
#else
/* So NOW we strip path segments from the right as long as they don't */
/* exist -- we only call access() for path segments that *do* exist.. */
/* (But this isn't quite right either since now zchko(/foo/bar/baz/xxx) */
/* succeeds when I have write access to foo and bar but baz doesn't exit.) */

    if (itsadir && i > 0) {
        s[i-1] = '\0';
        while (s[0] && !isdir(s)) {
            for (i = (int)strlen(s); i > 0; i--) {
                if (ISDIRSEP(s[i-1])) {
                    s[i-1] = '\0';
                    break;
                }
            }
            if (i == 0)
              s[0] = '\0';
        }
    } else {
        s[i++] = '.';                   /* Append "." to path. */
        s[i] = '\0';
    }
#endif /* COMMENT */

    if (!s[0])
        strcpy(s,".");

#ifdef NT
    {
        /* We are only checking directory access at this point. 
         * The file attribute READ_ONLY is meaningless for directories
         * in Windows.  So all we can do is see whether we can access
         * the directory information at all.  We can't use access()
         * for this because it is broken.
         */
        DWORD attr = GetFileAttributes((LPTSTR)s);
        x = (attr == 0xffffffff) ? -1 : 0;
    }
#else /* NT */
    x = 0;              /* No unwritable directories in OS/2 */
#endif /* NT */

    if (x < 0)
      debug(F111,"zchko access failed:",s,errno);
    else
      debug(F111,"zchko access ok:",s,x);
    free(s);                            /* Free temporary storage */
    return((x < 0) ? -1 : 0);           /* and return. */
}

static char *
UNCname(char * name) {
    int i;
    for ( i=0; name[i] && i <= MAXNAMLEN; i++ )
        UNCnam[i] = (name[i] == '/' ? '\\' : name[i]);
    UNCnam[i] = '\0';
    return(UNCnam);
}

/*  Z D E L E T  --  Delete the named file.  */

int
zdelet(name) char *name; {
    int x;
#ifdef CK_LOGIN
    if (isguest)
      return(-1);
#endif /* CK_LOGIN */

#ifdef CKROOT
    debug(F111,"zdelet setroot",ckroot,ckrootset);
    if (ckrootset)
        if (!zinroot(name)) {
            debug(F110,"zdelet setroot violation",name,0);
            return(-1);
        }
#endif /* CKROOT */

    if ( !strncmp(name,"//",2) || !strncmp(name,"\\\\",2))
        x =unlink(UNCname(name));
    else
        x = unlink(name);
    debug(F111,"zdelet",name,x);
#ifdef CKSYSLOG
    if (ckxsyslog >= SYSLG_FC && ckxlogging) {
        char buf[1024];
        fullname[0] = '\0';
        zfnqfp(name,CKMAXPATH,fullname);
        debug(F110,"zdelet fullname",fullname,0);
        if (x < 0) {
            sprintf(buf, "file[] %s: delete failed", fullname);
            cksyslog(SYSLG_FC, 0, buf, NULL, NULL);
        } else {
            sprintf(buf, "file[] %s: delete ok", fullname);
            cksyslog(SYSLG_FC, 1, buf, NULL, NULL);
        }
    }
#endif /* CKSYSLOG */
    return(x);
}

void
ChangeNameForFAT(char *name) {
    char *src, *dst, *next, *ptr, *dot, *start;
    static char invalid[] = ":;,=+\"[]<>| \t";


    /* Skip over the drive letter (if any) */
    if ( isalpha(name[0]) && (name[1] == ':') )
        start = name + 2;
    else
        start = name;

    /* Absolute path? */
    src = dst = start;
    if ( ISDIRSEP(*src) )
        src++, dst++;

    while ( *src )
    {
        /* Find the end of the directory or file name */
        for ( next = src;
              *next && !(ISDIRSEP(*next));
              next++ );

        /* Find the last dot in this name */
        for ( ptr = src, dot = NULL; ptr < next; ptr++ )
            if ( *ptr == '.' )
            {
                dot = ptr; /* remember last dot */
                *ptr = '_';
            }

        /* If no DOT then treat an Underscore as a DOT */
        if ( dot == NULL )
            for ( ptr = src; ptr < next; ptr++ )
                if ( *ptr == '_' )
                    dot = ptr; /* remember last _ as if it were a dot */

        /* Does the name meet 8.3 format? */
        if ( dot && (dot > src) &&
             ((next - dot <= 4) ||
               ((next - src > 8) && (dot - src > 3))) )
        {
            if ( dot )
                *dot = '.';

            for ( ptr = src; (ptr < dot) && ((ptr - src) < 8); ptr++ )
                *dst++ = *ptr;

            for ( ptr = dot; (ptr < next) && ((ptr - dot) < 4); ptr++ )
                *dst++ = *ptr;
        }
        else
        {
            if ( dot && (next - src == 1) )
                *dot = '.';           /* special case: "." as a path component */

            for ( ptr = src; (ptr < next) && ((ptr - src) < 8); ptr++ )
                *dst++ = *ptr;
        }

        *dst++ = *next; /* either '/' or 0 */

        if ( *next )
        {
            src = next + 1;

            if ( *src == 0 ) /* handle trailing '/' on dirs ! */
                *dst = 0;
        }
        else
            break;
    }

    /* Finally, convert invalid characters */
    for ( src = start; *src != 0; ++src )
        if ( strchr(invalid, *src) != NULL )
            *src = '_';
}


int
IsFileNameValid(char *name)
{
    int rc = FALSE;
    static char prevpath[CKMAXPATH+4] = { '\0', '\0' };
    static int prevstat = -1;

#ifdef NT
    HANDLE hf ;
    DWORD lasterror = 0 ;
#else /* NT */
    HFILE hf;
    ULONG uAction;
#endif /* NT */

    if ( prevstat > -1 ) {
        if ( name[0] == prevpath[0] ) {
            if ( !strcmp(name,prevpath) ) {
                debug(F111,"isdir cache hit",name,prevstat);
                return(prevstat);
            }
        }
    }

#ifdef NT
    /* A difference between Win9x and NT/2000 is that on Win9x a
       ERROR_FILE_NOT_FOUND when the name is invalid such as a name
       surrounded by double-quotes when OPEN_EXISTING is used instead
       of OPEN_ALWAYS.
    */
    hf = CreateFile( name,
                     GENERIC_READ | GENERIC_WRITE,
                     FILE_SHARE_READ | FILE_SHARE_WRITE, /* Allow Sharing */
                     NULL, /* No Security Attributes specified */
                     OPEN_EXISTING,
                     FILE_ATTRIBUTE_NORMAL,
                     NULL ) ;
    lasterror = GetLastError();
    if ( hf == INVALID_HANDLE_VALUE )
    {
        switch ( lasterror ) {
        case ERROR_INVALID_PARAMETER:
            debug(F111,"IsFileNameValid() Invalid Handle","Invalid Parameter",lasterror);
            break;
        case ERROR_INVALID_NAME:
            debug(F111,"IsFileNameValid() Invalid Handle","Invalid Name",lasterror);
            break;
        case ERROR_FILENAME_EXCED_RANGE:
            debug(F111,"IsFileNameValid() Invalid Handle","FileName Exceeds Range",lasterror);
            break;
        case ERROR_FILE_NOT_FOUND:
            debug(F111,"IsFileNameValid() Invalid Handle","File Not Found",lasterror);
            if ( isWin95() ) {
                hf = CreateFile( name,
                                 GENERIC_READ | GENERIC_WRITE,
                                 FILE_SHARE_READ | FILE_SHARE_WRITE, /* Allow Sharing */
                                 NULL, /* No Security Attributes specified */
                                 OPEN_ALWAYS,
                                 FILE_ATTRIBUTE_NORMAL,
                                 NULL ) ;
                if ( hf != INVALID_HANDLE_VALUE ) {
                                        CloseHandle(hf);
                    debug(F111,"IsFileNameValid() Win9x Valid Handle",
                           "File Created",lasterror);
                    rc = TRUE;
                    DeleteFile( name ) ;
                }
                break;
            }
            /* fall through */
        default:
            debug(F111,"IsFileNameValid() Invalid Handle","returning TRUE",lasterror);
            rc = TRUE;
        }
    }
    else
    {
        debug(F101,"IsFileNameValid() Valid Handle","",lasterror);
        CloseHandle( hf ) ;
        rc = TRUE ;
    }
#else /* NT */
    switch( DosOpen(name, &hf, &uAction, 0, 0, FILE_OPEN,
                     OPEN_ACCESS_READONLY | OPEN_SHARE_DENYNONE, 0) )
    {
    case ERROR_INVALID_NAME:
    case ERROR_FILENAME_EXCED_RANGE:
    case ERROR_INVALID_PARAMETER:
        break;          /* FALSE */
    case NO_ERROR:
        DosClose(hf);
    default:
        rc = TRUE;
    }
#endif /* NT */
    ckstrncpy(prevpath,name,CKMAXPATH+4);
    prevstat = rc;
    return rc;
}

CK_OFF_T
zdskspace(int drive) {
#ifdef NT
    DWORD spc, bps, fc, c ;
    char rootpath[4] ;
    int gle;
    BOOL (_stdcall *pGetDiskFreeSpaceEx)
        ( LPCTSTR lpDirectoryName,                 // directory name
          PULARGE_INTEGER lpFreeBytesAvailable,    // bytes available to caller
          PULARGE_INTEGER lpTotalNumberOfBytes,    // bytes on disk
          PULARGE_INTEGER lpTotalNumberOfFreeBytes // free bytes on disk
          )=NULL;
    ULARGE_INTEGER i64FreeBytesToCaller, i64TotalBytes, i64FreeBytes;
    (FARPROC) pGetDiskFreeSpaceEx = GetProcAddress( GetModuleHandle("kernel32.dll"),
                                          "GetDiskFreeSpaceExA");

    if (drive)
    {
        rootpath[0] = '@' + drive ;
        rootpath[1] = ':' ;
        rootpath[2] = '\\' ;
        rootpath[3] = '\0' ;

        debug(F110,"zdskspace() rootpath",rootpath,0);

        if (pGetDiskFreeSpaceEx)
        {
            debug(F110,"zdskspace()","pGetDiskFreeSpaceEx",0);

            if ( !pGetDiskFreeSpaceEx (rootpath,
                                        (PULARGE_INTEGER)&i64FreeBytesToCaller,
                                        (PULARGE_INTEGER)&i64TotalBytes,
                                        (PULARGE_INTEGER)&i64FreeBytes)) {
                gle = GetLastError();
                debug(F111,"zdskspace() failure","lastError",gle);
                return (1) ;
            }
            /*return(i64FreeBytesToCaller.HighPart ? (unsigned long)-1 :
                    i64FreeBytesToCaller.LowPart);*/
            return i64FreeBytesToCaller.QuadPart;
        } else {
            debug(F110,"zdskspace()","GetDiskFreeSpace",0);
            if ( !GetDiskFreeSpace( rootpath, &spc, &bps, &fc, &c )) {
                gle = GetLastError();
                debug(F111,"zdskspace() failure","lastError",gle);
                return (1) ;
            }
            return spc * bps * fc ;
        }
    }
    else {
        debug(F110,"zdskspace()","no drive specified",0);

        if (pGetDiskFreeSpaceEx)
        {
            debug(F110,"zdskspace()","pGetDiskFreeSpaceEx",0);
            if ( !pGetDiskFreeSpaceEx (NULL,
                                        (PULARGE_INTEGER)&i64FreeBytesToCaller,
                                        (PULARGE_INTEGER)&i64TotalBytes,
                                        (PULARGE_INTEGER)&i64FreeBytes)) {
                gle = GetLastError();
                debug(F111,"zdskspace() failure","lastError",gle);
                return (1) ;
            }
            /*return(i64FreeBytesToCaller.HighPart  ? (unsigned long)-1 :
                    i64FreeBytesToCaller.LowPart);*/
            return i64FreeBytesToCaller.QuadPart;
        } else {
            debug(F110,"zdskspace()","GetDiskFreeSpace",0);
            if ( !GetDiskFreeSpace( NULL, &spc, &bps, &fc, &c )) {
                gle = GetLastError();
                debug(F111,"zdskspace() failure","lastError",gle);
                return (1) ;
            }
        }
        return spc * bps * fc ;
    }
#else /* NT */
    FSALLOCATE fsa;
    if ( DosQueryFSInfo(drive, 1, (PBYTE) &fsa, sizeof(fsa)) )
        return 0;
    return fsa.cUnitAvail * fsa.cSectorUnit * fsa.cbSector;
#endif /* NT */
}

char *
GetLoadPath(void) {
#ifdef NT
   static char filename[512] ;
   filename[0] = '\0' ;
   GetModuleFileName( NULL, filename, 512 ) ;
   debug(F111,"GetLoadPath",filename,strlen(filename));
   return filename;
#else /* NT */
   PTIB pptib;
   PPIB pppib;
   char *szPath;

   DosGetInfoBlocks(&pptib, &pppib);

   szPath = pppib -> pib_pchenv;

   while (*szPath)
      szPath = strchr(szPath, 0) + 1;

   return szPath + 1;
#endif /* NT */
}


/*  Z R T O L  --  Convert remote filename into local form  */

VOID
zrtol(name,name2) char *name, *name2; {
    nzrtol(name,name2,1,0,CKMAXPATH);
}

VOID
nzrtol(name,name2,fncnv,fnrpath,max)
    char *name, *name2; int fncnv, fnrpath, max;
{ /* nzrtol */
    char *p; int flag = 0, n = 0;
    char tmpbuf[CKMAXPATH+1], *tmp;
    int devnull = 0;
    int acase = 0;
    if (!name2) return;
    if (!name) name = "";
    debug(F110,"zrtol original name",name,0);

    /* Handle the path -- we don't have to convert its format, since */
    /* the standard path format and our internal format are the same. */

    tmpbuf[0] = NUL;
    tmp = tmpbuf;
    devnull = !strcmp(name,"/dev/null");

    if (!devnull && fnrpath == PATH_OFF) {/* RECEIVE PATHNAMES OFF */
        zstrip(name,&p);
        ckstrncpy(tmpbuf,p,CKMAXPATH);
    } else if (!devnull &&
                fnrpath == PATH_ABS) {  /* RECEIVE PATHNAMES ABSOLUTE */
        ckstrncpy(tmpbuf,name,CKMAXPATH);
    } else if (!devnull &&
                isabsolute(name)) {     /* RECEIVE PATHNAMES RELATIVE */
        sprintf(tmpbuf,".%s",name);
    } else {                            /* Ditto */
        ckstrncpy(tmpbuf,name,CKMAXPATH);
    }
    tmpbuf[CKMAXPATH] = NUL;
    debug(F110,"zrtol tmpbuf",tmpbuf,0);

    /* Even if we are not converting need to make sure that  */
    /* the directory separator is '/' and that a few special */
    /* characters are not used.                              */

    p = tmpbuf;
    while ( *p ) {
        if ( *p == '\\')
            *p = '/';
        p++;
    }

    if (!fncnv || devnull) {            /* Not converting */
        ckstrncpy(name2,tmpbuf,max);    /* We're done. */
        if (!IsFileNameValid(name2)) {
            /* Lets first try just altering some illegal characters*/
            p = name2;
            while ( *p ) {
                switch ( *p ) {
                case ':':
                case '*':
                case '?':
                case '|':
                case '"':
                case '<':
                case '>':
                    *p = '_';
                    break;
                }
                p++;
            }
            if (!IsFileNameValid(name2))
                ChangeNameForFAT(name2);
        }
        debug(F110,"zrtol non-converted name",name2,0);
        return;
    }
    name = tmpbuf;                      /* Converting */

    p = name2;
    for (; *name != '\0' && n < maxnam; name++) {
        if (*name > SP) flag = 1;       /* Strip leading blanks and controls */
        if (flag == 0 && *name < '!')
          continue;
        if (fncnv > 0) {
            if (isupper(*name))             /* Check for mixed case */
                acase |= 1;
            else if (islower(*name))
                acase |= 2;
        }
        switch ( *name ) {
        case ':':                       /* Convert illegal chars */
        case '*':
        case '?':
        case '|':
        case '"':
        case '<':
        case '>':
        case ' ':                       /* And Space */
                break;
            *p = '_';                   /* To underscore */
            break;
        default:
            *p = *name;
        }
        p++;
        n++;
    }
    *p-- = '\0';                        /* Terminate */
    while (*p < '!' && p > name2)       /* Strip trailing blanks & controls */
      *p-- = '\0';

    if (*name2 == '\0') {               /* Nothing left? */
        strcpy(name2,"NONAME");         /* do this... */
    } else if (acase == 1) {            /* All uppercase? */
        p = name2;                      /* So convert all letters to lower */
        while (*p) {
            if (isupper(*p))
              *p = tolower(*p);
            p++;
        }
    }
    if (!IsFileNameValid(name2))
      ChangeNameForFAT(name2);
    debug(F110,"zrtol new name",name2,0);
}

/*  Z S T R I P  --  Strip device & directory name from file specification */

/*  Strip pathname from filename "name", return pointer to result in name2 */

#ifdef pdp11
#define ZSTRPLEN 64
#else
#define ZSTRPLEN MAXPATH
#endif /* pdp11 */
static char work[ZSTRPLEN+1];

VOID
zstrip(name,name2) char *name, **name2; {
    char *cp, *pp;
    int n = 0;
    debug(F110,"zstrip before",name,0);
    if (!name) { *name2 = ""; return; }
    pp = work;
    /* Strip disk letter and colon */
    if (isalpha(*name) && (*(name+1) == ':')) name += 2;
    debug(F110,"zstrip after disk-stripping",name,0);

    for (cp = name; *cp; cp++) {
        if (ISDIRSEP(*cp)) {
            pp = work;
            n = 0;
        } else {
            *pp++ = *cp;
            if (n++ >= ZSTRPLEN)
              break;
        }
    }
    *pp = '\0';                         /* Terminate the string */
    *name2 = work;
    debug(F110,"zstrip after",*name2,0);
}

/*  Z L T O R  --  Local TO Remote */

VOID
zltor(name,name2) char *name, *name2; {
    nzltor(name,name2,1,0,CKMAXPATH);
}

/*  N Z L T O R  --  New Local TO Remote */

/* fncnv = 0 for no conversion, > 0 for regular conversion, < 0 for minimal */

VOID
nzltor(name,name2,fncnv,fnspath,max)
    char *name, *name2; int fncnv, fnspath, max;
{ /* nzltor */
    char *cp=NULL, *pp=NULL;
    int n = 0;
    char *dotp = NULL;
    char tmpbuf[CKMAXPATH+1];
    char *p=NULL;

    CHAR c;

#ifndef NOCSETS
    extern int fcharset, /* tcharset, */ language;
    int langsv;
    _PROTOTYP ( CHAR (*sxo), (CHAR) ) = NULL; /* Translation functions */
#ifdef CK_ANSIC
    extern CHAR (*xls[MAXTCSETS+1][MAXFCSETS+1])(CHAR);
#else
    extern CHAR (*xls[MAXTCSETS+1][MAXFCSETS+1])();
#endif /* CK_ANSIC */
    langsv = language;
    language = L_USASCII;
#ifdef COMMENT
    /* Proper translation of filenames must be done elsewhere */
    n = tcharset ? tcharset : TC_USASCII;
    sxo = xls[n][fcharset];
#else
    sxo = xls[TC_USASCII][fcharset];
#endif /* COMMENT */
#endif /* NOCSETS */

    debug(F110,"nzltor",name,0);

    /* Handle pathname */

    tmpbuf[0] = NUL;
    if (fnspath == PATH_OFF) {          /* PATHNAMES OFF */
        zstrip(name,&p);
        ckstrncpy(tmpbuf,p,CKMAXPATH);
        tmpbuf[CKMAXPATH] = NUL;
    } else {
        int x = 0;
        if ( name[0] == '.' && ISDIRSEP(name[1]) )
            x = 2;
        else if ( name[0] == '.' && name[1] == '.' && ISDIRSEP(name[2]) )
            x = 3;
        if ( fnspath == PATH_ABS ) {    /* ABSOLUTE */
            zfnqfp(name+x,CKMAXPATH,tmpbuf);
        } else {                        /* RELATIVE */
            ckstrncpy(tmpbuf,name+x,CKMAXPATH);
        }
    }
    debug(F110,"nzltor tmpbuf",tmpbuf,0);

    if (!fncnv) {                       /* Not converting */
        if (isalpha(*tmpbuf) && (*(tmpbuf+1) == ':')) /* Strip disk name */
            ckstrncpy(name2,&tmpbuf[2],max);    /* We're done. */
        else
            ckstrncpy(name2,tmpbuf,max);
#ifndef NOCSETS
        langsv = language;
#endif /* NOCSETS */
        return;
    }
    name = tmpbuf;                      /* Converting */
    if (isalpha(*name) && (*(name+1) == ':')) /* Strip disk name */
        name += 2;

    pp = work;
    for (cp = name, n=0; *cp && n < max; cp++, n++) {        /* Strip path name */
        c= *cp;
#ifndef NOCSETS
        if (sxo) c = (*sxo)(c);         /* Convert to ASCII */
#endif /* NOCSETS */
        if (fncnv > 0 && islower(c))       /* Uppercase letters */
            *pp++ = toupper(c);               /* Change tilde to 'X' */
        else if (c == '~')
            *pp++ = 'X';
        else if (fncnv > 0 && c == '#')            /* Change number sign to 'X' */
            *pp++ = 'X';
        else if (c == '*' || c == '?')
            *pp++ = 'X';
        else if (c <= ' ')            /* Change space and controls to 'X' */
            *pp++ = '_';                /* was 'X' */
        else if (fncnv > 0 && c == '.') {          /* Change dot to underscore */
            dotp = pp;                  /* Remember where we last did this */
            *pp++ = '_';
        } else
            *pp++ = c;
    }
    *pp = NUL;                          /* Tie it off. */
    if (dotp) *dotp = '.';              /* Restore last dot (if any) */
    cp = name2;                         /* If nothing before dot, */
    if (*work == '.') *cp++ = 'X';      /* insert 'X' */
    ckstrncpy(cp,work,max);
#ifndef NOCSETS
    language = langsv;
#endif /* NOCSETS */
    debug(F110,"zltor name2",name2,0);
}


/*  Z C H D I R  --  Change directory  */
/*
  Call with:
    dirnam = pointer to name of directory to change to,
      which may be "" or NULL to indicate user's home directory.
  Returns:
    0 on failure
    1 on success
*/
int
zchdir(dirnam) char *dirnam; {
#ifndef NOSPL
    extern struct mtab *mactab;             /* Main macro table */
    extern int nmac;                        /* Number of macros */
#endif /* NOSPL */
    char hd[CKMAXPATH+1];
    char *p;
    int len;

    debug(F110,"zchdir",dirnam,0);
    if (dirnam == NULL || dirnam == "" || *dirnam == '\0') /* If arg is null */
      dirnam = zhome();                 /* use user's home directory. */
    debug(F110,"zchdir 2",dirnam,0);

    ckstrncpy(hd,dirnam,CKMAXPATH+1);
    debug(F110,"zchdir 3",hd,0);

#ifdef CKROOT
    debug(F111,"zchdir setroot",ckroot,ckrootset);
    if (ckrootset)
        if (!zinroot(hd)) {
            debug(F110,"zchdir setroot violation",hd,0);
            return(0);
        }
#endif /* CKROOT */

    if (isalpha(hd[0]) && hd[1] == ':') {
        if (zchdsk(hd[0]))
          return(0);
        if (hd[2] == 0)
          return(1);                    /* Handle drive-only case */
    }


    len = strlen(hd) ;
    if ( !strncmp(hd,"//",2) || !strncmp(hd,"\\\\",2)) {
        int i;
        if ( ISDIRSEP(hd[len-1]) ) {
            /* We now need to strip the trailing directory separator if it is not */
            /* part of the machine or object name.                                */
            for ( i=2 ; i<len && !(ISDIRSEP(hd[i])) ; i++ ); /* machine */
            for ( i++ ; i<len && !(ISDIRSEP(hd[i])) ; i++ ); /* object */
            if (i < len-1 )
                hd[len-1] = '\0';
        }
    } else {
        /* strip trailing DIRSEP except after d:; chdir() doesn't like it */
        if ( len > 1 && ISDIRSEP(hd[len - 1]) && hd[len - 2] != ':')
            hd[len - 1] = 0;
    }

    if (chdir(hd) == 0) {
#ifdef IKSDB
        extern int ikdbopen;
        if (inserver && ikdbopen) {
#ifdef CKROOT
#ifdef CK_LOGIN
            slotdir(isguest ? anonroot : "", zgtdir());
#else
            slotdir("", zgtdir());
#endif /* CK_LOGIN */
#else
            slotdir("", zgtdir());
#endif /* CKROOT */
        }
#endif /* IKSDB */

#ifndef NOSPL
        if (nmac) {             /* Any macros defined? */
            int k;                /* Yes */
            static on_cd = 0;
            if ( !on_cd ) {
                on_cd = 1;
                k = mlook(mactab,"on_cd",nmac); /* Look this up */
                if (k >= 0) {                   /* If found, */
                    if (dodo(k,zgtdir(),0) > -1)  /* set it up, */
                        parser(1);                  /* and execute it */
                }
                on_cd = 0;
            }
        }
#endif /* NOSPL */
        return(1);              /* Try to cd */
    } else
        return(0);
}

/*  Z H O M E  --  Return pointer to user's home directory  */

static char homedir[CKMAXPATH+1]="";

char *
zhome() {
    extern char startupdir[];
    int i;
    char * home;

#ifdef CKROOT
    if (ckrootset)
      return((char *)ckroot);
#endif /* CKROOT */

    if (homedir[0])
        return(homedir);

    home = getenv("HOME");
    if (home) {
        ckstrncpy(homedir,home,CKMAXPATH);
    } else {
#ifdef NT
        char * homedrive = (char *)GetHomeDrive();
        char * homepath  = (char *)GetHomePath();
        char * profile   = getenv("USERPROFILE");
        char * personal  = (char *)GetPersonal();
        if (homedrive && homepath)
            sprintf(homedir,"%s%s",homedrive,homepath);
        else if ( profile )
            sprintf(homedir,"%s",profile);
        else if ( personal )
            ckstrncpy(homedir,personal,CKMAXPATH);
        else
#endif /* NT */
            ckstrncpy(homedir,startupdir,CKMAXPATH);
    }

#ifdef NT
    GetShortPathName(homedir,homedir,CKMAXPATH);
#endif /* NT */

    /* we know have the directory, but need to make it consistent */
    /* with all of the other Kermit directory variables: forward  */
    /* slashes and a trailing slash.                              */
    i = strlen(homedir)-1;
    if ( !(ISDIRSEP(homedir[i])) ) {
        homedir[i+1] = '/';
        homedir[i+2] = NUL;
    }
    for (i-- ; i >= 0 ; i--) {
        if ( homedir[i] == '\\' )
          homedir[i] = '/';
    }

    debug(F110,"zhome sets homedir to",homedir,0);
    return(homedir);
}

/*  Z G T D I R  --  Return pointer to user's current directory  */

#define CWDBL MAXPATH
static char cwdbuf[CWDBL+1];

char *
zgtdir() {
    char *buf;
    int x=0,len=0;

#ifndef __IBMC__ /* which has a macro for this */
    extern char *getcwd();
#endif /* __IBMC__ */
    buf = cwdbuf;
    debug(F101,"zgtdir CWDBL","",CWDBL);
    getcwd(buf,CWDBL);
    len = strlen(buf);
    for ( x=len-1;x >= 0; x-- )
        if ( buf[x] == '\\' )
            buf[x] = '/';
    if ( buf[len-1] != '/' ) {
        buf[len] = '/';
        buf[len+1] = NUL;
    }
    debug(F110,"zgtdir getcwd()",buf,0);
    return(buf);
}

/*  Z X C M D -- Run a system command so its output can be read like a file */

int
zxcmd(filnum,comand) int filnum; char *comand; {
    int out, rc;
    int pipes[2];

    debug(F111,"zxcmd",comand,filnum);
    if (chkfn(filnum) < 0) return(-1);  /* Need a valid Kermit file number. */
    if (filnum == ZSTDIO || filnum == ZCTERM) /* But not one of these. */
      return(0);

    /* popen() invalidates the StdHandle temporarily, don't do screen updates */
    /* while it starts the child process                                      */
    rc = RequestScreenMutex( 5000 );
    if ( rc )
    {
        debug(F100,"zxcmd() unable to retrieve Screen Mutex","",0);
        return -1;
    }

    out = (filnum == ZIFILE || filnum == ZRFILE) ? 0 : 1 ;
    debug(F101,"zxcmd out",comand,out);

    if (!out) {                         /* Input from a command */
        if (priv_chk() ||
             ((fp[filnum] = popen(comand,"rb")) == NULL)
             ) {
            ReleaseScreenMutex();
            return(0);
        }
        zincnt = 0;                     /* (PWP) reset input buffer */
        zinptr = zinbuffer;
    } else { /* Output to a command */
        if (priv_chk() ||
             ((fp[filnum] = popen(comand,"wb")) == NULL)
             ) {
            ReleaseScreenMutex();
            return(0);
        }
        zoutcnt = 0;                    /* (PWP) reset input buffer */
        zoutptr = zoutbuffer;
    }
    ReleaseScreenMutex();
    fp[ZSYSFN] = fp[filnum];            /* remember the pipe handle */
    ispipe[filnum] = 1;
    return(1);
} /* zxcmd */

/*  Z C L O S F  - wait for the child fork to terminate and close the pipe. */

/*  Used internally by zclose - returns -1 on failure, 1 on success. */

int
zclosf(filnum) int filnum; {
    int wstat, out;
    debug(F101,"zclosf filnum","",filnum);
    out = (filnum == ZIFILE || filnum == ZRFILE) ? 0 : 1 ;
    debug(F101,"zclosf out","",out);

#ifndef NOPOPEN
    if (ispipe[filnum]) {
        int x;
        x = pclose(fp[filnum]);
        pexitstat = x;                  /* *** CHECK THIS *** */
        fp[filnum] = NULL;
        ispipe[filnum] = 0;
        return((x != 0) ? -1 : 1);
    }
#endif /* NOPOPEN */
    debug(F101,"zclosf fp[filnum]","", fp[filnum]);
    debug(F101,"zclosf fp[ZSYSFN]","", fp[ZSYSFN]);

    fclose(fp[filnum]);
    fp[filnum] = NULL;
    ispipe[filnum] = 0;
    debug(F101,"zclosf fp[filnum]","",fp[filnum]);
#ifdef CK_CHILD
    return(pexitstat == 0 ? 1 : 0);
#else
    return(1);
#endif /* CK_CHILD */
}

#ifdef NT
static HANDLE hKernel = INVALID_HANDLE_VALUE;
static DWORD (WINAPI *p_GetLongPathNameA)(
    LPCSTR lpFileName,
    LPSTR lpBuffer,
    DWORD cchBuffer
    ) = NULL;

DWORD 
ckGetLongPathName(LPCSTR lpFileName, LPSTR lpBuffer, DWORD cchBuffer)
{
    if ( !p_GetLongPathNameA ) {
        if (hKernel == INVALID_HANDLE_VALUE)
            hKernel = LoadLibrary("kernel32.dll");
        if (hKernel != INVALID_HANDLE_VALUE)
            (FARPROC) p_GetLongPathNameA =
                GetProcAddress( hKernel, "GetLongPathNameA" );
    }

    if ( !p_GetLongPathNameA ) {
        DWORD len, i;
        if ( !lpFileName || !lpBuffer )
            return(0);

        len = strlen(lpFileName);
        if ( len + 1 <= cchBuffer )
            ckstrncpy(lpBuffer,lpFileName,cchBuffer);
        return (len);
    } else {
        return(p_GetLongPathNameA(lpFileName,lpBuffer,cchBuffer));
    }
}
#endif /* NT */

/*  Z X P A N D  --  Expand a wildcard string into an array of strings  */
/*
  Returns the number of files that match fn1, with data structures set up
  so that first file (if any) will be returned by the next znext() call.

  Depends on external variable wildxpand: 0 means we expand wildcards
  internally, nonzero means we call the shell to do it.
  OS2 ignores wildxpand since it always expands the list internally.
*/

int win95_8_3 = 0;              /* Do not use 8.3 FAT file name notation */

static char findpath[2][MAXPATH+4];
static char findpathrel[2][MAXPATH+4];
static char findfspec[2][MAXPATH];
static char findfspec2[2][MAXPATH];
static int findpathwild[2];
#define MAX_FIND_LEVEL 63
/* findlevel indicates how deeply nested we are in the directory tree */
/* it is used as an index into the other structures.                  */
static int  findlevel[2] = {-1,-1};

/* findeop - (end of path) is a pointer to the end of the path at the */
/* current depth in the tree.  It is used as a method for quickly     */
/* restoring the previous level when setting up the next query.       */
static char * findeop[2][MAX_FIND_LEVEL+1] = {{
    NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
    NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
    NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
    NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
    NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
    NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
    NULL,NULL,NULL,NULL
},{
    NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
    NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
    NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
    NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
    NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
    NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
    NULL,NULL,NULL,NULL
}};

#ifdef NT
/* finddata - the directory entry from the most recent query */
static WIN32_FIND_DATA finddata[2];

/* findhandle - a stack of handles for each of the outstanding queries */
static HANDLE findhandle[2][MAX_FIND_LEVEL+1] = {{
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE
},{
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,INVALID_HANDLE_VALUE,
    INVALID_HANDLE_VALUE
}};
#else /* NT */
/* finddata - the directory entry from the most recent query */
static FILEFINDBUF3    finddata[2];

/* findhandle - a stack of handles for each of the outstanding queries */
static HDIR            findhandle[2][MAX_FIND_LEVEL+1] = {{
    HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,
    HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,
    HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,
    HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,
    HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,
    HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,
    HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,
    HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,
    HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,
    HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,
    HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE
},{
    HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,
    HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,
    HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,
    HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,
    HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,
    HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,
    HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,
    HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,
    HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,
    HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,
    HDIR_CREATE,HDIR_CREATE,HDIR_CREATE,HDIR_CREATE
}};
static ULONG           findcount=0;
#endif /* NT */
static int os2dironly[2]={0,0};         /* only report directories?   */
static int os2fileonly[2]={0,0};        /* only report files?         */
static int os2recursive[2]={0,0};       /* recursively traverse tree? */
static char localfn[2][MAXPATH+4];
static char rewindfn[2][MAXPATH+4];
static char rewindpath[2][MAXPATH+4];
static char rewindpathrel[2][MAXPATH+4];
static char rewindfspec[2][MAXPATH];
static char stream[2][MAXPATH];
static int lasterror[2]={0,0};
int zxpn = 0;                       /* 0 or 1, allows separate searches */

#ifdef RECURSIVE
int
os2findpush(void) {
    if ( findlevel[zxpn] >= MAX_FIND_LEVEL )
      return(0);
    findlevel[zxpn]++;
    findeop[zxpn][findlevel[zxpn]] = findpath[zxpn] + strlen(findpath[zxpn]);
#ifdef NT
    ckstrncat(findpath[zxpn],finddata[zxpn].cFileName,MAXPATH+4);
#else /* NT */
    ckstrncat(findpath[zxpn],finddata[zxpn].achName,MAXPATH+4 );
#endif /* NT */
    ckstrncat(findpath[zxpn],"/",MAXPATH+4);
    ckstrncpy(localfn[zxpn],findpath[zxpn],MAXPATH+4);
    ckstrncat(localfn[zxpn],findpathrel[zxpn],MAXPATH+4);
    ckstrncat(localfn[zxpn],findfspec[zxpn],MAXPATH+4);
    debug(F111,"os2findpush",findpath[zxpn],findlevel[zxpn]);
    return(1);
}

int
os2findpop(void) {
    debug(F111,"os2findpop",findpath[zxpn],findlevel[zxpn]);
    if ( findlevel[zxpn] <= 0 )
      return(0);
    *findeop[zxpn][findlevel[zxpn]] = NUL;
    findeop[zxpn][findlevel[zxpn]] = NULL;
    findlevel[zxpn]--;
    ckstrncpy(localfn[zxpn],findpath[zxpn],MAXPATH+4);
    ckstrncat(localfn[zxpn],findpathrel[zxpn],MAXPATH+4);
    ckstrncat(localfn[zxpn],findfspec[zxpn],MAXPATH+4);
    return(1);
}
#endif /* RECURSIVE */

int
os2findfirstfile(char * fn) {
#ifndef NT
    HDIR hfind = HDIR_CREATE;
#endif /* NT */
    debug(F110,"os2findfirstfile searching for",fn,0);

    if ( findlevel[zxpn] < 0 )
        findlevel[zxpn] = 0;
#ifdef NT
    findhandle[zxpn][findlevel[zxpn]] = FindFirstFile( fn, &finddata[zxpn] );
    if ( findhandle[zxpn][findlevel[zxpn]] == INVALID_HANDLE_VALUE ) {
        return(0);
    }
    debug(F110,"os2findfirstfile found",finddata[zxpn].cFileName,0);
    debug(F110,"os2findfirstfile found",
           finddata[zxpn].cAlternateFileName,0);
    if ( finddata[zxpn].cAlternateFileName[0] == '\0' )
        ckstrncpy(finddata[zxpn].cAlternateFileName,finddata[zxpn].cFileName,14);
#else /* NT */
    findcount = 1;                      /* one entry at a time */
    lasterror[zxpn] =
      DosFindFirst(fn,
                   &hfind,
                   (ULONG) FILE_NORMAL | FILE_ARCHIVED | FILE_DIRECTORY |
                   FILE_HIDDEN | FILE_SYSTEM | FILE_READONLY,
                   (PVOID) &finddata[zxpn],
                   (ULONG) sizeof(finddata[0]),
                   &findcount, FIL_STANDARD);
    if (lasterror[zxpn] || findcount != 1) {
        DosFindClose(hfind);
        return(0);
    }
    findhandle[zxpn][findlevel[zxpn]] = hfind;
#endif /* NT */
    return(1);
}

int
os2findnextfile(void) {
#ifdef NT
    int rc = 0;
    if ( findlevel[zxpn] < 0 )
        return 0;
    if ( findhandle[zxpn][findlevel[zxpn]] == INVALID_HANDLE_VALUE)
      return 0;
    rc = FindNextFile( findhandle[zxpn][findlevel[zxpn]], &finddata[zxpn] );
    debug(F111,"os2findnextfile",finddata[zxpn].cFileName,rc);
    debug(F111,"os2findnextfile (alternate)",
           finddata[zxpn].cAlternateFileName,rc);
    if ( finddata[zxpn].cAlternateFileName[0] == '\0' ) {
        ckstrncpy(finddata[zxpn].cAlternateFileName,finddata[zxpn].cFileName,14);
        strupr(finddata[zxpn].cAlternateFileName);
    }
    return(rc);
#else /* NT */
    HDIR hfind = findhandle[zxpn][findlevel[zxpn]];
    if (hfind == HDIR_CREATE)
      return 0;
    findcount = 1;
    lasterror[zxpn] =
      DosFindNext(hfind,
                  (PVOID) &(finddata[zxpn]),
                  (ULONG) sizeof(finddata[0]),
                  &findcount);
    return(!lasterror[zxpn] && (findcount == 1));
#endif /* NT */
}

int
os2findclose(void) {
    debug(F111,"os2findclose","findlevel",findlevel[zxpn]);
    if ( findlevel[zxpn] < 0)
        return(0);
#ifdef NT
    FindClose(findhandle[zxpn][findlevel[zxpn]]);
    findhandle[zxpn][findlevel[zxpn]] = INVALID_HANDLE_VALUE;
#else
    DosFindClose(findhandle[zxpn][findlevel[zxpn]]);
    findhandle[zxpn][findlevel[zxpn]] = HDIR_CREATE;
#endif /* NT */
    return 1;
}

int
os2findisdir(void) {
#ifdef NT
    return (finddata[zxpn].dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
#else /* NT */
    return (finddata[zxpn].attrFile & FILE_DIRECTORY);
#endif /* NT */
}

/* Define DOTDIRS if you want znext() to return the "." and ".." entries */
/* in the directory pointed to by the relative path of 'fn'.             */

int
zxrewind() {
    int i,j,len;
    CHAR * FileName=NULL;

    /* Cancel previous zxpand if necessary */
    for (; findlevel[zxpn] >= 0; findlevel[zxpn]-- ) {
        if (findhandle[zxpn][findlevel[zxpn]] !=
#ifdef NT
             INVALID_HANDLE_VALUE
#else /* NT */
             HDIR_CREATE
#endif /* NT */
             )
        {
            os2findclose();
        }
        findeop[zxpn][findlevel[zxpn]] = NULL;
    }

    /* Initialize variables for expannsion */
    fcount[zxpn] = fcntsav[zxpn];
    fcountstream[zxpn] = fcntstreamsav[zxpn];
    strcpy(localfn[zxpn],rewindfn[zxpn]);
    strcpy(findpath[zxpn],rewindpath[zxpn]);
    strcpy(findpathrel[zxpn],rewindpathrel[zxpn]);
    strcpy(findfspec[zxpn],rewindfspec[zxpn]);
    debug(F111,"zxrewind","fcntsav[zxpn]",fcntsav[zxpn]);
    findlevel[zxpn] = 0;
    findeop[zxpn][0] = findpath[zxpn];

    /* Now that we know how many, reset for the call to znext() */
    if (!os2findfirstfile(localfn[zxpn])) {
#ifdef RECURSIVE
        if (os2recursive[zxpn]) {
            /* However, we are looking at subdirectories   */
            /* so we must now switch to the next directory */
            os2findclose();

          findfirstsubdir:
            /* prepare to list all subdirectories */
            if (findpath[zxpn][0])
              ckstrncpy(localfn[zxpn],findpath[zxpn],MAXPATH+4);
            else
              ckstrncpy(localfn[zxpn],"./",MAXPATH+4);
            ckstrncat(localfn[zxpn],"*",MAXPATH+4);

            /* find the first potential directory */
            if (os2findfirstfile(localfn[zxpn])>0) {
                do {
#ifdef NT
                    if ( win95_8_3 )
                        FileName = finddata[zxpn].cAlternateFileName;
                    else
                        FileName = finddata[zxpn].cFileName;
#else /* NT */
                    FileName = finddata[zxpn].achName;
#endif /* NT */
                    if (os2findisdir() && strcmp(".",FileName) &&
                         strcmp("..",FileName))
                    {
                        /* we have a directory that is not . or .. */
                        if (!os2findpush()) {
                            fcntsav[zxpn] = fcount[zxpn] = 
                                fcountstream[zxpn] =
                                fcntstreamsav[zxpn] = -1;
                            return(fcount[zxpn]+1);
                        }
                        if (os2findfirstfile(localfn[zxpn]) > 0) {
                            debug(F100,"","",0);
                            goto findfirstdone;
                        } else {
                            debug(F100,"","",0);
                            goto findfirstsubdir;
                        }
                    }
                } while ( os2findnextfile() );
            }

          findfirstdir2:
            /* There are no more subdirectories of the current dir */
            os2findclose();
            if (findlevel[zxpn] > 0) {
                os2findpop();
                while (os2findnextfile()) {
                    if (os2findisdir()) {
                        if (!os2findpush()) {
                            fcntsav[zxpn] = fcount[zxpn] = 
                                fcountstream[zxpn] =
                                fcntstreamsav[zxpn] = -1;
                            return(fcount[zxpn]+1);
                        }
                        if (os2findfirstfile(localfn[zxpn])>0) {
                            debug(F100,"","",0);
                            goto findfirstdone;
                        } else {
                            debug(F100,"","",0);
                            goto findfirstsubdir;
                        }
                    }
                }
                goto findfirstdir2;
            }
          findfirstdone:;
        }
        else
#endif /* RECURSIVE */
        {
            fcntsav[zxpn] = fcount[zxpn] = 
                fcountstream[zxpn] =
                    fcntstreamsav[zxpn] = -1;
            return(0);
        }
    }
    return(fcount[zxpn]);
}

/*  N Z X P A N D  --  Expland a file list, with options.  */
/*
  Call with:
   s = pointer to filename or pattern.
   flags = option bits:

     flags & ZX_FILONLY   Match regular files
     flags & ZX_DIRONLY   Match directories
     flags & ZX_RECURSE   Descend through directory tree
     flags & ZX_MATCHDOT  Match "dot files"
     flags & ZX_NOBACKUP  Don't match "backup files"

   Returns the number of files that match s, with data structures set up
   so that first file (if any) will be returned by the next znext() call.
*/
int
nzxpand(CHAR * fn, int flags) {
    int i,j,k,len,brace=0,bracket=0;
    CHAR * FileName=NULL;

    if (!fn)
      return(0);
    if (!(*fn))
      return(0);
    if (strlen(fn) > MAXPATH)
      return(-1);

    debug(F110,"nzxpand fn",fn,0);
    debug(F101,"nzxpand flags","",flags);

#ifdef CKROOT
    debug(F111,"zxpand setroot",ckroot,ckrootset);
    if (ckrootset)
        if (!zinroot(fn)) {
        debug(F110,"zxpand setroot violation",fn,0);
        return(0);
    }
#endif /* CKROOT */

    /* Cancel previous zxpand if necessary */
    for (; findlevel[zxpn] >= 0; findlevel[zxpn]-- ) {
        if (findhandle[zxpn][findlevel[zxpn]] !=
#ifdef NT
             INVALID_HANDLE_VALUE
#else /* NT */
             HDIR_CREATE
#endif /* NT */
             )
        {
            os2findclose();
        }
    }

    /* Initialize variables for expannsion */
    findlevel[zxpn] = 0;
    findeop[zxpn][0] = findpath[zxpn];
    fcntsav[zxpn] = fcount[zxpn] = 
        fcountstream[zxpn] =
            fcntstreamsav[zxpn] = 0;
    findpathwild[zxpn] = 0;
    findpathrel[zxpn][0] = NUL;
    stream[zxpn][0] = NUL;

    /* save values for calls to znext() */
    os2dironly[zxpn] = (flags & ZX_DIRONLY);
#ifdef RECURSIVE
    os2recursive[zxpn] = (flags & ZX_RECURSE);
#endif /* RECURSIVE */
    os2fileonly[zxpn] = (flags & ZX_FILONLY);

    /* findpath is just the directory */
    ckstrncpy(findpath[zxpn], fn, MAXPATH); /* Leave room to add slash-star */

    /* localfn is the path and the file specification */
    ckstrncpy(localfn[zxpn], fn, MAXPATH);

    /* is fn just a directory? */
    if (isdir(localfn[zxpn]))
    {
        /* if yes, but its really a drive, append "./*" */
        if (strlen(localfn[zxpn]) == 2 && localfn[zxpn][1] == ':' ) {
            ckstrncat(localfn[zxpn], "./*",MAXPATH+4);
        } else {
            /* append "/*" */
            ckstrncat(findpath[zxpn], "/",MAXPATH+4);
            ckstrncat(localfn[zxpn], "/*",MAXPATH+4);
        }
        /* to specify that we want to find all files */
    }
    else {
        /* could be a file name?  or a path/filename?  */
        /* or a path with wildcards?                   */
        /* we can't handle the wildcards yet.          */

        /* look for a directory separator or drive indicator */
        for (i = strlen(findpath[zxpn]); i >= 0; i--) {
            if (ISDIRSEP(findpath[zxpn][i]) ||
                 (i == 1 && findpath[zxpn][i] == ':'))
            {
                /* we found a directory separator or drive indicator. */
                /* are we at the last character in the string? */
                if (!findpath[zxpn][i+1])
                    ckstrncat(localfn[zxpn], "*",MAXPATH+4); /* yes, we want all files */
                else
                    findpath[zxpn][i+1] = '\0'; /* no, isolate the path */
                break;
            }
        }

        /* if we didn't find a path.  assume its just a filename */
        /* so prepend the current directory to the local filename */
        if (i < 0 && !ISDIRSEP(localfn[zxpn][0]) && localfn[zxpn][1] != ':') {
            findpath[zxpn][0] = '\0';
            ckstrncpy(nambuf, "./",MAXNAMLEN+4);
            ckstrncat(nambuf, localfn[zxpn],MAXPATH+4);
            ckstrncpy(localfn[zxpn], nambuf,MAXPATH+4);
        }
    }

    /* Now that we know the path and the filename we need to look for */
    /* patterns in the file name.                                     */
    for (i = strlen(localfn[zxpn]); i >= 0; i--) {
        if (ISDIRSEP(localfn[zxpn][i]) || localfn[zxpn][i] == ':') {

            /* NTFS supports streams which are indicated by
             *
             *    filename:stream
             */

            if (i > 1 && localfn[zxpn][i] == ':') {
                /* Assume we have a stream only if the ':' is found
                 * beyond the traditional drive indicator.  We copy
                 * the ':' because it makes it easier to append later on.
                 */
                ckstrncpy(stream[zxpn],localfn[zxpn]+i,MAXPATH);
                localfn[zxpn][i] = NUL;
                continue;
            }

            /* Save the file specification */
            ckstrncpy(findfspec[zxpn],localfn[zxpn]+i+1,MAXPATH+4);

            /* Now create the file specification with {} and [] replaced */
            /* with * and ? respectively.                                */
            for ( j=0,k=0,len=strlen(findfspec[zxpn]),brace=0,bracket=0;
                  j<=len ; j++)
            {
                /* copy everything including the trailing NUL */
                switch( findfspec[zxpn][j] ) {
                case '{':
                    if ( !brace && !bracket ) {
                        findfspec2[zxpn][k++] = '*';
                    }
                    brace++;
                    break;
                case '}':
                    brace--;
                    break;
                case '[':
                    if ( !bracket && !brace ) {
                        findfspec2[zxpn][k++] = '?';
                    }
                    bracket++;
                    break;
                case ']':
                    bracket--;
                    break;
                default:
                    if ( !bracket && !brace ) {
                        findfspec2[zxpn][k++] = findfspec[zxpn][j];
                    }
                }
            }

            /* and then replace the local filename string with the modified */
            /* pattern if a set of braces or brackets were found. */
            if ( strcmp(findfspec[zxpn],findfspec2[zxpn]) )
                strcpy(localfn[zxpn]+i+1,findfspec2[zxpn]);
            else
                findfspec2[zxpn][0] = '\0';
            break;
        }
    }

    /* At this point we should have a path in findpath[zxpn] if only "." */
    /* If not, something has gone wrong above.                           */

    /* Is there a relative path? */
    if ( !isdir(findpath[zxpn]) ){ /* The path is not a valid directory */
        i = strlen(findpath[zxpn]);
        if (i) {
            for (i = strlen(findpath[zxpn]) ; i >= 0 ; i--) {
                if ( findpath[zxpn][i] == '*' ||
                     findpath[zxpn][i] == '?')
                    findpathwild[zxpn] = 1;

                if (ISDIRSEP(findpath[zxpn][i])) {
                    char saved = findpath[zxpn][i];
                    findpath[zxpn][i] = NUL;
                    if (isdir(findpath[zxpn])) {
                        strcpy(findpathrel[zxpn],&findpath[zxpn][i+1]);
                        break;
                    } else {
                        findpath[zxpn][i] = saved;
                    }
                } else if (findpath[zxpn][i] == ':') {
                    char saved = findpath[zxpn][i+1];
                    findpath[zxpn][i+1] = NUL;
                    if (isdir(findpath[zxpn])) {
                        findpath[zxpn][i+1] = saved;
                        strcpy(findpathrel[zxpn],&findpath[zxpn][i+1]);
                        findpath[zxpn][i+1] = NUL;
                        break;
                    } else {
                        findpath[zxpn][i+1] = saved;
                    }
                }
            }
            if (i < 0) {
                strcpy(findpathrel[zxpn],findpath[zxpn]);
                findpath[zxpn][0] = NUL;
            }
        }
    }

    /* Get the first entry: either file or directory */
  findfirstfile:
    debug(F100,"zxpand about to FindFirst","",0);
    if (os2findfirstfile(localfn[zxpn])) {

/* Count the entry if it meets the requirements of dironly and fileonly */

#ifdef NT
        if ( win95_8_3 )
            FileName = finddata[zxpn].cAlternateFileName;
        else
            FileName = finddata[zxpn].cFileName;
#else /* NT */
        FileName = finddata[zxpn].achName;
#endif /* NT */

        if ((!(os2dironly[zxpn] || os2fileonly[zxpn]) ||
             (os2dironly[zxpn] && os2findisdir()) ||
             (os2fileonly[zxpn] && !os2findisdir())) &&
            (
#ifdef DOTDIRS
              (findlevel[zxpn] == 0) ||
              !os2recursive[zxpn] ||
#endif /* DOTDIRS */
             (strcmp(".",FileName) && strcmp("..",FileName)) ))
        {
            if ( !findfspec2[zxpn][0] ||
                 ckmatch(findfspec[zxpn], FileName, 0, 1) ) 
            {
                fcount[zxpn]++;
#ifdef STREAMS
                if ( stream[zxpn][0] )
                    fcountstream[zxpn] += StreamCount(findpath[zxpn],findpathrel[zxpn],
                                                      FileName, stream[zxpn]);
#endif /* STREAMS */
            } else
                debug(F111,"zxpand rejected",FileName,fcount[zxpn]);
        }
        else
            debug(F111,"zxpand rejected",FileName,fcount[zxpn]);

        /* Findnextfile: */
        while (os2findnextfile()) {
#ifdef NT
            if ( win95_8_3 )
                FileName = finddata[zxpn].cAlternateFileName;
            else
                FileName = finddata[zxpn].cFileName;
#else /* NT */
            FileName = finddata[zxpn].achName;
#endif /* NT */

            if ((!(os2dironly[zxpn] || os2fileonly[zxpn]) ||
                 (os2dironly[zxpn] && os2findisdir()) ||
                 (os2fileonly[zxpn] && !os2findisdir())) &&
                (
#ifdef DOTDIRS
                  (findlevel[zxpn] == 0) ||
                  !os2recursive[zxpn] ||
#endif /* DOTDIRS */
                 (strcmp(".",FileName) && strcmp("..",FileName)) ))
            {
                if ( !findfspec2[zxpn][0] ||
                     ckmatch(findfspec[zxpn],FileName,0, 1) )
                {
                    fcount[zxpn]++;
#ifdef STREAMS
                    if ( stream[zxpn][0] )
                        fcountstream[zxpn] += StreamCount(findpath[zxpn],findpathrel[zxpn],
                                                      FileName, stream[zxpn]);
#endif /* STREAMS */
                } else
                    debug(F111,"zxpand rejected",FileName,fcount[zxpn]);
            }
            else
                debug(F111,"zxpand rejected",FileName,fcount[zxpn]);
        }
    }
#ifdef RECURSIVE
    if (os2recursive[zxpn]) {
/*
  Done expanding the files for the current directory,
  now look for subdirectories.
 */
        os2findclose();
        if (findpath[zxpn][0])
          ckstrncpy(localfn[zxpn],findpath[zxpn],MAXPATH+4);
        else
          ckstrncpy(localfn[zxpn],"./",MAXPATH+4);
        ckstrncat(localfn[zxpn],"*",MAXPATH+4);

        if (os2findfirstfile(localfn[zxpn])) {
        do {
#ifdef NT
            if ( win95_8_3 )
                FileName = finddata[zxpn].cAlternateFileName;
            else
                FileName = finddata[zxpn].cFileName;
#else /* NT */
            FileName = finddata[zxpn].achName;
#endif /* NT */
            if (os2findisdir() && strcmp(".",FileName) && strcmp("..",FileName))
            {
                if (!os2findpush()) {
                    fcntsav[zxpn] = fcount[zxpn] = 
                        fcountstream[zxpn] =
                            fcntstreamsav[zxpn] = 0;
                    return(fcount[zxpn]);
                } else {
                    debug(F100,"","",0);
                    goto findfirstfile;
                }
            }
        } while (os2findnextfile());
                }

      findnextdir:
        if (findlevel[zxpn] > 0) {
            os2findclose();
            os2findpop();
            while (os2findnextfile()) {
                if (os2findisdir()) {
                    if (!os2findpush()) {
                        fcntsav[zxpn] = fcount[zxpn] = 
                            fcountstream[zxpn] =
                                fcntstreamsav[zxpn] = 0;
                        return(fcount[zxpn]);
                    } else {
                        debug(F100,"","",0);
                        goto findfirstfile;
                    }
                }
            }
            goto findnextdir;
        }
    }
#endif /* RECURSIVE */

#ifdef NT
    lasterror[zxpn] = GetLastError();
#endif /* NT */
    os2findclose();

    if (lasterror[zxpn] != ERROR_NO_MORE_FILES) {
        fcntsav[zxpn] = fcount[zxpn] = 
            fcountstream[zxpn] =
                fcntstreamsav[zxpn] = 0;
        return(fcount[zxpn]);
    }
    debug(F111,"zxpand","fcount[zxpn]",fcount[zxpn]);
    fcntsav[zxpn] = fcount[zxpn];
    fcntstreamsav[zxpn] = fcountstream[zxpn];
    strcpy(rewindfn[zxpn],localfn[zxpn]);
    strcpy(rewindpath[zxpn],findpath[zxpn]);
    strcpy(rewindpathrel[zxpn],findpathrel[zxpn]);
    strcpy(rewindfspec[zxpn],findfspec[zxpn]);

    /* Before we leave fix findpath[] so that it does not contain    */
    /* extraneous information such as drive letter or . and .. paths */
    strcpy(nambuf,findpath[zxpn]);
    i = 0;
    len = strlen(nambuf);
    for ( j=0; i <= len ; i++,j++ ) {
        while ((i == 0 || nambuf[i-1] == ':' || ISDIRSEP(nambuf[i-1])) &&
                nambuf[i] == '.' && ISDIRSEP(nambuf[i+1]))
            i+=2;
        findpath[zxpn][j] = nambuf[i];
    }

    zxrewind(); /* places its return value into fcount[] */
    return(fcount[zxpn]>0?fcount[zxpn]:0);
}

/*  Z N E X T  --  Get name of next file from list created by zxpand(). */
/*
   Returns > 0 if there's another file, with its name copied into the argument
   string, or 0 if no more files in list.
*/
int
znext(fn) char *fn; {
    extern int zxpn;
    char * fn_file = NULL;
    CHAR * FileName;

    debug(F111,"znext","fcount[zxpn]",fcount[zxpn]);

    if ( fn == NULL )
        return(-1);
    fn[0] = '\0';               /* initialize the buffer in case of error */

  findnextfile3:
    if ( findlevel[zxpn] < 0)
        return(-1);

    if (fcount[zxpn]-- > 0
#ifdef NT
        && findhandle[zxpn][findlevel[zxpn]] != INVALID_HANDLE_VALUE
#else
        && findhandle[zxpn][findlevel[zxpn]] != HDIR_CREATE
#endif /* NT */
        ) {
        fcountstream[zxpn]--;

      findnextfile2:
        if ( deblog && fn[0] ) {
            debug(F111,"znext rejected",fn,fcount[zxpn]);
        }
        ckstrncpy(fn,findpath[zxpn],CKMAXPATH);

        if (os2recursive[zxpn])
            ckstrncat(fn,findpathrel[zxpn],CKMAXPATH);

#ifdef NT
        if ( win95_8_3 )
            FileName = finddata[zxpn].cAlternateFileName;
        else
            FileName = finddata[zxpn].cFileName;
#else /* NT */
        FileName = finddata[zxpn].achName;
#endif /* NT */
        while (os2dironly[zxpn] && !os2findisdir() ||
               ((
#ifdef DOTDIRS
                 os2recursive[zxpn] &&
                 (findlevel[zxpn] > 0) &&
#endif /* DOTDIRS */
                 os2findisdir() &&
#ifdef NT
                 (!strcmp(".",finddata[zxpn].cFileName) ||
                  !strcmp("..",finddata[zxpn].cFileName))
#else /* NT */
                 (!strcmp(".",finddata[zxpn].achName) ||
                  !strcmp("..",finddata[zxpn].achName))
#endif /* NT */
                 )) ||
               os2fileonly[zxpn] && os2findisdir()
               ) {
            /* The current file does not meet the necessary criteria */
            /* therefore we must look at the next file.              */
#ifdef NT
            if ( win95_8_3 )
                FileName = finddata[zxpn].cAlternateFileName;
            else
                FileName = finddata[zxpn].cFileName;
#else /* NT */
            FileName = finddata[zxpn].achName;
#endif /* NT */
            debug(F111,"znext rejected",FileName,fcount[zxpn]);

            if (!os2findnextfile()) {
                /* The next file does not exist */
#ifdef RECURSIVE
                if (os2recursive[zxpn]) {
                    /* However, we are looking at subdirectories   */
                    /* so we must now switch to the next directory */
                    os2findclose();

                  findnextsubdir:
                    /* prepare to list all subdirectories */
                    if (findpath[zxpn][0])
                      ckstrncpy(localfn[zxpn],findpath[zxpn],MAXPATH+4);
                    else
                      ckstrncpy(localfn[zxpn],"./",MAXPATH+4);
                    ckstrncat(localfn[zxpn],"*",MAXPATH+4);

                    /* Find the first potential directory */
                    if (os2findfirstfile(localfn[zxpn]) > 0)
                    do {
#ifdef NT
                        if ( win95_8_3 )
                            FileName = finddata[zxpn].cAlternateFileName;
                        else
                            FileName = finddata[zxpn].cFileName;
#else /* NT */
                        FileName = finddata[zxpn].achName;
#endif /* NT */
                        if (os2findisdir() &&
                            strcmp(".",FileName) &&
                            strcmp("..",FileName)
                            ) {
                            /* we have a directory that is not . or .. */
                            if (!os2findpush()) {
                                debug(F111,"znext bad 1",fn,fcount[zxpn]+1);
                                fcount[zxpn] = -1;
                                fcountstream[zxpn] = -1;
                                return(fcount[zxpn]);
                            }
                            if (os2findfirstfile(localfn[zxpn])>0) {
                                goto findnextfile2;
                            } else {
                                goto findnextsubdir;
                            }
                        }
                    } while ( os2findnextfile() );

                  findnextdir:
                    /* There are no more subdirectories of the current dir */
                    os2findclose();
                    if (findlevel[zxpn] > 0) {
                        os2findpop();
                        while (os2findnextfile()) {
                            if (os2findisdir()) {
                                if (!os2findpush()) {
                                    debug(F111,"znext bad 2",fn,fcount[zxpn]+1);
                                    fcount[zxpn] = -1;
                                    fcountstream[zxpn] = -1;
                                    return(fcount[zxpn]);
                                }
                                if (os2findfirstfile(localfn[zxpn])>0) {
                                    goto findnextfile2;
                                } else {
                                    goto findnextsubdir;
                                }
                            }
                        }
                        goto findnextdir;
                    }
                } else
#endif /* RECURSIVE */
                {
                    fcount[zxpn] = -1;
                    fcountstream[zxpn] = -1;
                    debug(F111,"znext bad 3",fn,fcount[zxpn]+1);
                    return(0);
                }
            }
#ifdef NT
            if ( win95_8_3 )
                FileName = finddata[zxpn].cAlternateFileName;
            else
                FileName = finddata[zxpn].cFileName;
#else /* NT */
            FileName = finddata[zxpn].achName;
#endif /* NT */
        }

        /* Later when we call ckmatch() we need to pass only the filename */
        /* and not the entire path, so remember where the filename is now */
        fn_file = fn + strlen(fn);
#ifdef NT
        if ( win95_8_3 )
            FileName = finddata[zxpn].cAlternateFileName;
        else
            FileName = finddata[zxpn].cFileName;
#else /* NT */
        FileName = finddata[zxpn].achName;
#endif /* NT */
        ckstrncat(fn,FileName,CKMAXPATH);
        if ( stream[0] )
            ckstrncat(fn,stream[zxpn],CKMAXPATH);

        if (!os2findnextfile()) {
            /* The next file does not exist */
#ifdef RECURSIVE
            if (os2recursive[zxpn]) {
                /* However, we are looking at subdirectories   */
                /* so we must now switch to the next directory */
                os2findclose();

              findnextsubdir2:
                /* prepare to list all subdirectories */
                if (findpath[zxpn][0])
                  ckstrncpy(localfn[zxpn],findpath[zxpn],MAXPATH+4);
                else
                  ckstrncpy(localfn[zxpn],"./",MAXPATH+4);
                ckstrncat(localfn[zxpn],"*",MAXPATH+4);

                /* Find the first potential directory */
                if (os2findfirstfile(localfn[zxpn])>0)
                  do {
#ifdef NT
                      if ( win95_8_3 )
                          FileName = finddata[zxpn].cAlternateFileName;
                      else
                          FileName = finddata[zxpn].cFileName;
#else /* NT */
                      FileName = finddata[zxpn].achName;
#endif /* NT */
                      if ( os2findisdir() &&
                          strcmp(".",FileName) &&
                          strcmp("..",FileName)
                          ) {
                          /* we have a directory that is not . or .. */
                          if (!os2findpush()) {
                              debug(F111,"znext bad 4",fn,fcount[zxpn]+1);
                              fcount[zxpn] = -1;
                              fcountstream[zxpn] = -1;
                              return(fcount[zxpn]);
                          }
                          if(os2findfirstfile(localfn[zxpn])>0) {
                              goto findnextdone;
                          } else {
                              goto findnextsubdir2;
                          }
                      }
                  } while ( os2findnextfile() );

              findnextdir2:
                /* There are no more subdirectories of the current dir */
                os2findclose();
                if (findlevel[zxpn] > 0) {
                    os2findpop();
                    while (os2findnextfile()) {
                        if (os2findisdir()) {
                            if (!os2findpush()) {
                                debug(F111,"znext bad 5",fn,fcount[zxpn]+1);
                                fcount[zxpn] = -1;
                                fcountstream[zxpn] = -1;
                                return(fcount[zxpn]);
                            }
                            if (os2findfirstfile(localfn[zxpn])>0) {
                                goto findnextdone;
                            } else {
                                goto findnextsubdir2;
                            }
                        }
                    }
                    goto findnextdir2;
                }
              findnextdone:;
            }
#endif /* RECURSIVE */
        }
    } else {
        *fn = '\0';
        fcount[zxpn] = -1;
        fcountstream[zxpn] = -1;
    }

    /* if the current fn is not in the matchlist */
    /* then try the next file name until we find */
    /* the next match.                           */
    if ( *fn && findfspec2[zxpn][0] &&
         !ckmatch(findfspec[zxpn], fn_file, 0, 1) ) {
#ifdef NT
        if ( win95_8_3 )
            FileName = finddata[zxpn].cAlternateFileName;
        else
            FileName = finddata[zxpn].cFileName;
#else /* NT */
        FileName = finddata[zxpn].achName;
#endif /* NT */
        debug(F111,"znext rejected",FileName,fcount[zxpn]);
        fcount[zxpn]++;                 /* we didn't really find the file */
#ifdef STREAMS
        if ( stream[zxpn][0] )
            fcountstream[zxpn] += StreamCount(findpath[zxpn],findpathrel[zxpn],
                                               FileName, stream[zxpn]);
#endif /* STREAMS */
        goto findnextfile3;
    }

#ifdef NT
    if ( win95_8_3 ) {
        char name8_3[CKMAXPATH+1];
        DWORD len;

        len = GetShortPathName(fn,name8_3,CKMAXPATH+1);
        if ( len > 0 && len <= CKMAXPATH )
            ckstrncpy(fn,name8_3,CKMAXPATH+1);
    }
#endif /* NT */

    debug(F111,"znext",fn,fcount[zxpn]+1);
    return(fcount[zxpn]);
}


/*  Z C H K S P A  --  Check if there is enough space to store the file  */

/*
 Call with file specification f, size n in bytes.
 Returns -1 on error, 0 if not enough space, 1 if enough space.
*/
int
zchkspa(char *f, CK_OFF_T n)
{
/* OS/2 gives us an easy way to do this. */
    CK_OFF_T x, filesize = 0L;
    debug(F111,"zchkspa",f,n);
    if (isalpha(f[0]) && f[1] == ':') {
        x = zdskspace(toupper(f[0]) - 'A' + 1);
        debug(F111,"zchkspa disk","size",x);
    } else {
        x = zdskspace(0);
        debug(F111,"zchkspa no disk","size",x);
    }
    if ( x > 0 && x < 1024 )    /* zdskspace() unable to return space */
        return(1);

    if (x > n)                  /* plenty of space */
        return(1);

    if (fncact == XYFX_U || fncact == XYFX_X) { /* Update or Replace */
      filesize = zchki(f);
      return((x+filesize >= n) ? 1 : 0);
    }

    return(0);
}


/*  Z N E W N  --  Make a new name for the given file  */

/*
  Given the name, fn, of a file that already exists, this function builds a
  new name of the form "<oldname>.~<n>~", where <oldname> is argument name
  (fn), and <n> is a version number, one higher than any existing version
  number for that file, up to 9999.  This format is consistent with that used
  by GNU EMACS.  If the constructed name is too long for the system's maximum,
  enough characters are truncated from the end of <fn> to allow the version
  number to fit.  If no free version numbers exist between 1 and 9999, a
  version number of "xxxx" is used.  Returns a pointer to the new name in
  argument s.

  On systems which use 8.3 notation, or when SET MSKERMIT ... 
  is used we borrow the technique used in MS-DOS Kermit:
  "The idea is to pad out the main name part (8 chars) with ascii zeros and
   then change the last chars successively to a 1, 2, etc. until
   a unique name is found. All registers are preserved"
*/

VOID
znewn(fn,s) char *fn, **s; {
#define ZNEWNBL 255
#define ZNEWNMD 4
    static char buf[ZNEWNBL+1];
    char *bp, *xp, *yp;
    static char localfn[MAXPATH+1];
    char *tp=NULL, *zp=NULL, ch, temp[14];
    int len = 0, d = 0, n, t, i, j, k, power = 1;

    int max = MAXNAMLEN;                /* Maximum name length */

    if (max < 14) max = 14;             /* Make it reasonable */
    if (max > ZNEWNBL) max = ZNEWNBL;
    bp = buf;                           /* Buffer for building new name */
    yp = fn;
    while (*yp) {                       /* Copy old name into buffer */
        *bp++ = *yp++;
        if (len++ > ZNEWNBL) break;     /* ...up to buffer length */
    }
    *s = NULL;
    for (i = 1; i < ZNEWNMD + 1; i++) { /* Version numbers up to 10**i - 1 */
        power *= 10;                    /* Next power of 10 */
        j = max - len;                  /* Space left for version number */
        k = 3 + i;                      /* Space needed for it */
        if (j < k) {                    /* Make room if necessary */
            len -= (k - j);             /* Adjust length of filename */
            bp = buf + len;             /* Point to new end */
        }
        *bp++ = '*';                    /* Put a star on the end (UNIX) */
        *bp-- = '\0';                   /* Terminate with null */

        debug(F110,"znewn: about to expand",buf,0);
        n = nzxpand(buf,0);             /* Expand the resulting wild name */
                                        /* n is the number of matches */
        debug(F101,"znewn matches","",n);
        while (n-- > 0) {               /* Find any existing name.~n~ files */
            znext(localfn);
            xp = localfn;
            xp += len;                  /* Look for .~<n>~ at the end of it */
            if (*xp == '.' && *(xp+1) == '~') { /* Has a version number */
                t = atoi(xp+2);                 /* Get it */
                if (t > d) d = t;       /* Save d = highest version number */
            }
        }
        if (d < power-1) {              /* Less than maximum possible? */
            debug(F110,"znewn number ok",buf,0);
            sprintf(bp,".~%d~",d+1);    /* Yes, make "name.~<d+1>~" */
            *s = buf;                   /* Point to new name */
            ck_znewn = d+1;             /* Also make it available globally */
            break;                      /* Done, return it */
        }
    }
    if (*s == NULL) {
        debug(F110,"znewn: too many names",buf,0);
        sprintf(bp,".~xxxx~");          /* Too many, use xxxx. */
        ck_znewn = -1;                  /* Also make it available globally */
        *s = buf;
    }
    {
        char *p;
        p = (char *) malloc(ZNEWNBL+1); /* Get fully qualified name */
        if (p) {
            if (zfnqfp(buf, ZNEWNBL, p))
              ckstrncpy(buf,p,ZNEWNBL);
            free(p);
        }
    }
    if (!mskrename && IsFileNameValid(buf)) {
        debug(F110,"znewn: os2 filename valid",buf,0);
        return; /* HPFS */
    }

    /* otherwise make FAT 8.3 name */
    debug(F110,"znewn: os2 filename invalid",buf,0);
    xp = bp = buf;
    yp = fn;
    while (*yp) {                       /* Copy name into buf */
        ch = *bp++ = *yp++;
        if (ISDIRSEP(ch) || (ch == ':')) xp=bp;
    }
    *bp = '\0';

    if ( mskrename ) {
        zp = yp = xp;
        i = 1;
        while (*yp && (*yp != '.')) {
            yp++;
            if (++i<=8)
                zp=yp;
        }
        /* zp points to 8th character in name, or yp, whichever occurs first. */
        strcpy(temp,yp);                    /* Copy extension, if any */

        /* pad out with zeros and make the initial filename '1'. */
        while (zp != xp+8) {
			if ( zp == xp )
				*zp++='X';
            if ( zp < xp+7 ) 
                *zp++='0';
            else 
                *zp++='1';
        }
        strcpy(zp--,temp);                    /* Get the extension back */
        
        while (1) {
            n = nzxpand(buf,0);                 /* Expand the resulting wild name */
            debug(F101,"znewn: matches","",n);
            if (n == 0) {
                debug(F110,"znewn: os2 file name is MS-Kermit",buf,0);
                return;
            }

            for ( zp = xp+7; zp > xp; zp--) {
                if (*zp == '9') {
                    *zp = '0';
                } else {
                    (*zp)++;
                    break;
                }
            }
            if ( zp == xp ) {
                debug(F110,"znewn: os2 file name is MS-Kermit (overflow)",buf,0);
                return;
            }
        }
    } else {
        zp = yp = xp;
        i = 1;
        while (*yp && (*yp != '.')) {
            yp++;
            if (++i<=6)
                zp=yp;
        }
        /* zp points to 6th character in name, or yp, whichever occurs first. */
        strcpy(temp,yp);                    /* Copy extension, if any */

        while (zp != xp+8) {
            if ( zp < xp+5 ) *zp++='0';
            else *zp++='?';                 /* Pad out with wild cards */
        }
        strcpy(zp,temp);                    /* Get the extension back */
        debug(F110,"znewn: about to expand",buf,0);
        n = nzxpand(buf,0);                 /* Expand the resulting wild name */
        debug(F101,"znewn: matches","",n);
        d = 0;                              /* Index number */
        debug(F110,"znewn: temp",temp,0);
        while (znext(temp)) {
            if ( tp = strrchr( temp, '/' ) )
                tp++;
            else
                tp = temp;
            i = atoi(tp+5);
            debug(F111,"znewn: tp=atoi(tp+5)",tp,i);
            if (i > d) d = i;
            debug(F101,"znewn: d","",d);
        }
        sprintf(temp,"%03d",d+1);           /* Get the number into a string */
        ck_znewn = d+1;
        memcpy(xp+5, temp, 3);
        debug(F110,"znewn: os2 file name is FAT",buf,0);
    }
    return;
}

/*  Z R E N A M E  --  Rename a file  */
/*
   Call with old and new names.
   If new name is the name of a directory, the 'old' file is moved to
   that directory.
   Returns 0 on success, -1 on failure.
*/
int
zrename(old,new) char *old, *new; {
    char *p, *s;
    int x;
    int len;

    if (!old) old = "";
    if (!new) new = "";
    debug(F110,"zrename old",old,0);
    debug(F110,"zrename new",new,0);
    if (!*old) return(-1);
    if (!*new) return(-1);

#ifdef IKSD
#ifdef CK_LOGIN
    if (inserver && isguest)
      return(-1);
#endif /* CK_LOGIN */
#endif /* IKSD */

#ifdef CKROOT
    debug(F111,"zrename setroot",ckroot,ckrootset);
    if (ckrootset) {
        if (!zinroot(old)) {
            debug(F110,"zrename old: setroot violation",old,0);
            return(-1);
        }
        if (!zinroot(new)) {
            debug(F110,"zrename new: setroot violation",new,0);
            return(-1);
        }
    }
#endif /* CKROOT */

    p = NULL;

    if (isdir(new)) {
        char *q = NULL;

        x = strlen(new);
        len = strlen(new) + strlen(old) + 2;
        if (!(p = malloc(len)))
          return(-1);
        ckstrncpy(p,new,len);                  /* Directory part */
        if (!ISDIRSEP(*(new+x-1)))      /* Separator, if needed */
          ckstrncat(p,"/",len);
        zstrip(old,&q);                 /* Strip path part from old name */
        ckstrncat(p,q,len);                    /* Concatenate to new directory */
        debug(F110,"zrename dir",p,0);
    } else {
#ifdef DEBUG
        debug(F110,"zrename no dir",new,0);
#endif
        len = strlen(new) + 64;
        if (!(p = malloc(len)))
          return(-1);
        ckstrncpy(p,new,len);
    }
    s = p;

#ifdef IKSD
        if ( inserver && (!ENABLED(en_del)) ) {
            if ( zchki(s) > -1 ) {
                if (p) free(p);
                return(-1);
            }
        }
#endif /* IKSD */
    if ( !strncmp(s,"//",2) || !strncmp(s,"\\\\",2))
         ckstrncpy(s,UNCname(s),len);
    if ( !strncmp(s,"//",2) || !strncmp(s,"\\\\",2) )
        old = UNCname(old);
/*
  Atomic, preferred, uses a single system call, rename(), if available.
  OS/2 rename() returns nonzero, but not necessarily -1 (?), on failure.
*/
    x = rename(old,s);
#ifdef CKSYSLOG
    if (ckxsyslog >= SYSLG_FC && ckxlogging) {
        char buf[1024];
        fullname[0] = '\0';
        zfnqfp(old,CKMAXPATH,fullname);
        tmp2[0] = '\0';
        zfnqfp(s,CKMAXPATH,tmp2);
        if (x) {
            sprintf(buf,"file[] %s: rename to %s failed (%m)",fullname,tmp2);
            cksyslog(SYSLG_FC, 0, buf, NULL, NULL);
        } else {
            sprintf(buf,"file[] %s: renamed to %s ok", fullname, tmp2);
            cksyslog(SYSLG_FC, 1, buf, NULL, NULL);
        }
    }
#endif /* CKSYSLOG */

    if (p) free(p);
    return(x ? -1 : 0);
}

/*  Z C O P Y  --  Copy a file  */
/*
   Call with source and destination names.
   If destination name is the name of a directory, the source file is
   copied to that directory with the original name.
   Returns 0 on success, -1 on failure.
*/
int
zcopy(source,destination) char *source, *destination; {
    char *p = NULL, *s;
    int x;
    int len;
#ifdef NT
    BOOL bCancel = 0;
    static BOOL (WINAPI * p_CopyFileExA)(LPCSTR lpExistingFileName,
                                          LPCSTR lpNewFileName,
#if _MSC_VER > 1000
                                          LPPROGRESS_ROUTINE lpProgressRoutine OPTIONAL,
#else
                              /* The Platform SDK included in Visual C++ 4.0
                               * and earlier doesn't include CopyFileExA so
                               * no defninition for LPPROGRESS_ROUTINE. We never
                               * pass a value other than NULL so its real type
                               * is probably irrelevant.*/
                                          LPVOID lpProgressRoutine OPTIONAL,
#endif
                                          LPVOID lpData OPTIONAL,
                                          LPBOOL pbCancel OPTIONAL,
                                          DWORD dwCopyFlags
                                          )=NULL;
#endif /* NT */

    if (!source) source = "";
    if (!destination) destination = "";

    debug(F110,"zcopy src arg",source,0);
    debug(F110,"zcopy dst arg",destination,0);

    if (!*source) return(-1);
    if (!*destination) return(-1);

#ifdef IKSD
#ifdef CK_LOGIN
    if (inserver && isguest)
      return(-4);
#endif /* CK_LOGIN */
#endif /* IKSD */

#ifdef CKROOT
    debug(F111,"zcopy setroot",ckroot,ckrootset);
    if (ckrootset) {
        if (!zinroot(source)) {
            debug(F110,"zcopy source: setroot violation",source,0);
            return(-1);
        }
        if (!zinroot(destination)) {
            debug(F110,"zcopy destination: setroot violation",destination,0);
            return(-1);
        }
    }
#endif /* CKROOT */

    s = destination;

    if (isdir(destination)) {
        char *q = NULL;

        x = strlen(destination);
        len = strlen(destination) + strlen(source) + 2;
        if (!(p = malloc(len)))
          return(-1);
        ckstrncpy(p,destination,len);          /* Directory part */
        if (!ISDIRSEP(*(destination+x-1))) /* Separator, if needed */
          ckstrncat(p,"/",len);
        zstrip(source,&q);              /* Strip path part from old name */
        ckstrncat(p,q,len);                    /* Concatenate to new directory */
        debug(F110,"zcopy dir",p,0);
    } else {
        debug(F110,"zcopy no dir",destination,0);
        len = strlen(destination) + 64;
        if (!(p = malloc(len)))
          return(-1);
        ckstrncpy(p,destination,len);
    }

    s = p;

    if ( !strncmp(s,"//",2) || !strncmp(s,"\\\\",2) )
         ckstrncpy(s,UNCname(s),len);
    if ( !strncmp(s,"//",2) || !strncmp(s,"\\\\",2) )
        source = UNCname(source);

#ifdef IKSD
    if ( inserver && (!ENABLED(en_del)) ) {
        if ( zchki(s) > -1 ) {
            if (p) free(p);
            return(-4);
        }
    }
#endif /* IKSD */

#ifndef NT
    x = (DosCopy( source, s, DCPY_FAILEAS ) ? -1 : 0);
#else /* NT */
    if ( !p_CopyFileExA ) {
        if (hKernel == INVALID_HANDLE_VALUE)
            hKernel = LoadLibrary("kernel32.dll");
        if (hKernel != INVALID_HANDLE_VALUE)
            (FARPROC) p_CopyFileExA =
                GetProcAddress( hKernel, "CopyFileExA" );
    }

    if ( p_CopyFileExA ) {
        x = (p_CopyFileExA( source, s, NULL, NULL, &bCancel, 0) ? 0 : -1);
        if (x<0) {
            DWORD lasterror = GetLastError();
            debug(F111,"CopyFileEx failed to",s,lasterror);
            if (lasterror == ERROR_CALL_NOT_IMPLEMENTED)
                goto copyfile;
        }
    } else {
      copyfile:
        x = (CopyFile( source, s, FALSE ) ? 0 : -1);
        if (x<0)
            debug(F111,"CopyFile failed to",s,GetLastError());
    }
#endif /* NT */

#ifdef CKSYSLOG
    if (x > -1 && ckxsyslog >= SYSLG_FC && ckxlogging) {
        char buf[1024];
        if (x) {
            sprintf(buf,"file[] %s: copy to %s failed", fullname, tmp2);
            cksyslog(SYSLG_FC, 0, buf, NULL, NULL);
        } else {
            sprintf(buf,"file[] %s: copy to %s ok", fullname, tmp2);
            cksyslog(SYSLG_FC, 1, buf, NULL, NULL);
        }
    }
#endif /* CKSYSLOG */

    if (p) free(p);
    return(x);
}

/*  Z L I N K  --  Link a file  */
/*
   Call with source and destination names.
   If destination name is the name of a directory, the source file is
   copied to that directory with the original name.
   Returns 0 on success, -1 on failure.
*/
int
zlink(source,destination) char *source, *destination; {
#ifdef OS2ONLY
    return -1;
#else /* OS2ONLY */
    char *p = NULL, *s;
    int x;
    int len;
#ifdef NT
    BOOL bCancel = 0;
#endif /* NT */
    static BOOL (WINAPI * p_CreateHardLinkA)(LPCSTR lpFileName,
                                             LPCSTR lpExistingFileName,
                                             LPSECURITY_ATTRIBUTES lpSecurityAttributes
                                             )=NULL;
    static HANDLE hKernel = INVALID_HANDLE_VALUE;
    
    if ( !p_CreateHardLinkA ) {
        if (hKernel == INVALID_HANDLE_VALUE)
            hKernel = LoadLibrary("kernel32.dll");
        if (hKernel != INVALID_HANDLE_VALUE)
            (FARPROC) p_CreateHardLinkA =
                GetProcAddress( hKernel, "CreateHardLinkA" );
    }

    if ( !p_CreateHardLinkA )
        return -1;

    if (!source) source = "";
    if (!destination) destination = "";

    debug(F110,"zlink src arg",source,0);
    debug(F110,"zlink dst arg",destination,0);

    if (!*source) return(-1);
    if (!*destination) return(-1);

#ifdef IKSD
#ifdef CK_LOGIN
    if (inserver && isguest)
      return(-4);
#endif /* CK_LOGIN */
#endif /* IKSD */

#ifdef CKROOT
    debug(F111,"zlink setroot",ckroot,ckrootset);
    if (ckrootset) {
        if (!zinroot(source)) {
            debug(F110,"zlink source: setroot violation",source,0);
            return(-1);
        }
        if (!zinroot(destination)) {
            debug(F110,"zlink destination: setroot violation",destination,0);
            return(-1);
        }
    }
#endif /* CKROOT */

    s = destination;

    if (isdir(destination)) {
        char *q = NULL;

        x = strlen(destination);
        len = strlen(destination) + strlen(source) + 2;
        if (!(p = malloc(len)))
          return(-1);
        ckstrncpy(p,destination,len);          /* Directory part */
        if (!ISDIRSEP(*(destination+x-1)))      /* Separator, if needed */
          ckstrncat(p,"/",len);
        zstrip(source,&q);                      /* Strip path part from old name */
        ckstrncat(p,q,len);                    /* Concatenate to new directory */
        debug(F110,"zlink dir",p,0);
    } else {
        debug(F110,"zlink no dir",destination,0);
        len = strlen(destination) + 64;
        if (!(p = malloc(len)))
          return(-1);
        ckstrncpy(p,destination,len);
    }

    s = p;

    if ( !strncmp(s,"//",2) || !strncmp(s,"\\\\",2) )
         ckstrncpy(s,UNCname(s),len);
    if ( !strncmp(s,"//",2) || !strncmp(s,"\\\\",2) )
        source = UNCname(source);

#ifdef IKSD
    if ( inserver && (!ENABLED(en_del)) ) {
        if ( zchki(s) > -1 ) {
            if (p) free(p);
            return(-4);
        }
    }
#endif /* IKSD */

    x = (p_CreateHardLinkA(s,source,NULL) ? 0 : -1 );

#ifdef CKSYSLOG
    if (x > -1 && ckxsyslog >= SYSLG_FC && ckxlogging) {
        char buf[1024];
        if (x) {
            sprintf(buf,"file[] %s: link to %s failed", fullname, tmp2);
            cksyslog(SYSLG_FC, 0, buf, NULL, NULL);
        } else {
            sprintf(buf,"file[] %s: link to %s ok", fullname, tmp2);
            cksyslog(SYSLG_FC, 1, buf, NULL, NULL);
        }
    }
#endif /* CKSYSLOG */

    if (p) free(p);
    return(x);
#endif /* OS2ONLY */
}

/*  Z S A T T R */
/*
 Fills in a Kermit file attribute structure for the file which is to be sent.
 Returns 0 on success with the structure filled in, or -1 on failure.
 If any string member is null, then it should be ignored.
 If any numeric member is -1, then it should be ignored.
*/
#ifdef CK_PERMS

#ifdef CK_GPERMS
#undef CK_GPERMS
#endif /* CK_GPERMS */

#ifdef S_IRUSR
#ifdef S_IWUSR
#ifdef S_IXUSR
#define CK_GPERMS
#endif /* S_IXUSR */
#endif /* S_IWUSR */
#endif /* S_IRUSR */

static char gperms[2];

#endif /* CK_GPERMS */

static char lperms[24];

#ifdef CK_PERMS
static char xlperms[24];

char *
zgperm(f) char *f; {
    int x; char *s = (char *)xlperms;
    struct stat buf;
    debug(F110,"zgperm",f,0);
    if (!f) return("----------");
    if (!*f) return("----------");

#ifdef CKROOT
    debug(F111,"zgperm setroot",ckroot,ckrootset);
    if (ckrootset) if (!zinroot(f)) {
        debug(F110,"zgperm setroot violation",f,0);
        return("----------");
    }
#endif /* CKROOT */

    x = os2stat(f,&buf);
    if (x < 0)
      return(NULL);
    sprintf(s,"%o",buf.st_mode);
    debug(F110,"zgperm",s,0);
    return(s);
}
#else
char *
zgperm(f) char *f; {
    return(NULL);
}
#endif /* CK_PERMS */

int
zsattr(xx) struct zattr *xx; {
    long k; int x;
    struct stat buf;

    k = iflen % 1024L;                  /* File length in K */
    if (k != 0L) k = 1L;
    xx->lengthk = (iflen / 1024L) + k;
    xx->type.len = 0;                   /* File type can't be filled in here */
    xx->type.val = "";
    if (*nambuf) {
        xx->date.val = zfcdat(nambuf);  /* File creation date */
        xx->date.len = (int)strlen(xx->date.val);
    } else {
        xx->date.len = 0;
        xx->date.val = "";
    }
    xx->creator.len = 0;                /* File creator */
    xx->creator.val = "";
    xx->account.len = 0;                /* File account */
    xx->account.val = "";
    xx->area.len = 0;                   /* File area */
    xx->area.val = "";
    xx->password.len = 0;               /* Area password */
    xx->password.val = "";
    xx->blksize = -1L;                  /* File blocksize */
    xx->xaccess.len = 0;                /* File access */
    xx->xaccess.val = "";
    xx->encoding.len = 0;               /* Transfer syntax */
    xx->encoding.val = 0;
    xx->disp.len = 0;                   /* Disposition upon arrival */
    xx->disp.val = "";
    xx->lprotect.len = 0;               /* Local protection */
    xx->lprotect.val = "";
    xx->gprotect.len = 0;               /* Generic protection */
    xx->gprotect.val = "";
    debug(F111,"zsattr lperms",xx->lprotect.val,xx->lprotect.len);
    debug(F111,"zsattr gperms",xx->gprotect.val,xx->gprotect.len);
#ifdef NT
    xx->systemid.val = "UN";            /* UN = Win32 */
#else
    xx->systemid.val = "UO";            /* UO = OS/2 */
#endif /* NT */
    xx->systemid.len = 2;               /* System ID */
    xx->recfm.len = 0;                  /* Record format */
    xx->recfm.val = "";
    xx->sysparam.len = 0;               /* System-dependent parameters */
    xx->sysparam.val = "";
    xx->length = iflen;                 /* Length */
    return(0);
}

/* Z F C D A T  --  Get file creation date */
/*
  Call with pointer to filename.
  On success, returns pointer to modification date in yyyymmdd hh:mm:ss format.
  On failure, returns pointer to null string.
*/
static char datbuf[40];

char *
#ifdef CK_ANSIC
zdtstr(time_t time)
#else
zdtstr(time) time_t time;
#endif /* CK_ANSIC */
/* zdtstr */ {
    struct tm * time_stamp;
    struct tm * localtime();
    int yy, ss;
    struct tm lts;

    debug(F101,"zdatstr time","",time);
    if (time < 0)
      return("");
    time_stamp = localtime(&(time));
    if (!time_stamp) {
        debug(F100,"localtime returns null","",0);
        return("");
    }
    memcpy(&lts,time_stamp,sizeof(struct tm));
    time_stamp = &lts;
    yy = time_stamp->tm_year;           /* Year - 1900 */
    yy += 1900;
    debug(F101,"zdatstr year","",yy);

    if (time_stamp->tm_mon  < 0 || time_stamp->tm_mon  > 11)
      return("");
    if (time_stamp->tm_mday < 0 || time_stamp->tm_mday > 31)
      return("");
    if (time_stamp->tm_hour < 0 || time_stamp->tm_hour > 23)
      return("");
    if (time_stamp->tm_min  < 0 || time_stamp->tm_min  > 59)
      return("");
    ss = time_stamp->tm_sec;            /* Seconds */
    if (ss < 0 || ss  > 59)             /* Some systems give a BIG number */
      ss = 0;
    sprintf(datbuf,
            "%04d%02d%02d %02d:%02d:%02d",
            yy,
            time_stamp->tm_mon + 1,
            time_stamp->tm_mday,
            time_stamp->tm_hour,
            time_stamp->tm_min
            , ss
            );
    yy = (int)strlen(datbuf);
    debug(F111,"zdatstr",datbuf,yy);
    if (yy > 17) datbuf[17] = '\0';
    return(datbuf);
}

char *
zfcdat(name) char *name; {
    struct stat buffer;

    datbuf[0] = '\0';

#ifdef CKROOT
    debug(F111,"zfcdat setroot",ckroot,ckrootset);
    if (ckrootset)
        if (!zinroot(name)) {
            debug(F110,"zfcdat setroot violation",name,0);
            return(0);
        }
#endif /* CKROOT */

    if (os2stat(name,&buffer) != 0) {
        debug(F110,"zfcdat stat failed",name,0);
        return("");
    }
    return(zdtstr(buffer.st_mtime));
}

static char zjdbuf[32] = { NUL, NUL };  /* Julian date buffer */

time_t
zstrdt(date,len) char * date; int len; {
/*
  To do: adapt code from OS-9 Kermit's ck9fio.c zstime function, which
  is more flexible, allowing [yy]yymmdd[ hh:mm[:ss]].
*/
    long tmx=0, days;
    int i, n, isleapyear;
                   /*       J  F  M  A   M   J   J   A   S   O   N   D   */
                   /*      31 28 31 30  31  30  31  31  30  31  30  31   */
    static
    int monthdays [13] = {  0,0,31,59,90,120,151,181,212,243,273,304,334 };
    char s[5];
    struct tm *time_stamp;

#ifdef __WATCOMC__
/* Watcom provides utimbuf instead of _utimbuf */
struct utimbuf tp;
#else
#ifdef NT
struct _utimbuf tp;
#else /* NT */
struct utimbuf tp;
#endif /* NT */
#endif /* __WATCOMC__

#ifdef ANYBSD
    long timezone = 0L;
    static struct timeb tbp;
#endif /* ANYBSD */

    debug(F111,"zstrdt",date,len);

    if ((len == 0)
        || (len != 17)
        || (date[8] != ' ')
        || (date[11] != ':')
        || (date[14] != ':') ) {
        debug(F111,"Bad creation date ",date,len);
        return(-1);
    }
    debug(F111,"zstrdt date check 1",date,len);
    for(i = 0; i < 8; i++) {
        if (!isdigit(date[i])) {
            debug(F111,"Bad creation date ",date,len);
            return(-1);
        }
    }
    debug(F111,"zstrdt date check 2",date,len);
    i++;

    for (; i < 16; i += 3) {
        if ((!isdigit(date[i])) || (!isdigit(date[i + 1]))) {
            debug(F111,"Bad creation date ",date,len);
            return(-1);
        }
    }
    debug(F111,"zstrdt date check 3",date,len);
    debug(F100,"zstrdt so far so good","",0);

    s[4] = '\0';
    for (i = 0; i < 4; i++)             /* Fix the year */
      s[i] = date[i];

    n = atoi(s);
    debug(F111,"zstrdt year",s,n);
    if (n < 1970) {
        debug(F100,"zstrdt fails - year","",n);
        return(-1);
    }

/*  Previous year's leap days.  This won't work after year 2100. */

    isleapyear = (( n % 4 == 0 && n % 100 !=0) || n % 400 == 0);
    days = (long) (n - 1970) * 365;
    days += (n - 1968 - 1) / 4 - (n - 1900 - 1) / 100 + (n - 1600 - 1) / 400;

    s[2] = '\0';

    for (i = 4; i < 16; i += 2) {
        s[0] = date[i];
        s[1] = date[i + 1];
        n = atoi(s);
        switch (i) {
          case 4:                       /* MM: month */
            if ((n < 1 ) || ( n > 12)) {
                debug(F111,"zstrdt 4 bad date ",date,len);
                return(-1);
            }
            days += monthdays [n];
            if (isleapyear && n > 2)
              ++days;
            continue;

          case 6:                       /* DD: day */
            if ((n < 1 ) || ( n > 31)) {
                debug(F111,"zstrdt 6 bad date ",date,len);
                return(-1);
            }
            tmx = (days + n - 1) * 24L * 60L * 60L;
            i++;                        /* Skip the space */
            continue;

          case 9:                       /* hh: hour */
            if ((n < 0 ) || ( n > 23)) {
                debug(F111,"zstrdt 9 bad date ",date,len);
                return(-1);
            }
            tmx += n * 60L * 60L;
            i++;                        /* Skip the colon */
            continue;

          case 12:                      /* mm: minute */
            if ((n < 0 ) || ( n > 59)) {
                debug(F111,"zstrdt 12 bad date ",date,len);
                return(-1);
            }
            tmx += timezone;
            tmx += n * 60L;
            i++;                        /* Skip the colon */
            continue;

          case 15:                      /* ss: second */
            if ((n < 0 ) || ( n > 59)) {
                debug(F111,"zstrdt 15 bad date ",date,len);
                return(-1);
            }
            tmx += n;
        }
        time_stamp = localtime(&tmx);
        debug(F101,"zstrdt tmx 1","",tmx);
        if (!time_stamp)
          return(-1);
        time_stamp = localtime(&tmx);
        debug(F101,"zstrdt tmx 2","",tmx);
        if (time_stamp->tm_isdst)
          tmx -= 60L * 60L;             /* Adjust for daylight savings time */
        n = time_stamp->tm_year;
        if (n < 300) n += 1900;
        sprintf(zjdbuf,"%04d%03d",n,(time_stamp->tm_yday)+1);
    }
    return(tmx);
}

#ifdef COMMENT
char *
zjdate(date) char * date; {
    int x;
    if (!date) date = "";
    x = strlen(date);
    if (x < 1) return("0");
    if (zstrdt(date,x) < 0)
      return("-1");
    else
      return((char *)zjdbuf);
}
#endif /* COMMENT */

#ifdef ZLOCALTIME
/* Z L O C A L T I M E  --  GMT/UTC time string to local time string */

/*
   Call with: "yyyymmdd hh:mm:ss" GMT/UTC date-time.
   Returns:   "yyyymmdd hh:mm:ss" local date-time on success, NULL on failure.
*/
static char zltimbuf[64];

char *
zlocaltime(gmtstring) char * gmtstring; {
    time_t tmx;
    long days;
    int i, n, x, isleapyear;
                   /*       J  F  M  A   M   J   J   A   S   O   N   D   */
                   /*      31 28 31 30  31  30  31  31  30  31  30  31   */
    static
    int monthdays [13] = {  0,0,31,59,90,120,151,181,212,243,273,304,334 };
    char s[5];
    struct tm *time_stamp;

    char * date = gmtstring;
    int len;

    len = strlen(date);
    debug(F111,"zlocaltime",date,len);

    if ((len == 0)
        || (len != 17)
        || (date[8] != ' ')
        || (date[11] != ':')
        || (date[14] != ':') ) {
        debug(F111,"Bad creation date ",date,len);
        return(NULL);
    }
    debug(F111,"zlocaltime date check 1",date,len);
    for(i = 0; i < 8; i++) {
        if (!isdigit(date[i])) {
            debug(F111,"Bad creation date ",date,len);
            return(NULL);
        }
    }
    debug(F111,"zlocaltime date check 2",date,len);
    i++;

    for (; i < 16; i += 3) {
        if ((!isdigit(date[i])) || (!isdigit(date[i + 1]))) {
            debug(F111,"Bad creation date ",date,len);
            return(NULL);
        }
    }
    debug(F111,"zlocaltime date check 3",date,len);

    debug(F100,"zlocaltime so far so good","",0);

    s[4] = '\0';
    for (i = 0; i < 4; i++)             /* Fix the year */
      s[i] = date[i];

    n = atoi(s);
    debug(F111,"zlocaltime year",s,n);
    if (n < 1970) {
        debug(F100,"zlocaltime fails - year","",n);
        return(NULL);
    }

/*  Previous year's leap days.  This won't work after year 2100. */

    isleapyear = (( n % 4 == 0 && n % 100 !=0) || n % 400 == 0);
    days = (long) (n - 1970) * 365;
    days += (n - 1968 - 1) / 4 - (n - 1900 - 1) / 100 + (n - 1600 - 1) / 400;

    s[2] = '\0';

    for (i = 4; i < 16; i += 2) {
        s[0] = date[i];
        s[1] = date[i + 1];
        n = atoi(s);
        switch (i) {
          case 4:                       /* MM: month */
            if ((n < 1 ) || ( n > 12)) {
                debug(F111,"zlocaltime 4 bad date ",date,len);
                return(NULL);
            }
            days += monthdays [n];
            if (isleapyear && n > 2)
              ++days;
            continue;

          case 6:                       /* DD: day */
            if ((n < 1 ) || ( n > 31)) {
                debug(F111,"zlocaltime 6 bad date ",date,len);
                return(NULL);
            }
            tmx = (days + n - 1) * 24L * 60L * 60L;
            i++;                        /* Skip the space */
            continue;

          case 9:                       /* hh: hour */
            if ((n < 0 ) || ( n > 23)) {
                debug(F111,"zlocaltime 9 bad date ",date,len);
                return(NULL);
            }
            tmx += n * 60L * 60L;
            i++;                        /* Skip the colon */
            continue;

          case 12:                      /* mm: minute */
            if ((n < 0 ) || ( n > 59)) {
                debug(F111,"zlocaltime 12 bad date ",date,len);
                return(NULL);
            }
            tmx += n * 60L;
            i++;                        /* Skip the colon */
            continue;

          case 15:                      /* ss: second */
            if ((n < 0 ) || ( n > 59)) {
                debug(F111,"zlocaltime 15 bad date ",date,len);
                return(NULL);
            }
            tmx += n;
        }

/*
  At this point tmx is the time_t representation of the argument date-time
  string without any timezone or DST adjustments.  Therefore it should be
  the same as the time_t representation of the GMT/UTC time.  Now we should
  be able to feed it to localtime() and have it converted to a struct tm
  representing the local time equivalent of the given UTC time.
*/
        time_stamp = localtime(&tmx);
        if (!time_stamp)
          return(NULL);
    }

/* Now we simply reformat the struct tm to a string */

    x = time_stamp->tm_year;
    if (time_stamp->tm_year < 70 || time_stamp->tm_year > 8099)
      return(NULL);
    if (time_stamp->tm_mon < 0 || time_stamp->tm_mon > 11)
      return(NULL);
    if (time_stamp->tm_mday < 1 || time_stamp->tm_mday > 31)
      return(NULL);
    if (time_stamp->tm_hour < 0 || time_stamp->tm_hour > 24)
      return(NULL);
    if (time_stamp->tm_min < 0 || time_stamp->tm_min > 60)
      return(NULL);
    if (time_stamp->tm_sec < 0 || time_stamp->tm_sec > 60)
      return(NULL);
    sprintf(zltimbuf,"%04d%02d%02d %02d:%02d:%02d",
            time_stamp->tm_year + 1900,
            time_stamp->tm_mon + 1,
            time_stamp->tm_mday,
            time_stamp->tm_hour,
            time_stamp->tm_min,
            time_stamp->tm_sec
            );
    return((char *)zltimbuf);
}
#endif /* ZLOCALTIME */

/* Z S T I M E  --  Set modification date/time+permissions for incoming file */
/*
 Call with:
 f  = pointer to name of existing file.
 yy = pointer to a Kermit file attribute structure in which yy->date.val
      is a date of the form yyyymmdd hh:mm:ss, e.g. 19900208 13:00:00.
      yy->lprotect.val & yy->gprotect.val are permission/protection values.
 x  = is a function code: 0 means to set the file's attributes as given.
      1 means compare the date in struct yy with the file creation date.
 Returns:
 -1 on any kind of error.
  0 if x is 0 and the attributes were set successfully.
  0 if x is 1 and date from attribute structure <= file creation date.
  1 if x is 1 and date from attribute structure > file creation date.
*/
int
zstime(f,yy,x) char *f; struct zattr *yy; int x;
/* zstime */ {
    int r = -1;                         /* Return code */
#ifdef CK_PERMS
    int setperms = 0;
#endif /* CK_PERMS */
    int setdate = 0;
    struct stat sb;
    struct utimbuf tp;
    long tm=0;

    debug(F110,"zstime",f,0);
    debug(F111,"zstime date",yy->date.val,yy->date.len);

#ifdef CKROOT
    debug(F111,"zstime setroot",ckroot,ckrootset);
    if (ckrootset)
        if (!zinroot(f)) {
            debug(F110,"zstime setroot violation",f,0);
            return(0);
        }
#endif /* CKROOT */

    if (os2stat(f,&sb)) {                       /* Get the time for the file */
        debug(F110,"zstime: Can't stat file:",f,0);
        return(-1);
    }

    if (yy->date.len == 0) {            /* No date in struct */
        if (yy->lprotect.len != 0) {    /* So go do permissions */
            goto zsperms;
        } else {
            debug(F100,"zstime: nothing to do","",0);
            return(0);
        }
    }
    if ((tm = zstrdt(yy->date.val,yy->date.len)) < 0) {
        debug(F101,"zstime: zstrdt fails","",0);
        return(-1);
    }
    debug(F101,"zstime: tm","",tm);
    debug(F111,"zstime: A-pkt date ok ",yy->date.val,yy->date.len);

    setdate = 1;

  zsperms:
#ifdef CK_PERMS
    {
        int i, x = 0, xx, flag = 0;
        char * s;
#ifdef DEBUG
        char obuf[24];
        if (deblog) {
            debug(F111,"zstime lperms",yy->lprotect.val,yy->lprotect.len);
            debug(F111,"zstime gperms",yy->gprotect.val,yy->gprotect.len);
            debug(F110,"zstime system id",yy->systemid.val,0);
            sprintf(obuf,"%o",sb.st_mode);
            debug(F110,"zstime file perms before",obuf,0);
        }
#endif /* DEBUG */
        if ((yy->lprotect.len > 0 &&    /* Have local-format permissions */
            yy->systemid.len > 0 &&     /* from A-packet... */
            0 ) || (yy->lprotect.len < 0) /* OR by inheritance from old file */
            ) {
            flag = 1;
            s = yy->lprotect.val;       /* UNIX filemode */
            xx = yy->lprotect.len;
            if (xx < 0)                 /* len < 0 means inheritance */
              xx = 0 - xx;
            for (i = 0; i < xx; i++) {  /* Decode octal string */
                if (*s <= '7' && *s >= '0') {
                    x = 8 * x + (int)(*s) - '0';
                } else {
                    flag = 0;
                    break;
                }
                s++;
            }
#ifdef DEBUG
            sprintf(obuf,"%o",x);
            debug(F110,"zstime octal lperm",obuf,0);
#endif /* DEBUG */
        } else if (!flag && yy->gprotect.len > 0) {
            int g;
            g = xunchar(*(yy->gprotect.val));
            debug(F101,"zstime gprotect","",g);
#ifdef S_IRUSR
            debug(F100,"zstime S_IRUSR","",0);
            if (g & 1) x |= S_IRUSR;    /* Read permission */
            flag = 1;
#endif /* S_IRUSR */
#ifdef S_IWUSR
            debug(F100,"zstime S_IWUSR","",0);
            if (g & 2) x |= S_IWUSR;    /* Write permission */
            if (g & 16) x |= S_IWUSR;   /* Delete permission */
            flag = 1;
#endif /* S_IWUSR */
#ifdef S_IXUSR
            debug(F100,"zstime S_IXUSR","",0);
            if (g & 4) x |= S_IXUSR;
            flag = 1;
#endif /* S_IXUSR */
        }
        if (flag) {
#ifdef S_IFMT
            sb.st_mode = (sb.st_mode & S_IFMT) | x;
            setperms = 1;
#else
#ifdef _IFMT
            sb.st_mode = (sb.st_mode & _IFMT) | x;
            setperms = 1;
#endif /* _IFMT */
#endif /* S_IFMT */
        }
#ifdef DEBUG
        sprintf(obuf,"%o",sb.st_mode);
        debug(F110,"zstime file perms after",obuf,0);
#endif /* DEBUG */
    }
#endif /* CK_PERMS */

    debug(F101,"zstime: sb.st_atime","",sb.st_atime);

    tp.modtime = tm;                    /* Set modif. time to creation date */
    tp.actime = sb.st_atime;            /* Don't change the access time */

    switch (x) {                        /* Execute desired function */
      case 0:                           /* Set the creation date of the file */
#ifdef CK_PERMS                         /* And permissions */
/*
  NOTE: If we are inheriting permissions from a previous file, and the
  previous file was a directory, this would turn the new file into a directory
  too, but it's not, so we try to unset the right bit.  Luckily, this code
  will probably never be executed since the upper level modules do not allow
  reception of a file that has the same name as a directory.
*/
        {
            int x;
            debug(F101,"zstime setperms","",setperms);
            if (S_ISDIR(sb.st_mode)) {
                debug(F101,"zstime DIRECTORY bit on","",sb.st_mode);
                sb.st_mode ^= 0040000;
                debug(F101,"zstime DIRECTORY bit off","",sb.st_mode);
            }
            if (setperms) {
                x = chmod(f,sb.st_mode);
                debug(F101,"zstime chmod","",x);
            }
        }
        if (x < 0) return(-1);
#endif /* CK_PERMS */

        if (!setdate)                   /* We don't have a date */
          return(0);                    /* so skip the following... */

        if (utime(f,&tp)) {             /* Fix modification time */
            debug(F110,"zstime 0: can't set modtime for file",f,0);
            r = -1;
        } else  {
            debug(F110,"zstime 0: modtime set for file",f,0);
            r = 0;
        }
        break;

      case 1:                           /* Compare the dates */
/*
  This was st_atime, which was wrong.  We want the file-data modification
  time, st_mtime.
*/
        debug(F111,"zstime 1: compare",f,sb.st_mtime);
        debug(F111,"zstime 1: compare","packet",tm);

/*
  In OS/2, sb.st_mtime, at least on a FAT file system, is always even.
  In that case, if the incoming file is only one second newer than the
  local file, consider them the same (yuk).
*/
#ifdef COMMENT
          /* In IBM C 3.6 time_t is of type double.  This can't be done */
        if ((sb.st_mtime & 1) == 0)
#endif /* COMMENT */
          if ((tm - sb.st_mtime) == 1)
            tm--;
        r = (sb.st_mtime < tm) ? 0 : 1;
        break;

      default:                          /* Error */
        r = -1;
    }
    return(r);
}

/* Find initialization file. */

#ifndef NOFRILLS
int
zmail(p,f) char *p; char *f; {          /* Send file f as mail to address p */
/*
  Returns 0 on success
   2 if mail delivered but temp file can't be deleted
  -2 if mail can't be delivered
  The UNIX version always returns 0 because it can't get a good return
  code from zsyscmd.
*/

#ifdef CK_LOGIN
    if (isguest)
      return(-2);
#endif /* CK_LOGIN */

    if (!f) f = "";
    if (!*f) return(-1);

#ifdef CKROOT
    debug(F111,"zmail setroot",ckroot,ckrootset);
    if (ckrootset)
        if (!zinroot(f)) {
            debug(F110,"zmail setroot violation",f,0);
            return(-1);
        }
#endif /* CKROOT */

/* The idea is to use /usr/ucb/mail, rather than regular mail, so that   */
/* a subject line can be included with -s.  Since we can't depend on the */
/* user's path, we use the convention that /usr/ucb/Mail = /usr/ucb/mail */
/* and even if Mail has been moved to somewhere else, this should still  */
/* find it...  The search could be made more reliable by actually using  */
/* access() to see if /usr/ucb/Mail exists. */

/* Should also make some check on zmbuf overflow... */

    sprintf(zmbuf,"Mail -s %c%s%c %s < %s", '"', f, '"', p, f);
    zsyscmd(zmbuf);
    return(-1);
}
#endif /* NOFRILLS */

#ifndef NOFRILLS
#ifdef NT
_PROTOTYP(int Win32PrtFile,( char *, char *));
#endif /* NT */


int
zprint(p,f) char *p; char *f; {         /* Print file f with options p */
    extern char * printername;          /* From ckuus3.c */
    extern int printpipe;

    debug(F110,"zprint file",f,0);
    debug(F110,"zprint flags",p,0);
    debug(F110,"zprint printername",printername,0);
    debug(F101,"zprint printpipe","",printpipe);

#ifdef CK_LOGIN
    if (isguest)
      return(-2);
#endif /* CK_LOGIN */

    if (!f) f = "";
    if (!*f) return(-1);

#ifdef CKROOT
    debug(F111,"zprint setroot",ckroot,ckrootset);
    if (ckrootset)
        if (!zinroot(f)) {
            debug(F110,"zprint setroot violation",f,0);
            return(-1);
        }
#endif /* CKROOT */

    prtfile(f);
    *zmbuf = '\0';
    return(0);
}
#endif /* NOFRILLS */

char *
whoami()
{
    static char realname[MAXPATH];
#ifdef NT
    DWORD len = MAXPATH;
    GetUserName( realname, &len );
#else /* NT */
    char * env = getenv("USER");
    if (env)
        ckstrncpy(realname,env,MAXPATH);
    else
        strcpy(realname,"os2user");
#endif /* NT */
    return realname;
}

/*  T I L D E _ E X P A N D  --  expand ~user to the user's home directory. */

char *
tilde_expand(dirname) char *dirname; {
    return(NULL);
}

/*
  Functions for executing system commands.
  zsyscmd() executes the system command in the normal, default way for
  the system.  In UNIX, it does what system() does.  Thus, its results
  are always predictable.
  zshcmd() executes the command using the user's preferred shell.
*/
int
zsyscmd(s) char *s; {
#ifndef NOPUSH
    extern BYTE vmode;

/*
  We must set the priority back to normal.  Otherwise all of children
  processes are going to inherit our FOREGROUNDSERVER priority and that
  would not be good for the system or ourselves.
*/
   if (!priv_chk()) {
       ULONG rc = 0;
#ifndef KUI
       char title[80];
       title[0] = '\0';
       os2gettitle( title, 80 );
       msleep(100);
       RequestScreenMutex(SEM_INDEFINITE_WAIT);
       KbdHandlerCleanup();
#endif /* KUI */
       ResetThreadPrty();
       rc = system(s);
       SetThreadPrty(priority,15);
#ifndef KUI
       KbdHandlerInit();
       /* Reset mode and buffering for stdio */
       if ( k95stdin ) {
#ifdef NT
           _setmode(_fileno(stdin),_O_BINARY);
#else
           setmode(fileno(stdin),_O_BINARY);
#endif /* NT */
       }
       if ( k95stdout ) {
#ifdef NT
           _setmode(_fileno(stdout),_O_TEXT);
#else
           setmode(fileno(stdout),_O_TEXT);
#endif /* NT */
           setbuf(stdout,NULL);
       }
       if (
#ifndef NOSPL
        cmdlvl == 0
#else
        tlevel < 0
#endif /* NOSPL */
        )
           OS2WaitForKey();
       ReleaseScreenMutex();
       VscrnForceFullUpdate();
       VscrnIsDirty(vmode);
       os2settitle(title, FALSE);
#endif /* KUI */
       return(rc);
   }
   return(0);
#else /* NOPUSH */
    return(-1);
#endif /* NOPUSH */
}

/*
  Original UNIX code by H. Fischer; copyright rights assigned to Columbia U.
  Adapted to use getpwuid to find login shell because many systems do not
  have SHELL in environment, and to use direct calling of shell rather
  than intermediate system() call. -- H. Fischer (1985); many changes since
  then.  Call with s pointing to command to execute.
*/

int
_zshcmd(s,wait) char *s; int wait; {
#ifndef NOPUSH
    PID_T pid;
    int rc;
    char title[80];
#ifdef NT
    SIGTYP (* savint)(int);
#endif /* NT */
    char *shell = getenv("SHELL");
    extern BYTE vmode;

    if ( !shell )
       shell = getenv("COMSPEC");

    pexitstat = -3;                     /* Initialize process exit status */

    if (!priv_chk()) {
#ifndef KUI
        os2gettitle( title, 80 );
        msleep(100);
        RequestScreenMutex(SEM_INDEFINITE_WAIT);
        KbdHandlerCleanup();
#endif /* KUI */

        ResetThreadPrty();
#ifdef NT
        savint = signal( SIGINT, SIG_IGN );
#endif /* NT */

        if (!s || *s == '\0')
          pexitstat = system(shell);    /* was _spawnlp(P_WAIT, shell, NULL) */
        else
          pexitstat = system(s);
#ifdef NT
        signal( SIGINT, savint );
#endif /* NT */

        SetThreadPrty(priority,15);

#ifndef KUI
        KbdHandlerInit();
        if ( k95stdin ) {
#ifdef NT
            _setmode(_fileno(stdin),_O_BINARY);
#else
            setmode(fileno(stdin),_O_BINARY);
#endif /* NT */
        }
        if ( k95stdout ) {
#ifdef NT
            _setmode(_fileno(stdout),_O_TEXT);
#else
            setmode(fileno(stdout),_O_TEXT);
#endif /* NT */
            setbuf(stdout,NULL);
        }
        if ( wait && cmdsrc() == 0 )
            OS2WaitForKey();
        ReleaseScreenMutex();
        VscrnForceFullUpdate();
        VscrnIsDirty(vmode);
        os2settitle( title, FALSE );
#endif /* KUI */
    }
#else /* NOPUSH */
    pexitstat = 1;
#endif /* NOPUSH */
    return(pexitstat == 0);
}

int
zshcmd(s) char *s; {
    return(_zshcmd(s,1));
}

/*  I S W I L D  --  Check if filespec is "wild"  */

/*
  Returns 0 if it is a single file, 1 if it contains wildcard characters.
  Note: must match the algorithm used by match(), hence no [a-z], etc.
*/
int
iswild(filespec) char *filespec; {
    char c; int x; char *p;
    int quo = 0;

    while ((c = *filespec++) != '\0') {
        if (c == '\\' && quo == 0) {
            quo = 1;
            continue;
        }
        if (!quo && (c == '*' || c == '?'
#ifdef CKREGEX
                      || c == '[' || c == '{'
#endif /* CKREGEX */
                      ))
            return(1);
        quo = 0;
    }
    return(0);
}


/*
   Tell if string pointer s is the name of an existing directory.
   Returns 1 if directory, 0 if not a directory.
*/
int
isdir(s) char *s; {
#ifdef NT
    DWORD attrs;
#else /* NT */
    int x;
    struct stat statbuf;
#endif /* NT */

    if (!s) return(0);
    if (!*s) return(0);

    /* Disk letter like A: is top-level directory on a disk */
    if (((int)strlen(s) == 2) && (isalpha(*s)) && (*(s+1) == ':')) {
        return(1);
    }
#ifdef NT
    attrs = GetFileAttributes(s);
    
/* Visual C++ 6 doesn't know about this */
#ifndef INVALID_FILE_ATTRIBUTES
#define INVALID_FILE_ATTRIBUTES -1
#endif
    
    if ( attrs == INVALID_FILE_ATTRIBUTES )
        return(0);
    return(attrs & FILE_ATTRIBUTE_DIRECTORY ? 1 : 0);

#else
    x = os2stat(s,&statbuf);
    debug(F111,"isdir stat",s,x);
    if (x == -1) {
        debug(F101,"isdir errno","",errno);
        return(0);
    } else {
        debug(F101,"isdir statbuf.st_mode","",statbuf.st_mode);
        return (S_ISDIR (statbuf.st_mode) ? 1 : 0);
    }
#endif
}

#ifdef CK_MKDIR
/* Some systems don't have mkdir(), e.g. Tandy Xenix 3.2.. */

/* Z M K D I R  --  Create directory(s) if necessary */
/*
   Call with:
    A pointer to a file specification that might contain directory
    information.  The filename is expected to be included.
    If the file specification does not include any directory separators,
    then it is assumed to be a plain file.
    If one or more directories are included in the file specification,
    this routine tries to create them if they don't already exist.
   Returns:
    0 or greater on success, i.e. the number of directories created.
   -1 on failure to create the directory
*/
int
zmkdir(path) char *path; {
    char *xp, *tp, c;
    int x, count = 0, i;

    if (!path) path = "";
    if (!*path) return(-1);

#ifdef CKROOT
    debug(F111,"zmkdir setroot",ckroot,ckrootset);
    if (ckrootset)
        if (!zinroot(path)) {
            debug(F110,"zmkdir setroot violation",path,0);
            return(-1);
        }
#endif /* CKROOT */

    x = strlen(path);
    debug(F111,"zmkdir",path,x);
    if (x < 1 || x > MAXPATH)           /* Check length */
      return(-1);
    if (!(tp = malloc(x+1)))            /* Make a temporary copy */
      return(-1);
    strcpy(tp,path);

    /* Fixup for UNC if necessary */
    if ( !strncmp(tp,"//",2) || !strncmp(tp,"\\\\",2) ) { 
        strcpy(tp,UNCname(tp));

        xp = &tp[2];                    /* */
        while ( !(ISDIRSEP(*xp)) )      /* Find end of machine name */
            xp++;
        xp++;                           /* Skip to beginning of object name */
        while ( !(ISDIRSEP(*xp)) )      /* Find end of object name */
            xp++;
        xp++;                           /* Skip to first directory name */

        if ( xp-tp > x ) {
            free(tp);
            return(-1);
        }
    } else {
        xp = tp;
        if (ISDIRSEP(*xp))              /* Don't create root directory! */
            xp++;
    }

    /* Go thru filespec from left to right... */
    for (; *xp; xp++) {                 /* Create parts that don't exist */
        if (!ISDIRSEP(*xp))             /* Find next directory separator */
          continue;
        c = *xp;                        /* Got one. */
        *xp = NUL;                      /* Make this the end of the string. */
        if (!isdir(tp)) {               /* This directory exists already? */
#ifdef CK_LOGIN
            if (isguest)                /* Not allowed for guests */
                return(-1);
#ifndef NOXFER
            else                    /* Nor if MKDIR and/or CD are disabled */
#endif /* CK_LOGIN */
                if ((server
#ifdef IKSD
                      || inserver
#endif /* IKSD */
                      ) && (!ENABLED(en_mkd) || !ENABLED(en_cwd)))
                    return(-1);
#endif /* IKSD */

            debug(F110,"zmkdir making",tp,0);
            x =                         /* No, try to create it */
              _mkdir(tp);               /* The IBM way */
            if (x < 0) {
                debug(F101,"zmkdir failed, errno","",errno);
                free(tp);               /* Free temporary buffer. */
                tp = NULL;
                return(-1);             /* Return failure code. */
            } else
              count++;
        }
        *xp = c;                        /* Replace the separator. */
    }
    free(tp);                           /* Free temporary buffer. */
    return(count);                      /* Return success code. */
}
#endif /* CK_MKDIR */

int
zrmdir(char * path) {
#ifdef CK_LOGIN
    if (isguest)
      return(-1);
#endif /* CK_LOGIN */

    if (!path) path = "";
    if (!*path) return(-1);

#ifdef CKROOT
    debug(F111,"zrmdir setroot",ckroot,ckrootset);
    if (ckrootset)
        if (!zinroot(path)) {
            debug(F110,"zrmdir setroot violation",path,0);
            return(-1);
        }
#endif /* CKROOT */


    if ( !strncmp(path,"//",2) || !strncmp(path,"\\\\",2) )
        path = UNCname(path);
    return(rmdir(path));
}

/* Z F S E E K  --  Position input file pointer */
/*
   Call with:
    Long int, 0-based, indicating desired position.
   Returns:
    0 on success.
   -1 on failure.
*/
#ifdef CK_RESEND
int
zfseek(CK_OFF_T pos)
{
#ifdef NT
    int rc;

    /* Previously K95 just assumed fpos_t is an integer type of some kind and
     * that it could just do this then pass the result into fsetpos:
    fpos_t fpos = pos;
     *
     * Problem is fpos_t is an opaque type so we really can't just go doing
     * that. It clearly does work on some versions of Visual C++ but not all
     * of them.
     */

    LARGE_INTEGER li;
    li.QuadPart = pos;

#endif /* NT */

    zincnt = -1 ;               /* must empty the input buffer */

    debug(F101,"zfseek","",pos);
#ifdef NT
    if (GetFileType((HANDLE)_get_osfhandle(_fileno(fp[ZIFILE])))
         == FILE_TYPE_PIPE) {
        debug(F100,"zfseek FILE_TYPE_PIPE","",0);
        return(-1);
    }
	/* The reason why fsetpos was being used was for seeking in long files.
	 * The correct way to do this on Windows is to use SetFilePointer, though
	 * some later versions of Visual C++ (since 2005?) do have an
	 * implementation of fseek that accepts 64bit positions.
    rc = fsetpos(fp[ZIFILE], &fpos);
     */

    rc = SetFilePointer((HANDLE)_get_osfhandle(_fileno(fp[ZIFILE])),
                        li.LowPart, &li.HighPart, FILE_BEGIN);


    if (rc == 0) {
        debug(F100,"zfseek success","",0);
        return(0);
    } else {
        debug(F100,"zfseek failed","",GetLastError());
        return(-1);
    }
#else
    return(fseek(fp[ZIFILE], pos, 0)?-1:0);
#endif
}
#endif /* CK_RESEND */

struct zfnfp *
zfnqfp(fname, buflen, buf)  char * fname; int buflen; char * buf; {
    int x = 0, y = 0;
    char * xp;
    static struct zfnfp fnfp;

    if (!fname)
      return(NULL);

    /* initialize the data structure */
    fnfp.len = buflen;
    fnfp.fpath = buf;
    fnfp.fname = NULL;

#ifdef NT
    if ( GetFullPathName( fname, buflen, fnfp.fpath, &fnfp.fname ) ) {
        while ( fnfp.fpath[y] ) {
            if ( fnfp.fpath[y] == '\\' )
                fnfp.fpath[y] = '/';
            y++;
        }
	if (isdir(fnfp.fpath)) {
	    if (fnfp.fpath[y-1] != '/' && y < (buflen - 1)) {
		fnfp.fpath[y++] = '/';
		fnfp.fpath[y] = NUL;
	    }
	    fnfp.fname = NULL;
	}
        fnfp.len = strlen(fnfp.fpath);
        return(&fnfp);
    }
    else
#else /* NT */
      if (!DosQueryPathInfo(fname,5 /* Full Path Info */, fnfp.fpath,buflen)) {
          x = y = strlen(fnfp.fpath);
          for (x = x + 1; x >= 0; x--) {/* Find where the filename starts */
              if (fnfp.fpath[x] == '/') /* There is guaranteed to be one */
                  fnfp.fname = fnfp.fpath + x; /* Got it, set pointer */
              else if (fnfp.fpath[x] == '\\') {
                  fnfp.fpath[x] = '/';
                  fnfp.fname = fnfp.fpath + x; /* Got it, set pointer */
              }
          }
	  if (isdir(fnfp.fpath)) {
	      fnfp.fpath = NULL;
	      if (fnfp.fpath[y-1] != '/' && y < (buflen - 1)) {
		  fnfp.fpath[y++] = '/';
		  fnfp.fpath[y] = NUL;
	      }
	  }
          fnfp.len = strlen(fnfp.fpath);
          return(&fnfp);                /* and return. */
      } else
#endif /* NT */
    return(NULL);
}

/*  Z C M P F N  --  Compare two filenames  */

/*  Returns 1 if the two names refer to the same existing file, 0 otherwise. */

int
zcmpfn(s1,s2) char * s1, * s2; {
    char buf1[CKMAXPATH+1];
    char buf2[CKMAXPATH+1];
    char linkname[CKMAXPATH+1];
    int x, rc = 0;
    struct stat buf;

    if (!s1) s1 = "";
    if (!s2) s2 = "";
    if (!*s1 || !*s2) return(0);

    if (zfnqfp(s1,CKMAXPATH,buf1)) {    /* Convert to full pathname */

        if (zfnqfp(s2,CKMAXPATH,buf2)) {
            debug(F110,"zcmpfn s1",buf1,0);
            debug(F110,"zcmpfn s2",buf2,0);
            if (!strncmp(buf1,buf2,CKMAXPATH))
              rc = 1;
        }
    }
    debug(F101,"zcmpfn result","",rc);
    return(rc);
}


#ifdef CKROOT

/* User-mode chroot() implementation */

int
zsetroot(s) char * s; {                 /* Sets the root */
    char buf[CKMAXPATH+1];
    if (!s) return(-1);
    if (!*s) return(-1);
    debug(F110,"zsetroot",s,0);
    if (!isdir(s)) return(-2);
    if (!zfnqfp(s,CKMAXPATH,buf))       /* Get full, real path */
      return(-3);
    if (access(buf,R_OK) < 0) {         /* Check access */
        debug(F110,"zsetroot access denied",buf,0);
        return(-4);
    }
    s = buf;
    if (ckrootset) {                    /* If root already set */
        if (!zinroot(s)) {              /* make sure new root is in it */
            debug(F110,"zsetroot new root not in root",ckroot,0);
            return(-5);
        }
    }
    if (zchdir(buf) < 1) return(-4);    /* Change directory to new root */
    ckrootset = ckstrncpy(ckroot,buf,CKMAXPATH); /* Now set the new root */
    if (ckroot[ckrootset-1] != '/') {
        ckroot[ckrootset++] = '/';
        ckroot[ckrootset] = '\0';
    }
    debug(F111,"zsetroot rootset",ckroot,ckrootset);
    ckrooterr = 0;                      /* Reset error flag */
    return(1);
}

char *
zgetroot() {                            /* Returns the root */
    if (!ckrootset)
      return(NULL);
    return((char *)ckroot);
}

int
zinroot(s) char * s; {                  /* Checks if file s is in the root */
    int x, n;
    struct zfnfp * f = NULL;
    char buf[CKMAXPATH+2];

    debug(F111,"zinroot setroot",ckroot,ckrootset);
    ckrooterr = 0;                      /* Reset global error flag */
    if (!ckrootset)                     /* Root not set */
      return(1);                        /* so it's ok - no need to check */
    if (!(f = zfnqfp(s,CKMAXPATH,buf))) /* Get full and real pathname */
      return(0);                        /* Fail if we can't  */
    n = f->len;                         /* Length of full pathname */
    debug(F111,"zinroot n",buf,n);
    if (n < (ckrootset - 1) || n > CKMAXPATH) { /* Bad length */
        ckrooterr = 1;                          /* Fail */
        return(0);
    }
    if (isdir(buf) && buf[n-1] != '/') {  /* If it's a directory name */
        buf[n++] = '/';                   /* make sure it ends with '/' */
        buf[n] = '\0';
    }
    x = ckstrcmp(buf,ckroot,ckrootset,0); /* Compare, case-insensitive */
    debug(F111,"zinroot checked",buf,x);
    if (x == 0)                         /* OK */
      return(1);
    ckrooterr = 1;                      /* Not OK */
    return(0);
}
#endif /* CKROOT */

#ifdef NT
static int
UnicodeToOEM(LPWSTR InString, char *OutString, int OutStringSize)
{
    return WideCharToMultiByte(CP_OEMCP, 0, InString, -1,
                                OutString, OutStringSize, NULL, NULL);
}

static int
OEMToUnicode(char *InString, LPWSTR OutString, int OutStringSize)
{
    return MultiByteToWideChar(CP_OEMCP, 0, InString, -1,
                                OutString, OutStringSize);
}

static int
UnicodeToANSI(LPWSTR InString, char *OutString, int OutStringSize)
{
    return WideCharToMultiByte(CP_ACP, 0, InString, -1,
                                OutString, OutStringSize, NULL, NULL);
}

static int
ANSIToUnicode(char *InString, LPWSTR OutString, int OutStringSize)
{
    return MultiByteToWideChar(CP_ACP, 0, InString, -1,
                                OutString, OutStringSize);
}
#endif /* NT */

#ifdef CK_LOGIN
_PROTOTYP(const char * SSPLogonDomain,(VOID));
_PROTOTYP(int IsSSPLogonAvail,(VOID));

#ifdef NT
static HANDLE hLoggedOn = INVALID_HANDLE_VALUE;
static PSID pSid = NULL;
static PSID pPriGroupSid = NULL;
static PTOKEN_GROUPS pTokenGroups = NULL;
static SID_NAME_USE SidNameUse;
static char HomeDir[MAXPATH] = "", ProfilePath[MAXPATH] = "";
static DWORD PrimaryGroupId;
CHAR * pReferenceDomainName = NULL;
static CHAR * pPDCName = NULL;

#ifndef PROFILEINFO
#define PI_NOUI 1                       // Prevents displaying of profile error messages.
#define PI_APPLYPOLICY 2                // Applies a Windows NT 4.0-style policy.

typedef struct _PROFILEINFOA {
    DWORD       dwSize;                 // Set to sizeof(PROFILEINFO) before calling
    DWORD       dwFlags;                // See flags above
    LPSTR       lpUserName;             // User name (required)
    LPSTR       lpProfilePath;          // Roaming profile path (optional, can be NULL)
    LPSTR       lpDefaultPath;          // Default user profile path (optional, can be NULL)
    LPSTR       lpServerName;           // Validating domain controller name in netbios format (optional, can be NULL but group NT4 style policy won't be applied)
    LPSTR       lpPolicyPath;           // Path to the NT4 style policy file (optional, can be NULL)
    HANDLE      hProfile;               // Filled in by the function.  Registry key handle open to the root.
} PROFILEINFOA, FAR * LPPROFILEINFOA;
#define PROFILEINFO PROFILEINFOA
#define LPPROFILEINFO LPPROFILEINFOA
#endif /* PROFILEINFO */
PROFILEINFO profinfo = { sizeof profinfo, 0, 0, 0, 0, 0, 0 };
VOID      * pEnvBlock = NULL;
static HINSTANCE hUserEnv=NULL, hAdvApi=NULL;
static BOOL (WINAPI * p_CreateEnvironmentBlock)(void **, HANDLE, BOOL)=NULL;
static BOOL (WINAPI * p_DestroyEnvironmentBlock)(void *)=NULL;
static BOOL (WINAPI * p_LoadUserProfileA)(HANDLE, PROFILEINFO *)=NULL;
static BOOL (WINAPI * p_UnloadUserProfileA)(HANDLE, HANDLE)=NULL;
static BOOL (WINAPI * p_DuplicateTokenEx)(HANDLE hExistingToken,
					   DWORD dwDesiredAccess,
					   LPSECURITY_ATTRIBUTES lpTokenAttributes,
					   SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
					   TOKEN_TYPE TokenType,
					   PHANDLE phNewToken)=NULL;

BOOL GetTextualSid(
    PSID pSid,            // binary Sid
    LPTSTR TextualSid,    // buffer for Textual representation of Sid
    LPDWORD lpdwBufferLen // required/provided TextualSid buffersize
    )
{
    PSID_IDENTIFIER_AUTHORITY psia;
    DWORD dwSubAuthorities;
    DWORD dwSidRev=SID_REVISION;
    DWORD dwCounter;
    DWORD dwSidSize;

    // Validate the binary SID.

    if(!IsValidSid(pSid)) return FALSE;

    // Get the identifier authority value from the SID.

    psia = GetSidIdentifierAuthority(pSid);

    // Get the number of subauthorities in the SID.

    dwSubAuthorities = *GetSidSubAuthorityCount(pSid);

    // Compute the buffer length.
    // S-SID_REVISION- + IdentifierAuthority- + subauthorities- + NULL

    dwSidSize=(15 + 12 + (12 * dwSubAuthorities) + 1) * sizeof(TCHAR);

    // Check input buffer length.
    // If too small, indicate the proper size and set last error.

    if (*lpdwBufferLen < dwSidSize)
    {
        *lpdwBufferLen = dwSidSize;
        SetLastError(ERROR_INSUFFICIENT_BUFFER);
        return FALSE;
    }

    // Add 'S' prefix and revision number to the string.

    dwSidSize=wsprintf(TextualSid, TEXT("S-%lu-"), dwSidRev );

    // Add SID identifier authority to the string.

    if ( (psia->Value[0] != 0) || (psia->Value[1] != 0) )
    {
        dwSidSize+=wsprintf(TextualSid + lstrlen(TextualSid),
                    TEXT("0x%02hx%02hx%02hx%02hx%02hx%02hx"),
                    (USHORT)psia->Value[0],
                    (USHORT)psia->Value[1],
                    (USHORT)psia->Value[2],
                    (USHORT)psia->Value[3],
                    (USHORT)psia->Value[4],
                    (USHORT)psia->Value[5]);
    }
    else
    {
        dwSidSize+=wsprintf(TextualSid + lstrlen(TextualSid),
                    TEXT("%lu"),
                    (ULONG)(psia->Value[5]      )   +
                    (ULONG)(psia->Value[4] <<  8)   +
                    (ULONG)(psia->Value[3] << 16)   +
                    (ULONG)(psia->Value[2] << 24)   );
    }

    // Add SID subauthorities to the string.
    //
    for (dwCounter=0 ; dwCounter < dwSubAuthorities ; dwCounter++)
    {
        dwSidSize+=wsprintf(TextualSid + dwSidSize, TEXT("-%lu"),
                    *GetSidSubAuthority(pSid, dwCounter) );
    }

    return TRUE;
}

#ifdef NTCREATETOKEN
#define MAXSIZE 4096
#define gle (GetLastError())
static BOOL getSids( TOKEN_PRIVILEGES **tokenPrivs, PSID *ownerSid, PSID *priGroupSid, TOKEN_GROUPS **tokenGroups, LUID *logonSessionId );
static BOOL showToken( HANDLE ht );
static void showSid( int indent, PSID ps );
static BOOL Sid2Text( PSID ps, char *buf, int bufSize );
static BOOL IsLogonSid( PSID ps );
static BOOL IsLocalSid( PSID ps );
static BOOL IsInteractiveSid( PSID ps );
static BOOL getPriv( const char *privName );

HANDLE
CreateTokenForUser(char * szUsername, char * szDomain, PSID luserSid, PSID preGroupSid,
                    TOKEN_GROUPS * userTokenGroups) {
    HINSTANCE hlib;
    DWORD rc = 0;
    PSID ownerSid = 0, iksdSid = 0;
    PSID admingrpSid = 0, worldSid = 0;
    LUID logonSessionId;
    TOKEN_PRIVILEGES *tokenPrivs;
    SID_IDENTIFIER_AUTHORITY NTsia = SECURITY_NT_AUTHORITY;
    SID_IDENTIFIER_AUTHORITY worldsia = SECURITY_WORLD_SID_AUTHORITY;
    char *domainName;
    DWORD luserSidSize;
    DWORD domainNameSize;
    SID_NAME_USE luserSidType;
    // returned handle
    HANDLE tokenHandle = INVALID_HANDLE_VALUE;
    // permissions with which the handle is opened
    DWORD requestedPermissions = TOKEN_ALL_ACCESS;
    PACL newTokenDacl;
    SECURITY_DESCRIPTOR newTokenSD;
    SECURITY_QUALITY_OF_SERVICE sqos;
    LSA_OBJECT_ATTRIBUTES ObjAttr;
    TOKEN_TYPE tokenType;
    LUID sessionLuid, serviceSessionLuid = { 0x3e7, 0 };
    FILETIME expirationTime;
    SID_AND_ATTRIBUTES tokenUser;
    PSID newOwnerSid, newPriGroupSid;
    PACL newDefaultDacl;
    TOKEN_SOURCE tokenSource = { { '*', 'i', 'k', 's', 'd', '*' }, { 0, 0 } }; // TOKEN_SOURCE


    typedef DWORD (__stdcall *tNCT)( HANDLE                     *phToken,               // returned handle
                                     DWORD                      *pReqPermissionMask,    // requested permissions for returned handle
                                     LSA_OBJECT_ATTRIBUTES      *pObjAttr,              // contains SD controlling access to the new token
                                     TOKEN_TYPE                 tokenType,              // TokenPrimary == 1 == primary, TokenImpersonation == 2 == impersonation
                                     LUID                       *pLogonSessionLuid,     // LUID for logon session, goes into the token
                                     FILETIME                   *expirationTime,        // token expiration deadline. unsupported -- set to 2^63 - 1
                                     SID_AND_ATTRIBUTES         *pTokenUser,            // returned by GetTokenInformation( TokenUser )
                                     TOKEN_GROUPS               *pTokenGroups,          // exploded list of groups for the user
                                     TOKEN_PRIVILEGES           *pTokenPrivileges,      // list of privs assigned (not necessarily enabled)
                                     PSID                       *pOwnerSid,             // owner SID
                                     PSID                       *pPrimaryGroupSid,      // primary group SID
                                     PACL                       *pDefaultDacl,          // will be used as default if a process using this token assigns no security to new objects
                                     TOKEN_SOURCE               *pTokenSource           // contains creator and creator-specified LUID
                                     );
    tNCT pNCT;

    debug(F110,"GetTokenForUser szDomain",szDomain,0);
    debug(F110,"GetTokenForUser szUsername",szUsername,0);

    // extract a few useful things from our current process token
    //
    if (!getSids( &tokenPrivs, &ownerSid,
                  pPriGroupSid ? NULL : &pPriGroupSid,
                  userTokenGroups ? NULL : &userTokenGroups,
                  &logonSessionId )) {
#ifdef BETADEBUG
        printf("  getSids() failed\r\n");
#endif /* BETADEBUG */
        return(INVALID_HANDLE_VALUE);
    }

    // the next sid is really the domain admin -- check the reserved RID at the end
    AllocateAndInitializeSid( &NTsia, 5, 21, 484763869, 764733703, 1202660629, 500, 0, 0, 0, &iksdSid );

    // local admins group
    AllocateAndInitializeSid( &NTsia, 2, 0x20, 0x220, 0, 0, 0, 0, 0, 0, &admingrpSid );

    // and a crowd pleaser
    AllocateAndInitializeSid( &NTsia, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &worldSid );

    // SeCreateTokenPrivilege must be explicitly enabled
    getPriv( SE_CREATE_TOKEN_NAME );

    // init guessed arguments

    // this DACL determines who may mess with the token-to-be
    newTokenDacl = (PACL) malloc( MAXSIZE );
    InitializeAcl( newTokenDacl, MAXSIZE, ACL_REVISION );
    AddAccessAllowedAce( newTokenDacl, ACL_REVISION, TOKEN_ALL_ACCESS, worldSid );
    AddAccessAllowedAce( newTokenDacl, ACL_REVISION, TOKEN_ALL_ACCESS, iksdSid );
    AddAccessAllowedAce( newTokenDacl, ACL_REVISION, TOKEN_ALL_ACCESS, admingrpSid );
    // AddAccessAllowedAce( newTokenDacl, ACL_REVISION, TOKEN_ALL_ACCESS, luserSid );

    // this SD says that Administrators is the owner of the token itself
    // (the "owner" below is what gets used as the default owner for, say, files created under this token)
    newTokenSD.Revision = 1;
    newTokenSD.Sbz1 = 0;
    newTokenSD.Control = 4;
    newTokenSD.Owner = admingrpSid;
    newTokenSD.Group = 0;
    newTokenSD.Sacl = 0;
    newTokenSD.Dacl = newTokenDacl;

    // this says we wish to create a token that we can impersonate, and which reflects
    // ongoing changes in permissions/groups/... if the security provider can do that,
    // and which allows an impersonating server to enable/disable groups or privs
    sqos.Length = sizeof(SECURITY_QUALITY_OF_SERVICE);
    sqos.ImpersonationLevel = SecurityImpersonation;
    sqos.ContextTrackingMode = SECURITY_STATIC_TRACKING;
    sqos.EffectiveOnly = FALSE;

    // OBJECT_ATTRIBUTES is the NT bog-standard way of passing the name
    // and security for an object to be created or opened
    ObjAttr.Length = sizeof(ObjAttr);
    ObjAttr.RootDirectory = 0;
    ObjAttr.ObjectName = 0;
    ObjAttr.Attributes = 0;
    ObjAttr.SecurityDescriptor = &newTokenSD;
    ObjAttr.SecurityQualityOfService = &sqos;

    // primary tokens are usable for CreateProcessAsUser()
    tokenType = TokenPrimary;

    // logon session ID
    // use either the service logon ID, or the LUID extracted from our own token:
    // sessionLuid = serviceSessionLuid; // this would be the logon session for services
    sessionLuid = logonSessionId; // this would be the logon session for services

    // FILETIME format for token expiration time. 2^63-1 means no expiration.
    // NTLM does not support this -- Kerberos should, but I didn't test it
    expirationTime.dwLowDateTime = 0xffffffffUL;
    expirationTime.dwHighDateTime = 0x7fffffffUL;

    // token user -- the user who will be impersonated or whatever
    tokenUser.Sid = luserSid;
    tokenUser.Attributes = 0;

    // optional: remove our logon SID from processTokenGroups, add logon SID as per session LUID above
    // or collect group info for account and hand-build group list

    // optional: filter tokenPrivs

    // owner SID -- will be stamped on all objects that get created without an explicit SD under our token
    newOwnerSid = luserSid;

    // primary group SID -- will be stamped on all objects that get created without an explicit SD under our token
    newPriGroupSid = pPriGroupSid;

    // this DACL will be stamped on all objects that get created without an explicit SD under our token
    newDefaultDacl = (PACL) malloc( MAXSIZE );
    InitializeAcl( newDefaultDacl, MAXSIZE, ACL_REVISION );
    AddAccessAllowedAce( newDefaultDacl, ACL_REVISION, MAXDWORD, worldSid );


    hlib = LoadLibrary( "ntdll.dll" );
    if ( hlib ) {
        pNCT = (tNCT) GetProcAddress( hlib, "NtCreateToken" );
        if ( pNCT )
        {
            debug(F100,"calling NtCreateToken()","",0);
            rc = pNCT( &tokenHandle, &requestedPermissions, &ObjAttr, tokenType,
                       &sessionLuid, &expirationTime, &tokenUser, userTokenGroups, tokenPrivs,
                       &newOwnerSid, &newPriGroupSid, &newDefaultDacl, &tokenSource );
            debug(F111,"NtCreateToken() returns","rc",rc);
            if ( rc == 0 )
            {
                HANDLE ht = 0;
#ifdef BETADEBUG
                printf( "token handle: %08lXh\n", tokenHandle );
#endif /* BETADEBUG */
                // odd thing: the tokenHandle is unusable ... but it _can_ be
                // duplicated with full access. Need to check if this is only
                // because we are the owner or whatever!
                if ( DuplicateHandle( GetCurrentProcess(), tokenHandle,
                                      GetCurrentProcess(), &ht, TOKEN_ALL_ACCESS, FALSE, DUPLICATE_CLOSE_SOURCE ) )
                {
                    if (ht != tokenHandle) {
                      CloseHandle( tokenHandle );
                      tokenHandle = ht;
#ifdef BETADEBUG
                      printf( "dup token handle: %08lXh\n", tokenHandle );
#endif /* BETADEBUG */
                    }
                }
#ifdef BETADEBUG
                else
                   printf( "DH(): gle = %lu\n", gle );
#endif /* BETADEBUG */
            }
#ifdef BETADEBUG
            else {
               printf( "NCT(): gle = %lu\n", gle );
               printf( "NCT(): rc = %08lXh\n", rc );
            }
#endif /* BETADEBUG */
        }
        FreeLibrary( hlib );
    }

    if ( luserSid != pSid )
        free(luserSid);

#ifdef BETADEBUG
    printf( "token handle: %08lXh\n", tokenHandle );
#endif /* BETADEBUG */
    return(tokenHandle);
}


BOOL
getSids( TOKEN_PRIVILEGES **tokenPrivs, PSID *ownerSid, PSID *priGroupSid, TOKEN_GROUPS **processTokenGroups, LUID *logonSessionId )
{
    DWORD needed;
    HANDLE ht;

    TOKEN_STATISTICS tstat;
    TOKEN_OWNER *pto;
    TOKEN_PRIMARY_GROUP *ptpg;
    TOKEN_GROUPS *ptg;
    TOKEN_PRIVILEGES *ptp;

    debug(F100,"getSids","",0);

    // open process token

    if ( ! OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY | TOKEN_QUERY_SOURCE, &ht ) )
    {
        if ( ! OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, &ht ) )
        {
            DWORD error = gle;
            if (error != ERROR_CALL_NOT_IMPLEMENTED)
                printf("  OpenProcessToken(): gle = %lu\n", error);
            return FALSE;
        }
    }

    // grab token information

    // logon session LUID
    if ( logonSessionId ) {
        if ( ! GetTokenInformation( ht, TokenStatistics, &tstat, sizeof tstat, &needed ) )
            printf( "  GetTokenInformation( TokenStatistics ): gle = %lu\n", gle );
        else
        {
            *logonSessionId = tstat.AuthenticationId;
        }
    }

    // token owner
    if ( ownerSid ) {
        pto = (TOKEN_OWNER *) malloc( MAXSIZE );
        if ( ! GetTokenInformation( ht, TokenOwner, pto, MAXSIZE, &needed ) )
            printf( "  GetTokenInformation( TokenOwner ): gle = %lu\n", gle );
        else
        {
            *ownerSid = (PSID) malloc( MAXSIZE );
            CopySid( MAXSIZE, *ownerSid, pto->Owner );
        }
        free(pto);
    }

    // token primary group
    if ( priGroupSid ) {
        ptpg = (TOKEN_PRIMARY_GROUP *) malloc( MAXSIZE );
        if ( ! GetTokenInformation( ht, TokenPrimaryGroup, ptpg, MAXSIZE, &needed ) )
            printf( "  GetTokenInformation( TokenPrimaryGroup ): gle = %lu\n", gle );
        else
        {
            *priGroupSid = (PSID) malloc( MAXSIZE );
            CopySid( MAXSIZE, *priGroupSid, ptpg->PrimaryGroup );
        }
        free(ptpg);
    }

    // token groups
    if ( processTokenGroups ) {
        ptg = (TOKEN_GROUPS *) malloc( MAXSIZE );
        if ( ! GetTokenInformation( ht, TokenGroups, ptg, MAXSIZE, &needed ) )
            printf( "  GetTokenInformation( TokenGroups ): gle = %lu\n", gle );
        else
        {
            *processTokenGroups = ptg;
        }
    }

    if ( tokenPrivs ) {
        ptp = (TOKEN_PRIVILEGES *) malloc( MAXSIZE );
        if ( ! GetTokenInformation( ht, TokenPrivileges, ptp, MAXSIZE, &needed ) )
            printf( "  GetTokenInformation( TokenPrivileges ): gle = %lu\n", gle );
        else
        {
            *tokenPrivs = ptp;
        }
    }

    CloseHandle( ht );
    return TRUE;
}



const char *formatTime( FILETIME *t )
{
    SYSTEMTIME st;
    DWORD len = MAXSIZE;
    static char buf[MAXSIZE];

    if ( t->dwHighDateTime > 0x7fffffffUL ||
         ( t->dwHighDateTime == 0x7fffffffUL && t->dwLowDateTime == 0xffffffffUL ) )
    {
        ckstrncpy( buf, "-none-", MAXSIZE );
        return buf;
    }

    FileTimeToSystemTime( t, &st );
    GetDateFormat( LOCALE_USER_DEFAULT, DATE_LONGDATE, &st, 0, buf, len );
    ckstrncat( buf, " ", MAXSIZE );
    len -= strlen( buf );
    GetTimeFormat( LOCALE_USER_DEFAULT, TIME_FORCE24HOURFORMAT, &st, 0, &buf[strlen( buf )], len );
    return buf;
}



BOOL
showToken( HANDLE ht )
{
    DWORD needed;
    DWORD i;

    TOKEN_SOURCE ts;
    TOKEN_STATISTICS tstat;
    TOKEN_OWNER *pto;
    TOKEN_USER *ptu;
    TOKEN_PRIMARY_GROUP *ptpg;
    TOKEN_GROUPS *ptg;

    // dump token information

    // creating subsystem
    if ( ! GetTokenInformation( ht, TokenSource, &ts, sizeof ts, &needed ) )
        printf( "  GetTokenInformation( TokenSource ): gle = %lu\n", gle );
    else
        printf( "  Token source: \"%-8.8s\" (luid = %I64d)\n",
                ts.SourceName, ts.SourceIdentifier );

    // logon session LUID
    if ( ! GetTokenInformation( ht, TokenStatistics, &tstat, sizeof tstat, &needed ) )
        printf( "  GetTokenInformation( TokenStatistics ): gle = %lu\n", gle );
    else
    {
        printf( "  Token Statistics:\n" );
        printf( "    TokenId:            %I64d (%lu-%lu)\n", *( (__int64 *) &tstat.TokenId ),
                tstat.TokenId.HighPart, tstat.TokenId.LowPart );
        printf( "    AuthenticationId:   %I64d (%lu-%lu)\n", *( (__int64 *) &tstat.AuthenticationId ),
                tstat.AuthenticationId.HighPart, tstat.AuthenticationId.LowPart );
        printf( "    ExpirationTime:     %016I64Xh (%s)\n", *( (__int64 *) &tstat.ExpirationTime ),
                formatTime( (FILETIME *) &tstat.ExpirationTime ) );
        printf( "    TokenType:          %d (%s)\n", tstat.TokenType,
                tstat.TokenType == 1? "Primary":
                ( tstat.TokenType == 2? "Impersonation": "unknown" ) );
        printf( "    ImpersonationLevel: %d (%s)\n", tstat.ImpersonationLevel,
                tstat.ImpersonationLevel == SecurityAnonymous? "Anonymous":
                ( tstat.ImpersonationLevel == SecurityIdentification? "Identification":
                  ( tstat.ImpersonationLevel == SecurityImpersonation? "Impersonation":
                    ( tstat.ImpersonationLevel == SecurityDelegation? "Delegation": "unknown" ) ) ) );
        printf( "    DynamicCharged:     %lu bytes\n", tstat.DynamicCharged );
        printf( "    DynamicAvailable:   %lu bytes\n", tstat.DynamicAvailable );
        printf( "    GroupCount:         %lu\n", tstat.GroupCount );
        printf( "    PrivilegeCount:     %lu\n", tstat.PrivilegeCount );
        printf( "    ModifiedId:         %I64d (%lu-%lu)\n", *( (__int64 *) &tstat.ModifiedId ),
                tstat.ModifiedId.HighPart, tstat.ModifiedId.LowPart );
    }

    // token owner
    pto = (TOKEN_OWNER *) malloc( MAXSIZE );
    if ( ! GetTokenInformation( ht, TokenOwner, pto, MAXSIZE, &needed ) )
        printf( "  GetTokenInformation( TokenOwner ): gle = %lu\n", gle );
    else
    {
        printf( "  Token owner:\n    " );
        showSid( 4, pto->Owner );
        putchar( '\n' );
    }

    // token user
    ptu = (TOKEN_USER *) malloc( MAXSIZE );
    if ( ! GetTokenInformation( ht, TokenUser, ptu, MAXSIZE, &needed ) )
        printf( "  GetTokenInformation( TokenUser ): gle = %lu\n", gle );
    else
    {
        printf( "  Token user:\n    " );
        showSid( 4, ptu->User.Sid );
        putchar( '\n' );
    }

    // token primary group
    ptpg = (TOKEN_PRIMARY_GROUP *) malloc( MAXSIZE );
    if ( ! GetTokenInformation( ht, TokenPrimaryGroup, ptpg, MAXSIZE, &needed ) )
        printf( "  GetTokenInformation( TokenPrimaryGroup ): gle = %lu\n", gle );
    else
    {
        printf( "  Token primary group:\n    " );
        showSid( 4, ptpg->PrimaryGroup );
        putchar( '\n' );
    }

    // token groups
    ptg = (TOKEN_GROUPS *) malloc( MAXSIZE );
    if ( ! GetTokenInformation( ht, TokenGroups, ptg, MAXSIZE, &needed ) )
        printf( "  GetTokenInformation( TokenGroups ): gle = %lu\n", gle );
    else
    {
        if ( ptg->GroupCount == 0 )
            printf( "  Token groups: (none)\n" );
        else
        {
            printf( "  Token groups:\n" );
            for ( i = 0; i < ptg->GroupCount; ++ i )
            {
                printf( "    %08lXh  ", ptg->Groups[i].Attributes );
                showSid( 15, ptg->Groups[i].Sid );
                putchar( '\n' );
            }
        }
    }

    free( pto );
    free( ptu );
    free( ptpg );
    free( ptg );

    return TRUE;
}

void showSid( int indent, PSID ps )
{
    char textSid[MAX_PATH], user[MAX_PATH], domain[MAX_PATH], buf[MAX_PATH];
    DWORD sizeUser, sizeDomain;
    SID_NAME_USE snu;
    DWORD charsleft;
    const char *t;
    const char *sep = "\\"; // separator for domain\user display

    Sid2Text( ps, textSid, sizeof textSid );
    fputs( textSid, stdout );
    charsleft = 78 - indent - strlen( textSid );

    sizeUser = sizeof user;
    sizeDomain = sizeof domain;
    if ( ! LookupAccountSid( NULL, ps, user, &sizeUser, domain, &sizeDomain, &snu ) )
    {
        DWORD rc = gle;

        if ( IsLogonSid( ps ) )
            strcpy( buf, "(interactive logon session SID)" );
        else
            _snprintf( buf, sizeof(buf)-1, "[LAS(): gle = %lu]", rc );
        if ( strlen( buf ) > charsleft )
            printf( "\n%*s", indent, "" );
        else
            putchar( ' ' );
        fputs( buf, stdout );
        return;
    }

    switch ( snu )
    {
    case SidTypeUser:
        t = "user";
        break;
    case SidTypeGroup:
        t = "group";
        break;
    case SidTypeDomain:
        t = "domain";
        break;
    case SidTypeAlias:
        t = "alias";
        break;
    case SidTypeWellKnownGroup:
        t = "well-known group";
        break;
    case SidTypeDeletedAccount:
        t = "deleted";
        break;
    case SidTypeInvalid:
        t = "invalid";
        break;
    case SidTypeUnknown:
        t = "unknown";
        break;
#ifndef _M_ALPHA
#ifndef _M_PPC
    case SidTypeComputer:
        t = "computer";
        break;
#endif /* _M_PPC */
#endif /* _M_ALPHA */
    default:
        t = "*?unknown?*";
        break;
    }

    if ( domain[0] == '\0' || user[0] == '\0' )
        sep = "";

    _snprintf( buf, sizeof(buf)-1, "\"%s%s%s\" (%s)", domain, sep, user, t );
    if ( strlen( buf ) > charsleft )
        printf( "\n%*s", indent, "" );
    else
        putchar( ' ' );
    fputs( buf, stdout );
}



BOOL IsLocalSid( PSID ps )
{
    static PSID pComparisonSid = NULL;

    if ( pComparisonSid == NULL )
    {
        // build "BUILTIN\LOCAL" SID for comparison: S-1-2-0
        SID_IDENTIFIER_AUTHORITY sia = SECURITY_LOCAL_SID_AUTHORITY;
        AllocateAndInitializeSid( &sia, 1, 0, 0, 0, 0, 0, 0, 0, 0, &pComparisonSid );
    }

    return EqualSid( ps, pComparisonSid );
}



BOOL IsInteractiveSid( PSID ps )
{
    static PSID pComparisonSid = NULL;

    if ( pComparisonSid == NULL )
    {
        // build "BUILTIN\LOCAL" SID for comparison: S-1-5-4
        SID_IDENTIFIER_AUTHORITY sia = SECURITY_NT_AUTHORITY; // "-5-"
        AllocateAndInitializeSid( &sia, 1, 4, 0, 0, 0, 0, 0, 0, 0, &pComparisonSid );
    }

    return EqualSid( ps, pComparisonSid );
}



BOOL IsLogonSid( PSID ps )
{
    static SID_IDENTIFIER_AUTHORITY sia = SECURITY_NT_AUTHORITY;

    // a logon SID has: sia = 5, subauth count = 3, first subauth = 5
    // the following three lines test these three conditions
    if ( ! memcmp( GetSidIdentifierAuthority( ps ), &sia, sizeof sia )  && // is sia == 5?
         *GetSidSubAuthorityCount( ps ) == 3                                                            && // is subauth count == 3?
         *GetSidSubAuthority( ps, 0 ) == 5                                                                      )  // first subauth == 5?
        return TRUE;
    else
        return FALSE;
}



// nearly straight from the SDK
BOOL Sid2Text( PSID ps, char *buf, int bufSize )
{
    PSID_IDENTIFIER_AUTHORITY psia;
    DWORD dwSubAuthorities;
    DWORD dwSidRev = SID_REVISION;
    DWORD i;
    int n, size;
    char *p;

    // Validate the binary SID.

    if ( ! IsValidSid( ps ) )
        return FALSE;

    // Get the identifier authority value from the SID.

    psia = GetSidIdentifierAuthority( ps );

    // Get the number of subauthorities in the SID.

    dwSubAuthorities = *GetSidSubAuthorityCount( ps );

    // Compute the buffer length.
    // S-SID_REVISION- + IdentifierAuthority- + subauthorities- + NULL

    size = 15 + 12 + ( 12 * dwSubAuthorities ) + 1;

    // Check input buffer length.
    // If too small, indicate the proper size and set last error.

    if ( bufSize < size )
    {
        SetLastError( ERROR_INSUFFICIENT_BUFFER );
        return FALSE;
    }

    // Add 'S' prefix and revision number to the string.

    size = sprintf( buf, "S-%lu-", dwSidRev );
    p = buf + size;

    // Add SID identifier authority to the string.

    if ( psia->Value[0] != 0 || psia->Value[1] != 0 )
    {
        n = sprintf( p, "0x%02hx%02hx%02hx%02hx%02hx%02hx",
                     (USHORT) psia->Value[0], (USHORT) psia->Value[1],
                     (USHORT) psia->Value[2], (USHORT) psia->Value[3],
                     (USHORT) psia->Value[4], (USHORT) psia->Value[5] );
        size += n;
        p += n;
    }
    else
    {
        n = sprintf( p, "%lu", ( (ULONG) psia->Value[5] ) +
                     ( (ULONG) psia->Value[4] << 8 ) + ( (ULONG) psia->Value[3] << 16 ) +
                     ( (ULONG) psia->Value[2] << 24 ) );
        size += n;
        p += n;
    }

    // Add SID subauthorities to the string.

    for ( i = 0; i < dwSubAuthorities; ++ i )
    {
        n = sprintf( p, "-%lu", *GetSidSubAuthority( ps, i ) );
        size += n;
        p += n;
    }

    return TRUE;
}

BOOL
getPriv( const char *privName )
{
    BOOL rc;
    HANDLE hToken;
    LUID privValue;
    TOKEN_PRIVILEGES tkp;

    if ( ! OpenProcessToken( GetCurrentProcess(),
                             TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken ) ) {
        return FALSE;
    }

    if ( !LookupPrivilegeValue( NULL, privName, &privValue ) )
    {
        CloseHandle( hToken );
        return FALSE;
    }

    tkp.PrivilegeCount = 1;
    tkp.Privileges[0].Luid = privValue;
    tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

    rc = !! AdjustTokenPrivileges( hToken, FALSE, &tkp, sizeof tkp, NULL, NULL );
    CloseHandle( hToken );
    return rc;
}
#endif /* NTCREATETOKEN */
#endif /* NT */

/*  Z V U S E R  --  Verify user, Returns 1 if user OK, 0 otherwise.  */

int
zvuser(username) char *username; {
#ifdef NT
    int rc = 0;
    DWORD dwSid = 0;
    DWORD dwName = 0;
    DWORD LastError;
    char buf[4096], *p, *name;

    if (logged_in)
        return(0);

#ifdef CKSYSLOG
    if (ckxsyslog && ckxlogging) {
        sprintf(buf, "login: user %s",username);
        cksyslog(SYSLG_LI, 1, buf, NULL, NULL);
    }
#endif /* CKSYSLOG */

    /* Cleanup from previous calls (if any) */

    if ( !p_CreateEnvironmentBlock || !p_DestroyEnvironmentBlock ||
         !p_LoadUserProfileA || !p_UnloadUserProfileA ) {

        if ( !hUserEnv )
            hUserEnv = LoadLibrary("userenv.dll");
        if ( hUserEnv ) {
            (FARPROC) p_CreateEnvironmentBlock =
                GetProcAddress( hUserEnv, "CreateEnvironmentBlock" );
            (FARPROC) p_DestroyEnvironmentBlock =
                GetProcAddress( hUserEnv, "DestroyEnvironmentBlock" );
            (FARPROC) p_LoadUserProfileA =
                GetProcAddress( hUserEnv, "LoadUserProfileA" );
            (FARPROC) p_UnloadUserProfileA =
                GetProcAddress( hUserEnv, "UnloadUserProfileA" );
        }
    }

    if ( hLoggedOn != INVALID_HANDLE_VALUE ) {
        debug(F110,"zvuser()","calling RevertToSelf()",0);
        RevertToSelf();

        if ( p_UnloadUserProfileA && profinfo.hProfile )
            p_UnloadUserProfileA(hLoggedOn, profinfo.hProfile);
        memset(&profinfo,0,sizeof(profinfo));
        profinfo.dwSize = sizeof(profinfo);

        if ( p_DestroyEnvironmentBlock && pEnvBlock ) {
            p_DestroyEnvironmentBlock(pEnvBlock);
            pEnvBlock = NULL;
        }
        CloseHandle(hLoggedOn);
        hLoggedOn = INVALID_HANDLE_VALUE;
    }
    if (pSid) {
        free(pSid);
        pSid = NULL;
    }
    if (pPriGroupSid) {
        free(pPriGroupSid);
        pPriGroupSid = NULL;
    }
    if (pTokenGroups) {
        int i;
        for (i=0 ; i < pTokenGroups->GroupCount; i++ )
            free(pTokenGroups->Groups[i].Sid);
        free(pTokenGroups);
        pTokenGroups = NULL;
    }

    if ( pReferenceDomainName ) {
        free(pReferenceDomainName);
        pReferenceDomainName = NULL;
    }
    if ( pPDCName ) {
        free(pPDCName);
        pPDCName = NULL;
    }

    /* Now start the login process */
    debug(F110,"zvuser() username",username,0);
    if ( ( !stricmp("anonymous",username)
#ifdef COMMENT
           || !stricmp("guest",username) || !stricmp("ftp",username)
#endif /* COMMENT */
           )) {
        if ( ckxanon )
            name = anonacct ? anonacct : "guest";
        else {
#ifdef CKSYSLOG
            if (ckxsyslog && ckxlogging) {
                sprintf(buf,
                       "login: anonymous login not allowed: %s",
                       clienthost ? clienthost : "(unknown host)"
                       );
                cksyslog(SYSLG_LI, 0, buf, NULL, NULL);

            }
#endif /* CKSYSLOG */
            return(0);
        }
    }
    else
        name = username;

    debug(F110,"zvuser() name",name,0);

    if ( name == NULL || name[0] == '\0' )
        return(0);

    if ( !isWin95() ) {
        for (p = name; *p && (*p != '\\') ; p++) ;
        if ( *p ) {
            /* a domain has already been specified */
            strcpy(buf,name);
        } else if (iks_domain && iks_domain[0]) {
            sprintf(buf,"%s\\%s",iks_domain,name);
        } else {
            strcpy(buf,name);
        }
        rc = LookupAccountName( NULL,buf,
                                pSid,&dwSid,pReferenceDomainName,&dwName,
                                &SidNameUse);
        pSid = (PSID) malloc(dwSid+1);
        if ( !pSid )
            return(0);
        pReferenceDomainName = (CHAR *) malloc(dwName+1);
        if ( !pReferenceDomainName ) {
            free(pSid);
            pSid = NULL;
            return(0);
        }
        rc = LookupAccountName( NULL,buf,
                                pSid,&dwSid,pReferenceDomainName,
                                &dwName,&SidNameUse);
        if ( rc && (SidNameUse == SidTypeDomain
#ifndef _M_ALPHA
#ifndef _M_PPC
                     || SidNameUse == SidTypeComputer
#endif /* _M_PPC */
#endif /* _M_ALPHA */
                     ) )
        {
            /* Username equals Machine name */
            sprintf(buf,"%s\\%s",name,name);
            debug(F110,"zvuser SidNameUse == SidTypeDomain",buf,0);
            free(pSid);
            pSid = NULL;
            free(pReferenceDomainName);
            pReferenceDomainName = NULL;
            dwSid = dwName = 0;
            LookupAccountName( NULL,buf,
                               pSid,&dwSid,pReferenceDomainName,&dwName,
                               &SidNameUse);
            pSid = (PSID) malloc(dwSid+1);
            if ( !pSid )
                return(0);
            pReferenceDomainName = (CHAR *) malloc(dwName+1);
            if ( !pReferenceDomainName ) {
                free(pSid);
                pSid = NULL;
                return(0);
            }
            rc = LookupAccountName( NULL,buf,
                                    pSid,&dwSid,pReferenceDomainName,
                                    &dwName,&SidNameUse);
        }
        if ( !rc ) {
            LastError = GetLastError();
            debug(F111,"zvuser","LookupAccount()",LastError);
            if ( FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
                                NULL,
                                LastError,
                                MAKELANGID(LANG_ENGLISH,SUBLANG_ENGLISH_US),
                                buf,
                                4096,
                                (va_list *) NULL
                                ) )
            {
                printf("\r\n  (%s) %s\r\n",name,buf);
            }
            free(pSid);
            pSid = NULL;
            free(pReferenceDomainName);
            pReferenceDomainName = NULL;

            if ( name != anonacct ) {
                for ( p = name; *p ; p++ ) {
                    if ( *p == '\\' ) {
                        *p = '\0';
                        makestr(&pReferenceDomainName,name);
                        p++;
                        strcpy(buf,p);
                        strcpy(name,buf);
                        break;
                    }
                }
                if ( !pReferenceDomainName && iks_domain )
                    makestr(&pReferenceDomainName,iks_domain);
            }
            return(0);
        }
        pReferenceDomainName[dwName] = '\0';
        debug(F110,"zvuser() Reference Domain Name",pReferenceDomainName,0);

        /* Strip off the domain if it is part of the name */
        if ( name != anonacct ) {
            for ( p=name; *p ; p++ ) {
                if ( *p == '\\' ) {
                    p++;
                    strcpy(buf,p);
                    strcpy(name,buf);
                    break;
                }
            }
        }
    } else {    /* Windows 95/98 */
        if ( name != anonacct ) {
            for ( p = name; *p ; p++ ) {
                if ( *p == '\\' ) {
                    *p = '\0';
                    makestr(&pReferenceDomainName,name);
                    p++;
                    strcpy(buf,p);
                    strcpy(name,buf);
                    break;
                }
            }
            if (!pReferenceDomainName) {
                if (iks_domain)
                    makestr(&pReferenceDomainName,iks_domain);
                else if ( IsSSPLogonAvail() )
                    makestr(&pReferenceDomainName,SSPLogonDomain());
            }
        }
    }
    if ( name == anonacct )
        strcpy(uidbuf,"anonymous");
    else if (uidbuf != name)
        ckstrncpy((char *)uidbuf,(char *)name,UIDBUFLEN);
#else /* NT */
    char * name;

    debug(F110,"zvuser() username",username,0);
    if ( !stricmp("anonymous",username) && ckxanon && anonacct )
        name = anonacct;
    else
        name = username;

    debug(F110,"zvuser() name",name,0);

    if ( name == anonacct )
        strcpy(uidbuf,"anonymous");
    else if (uidbuf != name) {
        ckstrncpy((char *)uidbuf,(char *)name,UIDBUFLEN);
    }
#endif /* NT */
    debug(F100,"zvuser() success","",0);
    return(1);
}

/*  Z V P A S S  --  Verify password; returns 1 if OK, 0 otherwise  */

int
zvpass(passwd) char *passwd; {
    int rc = 0;
    int ntlm=0;
    char * uid, * pwd;
    char buf[4096];
    char username[256];
#ifdef IKSDB
    extern int ikdbopen;
    extern unsigned long mydbslot;
#endif /* IKSDB */
#ifdef NT
    DWORD LastError, LogonUserError;
#endif /* NT */
#ifdef CK_SRP
    int srp_verified = 0;
#endif /* CK_SRP */

    debug(F110,"zvpass uidbuf",uidbuf,0);
    if (!stricmp("anonymous",uidbuf)) {
        /* Anonymous login */

        if ( !ckxanon ) {
            printf("Login unavailable: anonymous login refused.\r\n");
            debug(F110,"zvpass()","invalid anonymous login refused",0);
            return(0);
        }

        if (!passwd[0]) {
            printf("Login refused: e-mail address must be provided as password.\r\n");
#ifdef CKSYSLOG
            if (ckxsyslog && ckxlogging) {
                char buf[1024];
                sprintf(buf,"login: anonymous guests must specify a password");
                cksyslog(SYSLG_LI, 0, buf, NULL, NULL);
            }
#endif /* CKSYSLOG */
            return(0);
        }

        uid = anonacct ? anonacct : "guest";
        pwd = "";
        ckstrncpy(guestpass,passwd,GUESTPASS_LEN);
        isguest = 1;
    } else {
        uid = uidbuf;
        pwd = passwd ? passwd : "";
        guestpass[0] = '\0';
        isguest = 0;
    }

#ifdef NT
    if (pReferenceDomainName &&
        !ckstrcmp(uid,pReferenceDomainName,strlen(pReferenceDomainName),0) &&
         strlen(uid) != strlen(pReferenceDomainName) )
        uid = uid + strlen(pReferenceDomainName) + 1;
#endif /* NT */

    debug(F110,"zvpass",isguest ? guestpass : (pwd ? "xxxxxx" : "(null)"),isguest);

#ifdef NT
    if ( isWin95() ) {
        if (isguest || (ck_tn_auth_valid() == AUTH_VALID) && !pwd[0])
            /* already authenticated */
            goto logged_in;

        if (IsSSPLogonAvail()) {
            BOOL SSPLogonUser( LPTSTR DomainName, LPTSTR UserName, LPTSTR Password );
            const char * sspi_errstr(SECURITY_STATUS ss);

            LogonUserError = GetLastError();
            rc = SSPLogonUser(pReferenceDomainName?pReferenceDomainName:".",uid,pwd);
            if ( !rc ) { /* Failure */
                extern int SSPLogonError;
                printf("\r\n  %s\r\n",sspi_errstr(SSPLogonError));
#ifdef CKSYSLOG
                if (ckxsyslog >= SYSLG_LI && ckxlogging)
                    cksyslog(SYSLG_LI, 0, "LOGIN FAILURE",uid,
                              (char *)sspi_errstr(SSPLogonError));
#endif /* CKSYSLOG */
                debug(F110,"zvpass","SSPLogonUser failed",0);
                return(0);
            } else {
                ntlm_impersonate();
            }
        } else {
            debug(F110,"zvpass","SSPLogon not available",0);
#ifdef CK_SRP
            rc = t_verifypw (uid, pwd);
#ifndef PRE_SRP_1_4_4
            if (rc > -1)
                endtpent();
#endif /* PRE_SRP_1_4_4 */
            if (rc > 0) {
                srp_verified = 1;
                goto logged_in;
            }
#endif /* CK_SRP */

            printf("\r\n  Login denied\r\n");
#ifdef CKSYSLOG
            if (ckxsyslog >= SYSLG_LI && ckxlogging)
                cksyslog(SYSLG_LI, 0, "LOGIN FAILURE",uid,
                          "Login denied");
#endif /* CKSYSLOG */
            return(0);
        }
    } else {
        /* Get the User Info now so that we have the group list for the call */
        /* to CreateTokenForUser()                                           */
        GetUserInfo(pReferenceDomainName, uid);

        if (!isguest && (ck_tn_auth_valid() == AUTH_VALID) && !pwd[0]) {
            /* already authenticated */

            switch (ck_tn_authenticated()) {
            case AUTHTYPE_NTLM: {
                /* If we authenticated by NTLM SSPI then we do not have a Token yet    */
                /* So get one from the current thread after we impersonate the user    */
                HANDLE hThread, hThreadToken;
                DWORD foo;

		if ( !p_DuplicateTokenEx ) {
		    if ( !hAdvApi )
			hAdvApi = LoadLibrary("advapi32.dll");
		    if ( hAdvApi ) {
			(FARPROC) p_DuplicateTokenEx =
			    GetProcAddress( hAdvApi, "DuplicateTokenEx" );
		    }
		}

		ntlm_impersonate();
                hThread = GetCurrentThread();
                foo = OpenThreadToken(hThread, TOKEN_ALL_ACCESS, FALSE, &hThreadToken );
                debug(F111,"zvpass","OpenThreadToken()",foo ? 0 : GetLastError());
		if ( p_DuplicateTokenEx ) {
		    foo = p_DuplicateTokenEx(hThreadToken, MAXIMUM_ALLOWED, NULL, 
					     SecurityImpersonation,
					     TokenPrimary, &hLoggedOn);
		    debug(F111,"zvpass","DuplicateTokenEx()",foo ? 0 : GetLastError());
		}
                CloseHandle(hThreadToken);
                CloseHandle(hThread);
                ntlm = 1;
                break;
            }
#ifdef NTCREATETOKEN
            case AUTHTYPE_KERBEROS_V4:
            case AUTHTYPE_KERBEROS_V5:
            case AUTHTYPE_SRP:
                hLoggedOn = CreateTokenForUser(uid,pReferenceDomainName,
                                                pSid,pPriGroupSid,pTokenGroups);
                break;

            case AUTHTYPE_NULL:
            case AUTHTYPE_AUTO:
#ifdef CK_SSL
                if ((ssl_active_flag && ssl_get_subject_name(ssl_con))||
                     (tls_active_flag && ssl_get_subject_name(tls_con)))
                {
                    hLoggedOn = CreateTokenForUser(uid,pReferenceDomainName,
                                                    pSid,pPriGroupSid,pTokenGroups);
                    break;
                }
#endif /* CK_SSL */
#endif /* NTCREATETOKEN */
            default:
                rc = LogonUser(uid,pReferenceDomainName,pwd,
                                LOGON32_LOGON_INTERACTIVE,
                                LOGON32_PROVIDER_DEFAULT,
                                &hLoggedOn);
                if ( !rc ) {
                    debug(F110,"zvpass","AUTH_VALID login failed",0);

                    LastError = GetLastError();
                    if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
                                       NULL,
                                       LastError,
                                       MAKELANGID(LANG_ENGLISH,SUBLANG_ENGLISH_US),
                                       buf,
                                       4096,
                                       (va_list *) NULL
                                       ) )
                    {
                        printf("\r\n  %s\r\n",buf);
#ifdef CKSYSLOG
                        if (ckxsyslog >= SYSLG_LI && ckxlogging)
                            cksyslog(SYSLG_LI, 0, "LOGIN FAILURE",uid,buf);
#endif /* CKSYSLOG */
                    }
                    return(0);
                }
            }
        } else {
            rc = LogonUser(uid,
                            pReferenceDomainName,
                            pwd,
                            LOGON32_LOGON_INTERACTIVE,
                            LOGON32_PROVIDER_DEFAULT,
                            &hLoggedOn);
            debug(F111,"LogonUser()","rc",rc);
            if ( !rc ) {
                LastError = GetLastError();
                debug(F111,"zvpass LogonUser() failed",uid,LastError);

                if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
                                   NULL,
                                   LastError,
                                   MAKELANGID(LANG_ENGLISH,SUBLANG_ENGLISH_US),
                                   buf,
                                   4096,
                                   (va_list *) NULL
                                   ) )
                {
                    printf("\r\n  %s\r\n",buf);
#ifdef CKSYSLOG
                        if (ckxsyslog >= SYSLG_LI && ckxlogging)
                            cksyslog(SYSLG_LI, 0, "LOGIN FAILURE",uid,buf);
#endif /* CKSYSLOG */
                }
                return(0);
            }
        }

        /* Get the user's profile and environment information */
        /* If we have a Token for the user, then attempt to retrieve the  */
        /* user's profile (including home directory) and the Environment  */
        /* variables.  Be sure to do this before we Impersonate the user  */
        if ( p_LoadUserProfileA ) {
            profinfo.dwFlags = PI_NOUI;
            profinfo.lpUserName = uid;
            profinfo.lpServerName = pPDCName;

            debug(F100,"calling LoadUserProfileA()","",0);
            rc = p_LoadUserProfileA(hLoggedOn,&profinfo);
            debug(F111,"LoadUserProfileA() returns","rc",rc);
            LastError = GetLastError();
            if ( !rc && LastError )
            {
                if ( LastError != ERROR_INVALID_HANDLE &&
                     FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
                                    NULL,
                                    LastError,
                                    MAKELANGID(LANG_ENGLISH,SUBLANG_ENGLISH_US),
                                    buf,
                                    4096,
                                    (va_list *) NULL
                                    ) )
                {
                    printf("\r\n  Profile not loaded: %s\r\n",buf);
#ifdef CKSYSLOG
                    if (ckxsyslog >= SYSLG_LI && ckxlogging)
                        cksyslog(SYSLG_LI, 0, "PROFILE LOAD FAILURE",uid,buf);
#endif /* CKSYSLOG */
                }
                else {
#ifdef BETATEST
                    printf("\r\nLoadUserProfile Error: Invalid Handle\r\n");
#endif /* BETATEST */
#ifdef COMMENT
#ifdef CKSYSLOG
                    if (ckxsyslog >= SYSLG_LI && ckxlogging) {
                        sprintf(buf,"LoadUserProfile() Error = %d",LastError);
                        cksyslog(SYSLG_LI, 0, "PROFILE LOAD FAILURE",uid,buf);
                    }
#endif /* CKSYSLOG */
#endif /* COMMENT */
                }
                debug(F111,"zvpass","LoadUserProfile failed",LastError);
            }
        }

        if ( p_CreateEnvironmentBlock ) {
            debug(F100,"calling CreateEnvironmentBlock()","",0);
            rc = p_CreateEnvironmentBlock(&pEnvBlock,hLoggedOn,0);
            debug(F111,"CreateEnvironmentBlock() returns","rc",rc);
            if ( rc ) {
                char buf[1024];
                unsigned char *p, *q;
                unsigned short ch;
                int i;

                q = (char *) &ch;
                p = (char *) pEnvBlock;
                q[0] = p[0];
                q[1] = p[1];
                if ( ch ) {
                    i = 0;
                    buf[0] = '\0';
                    do {
                        if ( ch ) {
                            if ( ch >= 256 )
                                buf[i] = ((ch / 256) & 0xFF);
                            else
                                buf[i] = (ch & 0xFF);
                            i++;
                        } else {
                            buf[i] = '\0';
                            if ( buf[0] )
                                _putenv(buf);
                            i = 0;
                        }
                        p += 2;
                        q[0] = p[0];
                        q[1] = p[1];
                    } while ( !((i == 0) && (ch == 0) ) );
                }
            } else
                pEnvBlock = NULL;
        }

        if ( !ntlm ) {
            /* Impersonate the user */
            debug(F100,"calling ImpersonateLoggedOnUser()","",0);
            rc = ImpersonateLoggedOnUser(hLoggedOn);
            debug(F111,"ImpersonateLoggedOnUser() returns","rc",rc);
            if ( !rc ) {
                LastError = GetLastError();
                if ( LastError != ERROR_INVALID_HANDLE &&
                     FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
                                    NULL,
                                    LastError,
                                    MAKELANGID(LANG_ENGLISH,SUBLANG_ENGLISH_US),
                                    buf,
                                    4096,
                                    (va_list *) NULL
                                    ) )
                {
                    printf("\r\n  %s\r\n",buf);
#ifdef CKSYSLOG
                    if (ckxsyslog >= SYSLG_LI && ckxlogging)
                        cksyslog(SYSLG_LI, 0, "LOGIN FAILURE",uid,buf);
#endif /* CKSYSLOG */
                } else {
#ifdef BETATEST
#endif /* BETATEST */
#ifdef CKSYSLOG
                    if (ckxsyslog >= SYSLG_LI && ckxlogging) {
                        sprintf(buf,"ImpersonateLoggedOnUser() Error = %d",LastError);
                        cksyslog(SYSLG_LI, 0, "LOGIN FAILURE",uid,buf);
                    }
#endif /* CKSYSLOG */
                }
                debug(F111,"zvpass","ImpersonateLoggedOnUser failed",LastError);
                return(0);
            }
        }
    }
  logged_in:
    debug(F110,"zvpass()","logged_in",0);
    logged_in = 1;

    if ( HomeDir[0] ) {
        int i;

        ckstrncpy(homedir,HomeDir,CKMAXPATH+1);
#ifdef NT
        GetShortPathName(homedir,homedir,CKMAXPATH);
#endif /* NT */

        /* we know have the directory, but need to make it consistent */
        /* with all of the other Kermit directory variables: forward  */
        /* slashes and a trailing slash.                              */
        i = strlen(homedir)-1;
        if ( !(ISDIRSEP(homedir[i])) ) {
            homedir[i+1] = '/';
            homedir[i+2] = NUL;
        }
        for (i-- ; i >= 0 ; i--) {
            if ( homedir[i] == '\\' )
                homedir[i] = '/';
        }
    } else
        ckstrncpy(homedir,zgtdir(),CKMAXPATH+1);

    debug(F110,"zvpass() homedir",homedir,0);

    zchdir(zhome());

    if ( isguest ) {
#ifdef CKROOT
        if ( anonroot ) {
            if (zsetroot(anonroot) < 0) {
                printf("Login unavailable: invalid anonymous root directory\r\n");
                debug(F110,"zvpass()","invalid anonymous root directory",0);
                return(0);
            }
        }
#else /* CKROOT */
        debug(F110,"zvpass()","no anonymous root support",0);
#endif /* CKROOT */
    }
#else /* NT */
    if (!isguest && ck_tn_auth_valid() == AUTH_VALID &&
         (!passwd || !(*passwd))) { /* already authenticated */
        switch (ck_tn_authenticated()) {
        case AUTHTYPE_NTLM:
        case AUTHTYPE_KERBEROS_V4:
        case AUTHTYPE_KERBEROS_V5:
        case AUTHTYPE_SRP:
            break;
        case AUTHTYPE_NULL:
        case AUTHTYPE_AUTO:
#ifdef CK_SSL
            if ((ssl_active_flag && ssl_get_subject_name(ssl_con))||
                 (tls_active_flag && ssl_get_subject_name(tls_con)))
                break;
#endif /* CK_SSL */
        default:
            debug(F110,"zvpass","AUTH_VALID login failed",0);
            return(0);
        }
    } else {
#ifdef CK_SRP
        rc = t_verifypw (uid, pwd);
#ifndef PRE_SRP_1_4_4
        if (rc > -1)
            endtpent();
#endif /* PRE_SRP_1_4_4 */
        if (rc <= 0)
#endif /* CK_SRP */
        {
            debug(F110,"zvpass","login failed",0);
            return(0);
        }
    }

#ifdef CKROOT
    if ( isguest ) {
        if ( anonroot ) {
            if (zsetroot(anonroot) < 0) {
                printf("Login unavailable: invalid anonymous root directory\r\n");
                debug(F110,"zvpass()","invalid anonymous root directory",0);
                return(0);
            }
        }
    }
#endif /* CKROOT */
#endif /* NT */

#ifdef CKSYSLOG
    if (ckxsyslog >= SYSLG_LI && ckxlogging)
        cksyslog(SYSLG_LI, 1, "LOGIN",uid,clienthost);
#endif /* CKSYSLOG */

#ifdef IKSDB
    if (ikdbopen) {
        char * p, * q;
        int k;
        extern char dbrec[];
        extern unsigned long myflags, mydbslot;
        extern struct iksdbfld dbfld[];
        extern unsigned long myaflags, myamode, myatype;

        debug(F101,"XXX guest","",isguest);
        debug(F110,"XXX zvuname",uid,0);
        debug(F110,"XXX guestpass",guestpass,0);

        myflags |= DBF_LOGGED;
        p = isguest ? guestpass : uid;
        if (isguest) {
            p = (char *)guestpass;
            myflags &= ~DBF_USER;
        } else {
            p = (char *)uid;
            myflags |= DBF_USER;
        }
        k = strlen(p);
        strncpy(&dbrec[DB_ULEN],ulongtohex((unsigned long)k,4),4);
        lset(&dbrec[dbfld[db_USER].off],p,1024,32);
        strncpy(&dbrec[DB_FLAGS],ulongtohex(myflags,4),4);
#ifdef CK_AUTHENTICATION
        myamode = ck_tn_auth_valid();
        strncpy(&dbrec[DB_AMODE],ulongtohex(myamode,4),4);
        myatype = ck_tn_authenticated();
        strncpy(&dbrec[DB_ATYPE],ulongtohex(myatype,4),4);
#endif /* CK_AUTHENTICATION */
        p = zgtdir();

        strncpy(&dbrec[DB_DLEN],
                ulongtohex((unsigned long)strlen(p),4),
                4
                );
        lset(&dbrec[dbfld[db_DIR].off],p,1024,32);
        updslot(mydbslot);
    }
#endif /* IKSDB */
    debug(F110,"zvpass()","success",0);
    return(1);
}

VOID
zvlogout() {
#ifdef NT
    extern int haveNTLMContext;
#ifdef CKSYSLOG
    if (ckxsyslog >= SYSLG_LI && ckxlogging) {
        cksyslog(SYSLG_LI, 1, "LOGOUT",(char *) isguest ? anonacct : uidbuf,
                  clienthost);
    }
#endif /* CKSYSLOG */

    if ( haveNTLMContext ) {
        ntlm_logout();
    }
    if ( hLoggedOn != INVALID_HANDLE_VALUE ) {
        RevertToSelf();

        if ( p_UnloadUserProfileA && profinfo.hProfile )
            p_UnloadUserProfileA(hLoggedOn, profinfo.hProfile);
        memset(&profinfo,0,sizeof(profinfo));
        profinfo.dwSize = sizeof(profinfo);

        if ( p_DestroyEnvironmentBlock && pEnvBlock ) {
            p_DestroyEnvironmentBlock(pEnvBlock);
            pEnvBlock = NULL;
        }

        CloseHandle(hLoggedOn);
        hLoggedOn = INVALID_HANDLE_VALUE;
    }
    if (pSid) {
        free(pSid);
        pSid = NULL;
    }
    if ( pReferenceDomainName ) {
        free(pReferenceDomainName);
        pReferenceDomainName = NULL;
    }
#endif /* NT */
    homedir[0] = '\0';
    uidbuf[0] = '\0';
    isguest = 0;
    logged_in = 0;
    return;
}

#ifdef NT
/* From Peter Runestig */

static HINSTANCE hNetApi32=NULL;
static NET_API_STATUS
(NET_API_FUNCTION *p_NetUserGetInfo)( LPWSTR, LPWSTR, DWORD, LPBYTE *)=NULL;
static NET_API_STATUS
(NET_API_FUNCTION *p_NetGetDCName)(LPCWSTR, LPCWSTR, LPBYTE *)=NULL;
static NET_API_STATUS
(NET_API_FUNCTION *p_NetApiBufferFree)( LPVOID ) = NULL;
static NET_API_STATUS
(NET_API_FUNCTION *p_NetUserGetLocalGroups)(LPCWSTR, LPCWSTR, DWORD, DWORD, LPBYTE *,
                                             DWORD, LPDWORD, LPDWORD)=NULL;
static NET_API_STATUS
(NET_API_FUNCTION *p_NetUserGetGroups)(LPCWSTR, LPCWSTR, DWORD, LPBYTE *,
                                             DWORD, LPDWORD, LPDWORD)=NULL;
static NET_API_STATUS
(NET_API_FUNCTION *p_NetGroupGetInfo)(LPCWSTR, LPCWSTR, DWORD, LPBYTE)=NULL;


static int
LoadNetApi32()
{
    if ( !hNetApi32 ) {
        debug(F110,"LoadNetApi32","loading netapi32.dll",0);
        hNetApi32 = LoadLibrary( "netapi32.dll" );
        if ( hNetApi32 ) {
            (FARPROC) p_NetUserGetInfo = GetProcAddress( hNetApi32, "NetUserGetInfo" );
            (FARPROC) p_NetApiBufferFree = GetProcAddress( hNetApi32, "NetApiBufferFree" );
            (FARPROC) p_NetGetDCName = GetProcAddress( hNetApi32, "NetGetDCName" );
            (FARPROC) p_NetUserGetLocalGroups = GetProcAddress( hNetApi32, "NetUserGetLocalGroups" );
            (FARPROC) p_NetUserGetGroups = GetProcAddress( hNetApi32, "NetUserGetGroups" );
            (FARPROC) p_NetGroupGetInfo = GetProcAddress( hNetApi32, "NetGroupGetInfo" );

            if ( !p_NetUserGetInfo || !p_NetApiBufferFree || !p_NetGetDCName ||
                 !p_NetUserGetLocalGroups || !p_NetUserGetGroups || !p_NetGroupGetInfo )
            {
                debug(F110,"LoadNetApi32","loading netapi32.dll unable to load function",0);
                FreeLibrary(hNetApi32);
                hNetApi32 = NULL;
            }
        }
        if ( !hNetApi32 ) {
            debug(F110,"LoadNetApi32","loading netapi32.dll failed",0);
            return(0);
        }
    }
    return(1);
}

static int
_GetUserGroups(LPWSTR wCompName, LPWSTR wUserName)
{
    int err;
    LPLOCALGROUP_USERS_INFO_0 lpLocalGroupInfo=NULL;
    LPGROUP_USERS_INFO_0 lpGroupInfo=NULL;
    /*
     * typedef struct _LOCAL_GROUP_USERS_INFO_0 {
     *   LPWSTR  lgrui0_name;
     * } LOCALGROUP_USERS_INFO_0, *PLOCALGROUP_USERS_INFO_0, *LPLOCALGROUP_USERS_INFO_0;
     *
     * typedef struct _GROUP_USERS_INFO_0 {
     *   LPWSTR  grui0_name;
     * } GROUP_USERS_INFO_0, *PGROUP_USERS_INFO_0, *LPGROUP_USERS_INFO_0;
     */
    DWORD localEntriesRead, localEntriesTotal, remoteEntriesRead, remoteEntriesTotal;
    DWORD i,j;
    CHAR buf[256], compName[256];
    char group[UNLEN], domain[255];
    unsigned char sid[MAXSIZE], *sc;
    DWORD sz_sid = MAXSIZE, sz_domain = sizeof(domain), *sa;
    SID_NAME_USE sid_use;


    UnicodeToOEM(wCompName,compName,256);
    err = p_NetUserGetLocalGroups(wCompName, wUserName, 0, LG_INCLUDE_INDIRECT,
                                   (LPBYTE *)&lpLocalGroupInfo,
                                   MAX_PREFERRED_LENGTH, &localEntriesRead, &localEntriesTotal);
    switch ( err ) {
    case NERR_Success:
        debug(F111,"_GetUserGroups","localEntriesRead",localEntriesRead);
        debug(F111,"_GetUserGroups","localEntriesTotal",localEntriesTotal);
        break;
    case ERROR_ACCESS_DENIED:
        debug(F110,"_GetUserGroups","Access Denied",0);
        localEntriesRead = 0;
        break;
    case ERROR_MORE_DATA:
        debug(F110,"_GetUserGroups","More Data",0);
        localEntriesRead = 0;
        break;
    case NERR_InvalidComputer:
        debug(F110,"_GetUserGroups","Invalid Computer",0);
        localEntriesRead = 0;
        break;
    case NERR_UserNotFound:
        debug(F110,"_GetUserGroups","User Not Found",0);
        localEntriesRead = 0;
        break;
    default:
        debug(F111,"_GetUserGroups","other error",err);
        localEntriesRead = 0;
    }

    err = p_NetUserGetGroups(wCompName, wUserName, 0,
                              (LPBYTE *)&lpGroupInfo,
                              MAX_PREFERRED_LENGTH, &remoteEntriesRead, &remoteEntriesTotal);
    switch ( err ) {
    case NERR_Success:
        debug(F111,"_GetUserGroups","remoteEntriesRead",remoteEntriesRead);
        debug(F111,"_GetUserGroups","remoteEntriesTotal",remoteEntriesTotal);
        break;
    case ERROR_ACCESS_DENIED:
        debug(F110,"_GetUserGroups","Access Denied",0);
        remoteEntriesRead = 0;
        break;
    case ERROR_MORE_DATA:
        debug(F110,"_GetUserGroups","More Data",0);
        remoteEntriesRead = 0;
        break;
    case NERR_InvalidComputer:
        debug(F110,"_GetUserGroups","Invalid Computer",0);
        remoteEntriesRead = 0;
        break;
    case NERR_UserNotFound:
        debug(F110,"_GetUserGroups","User Not Found",0);
        remoteEntriesRead = 0;
        break;
    default:
        debug(F111,"_GetUserGroups","other error",err);
        remoteEntriesRead = 0;
    }

    pTokenGroups = (PTOKEN_GROUPS) malloc(sizeof(DWORD)
                 + (localEntriesRead + remoteEntriesRead) * sizeof(SID_AND_ATTRIBUTES));
    pTokenGroups->GroupCount = localEntriesRead + remoteEntriesRead;

    for ( i=0, j=0; i<localEntriesRead ; i++,j++ ) {
        UnicodeToOEM(lpLocalGroupInfo[i].lgrui0_name,buf,256);
#ifdef COMMENT
        printf("Local Groups: %s\r\n",buf);
#endif /* COMMENT */
        debug(F110,"_GetUserGroups LocalGroup",buf,0);
        // get the SID for each group
        UnicodeToOEM(lpLocalGroupInfo[i].lgrui0_name, group, sizeof(group));
        sz_sid = MAXSIZE;
        err = LookupAccountName(compName, group, (PSID) sid, &sz_sid, domain,
                                 &sz_domain, &sid_use);

        debug(F110,"_GetUserGroups LookupAccountName() domain",domain,0);

        pTokenGroups->Groups[j].Sid = (PSID) malloc( sz_sid );
        CopySid( sz_sid, pTokenGroups->Groups[j].Sid, sid );
        pTokenGroups->Groups[j].Attributes = SE_GROUP_ENABLED;

        // compare the last subauthority in the group's SID with the primary
        // group RID from the USER_INFO_3 struct
        sc = GetSidSubAuthorityCount((PSID) sid);
        sa = GetSidSubAuthority((PSID) sid, *sc - 1);
        if (*sa == PrimaryGroupId) {
            pPriGroupSid = (PSID) malloc( sz_sid );
            CopySid( sz_sid, pPriGroupSid, sid );
#ifdef COMMENT
            printf("Primary group is %s!\n", group);
#endif /* COMMENT */
            debug(F110,"_GetUserGroups PrimaryLocalGroup",buf,0);
        }
    }
    for ( i=0; i<remoteEntriesRead ; i++, j++ ) {
        UnicodeToOEM(lpGroupInfo[i].grui0_name,buf,256);
#ifdef COMMENT
        printf("Groups: %s\r\n",buf);
#endif /* COMMENT */
        debug(F110,"_GetUserGroups DomainGroup",buf,0);
        // get the SID for each group
        UnicodeToOEM(lpGroupInfo[i].grui0_name, group, sizeof(group));
        sz_sid = MAXSIZE;
        err = LookupAccountName(compName, group, (PSID) sid, &sz_sid, domain,
                                 &sz_domain, &sid_use);

        debug(F110,"_GetUserGroups LookupAccountName() domain",domain,0);

        pTokenGroups->Groups[j].Sid = (PSID) malloc( sz_sid );
        CopySid( sz_sid, pTokenGroups->Groups[j].Sid, sid );
        pTokenGroups->Groups[j].Attributes = SE_GROUP_ENABLED;

        // compare the last subauthority in the group's SID with the primary
        // group RID from the USER_INFO_3 struct
        sc = GetSidSubAuthorityCount((PSID) sid);
        sa = GetSidSubAuthority((PSID) sid, *sc - 1);
        if (*sa == PrimaryGroupId) {
            pPriGroupSid = (PSID) malloc( sz_sid );
            CopySid( sz_sid, pPriGroupSid, sid );
#ifdef COMMENT
            printf("Primary group is %s!\n", group);
#endif /* COMMENT */
            debug(F110,"_GetUserGroups DomainLocalGroup",buf,0);
        }
    }

    p_NetApiBufferFree(lpGroupInfo);
    p_NetApiBufferFree(lpLocalGroupInfo);

    return(err);
}

static int
_GetUserInfo(LPWSTR wCompName, LPWSTR wUserName)
{
  int err;
  LPUSER_INFO_3 UserInfo = NULL;
/*typedef struct _USER_INFO_3 {
    LPWSTR    usri3_name;
    LPWSTR    usri3_password;
    DWORD     usri3_password_age;
    DWORD     usri3_priv;
    LPWSTR    usri3_home_dir;
    LPWSTR    usri3_comment;
    DWORD     usri3_flags;
    LPWSTR    usri3_script_path;
    DWORD     usri3_auth_flags;
    LPWSTR    usri3_full_name;
    LPWSTR    usri3_usr_comment;
    LPWSTR    usri3_parms;
    LPWSTR    usri3_workstations;
    DWORD     usri3_last_logon;
    DWORD     usri3_last_logoff;
    DWORD     usri3_acct_expires;
    DWORD     usri3_max_storage;
    DWORD     usri3_units_per_week;
    PBYTE     usri3_logon_hours;
    DWORD     usri3_bad_pw_count;
    DWORD     usri3_num_logons;
    LPWSTR    usri3_logon_server;
    DWORD     usri3_country_code;
    DWORD     usri3_code_page;
    DWORD     usri3_user_id;
    DWORD     usri3_primary_group_id;
    LPWSTR    usri3_profile;
    LPWSTR    usri3_home_dir_drive;
    DWORD     usri3_password_expired;
}USER_INFO_3, *PUSER_INFO_3, *LPUSER_INFO_3;*/

    err = p_NetUserGetInfo(wCompName, wUserName, 3, (LPBYTE *)&UserInfo);
    debug(F111,"_GetUserInfo","NetUserGetInfo returns",err);
    if (!err) {
        debug(F111,"_GetUserInfo","UserInfo",UserInfo);
        UnicodeToOEM(UserInfo->usri3_home_dir, HomeDir, sizeof(HomeDir));
        debug(F110,"_GetUserInfo HomeDir",HomeDir,0);
        UnicodeToOEM(UserInfo->usri3_profile, ProfilePath, sizeof(ProfilePath));
        debug(F110,"_GetUserInfo ProfilePath",ProfilePath,0);
        PrimaryGroupId = UserInfo->usri3_primary_group_id;
        p_NetApiBufferFree(UserInfo);
    }
    return err;
}

int
GetUserInfo(LPSTR DomainName, LPSTR UserName)
{
    unsigned short wDomainName[128], wUserName[128], *wPDCName;
    unsigned char  LocalMachine[128]="";
    DWORD dwSize = 128;
    int err;

    if ( !LoadNetApi32() )
        return(-1);

    wDomainName[0] = 0;
    wUserName[0] = 0;

    OEMToUnicode(UserName, wUserName, sizeof(wUserName));

    debug(F110,"GetUserInfo DomainName",DomainName,0);
    debug(F110,"GetUserInfo UserName",UserName,0);

    if (DomainName) {
        GetComputerName(LocalMachine,&dwSize);
        if ( ckstrcmp(DomainName,LocalMachine, -1, 0) )
            OEMToUnicode(DomainName, wDomainName, sizeof(wDomainName));
    }

    debug(F110,"GetUserInfo","calling _GetUserInfo() with wDomainName",0);
    if (_GetUserInfo(wDomainName[0] ? wDomainName : NULL, wUserName)) {
        debug(F110,"GetUserInfo","calling NetGetDCName()",0);
        err = p_NetGetDCName(NULL, wDomainName, (LPBYTE *)&wPDCName);
        debug(F111,"GetUserInfo","NetGetDCName() returns",err);
        if (!err) {
            debug(F110,"GetUserInfo","calling _GetUserInfo() with wPDCName",0);
            if(!_GetUserInfo(wPDCName, wUserName)) {
                _GetUserGroups(wPDCName, wUserName);

                /* Lets save the PDCName */
                UnicodeToOEM(wPDCName, LocalMachine, sizeof(LocalMachine));
                makestr(&pPDCName,LocalMachine);
                debug(F110,"GetUserInfo PDCName",pPDCName,0);
            }
            p_NetApiBufferFree(wPDCName);
        } else {
            debug(F110,"GetUserInfo","calling _GetUserInfo() with NULL",0);
            if(!_GetUserInfo(NULL, wUserName)) {
                _GetUserGroups(NULL, wUserName);
            }
        }
    } else {
        _GetUserGroups(wDomainName, wUserName);
    }
    debug(F100,"GetUserInfo returns","",0);
    return 0;
}

VOID
setntcreds() {
    extern int haveNTLMContext;

    if ( !logged_in )
        return;

    if ( hLoggedOn != INVALID_HANDLE_VALUE ) {
        ImpersonateLoggedOnUser(hLoggedOn);
    } else if ( haveNTLMContext ) {
        ntlm_impersonate();
    }
#ifdef CK_AUTHENTICATE
    else if ( ck_tn_auth_valid() == AUTH_VALID &&
              ck_tn_authenticated() == AUTHTYPE_NTLM ) {
        ntlm_impersonate();
    }
#endif /* CK_AUTHENTICATE */
}
#endif /* NT */
#endif /* CK_LOGIN */

#ifdef CKSYSLOG
HANDLE hSysLog = INVALID_HANDLE_VALUE;
static char pidstr[16]="";

VOID
zsyslog() {
    if (ckxsyslog && !ckxlogging) {
        sprintf(pidstr,"PID=%d",os2getpid());
#ifdef NT
        if ( !isWin95() ) {
            hSysLog = RegisterEventSource(NULL,
#ifdef IKSD
                                           inserver ? "IKSD" :
#endif /* IKSD */
                                           "K95");
            ckxlogging = 1;
            debug(F100,"zsyslog syslog opened","",0);
        } else
#endif /* NT */
        {
            printf("syslog not available\r\n");
            debug(F100,"zsyslog syslog not opened","",0);
        }
    }
}
/*
  C K S Y S L O G  --  C-Kermit system logging function,

  For use by other modules.
  This module can, but doesn't have to, use it.
  Call with:
    n = SYSLG_xx values defined in ckcdeb.h
    s1, s2, s3: strings.
*/
VOID
cksyslog(n, m, s1, s2, s3) int n, m; char * s1, * s2, * s3; {
#ifdef IKSDB
    extern int what, ikdbopen;
#endif /* IKSDB */
    int level;
    char * messages[5];

#ifdef IKSDB
    if (inserver && ikdbopen) {
        slotstate(what,s1,s2,s3);
    }
#endif /* IKSDB */
    if (!ckxlogging) return;

    if (!s1) s1 = "";
    if (!s2) s2 = "";
    if (!s3) s3 = "";

    if ( n == SYSLG_LI ) {
        if ( m )
            level = EVENTLOG_AUDIT_SUCCESS;
        else
            level = EVENTLOG_AUDIT_FAILURE;
    } else {
        level = EVENTLOG_INFORMATION_TYPE;
    }

    debug(F110,"cksyslog s1",s1,0);
    debug(F110,"cksyslog s2",s2,0);
    debug(F110,"cksyslog s3",s3,0);

    messages[0] = pidstr;
    messages[1] = s1;
    messages[2] = s2;
    messages[3] = s3;
    messages[4] = "\0";

    ReportEvent(hSysLog,level, 0,0,
#ifdef CK_LOGIN
                 pSid,
#else /* CK_LOGIN */
                 NULL,
#endif /* CK_LOGIN */
                 5,
                 0,
                 messages,
                 NULL
                 );
    debug(F101,"cksyslog errno","",GetLastError());
}
#endif /* CKSYSLOG */


/*  Z C H D S K  --  Change currently selected disk device */

/* Returns -1 if error, otherwise 0 */

zchdsk(c) int c; {
    int i = toupper(c) - 64;
    return( _chdrive(i));
}

#ifdef NT
long
StreamSize(char * filename, char * streamname)
{
    HANDLE hf ;
    DWORD lasterror = 0 ;
    WIN32_STREAM_ID sid;
    DWORD dwStreamHeaderSize, dwRead, dw1, dw2;
    BOOL  bContinue;
    VOID *lpContext = NULL;
    WCHAR wszStreamName[CKMAXPATH+1];
    WCHAR wszStat[CKMAXPATH+1];
    char  stream[CKMAXPATH+1];
    int   i, diff;
    DWORD dwStreamId = 0xFFFFFFFF;
    long  size = -1;

    ckmakmsg(stream,CKMAXPATH+1,":",streamname,":$DATA",NULL);
    ANSIToUnicode(stream,wszStat,sizeof(wszStat));

    hf = CreateFile( filename,
                     GENERIC_READ | GENERIC_WRITE,
                     FILE_SHARE_READ | FILE_SHARE_WRITE, /* Allow Sharing */
                     NULL, /* No Security Attributes specified */
                     OPEN_EXISTING,
                     FILE_ATTRIBUTE_NORMAL,
                     NULL ) ;

    if ( hf != INVALID_HANDLE_VALUE ) {
        ZeroMemory(&sid, sizeof(WIN32_STREAM_ID));
        dwStreamHeaderSize = (LPBYTE)&sid.cStreamName - (LPBYTE)&sid;

        for ( ;; ) {
            bContinue = BackupRead(hf, (LPBYTE) &sid, dwStreamHeaderSize,
                                    &dwRead, FALSE, FALSE, &lpContext);
            if (!bContinue ||
                 sid.dwStreamNameSize > sizeof(wszStreamName))
                break;

            dwStreamId = sid.dwStreamId;
            bContinue = BackupRead(hf, (LPBYTE) &wszStreamName,
                                    sid.dwStreamNameSize,
                                    &dwRead, FALSE, FALSE, &lpContext);
            if ( !bContinue )
                break;

            if (sid.dwStreamNameSize) {
                wszStreamName[sid.dwStreamNameSize/2] = 0;
                UnicodeToANSI(wszStreamName,stream,sizeof(stream));
                debug(F110,"StreamSize ntfs stream found",stream,0);
                if (!_wcsicmp(wszStreamName,wszStat)) {
                    size = (sid.Size.HighPart << 32) + sid.Size.LowPart;
                    break;
                }
            }
            if (!BackupSeek(hf, sid.Size.LowPart, sid.Size.HighPart, &dw1, &dw2, &lpContext))
                break;
        }

        /* Close the backup context */
        BackupRead(hf, (LPBYTE) &sid, 0,
                    &dwRead, TRUE, FALSE, &lpContext);
        CloseHandle(hf);
    }
    return size;
}

#ifdef STREAMS
const char *
StreamType(DWORD StreamId)
{
    static char msg[32];

    switch (StreamId) {
    case BACKUP_INVALID:
        return "Invalid stream";
    case BACKUP_DATA:
        return "Standard data";
    case BACKUP_EA_DATA:
        return "Extended attribute data";
    case BACKUP_SECURITY_DATA:
        return "Security descriptor data";
    case BACKUP_ALTERNATE_DATA:
        return "Alternative data streams";
    case BACKUP_LINK:
        return "Hard link information";
    case BACKUP_PROPERTY_DATA:
        return "Property data";
    case BACKUP_OBJECT_ID:
        return "Objects identifiers";
    case BACKUP_REPARSE_DATA:
        return "Reparse points";
    case BACKUP_SPARSE_BLOCK:
        return "Sparse file.";
    default:
        sprintf(msg,"StreamType %u", StreamId);
        return msg;
    }
}

long
StreamCount(char * path, char * relpath, char * filename, char * pattern)
{
    HANDLE hf ;
    DWORD lasterror = 0 ;
    WIN32_STREAM_ID sid;
    DWORD dwStreamHeaderSize, dwRead, dw1, dw2;
    BOOL  bContinue;
    VOID *lpContext = NULL;
    WCHAR wszStreamName[CKMAXPATH+1];
    char  szStreamName[CKMAXPATH+1];
    char  streamsearch[CKMAXPATH+1], fullpath[CKMAXPATH+1];
    int   i, diff;
    long  count = 0;

    if ( !pattern )
        pattern = ":*";

    ckmakmsg(streamsearch,CKMAXPATH+1,pattern,":$DATA",NULL,NULL);
    ckmakmsg(fullpath,CKMAXPATH+1,path,relpath,filename,NULL);

    hf = CreateFile( fullpath,
                     GENERIC_READ | GENERIC_WRITE,
                     FILE_SHARE_READ | FILE_SHARE_WRITE, /* Allow Sharing */
                     NULL, /* No Security Attributes specified */
                     OPEN_EXISTING,
                     FILE_ATTRIBUTE_NORMAL,
                     NULL ) ;

    if ( hf != INVALID_HANDLE_VALUE ) {
        dwStreamHeaderSize = (LPBYTE)&sid.cStreamName - (LPBYTE)&sid;

        for ( ;; ) {
            ZeroMemory(&sid, sizeof(WIN32_STREAM_ID));
            wszStreamName[0] = 0;

            bContinue = BackupRead(hf, (LPBYTE) &sid, dwStreamHeaderSize,
                                    &dwRead, FALSE, FALSE, &lpContext);
            if (!bContinue || dwRead == 0 ||
                sid.dwStreamNameSize > sizeof(wszStreamName))
                break;

            bContinue = BackupRead(hf, (LPBYTE) &wszStreamName,
                                    sid.dwStreamNameSize,
                                    &dwRead, FALSE, FALSE, &lpContext);
            if ( !bContinue )
                break;

            debug(F111,"StreamSize ntfs stream found",szStreamName,sid.dwStreamId);
            if (sid.dwStreamNameSize) {
                wszStreamName[sid.dwStreamNameSize/2] = 0;
                UnicodeToANSI(wszStreamName,szStreamName,CKMAXPATH+1);
                if (ckmatch(streamsearch, szStreamName, 0, 1)) {
                    count++;
                }
            } else {
                if ( sid.dwStreamId == BACKUP_DATA &&
                     (!pattern[0] || 
                      pattern[0] == ':' && pattern[1] == '*' && !pattern[2]) )  {
                    count++;
                }
            }

            if (!BackupSeek(hf, sid.Size.LowPart, sid.Size.HighPart, &dw1, &dw2, &lpContext))
                break;
        }

        /* Close the backup context */
        BackupRead(hf, (LPBYTE) &sid, 0,
                    &dwRead, TRUE, FALSE, &lpContext);
        CloseHandle(hf);
    }

    return(count);
}
#endif /* STREAMS */
#endif /* NT */

int
os2stat(char *path, struct stat *st) {
    char local[MAXPATH];
    int len;
#ifdef NT
    int stream_idx=0;
#endif /* NT */
    int rc;

    if (!path)
        return(-1);

    if ( !strncmp(path,"//",2) || !strncmp(path,"\\\\",2)) {
        int i;
        len = ckstrncpy(local, UNCname(path), MAXPATH);
        if ( ISDIRSEP(local[len-1]) ) {
            /* We now need to strip the trailing directory separator if it is not */
            /* part of the machine or object name.                                */
            for ( i=2; i<len && !(ISDIRSEP(local[i])); i++ ); /* machine */
            for ( i++; i<len && !(ISDIRSEP(local[i])); i++ ); /* object */

            if (i < len-1 )
                local[len-1] = '\0';
        }
    } else {
        len = ckstrncpy(local, path, MAXPATH);

        if ( len == 2 && local[1] == ':' )
            local[2] = DIRSEP, local[3] = 0; /* if drive only, append / */
        else if ( len == 0 )
            local[0] = DIRSEP, local[1] = 0; /* if empty path, take / instead */
        else if ( len > 1 && ISDIRSEP(local[len - 1]) && local[len - 2] != ':')
            local[len - 1] = 0; /* strip trailing / except after d: */

#ifdef NT
        /* NTFS supports streams which are indicated by
         *    filename:stream
         */

        for (stream_idx = len; stream_idx > 1 ; stream_idx--) {
            if (local[stream_idx] == ':') {     /* found a stream */
                local[stream_idx] = NUL;
                break;
            }
        }
#endif /* NT */
    }
    rc = stat(local, st);
    debug(F111,"os2stat","rc",rc);
#ifdef NT
    if ( stream_idx > 1 ) {
        long size = StreamSize(local,&local[stream_idx+1]);
        if ( size >= 0 )
            st->st_size = size;
        else
            rc = -1;
    }
#endif /* NT */
    return(rc);
}

#ifdef CK_LABELED
/* O S 2 S E T L O N G N A M E -- Set .LONGNAME Extended Attribute */

/* Returns -1 if error, otherwise 0 */

int
os2setlongname( char * fn, char * ln ) {
   EAOP2         FileInfoBuf;
   ULONG         FileInfoSize;
   ULONG         PathInfoFlags;
   APIRET        rc;
   char          FEA2List[1024];
   FEA2 *        pfea2;
   WORD *        pEAdata;

   debug(F110,"os2setlongname: filename is",fn,0);
   debug(F110,"os2setlongname: longname is",ln,0);

   FileInfoSize = sizeof( EAOP2 );
   PathInfoFlags = DSPI_WRTTHRU; /* complete write operation before return */

   FileInfoBuf.fpGEA2List = 0;
   FileInfoBuf.fpFEA2List = (PFEA2LIST) &FEA2List;
   pfea2 = FileInfoBuf.fpFEA2List->list;

   pfea2->oNextEntryOffset = 0;
   pfea2->fEA = 0;
   pfea2->cbName = 9; /* length of ".LONGNAME" without \0 */
   pfea2->cbValue = strlen( ln ) + 2 * sizeof( WORD );
   strcpy( pfea2->szName, ".LONGNAME" );

   pEAdata = (WORD *) pfea2->szName + 10/sizeof(WORD);
   *pEAdata = EAT_ASCII;
   pEAdata++;
   *pEAdata = strlen( ln );
   pEAdata++;
   strcpy( (char *) pEAdata, ln );
   pEAdata += (strlen( ln )+1)/sizeof(WORD);

   FileInfoBuf.fpFEA2List->cbList = (char *) pEAdata -
                                    (char *) FileInfoBuf.fpFEA2List;

   rc = DosSetPathInfo( fn, 2, &FileInfoBuf, FileInfoSize, PathInfoFlags );
   debug(F101,"os2setlongname: rc=","",rc);
   if ( !rc )
      return 0;
   else
      return -1;
}

/* O S 2 G E T L O N G N A M E -- Get .LONGNAME Extended Attribute */

/* Returns -1 if error, otherwise 0 */

int
os2getlongname( char * fn, char ** ln ) {
   static char   * LongNameBuf = 0;
   EAOP2         FileInfoBuf;
   ULONG         FileInfoSize;
   ULONG         PathInfoFlags;
   APIRET        rc;
   char          FEA2List[1024];
   FEA2 *        pfea2;
   char          GEA2List[1024];
   GEA2 *        pgea2;
   WORD *        pEAdata;
   WORD          LongNameLength;

   *ln = 0;
   if ( !LongNameBuf )
      LongNameBuf = strdup( "Initialization of LongNameBuf" );
   debug(F110,"os2getlongname: filename is",fn,0);

   FileInfoSize = sizeof( EAOP2 );
   PathInfoFlags = DSPI_WRTTHRU; /* Complete write operation before return */

   FileInfoBuf.fpGEA2List = (PGEA2LIST) &GEA2List;
   FileInfoBuf.fpFEA2List = (PFEA2LIST) &FEA2List;
   pgea2 = FileInfoBuf.fpGEA2List->list;
   pfea2 = FileInfoBuf.fpFEA2List->list;

   pfea2->oNextEntryOffset = 0;
   pfea2->fEA = 0;
   pfea2->cbName = 9;                   /* Length of ".LONGNAME" without \0 */
   pfea2->cbValue = MAXPATH;
   strcpy( pfea2->szName, ".LONGNAME" );

   FileInfoBuf.fpGEA2List->cbList = sizeof(GEA2LIST)
                                  + pgea2->cbName + 1;
   pgea2->oNextEntryOffset = 0;
   pgea2->cbName = pfea2->cbName;
   strcpy(pgea2->szName,pfea2->szName);

   FileInfoBuf.fpFEA2List->cbList = 1024;

   rc = DosQueryPathInfo(fn,FIL_QUERYEASFROMLIST,&FileInfoBuf,FileInfoSize );
   LongNameLength =
     *(WORD *)((char *)pfea2 + sizeof(FEA2) + pfea2->cbName + sizeof(WORD));
   debug(F101,"os2getlongname: rc=","",rc);
   debug(F101,"   cbValue:","",pfea2->cbValue);
   debug(F101,"   cbName:","",pfea2->cbName);
   debug(F101,"   EA Value Length:","",LongNameLength );
   debug(F110,"   EA Value:",(char *)pfea2 + sizeof(FEA2)
                             + pfea2->cbName + (2 * sizeof(WORD)),0 );
   if ( rc ) {
       return -1;
   } else if ( pfea2->cbValue ) {
       if (LongNameBuf) {
           free(LongNameBuf);
           LongNameBuf = NULL;
       }
       LongNameBuf = (char *) malloc( LongNameLength + 1 );
       if (LongNameBuf) {
           ckstrncpy(LongNameBuf, (char *)pfea2 + sizeof(FEA2)
                   + pfea2->cbName + (2 * sizeof(WORD)),
                   LongNameLength
                   );
           debug(F110,"os2getlongname: longname is",LongNameBuf,0);
       } else
         debug(F100,"os2getlongname: malloc failed","",0);
   } else {
       if ( LongNameBuf )
         free( LongNameBuf );
       LongNameBuf = strdup( "" );
       debug(F110,
             "os2getlongname: there is no longname attribute",
             LongNameBuf,
             0
             );
   }
   *ln = LongNameBuf;
   return 0;
}


_PROTOTYP( VOID GetMem, (PVOID *, ULONG) );

/* O S 2 G E T E A S - Get all OS/2 Extended Attributes */

/* Returns 0 on success, -1 on failure */

int
os2geteas( char * name ) {
    CHAR *pAllocc=NULL; /* Holds the FEA struct returned by DosEnumAttribute */
                        /*  used to create the GEA2LIST for DosQueryPathInfo */

    ULONG ulEntryNum = 1; /* count of current EA to read (1-relative)        */
    ULONG ulEnumCnt;      /* Number of EAs for Enum to return, always 1      */

    ULONG FEAListSize = sizeof(ULONG);/* starting size of buffer necessary to
                                                        hold all FEA blocks */
    ULONG GEAListSize = MAXEACOUNT * sizeof(GEA2) + sizeof(ULONG);

    FEA2 *pFEA;           /* Used to read from Enum's return buffer          */
    GEA2 *pGEA, *pLastGEAIn; /* Used to write to pGEAList buffer             */
    GEA2LIST *pGEAList;/*Ptr used to set up buffer for DosQueryPathInfo call */
    EAOP2  eaopGet;       /* Used to call DosQueryPathInfo                   */
    APIRET rc;
    int offset;

                                /* Allocate enough room for any GEA List */
    GetMem((PPVOID)&pAllocc, MAX_GEA);
    pFEA = (FEA2 *) pAllocc;               /* pFEA always uses pAlloc buffer */

    GetMem((PPVOID)&pGEAList, GEAListSize ); /* alloc buffer for GEA2 list */
    pGEAList->cbList = GEAListSize;
    pGEA = pGEAList->list;
    pLastGEAIn = 0;

    if ( !pAllocc || ! pGEAList ) {
        FreeMem( pAllocc );
        FreeMem( pGEAList );
        return -1;
    }
    if ( pFEAList ) {
        FreeMem( pFEAList );
        pFEAList = 0;
   }
   while(TRUE) {              /* Loop continues until there are no more EAs */
       ulEnumCnt = 1;                   /* Only want to get one EA at a time */
       if(DosEnumAttribute(Ref_ASCIIZ,            /* Read into pAlloc Buffer */
                           name,                  /* Note that this does not */
                           ulEntryNum,            /* get the aValue field,   */
                           pAllocc,               /* so DosQueryPathInfo must*/
                           MAX_GEA,               /* be called to get it.    */
                           &ulEnumCnt,
                           (LONG) GetInfoLevel1)) {
           FreeMem(pGEAList);           /* There was some sort of error */
           FreeMem(pAllocc);
           return (-1);
       }
       if(ulEnumCnt != 1)               /* All the EAs have been read */
         break;

       ulEntryNum++;
       FEAListSize += sizeof(FEA2LIST) + pFEA->cbName+1 +
                      pFEA->cbValue + 4;

       if (pLastGEAIn)
         pLastGEAIn->oNextEntryOffset = (BYTE *)pGEA - (BYTE *)pLastGEAIn;
       pLastGEAIn = pGEA;

       pGEA->oNextEntryOffset = 0L;
       pGEA->cbName = pFEA->cbName;
       strcpy(pGEA->szName, pFEA->szName);

       /* must align GEA2 blocks on double word boundaries */
       offset = sizeof(GEA2) + pGEA->cbName + 1;
       offset += ( offset % 4 ? (4 - offset % 4) : 0 );
       pGEA = (GEA2 *) ((BYTE *) pGEA + offset);
   }
   debug(F111,"os2geteas: EA count",name,ulEntryNum-1);
   GetMem( (PPVOID) &pFEAList, FEAListSize );
   pFEAList->cbList = FEAListSize;

   eaopGet.fpGEA2List = pGEAList;
   eaopGet.fpFEA2List = pFEAList;

   rc = DosQueryPathInfo(name,                   /* Get the complete EA info */
                   GetInfoLevel3,
                   (PVOID) &eaopGet,
                    sizeof(EAOP2));

   debug(F111,"os2geteas: DosQueryPathInfo",name,rc);
   FreeMem( pGEAList );
   FreeMem( pAllocc );
   return ( rc ? -1 : 0 );
}

/* O S 2 S E T E A S - Set all OS/2 Extended Attributes */

/* Returns 0 on success, -1 on failure                  */

int
os2seteas( char * name ) {
   EAOP2  eaopSet;       /* Used to call DosSetPathInfo */
   APIRET rc;

   if ( !pFEAList ) {
     debug(F100,"os2seteas: EA List is empty","",0);
     return 0;
   }
   eaopSet.fpGEA2List = 0;
   eaopSet.fpFEA2List = pFEAList;

   rc = DosSetPathInfo(name,            /* Set the EA info */
                    SetInfoLevel2,
                   (PVOID) &eaopSet,
                    sizeof(EAOP2),
                    DSPI_WRTTHRU);
   debug(F111,"os2seteas: DosSetPathInfo",name,rc);

   if ( !rc ) {
      FreeMem( pFEAList );
      pFEAList = 0;
   }
   return ( rc ? -1 : 0 );
}

/* O S 2 G E T A T T R - Get all OS/2 Normal Attributes */

/* Returns 0 on success, -1 on failure                  */

int
os2getattr( char * name ) {
   FILESTATUS3 FileInfoBuf;
   APIRET rc;

   rc = DosQueryPathInfo(name,                   /* Get the complete EA info */
                   GetInfoLevel1,
                   (PVOID) &FileInfoBuf,
                    sizeof(FILESTATUS3));

   if ( !rc ) {
      os2attrs = FileInfoBuf.attrFile;
      return 0;
      }
   else {
      os2attrs = FILE_NORMAL;
      return -1;
      }
}

/* O S 2 S E T A T T R - Set all OS/2 Normal Attributes */

/* Returns 0 on success, -1 on failure                  */

int
os2setattr( char * name ) {
   FILESTATUS3 FileInfoBuf;
   APIRET rc;

   rc = DosQueryPathInfo(name,                   /* Get the complete EA info */
                   GetInfoLevel1,
                   (PVOID) &FileInfoBuf,
                    sizeof(FILESTATUS3));

   if ( !rc ) {
      FileInfoBuf.attrFile = lf_opts & os2attrs;
      rc = DosSetPathInfo( name,
                   GetInfoLevel1,
                   (PVOID) &FileInfoBuf,
                   sizeof(FILESTATUS3),
                   0);
      if ( !rc )
         return 0;
      }
   return -1;
}

/****************************************************************\
 *                                                              *
 *  Name:    GetMem (ppv, cb)                                   *
 *                                                              *
 *  Purpose: This routine returns a pointer to a available*     *
 *           memory space.                                      *
 *                                                              *
 *  Usage  :                                                    *
 *  Method : Routine should be bullet proof as it does its own  *
 *           error checking. It assumes that hwnd points to the *
 *           correct window with the name listbox in it.        *
 *                                                              *
 *  Returns: The current EA as determined by querying the l-box *
 *           selector.                                          *
 *                                                              *
\****************************************************************/
VOID GetMem (PVOID *ppv, ULONG cb) {
    BOOL        f;

    f =(BOOL) DosAllocMem(ppv, cb, fPERM|PAG_COMMIT);
    if (f) {
        *ppv = NULL;
        return;
    }
    return;
}

extern CHAR os2version[50];
extern long vernum;

/* D O _ L A B E L _ S E N D - Generate Label Header from EA List     */

/* Return 0 on success, -1 on failure                                 */

int
do_label_send(char * name) {
    char scratch[100];
    long buffreespace = INBUFSIZE;
    long easleft = 0;
    long memtocpy = 0;
    static BYTE * p = 0;

    debug(F110,"do_label_send",name,0);
    if (!pFEAList) {
        debug(F101,"do_label_send no EA list","",pFEAList);
        return(0);
    }
    if (!p) {
        debug(F100,"do_label_send: generate header","",0);
        zinptr += sprintf(zinptr,"KERMIT LABELED FILE:02UO04VERS");

        sprintf(scratch,"%d",strlen(get_os2_vers()));
        zinptr += sprintf(zinptr,"%02d%d%s",
                          strlen(scratch),
                          strlen(get_os2_vers()),
                          get_os2_vers()
                          );
        sprintf(scratch,"%d",vernum);
        zinptr += sprintf(zinptr,"05KVERS02%02d%d", strlen(scratch), vernum);
        sprintf(scratch,"%d",strlen(name));
        zinptr += sprintf(zinptr,"08FILENAME%02d%d%s",
                          strlen(scratch),
                          strlen(name), name
                          );
        zinptr += sprintf(zinptr,"04ATTR%02d",sizeof(ULONG));
        memcpy(zinptr, (BYTE *) &os2attrs, sizeof(ULONG));
        zinptr += sizeof(ULONG);

        sprintf( scratch, "%d", pFEAList->cbList );
        zinptr += sprintf(zinptr,"09EABUFSIZE%02d%ld",
                          strlen(scratch),
                          pFEAList->cbList
                          );
        p = (BYTE *) pFEAList;
        buffreespace -= (BYTE *) zinptr - (BYTE *) zinbuffer;
    }
    easleft = pFEAList->cbList - ( (BYTE *) p - (BYTE *) pFEAList );
    memtocpy = buffreespace > easleft ? easleft : buffreespace;
    memcpy( zinptr, p, memtocpy );
    zinptr = (BYTE *) zinptr + memtocpy;
    p = (BYTE *) p + memtocpy;
    buffreespace -= memtocpy;

    if (buffreespace > 0) {
        p = 0;
        FreeMem(pFEAList);
        pFEAList = 0;
        debug(F100,"do_label_send: terminate header","",0);
    }
    zincnt = (zinptr - zinbuffer);              /* Size of this beast */
    return(0);
}

/* D O _ L A B E L _ R E C V - Receive label info and create EA List  */

/* Return 0 on success, -1 on failure */

int
do_label_recv() {
    char *recv_ptr = zoutbuffer;
    int lblen;
    char buffer[16];
    size_t memtocpy, lefttocpy;
    static BYTE * pFEA = 0;

    if ( !pFEAList
         && strncmp(zoutbuffer,"KERMIT LABELED FILE:02UO04VERS",30) != 0) {
        debug(F100,"do_label_recv: not a labeled file","",0);
        return(0);                      /* Just continue if unlabeled */
    }

    if ( !pFEAList ) {
       recv_ptr += 30;                  /* start at front of buffer */
       zoutcnt  -= 30;

       /* get length of length of OS/2 version */
       memcpy(buffer, recv_ptr, 2);
       recv_ptr += 2;
       zoutcnt  -= 2;
       buffer[2] = '\0';
       lblen = atoi(buffer);

       /* get length of OS/2 version */
       memcpy(buffer, recv_ptr, lblen);
       recv_ptr += lblen;
       zoutcnt  -= lblen;
       buffer[lblen] = '\0';
       lblen = atoi(buffer);

       /* get os2 version */
       memcpy(buffer, recv_ptr, lblen);
       recv_ptr += lblen;
       zoutcnt  -= lblen;
       buffer[lblen] = '\0';
       debug(F111,"do_label_recv file created under OS/2",buffer,lblen);

       /* check sync with Kermit Version */
       memcpy(buffer, recv_ptr, 7);
       recv_ptr += 7;
       zoutcnt  -= 7;
       if (strncmp(buffer, "05KVERS", 7) != 0) {
           debug(F111,"do_label_recv lost sync at KVERS",recv_ptr-7,zoutcnt+7);
           return(-1);
       }

       /* get length of length of C-Kermit version */
       memcpy(buffer, recv_ptr, 2);
       recv_ptr += 2;
       zoutcnt  -= 2;
       buffer[2] = '\0';
       lblen = atoi(buffer);

       /* get length of C-Kermit version */
       memcpy(buffer, recv_ptr, lblen);
       recv_ptr += lblen;
       zoutcnt  -= lblen;
       buffer[lblen] = '\0';
       lblen = atoi(buffer);

       /* get C-Kermit version */
       memcpy(buffer, recv_ptr, lblen);
       recv_ptr += lblen;
       zoutcnt  -= lblen;
       buffer[lblen] = '\0';
       debug(F111,"do_label_recv file created with OS/2 C-Kermit",
             buffer,lblen);

       /* check sync with FILENAME */
       memcpy(buffer, recv_ptr, 10);
       recv_ptr += 10;
       zoutcnt  -= 10;
       if (strncmp(buffer, "08FILENAME", 10) != 0) {
           debug(F111,"do_label_recv lost sync at FILENAME",
                 recv_ptr-10,zoutcnt+10);
           return(-1);
       }

       /* get length of length of Filename */
       memcpy(buffer, recv_ptr, 2);
       recv_ptr += 2;
       zoutcnt  -= 2;
       buffer[2] = '\0';
       lblen = atoi(buffer);

       /* get length of File Name */
       memcpy(buffer, recv_ptr, lblen);
       recv_ptr += lblen;
       zoutcnt  -= lblen;
       buffer[lblen] = '\0';
       lblen = atoi(buffer);

       /* get File Name */
       memcpy(buffer, recv_ptr, lblen);
       recv_ptr += lblen;
       zoutcnt  -= lblen;
       buffer[lblen] = '\0';
       debug(F111,"  file sent with name: ",buffer,lblen);

       /* check sync with ATTR */
       memcpy(buffer, recv_ptr, 6);
       recv_ptr += 6;
       zoutcnt  -= 6;
       if (strncmp(buffer, "04ATTR", 6) != 0) {
           debug(F111,"  lost sync at ATTR",recv_ptr-6,zoutcnt+6);
           return(-1);
       }

       /* get length of attributes - should be sizeof(ULONG) */
       memcpy(buffer, recv_ptr, 2);
       recv_ptr += 2;
       zoutcnt  -= 2;
       buffer[2] = '\0';
       lblen = atoi(buffer);
       if ( lblen != sizeof(ULONG) ) {
           debug(F101,"   Attributes have wrong length","",lblen);
           return(-1);
       }

       /* get attributes */
       memcpy(&os2attrs, recv_ptr, sizeof(ULONG));
       recv_ptr += sizeof(ULONG);
       zoutcnt  -= sizeof(ULONG);

       /* check sync with EABUFSIZE */
       memcpy(buffer, recv_ptr, 11);
       recv_ptr += 11;
       zoutcnt  -= 11;
       if (strncmp(buffer, "09EABUFSIZE", 11) != 0) {
           debug(F111,"  lost sync at EABUFSIZE",recv_ptr-11,zoutcnt+11);
           return(-1);
       }

       /* get length of length of EA Buffer Size */
       memcpy(buffer, recv_ptr, 2);
       recv_ptr += 2;
       zoutcnt  -= 2;
       buffer[2] = '\0';
       lblen = atoi(buffer);

       /* get length of EA Buffer Size */
       memcpy(buffer, recv_ptr, lblen);
       recv_ptr += lblen;
       zoutcnt  -= lblen;
       buffer[lblen] = '\0';
       lblen = atoi(buffer);
       debug(F101,"  EA Buffer Size:","",lblen);

       GetMem( (PPVOID) &pFEAList, (ULONG) lblen );
       if ( !pFEAList ) {
         debug(F101,"   pFEAList","",pFEAList);
         return -1;
       }
       pFEAList->cbList = lblen;
       pFEA = (BYTE *) pFEAList;
   }

    if ( pFEAList && pFEA ) {
       /* get EA Buffer */
       lefttocpy = pFEAList->cbList - ( (BYTE *) pFEA - (BYTE *) pFEAList );
       memtocpy = ( zoutcnt < lefttocpy ? zoutcnt : lefttocpy );
       memcpy(pFEA, recv_ptr, memtocpy);
       recv_ptr += memtocpy;
       zoutcnt  -= memtocpy;
       pFEA += memtocpy;
       debug(F101,"   memtocpy","",memtocpy);
       debug(F101,"   zoutcnt","",zoutcnt);

       if ( pFEA == ( (BYTE *) pFEAList + pFEAList->cbList ) ) {
          pFEA = 0;  /* we are done copying the EA's to the EA List */
          debug(F100,"   done copying EA's","",0);
       }
    }

    /* if we have processed some of the data in the output buffer */
    /* then move the data in the buffer so that it is properly    */
    /* aligned with the beginning of the buffer and reset the ptr */
    if ( recv_ptr != zoutbuffer ) {
        memmove(zoutbuffer, recv_ptr, zoutcnt);
        if ( pFEA ) {
            zoutptr = zoutbuffer + zoutcnt ;
            return(1);                          /* Go fill some more */
        }
    }
    return (0);
}
#endif /* CK_LABELED */

#ifdef OS2ONLY
/*
 * given a template of the form "fnamXXXXXX", insert number on end
 * of template, insert unique letter if needed until unique filename
 * found or run out of letters.  The number is generated from the
 * Process ID.
 */

char *
mktemp(char * template)
{
    char *string = template;
    unsigned number;
    int letter = 'a';
    int xcount = 0;
    int olderrno;

    if (template == NULL || template[0] == '\0')
        return(NULL);

    number = os2getpid();

    while (*string)
        string++;

    /* replace last five X's */
    while (*--string == 'X' && xcount < 5)
    {
        xcount++;
        *string = (char)((number % 10) + '0');
        number /= 10;
    }

    /* too few X's ? */
    if (*string != 'X' || xcount < 5)
        return(NULL);

    /* set first X */
    *string = letter++;

    olderrno = errno;       /* save current errno */
    errno = 0;              /* make sure errno isn't EACCESS */

    /* check all the files 'a'-'z' */
    while ((access(template,0) == 0) || (errno == EACCES))
        /* while file exists */
    {
        errno = 0;
        if (letter == 'z' + 1) {
            errno = olderrno;
            return(NULL);
        }

        *string = (char)letter++;
    }

    errno = olderrno;
    return(template);
}
#endif /* OS2ONLY */
