/***************************************************************************
 *                                                                         *
 *   GALQWK.C                                                              *
 *                                                                         *
 *   Copyright (C) 1992-1997 Galacticomm, Inc. All Rights Reserved.        *
 *                                                                         *
 *   QWK-compatible module for Forums/E-mail                               *
 *                                                                         *
 *   - V1.0                                  - G. Hewgill  8/10/91         *
 *   - V2.0 adaptation                       - T. Stryker  8/18/92         *
 *   - converted to use GME                  - J. Alvrus   2/5/95          *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "majorbbs.h"
#include "filexfer.h"
#include "gme.h"
#include "chandir.h"
#include "galqwk.h"

#define FILREV "$Revision: 21 $"

typedef CHAR  byte;                /* code assumes unsigned bytes          */

struct sf_tree_entry {
     CHAR bit_length;
     SHORT code;
     CHAR value;
};

struct sf_tree {
     struct sf_tree_entry *tree;
     SHORT entries;
     SHORT max_length;
};

struct huft {
     byte e;                       /* number of extra bits or operation    */
     byte b;                       /* number of bits in this code/subcode  */
     union {
          USHORT n;                /* literal, length base, or dist base   */
          struct huft *t;          /* pointer to next level of table       */
     } v;
};

GBOOL qwkcfl(VOID *work,USHORT forum,LONG msgid);
VOID qwkmail_status(VOID);
GBOOL qwkmail(VOID);
INT qwkmail_menu(VOID);
INT fupqwk(INT fupcod);
VOID exit_qwkmail(VOID);
GBOOL start_dl(VOID);
GBOOL cont_dl(VOID);
GBOOL out_msg(VOID);
VOID cpylim(CHAR *dst,CHAR *src,UINT lim);
VOID dlerr(VOID);
VOID dlcbk(INT evt,INT rc);
VOID newfor(VOID);
GBOOL startctl(VOID);
GBOOL wrtfors(VOID);
GBOOL findl(VOID);
VOID sbmqwk(CHAR *protsl);
INT tshqwk(INT tshcod);
VOID abort_dl(VOID);
VOID upload_done(INT ok);
VOID upload_check(VOID);
GBOOL start_ul(VOID);
GBOOL cont_ul(VOID);
GBOOL rep2msg(VOID);
VOID hdlreq(VOID);
CHAR *grabfld(CHAR *src,UINT len);
GBOOL pfnerr(CHAR *uid,CHAR *str,USHORT forum);
VOID sndnew(VOID);
VOID sndrpl(VOID);
static VOID wrtnot(const CHAR *to);
VOID finish_ul(INT error_msg);
VOID delete_ul(VOID);
GBOOL zip_init(CHAR *fn);
GBOOL zip_open(CHAR *fn);
GBOOL zip_write(VOID *buf,INT len);
GBOOL zip_close(VOID);
GBOOL zip_copy(CHAR *src,CHAR *dest);
GBOOL zip_done(VOID);
GBOOL unzip_open(const CHAR *zn,CHAR *fn);
SHORT unzip_read(CHAR *p,INT n);
VOID unzip_close(VOID);
VOID load_sf_tree(struct sf_tree *t,SHORT tree_size);
SHORT reverse_bits(SHORT x);
SHORT read_via_tree(struct sf_tree *t);
SHORT read_bits(SHORT b);
VOID output_byte(CHAR b);
ULONG mks_to_long(ULONG x);
ULONG long_to_mks(ULONG x);
ULONG crc32block(VOID *buf,INT len,ULONG crc);
ULONG crc32byte(CHAR c,ULONG crc);
VOID close_if_open(FILE *f);
GBOOL exist(CHAR *fn);
VOID qwkmail_hangup(VOID);
GBOOL saveqs(VOID);
VOID restqs(VOID);
CHAR *qwkpath(CHAR *fname);
CHAR *uqwkpath(INT unum,CHAR *fname);
VOID qwksht(VOID);
VOID qwkmail_cleanup(VOID);
static GBOOL initfixd(VOID);
VOID inflate(VOID);
SHORT readbyte(USHORT *x);
SHORT outbyte(SHORT intc);
SHORT huft_build(USHORT *b,USHORT n,USHORT s,USHORT *d,
           USHORT *e,struct huft **t,SHORT *m);
SHORT huft_free(struct huft *t);
CHAR *tvqname(VOID);
CHAR *tvqtogg(VOID);

struct module mmail={              /* module interface block               */
     "",                           /*    name used to refer to this module */
     NULL,                         /*    user logon supplemental routine   */
     qwkmail,                      /*    input routine if selected         */
     qwkmail_status,               /*    status-input routine if selected  */
     NULL,                         /*    "injoth" routine for this module  */
     NULL,                         /*    user logoff supplemental routine  */
     qwkmail_hangup,               /*    hangup (lost carrier) routine     */
     qwkmail_cleanup,              /*    midnight cleanup routine          */
     NULL,                         /*    delete-account routine            */
     qwksht                        /*    finish-up (sys shutdown) routine  */
};

static
HMCVFILE qwkmail_msgs;             /* qwkmail message file                 */

#define QWKMAIL_NAME "GALQWK" /* name of the pseudo-account for the qwkmail*/
#define QWKMAIL_SUB  "GALQWK" /* name of subsubdirectory, channel-specific */
#define QSCIMAGE     "TMPQSC.IMG"  /* quickscan high message # save file   */
#define HIDECHR      '\2'     /* first char of from field for queue msgs   */

/* zip file structure definitions */
#define ZIP_LOCAL_HEADER_SIG       0x04034b50L
#define ZIP_CENTRAL_RECORD_SIG     0x02014b50L
#define ZIP_END_CENTRAL_RECORD_SIG 0x06054b50L

#define ZIP_STORED   0
#define ZIP_SHRUNK   1
#define ZIP_IMPLODED 6
#define ZIP_DEFLATED 8

#define ZLH_SIZE (4+2+2+2+2+2+4+4+4+2+2)

struct ziplhdr {                                /*  ZIP_LOCAL_HEADER  */
     ULONG signature;
     SHORT ver_needed;
     SHORT gen_flags;
     SHORT compression;
     SHORT ftime;
     SHORT fdate;
     ULONG crc;
     ULONG compressed;
     ULONG uncompressed;
     SHORT filename_len;
     SHORT extra_len;
     CHAR filename[GCMAXFNM];
};

struct flddef ziplhdrFDA[]={
     { CVTFLD_LONG  ,1        ,fldoff(ziplhdr,signature)    ,NULL },
     { CVTFLD_SHORT ,5        ,fldoff(ziplhdr,ver_needed)   ,NULL },
     { CVTFLD_LONG  ,3        ,fldoff(ziplhdr,crc)          ,NULL },
     { CVTFLD_SHORT ,2        ,fldoff(ziplhdr,filename_len) ,NULL },
     { CVTFLD_CHAR  ,GCMAXFNM ,fldoff(ziplhdr,filename)     ,NULL },
     { CVTFLD_END   ,0        ,0                            ,NULL }
};

struct zipcrec { /*  ZIP_CENTRAL_RECORD */
     ULONG signature;
     SHORT ver_made_by;
     SHORT ver_needed;
     SHORT gen_flags;
     SHORT compression;
     SHORT ftime;
     SHORT fdate;
     ULONG crc;
     ULONG compressed;
     ULONG uncompressed;
     SHORT filename_len;
     SHORT extra_len;
     SHORT fcomment_len;
     SHORT disk_start;
     SHORT internal_attrs;
     ULONG external_attrs;
     ULONG local_header_offset;
     CHAR filename[13];
};

struct flddef zipcrecFDA[]={
     { CVTFLD_LONG  ,1   ,fldoff(zipcrec,signature)         ,NULL },
     { CVTFLD_SHORT ,6   ,fldoff(zipcrec,ver_made_by)       ,NULL },
     { CVTFLD_LONG  ,3   ,fldoff(zipcrec,crc)               ,NULL },
     { CVTFLD_SHORT ,5   ,fldoff(zipcrec,filename_len)      ,NULL },
     { CVTFLD_LONG  ,2   ,fldoff(zipcrec,external_attrs)    ,NULL },
     { CVTFLD_CHAR  ,13  ,fldoff(zipcrec,filename)          ,NULL },
     { CVTFLD_END   ,0   ,0                                 ,NULL }
};

struct zipecrec {    /* ZIP_END_CENTRAL_RECORD */
     ULONG signature;
     SHORT disk_number;
     SHORT central_dir_disk;
     SHORT central_entries_disk;
     SHORT central_entries;
     ULONG central_size;
     ULONG central_start_offset;
     SHORT zcomment_len;
};

struct flddef zipecrecFDA[]={
     { CVTFLD_LONG  ,1   ,fldoff(zipecrec,signature)        ,NULL },
     { CVTFLD_SHORT ,4   ,fldoff(zipecrec,disk_number)      ,NULL },
     { CVTFLD_LONG  ,2   ,fldoff(zipecrec,central_size)     ,NULL },
     { CVTFLD_SHORT ,1   ,fldoff(zipecrec,zcomment_len)     ,NULL },
     { CVTFLD_END   ,0   ,0                                 ,NULL }
};

/* unzip data type declarations */

struct UNZIP_DATA {
     FILE *zip_file;               /* general zip stuff                    */
     CHAR zip_eof;
     ULONG crc;
     struct ziplhdr zlhdr;
     CHAR *input_buf;
     SHORT input_index;
     SHORT input_avail;
     LONG input_offset;
     CHAR *output_buf;
     SHORT output_index;
     SHORT output_avail;
     LONG output_offset;
     SHORT bits_left;
     SHORT *prefix_of;             /* unshrinking variables                */
     CHAR *suffix_of;
     CHAR *stack;
     SHORT stack_index;
     SHORT code_bits;
     SHORT first_free;
     SHORT final_char;
     SHORT last_code;
     struct sf_tree literal_tree;  /* exploding variables                  */
     struct sf_tree length_tree;
     struct sf_tree distance_tree;
     CHAR literal_tree_present;
     SHORT min_match_length;
     SHORT lower_dict_bits;
};

#define INPUT_BUFFER_SIZE  512
#define OUTPUT_BUFFER_SIZE (TXTLEN+8192)
#define INIT_BITS       9
#define MAX_BITS        13
#define MAX_CODE        (1<<MAX_BITS)
#define FIRST_ENTRY     257
#define CLEAR_CODE      256
#define ZIP_BUFFER_SIZE 8192
#define ATINFSZ         128        /* attachment info on end of text size  */

/* Stuff for PKUNZIP v2's INFLATE (method 8) */
#define INFLATE2  12               /* Various inflate() "states"           */
#define INFLATE3  13
#define INFLATE4  14
#define INFNOK    15
#define INFOK     16
#define INFLATE5  17
#define INFLATE6  18
#define INFLATE7  19
#define WSIZE     0x8000           /* window size: (a power of two >= 32K) */
#define BMAX   16
#define N_MAX  288
#define NEXTBYTE    (readbyte(&zp.bytebuf), zp.bytebuf)
#define NEEDBITS(n) {while(k<(n)){b|=((ULONG)NEXTBYTE)<<k;k+=8;}}
#define DUMPBITS(n) {b>>=(n);k-=(n);}
#define NEEDBITS2(n) {while(zp.k<(n))\
     {zp.b|=((ULONG)NEXTBYTE)<<zp.k;zp.k+=8;}}
#define DUMPBITS2(n) {zp.b>>=(n);zp.k-=(n);}
#define NEEDBITS3(n) {while(zp.k4<(n))\
     {zp.b4|=((ULONG)NEXTBYTE)<<zp.k4;zp.k4+=8;}}
#define DUMPBITS3(n) {zp.b4>>=(n);zp.k4-=(n);}
#define NEEDBITS4(n) {while(zp.k3<(n))\
     {zp.b3|=((ULONG)NEXTBYTE)<<zp.k3;zp.k3+=8;}}
#define DUMPBITS4(n) {zp.b3>>=(n);zp.k3-=(n);}
#define NEEDBITS5(n) {while(zp.k5<(n))\
     {zp.b5|=((ULONG)NEXTBYTE)<<zp.k5;zp.k5+=8;}}
#define DUMPBITS5(n) {zp.b5>>=(n);zp.k5-=(n);}
#define READBIT(nbits,zdest) {if(nbits>uz.bits_left) FillBitBuffer();\
     zdest=(SHORT)((USHORT)uz.bitbuf&mask_bits[nbits]);\
     uz.bitbuf>>=nbits;uz.bits_left-=nbits;}

struct inflate {
     USHORT bytebuf;
     USHORT hufts;
     USHORT wp;                    /* current position in slide            */
     ULONG bb;                     /* bit buffer                           */
     USHORT bk;                    /* bits in bit buffer                   */
     SHORT lbits;                  /* bits in base lit/len lookup table    */
     SHORT dbits;                  /* bits in base distance lookup table   */
     ULONG b;                      /* bit buffer                           */
     USHORT k;                     /* number of bits in bit buffer         */
     SHORT e2;                     /* last block flag                      */
     USHORT h2;                    /* maximum struct huft's malloc'ed      */
     USHORT t3;                    /* block type                           */
     ULONG b3;                     /* bit buffer                           */
     USHORT k3;                    /* number of bits in bit buffer         */
     SHORT i4;                     /* temporary variables                  */
     USHORT j4;
     USHORT l4;                    /* last length                          */
     USHORT m4;                    /* mask for bit lengths table           */
     USHORT n4;                    /* number of lengths to get             */
     struct huft *tl4;             /* literal/length code table            */
     struct huft *td4;             /* distance code table                  */
     SHORT bl4;                    /* lookup bits for tl                   */
     SHORT bd4;                    /* lookup bits for td                   */
     USHORT nb4;                   /* number of bit length codes           */
     USHORT nl4;                   /* number of literal/length codes       */
     USHORT nd4;                   /* number of distance codes             */
     USHORT ll4[286+30];           /* literal/length and distance code len */
     ULONG b4;                     /* bit buffer                           */
     USHORT k4;                    /* number of bits in bit buffer         */
     USHORT e5;                    /* table entry flag/number of xtra bits */
     USHORT n5, d5;                /* length and index for copy            */
     USHORT w5;                    /* current window position              */
     struct huft *t5;              /* pointer to table entry               */
     USHORT ml5, md5;              /* masks for bl and bd bits             */
     ULONG b5;                     /* bit buffer                           */
     USHORT k5;                    /* number of bits in bit buffer         */
     SHORT defstate;               /* current "state" of inflate routine   */
     LONG csize;                   /* bytes-remaining to inflate           */
     LONG cur_zip_bs;
     CHAR *inptr;
};

static USHORT border[]={16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15};
static USHORT cplens[]={3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,
               43,51,59,67,83,99,115,131,163,195,227,258,0,0};
static USHORT cplext[]={0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,
               4,4,5,5,5,5,0,99,99};
static USHORT cpdist[]={1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,
               257,385,513,769,1025,1537,2049,3073,4097,6145,
               8193,12289,16385,24577};
static USHORT cpdext[]={0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,
               10,10,11,11,12,12,13,13};
USHORT mask_bits[]={0x0000,0x0001,0x0003,0x0007,0x000f,0x001f,0x003f,
              0x007f,0x00ff,0x01ff,0x03ff,0x07ff,0x0fff,0x1fff,
              0x3fff,0x7fff,0xffff};

byte slide[WSIZE];
INT outcnt;

/* qwk packet structure definitions */

#define QMAIL_COPYRIGHT "Produced by Qmail...Copyright (c) 1987 by Sparkware."\
                        "  All Rights Reserved       "\
                        "Above for Compatibility with Qmail              "

#define STATUS_NORMAL              ' '
#define STATUS_NORMAL_READ         '-'
#define STATUS_RECEIVER_ONLY       '*'
#define STATUS_RECEIVER_ONLY_READ  '+'

#define MESSAGE_ACTIVE        225
#define MESSAGE_INACTIVE      226
#define PCBEOL                227

#define QWKMHDRSIZ            128  /* packed size of QWK_MESSAGE_HEADER    */
#define FILLEN                3 /* "fill" size at end of QWK_MESSAGE_HEADER */

struct qwkmhdr {     /* QWK_MESSAGE_HEADER  */
     CHAR status;
     CHAR number[7];
     CHAR date[8];
     CHAR time[5];
     CHAR whoto[25];
     CHAR from[25];
     CHAR subject[25];
     CHAR password[12];
     CHAR reference[8];
     CHAR blocks[6];               /* # 128-byte blocks in this message    */
     CHAR active;
     USHORT conference;
     CHAR fill[FILLEN];
};

struct flddef qwkmhdrFDA[]={
     { CVTFLD_CHAR  ,123 ,fldoff(qwkmhdr,status)       ,NULL },
     { CVTFLD_SHORT ,1   ,fldoff(qwkmhdr,conference)   ,NULL },
     { CVTFLD_CHAR  ,3   ,fldoff(qwkmhdr,fill)         ,NULL },
     { CVTFLD_END   ,0   ,0                            ,NULL }
};

struct qwkirec { /* QWK_INDEX_RECORD */
     ULONG block_number;
     CHAR conference;
};

struct flddef qwkirecFDA[]={
     { CVTFLD_LONG  ,1   ,fldoff(qwkirec,block_number) ,NULL },
     { CVTFLD_CHAR  ,1   ,fldoff(qwkirec,conference)   ,NULL },
     { CVTFLD_END   ,0   ,0                            ,NULL }
};

/* static data */
#define BUFFER_SIZE outbsz

static CHAR *buffer;

struct zipcrec     *zip_central_record;
struct zipecrec *zip_end_central_record;
struct qwkmhdr     *qwk_message_header,
                              *ulhdr;
struct qwkirec        qwk_index_record;

struct UPLOAD_DATA {               /* REP packet processing state data     */
     USHORT forum;                 /*   forum ID for current message       */
     LONG cachemsg;                /*   message ID of current cache message*/
     CHAR state;                   /*   current processing state code      */
     CHAR user_from[UIDSIZ];       /*   User-ID of sender                  */
     SHORT msgs_posted;            /*   number of messages posted so far   */
     CHAR work[GMEWRKSZ];          /*   upload GME work area               */
     struct message msg;           /*   header of message being sent       */
     CHAR *text;                   /*   pointer to message text buffer     */
} *ul;

                                   /* REP packet processing state codes    */
#define QWKIDLE 0                  /*   not processing a packet            */
#define QWKNEW  1                  /*   starting a new message             */
#define QWKSNDN 2                  /*   sending a new message              */
#define QWKSNDR 3                  /*   sending a reply                    */

static struct UNZIP_DATA uz;
static struct inflate zp;
static struct qscfg *qscptr;

static CHAR prop[] = "/-\\|";

INT mjmstt,                        /* QWK module state                     */
    mindly,                        /* cycle-based delay when dling packet  */
    numqwk;                        /* number of users building packets     */
UINT txtbufsz;                     /* size of message text buffers         */

struct VOLATILE_DATA {             /* volatile per-user data structure     */
     FILE *zip_file;
     FILE *zip_central_file;
     SHORT znum;
     LONG zoffset;
     struct ziplhdr zlhdr;
     USHORT curfor;                /*   current forum being downloaded     */
     LONG current_block;
     LONG totfpos;                 /*   file position of total forum count */
     SHORT prop_index;
     SHORT totfors;                /*   total # of forums user can access  */
     SHORT total_msgs;
     SHORT personal_msgs;
     SHORT sig_msgs;
     SHORT personal_sig_msgs;
     SHORT num_attachments;
     FILE *index_file;
     FILE *pindex_file;
     FILE *attach_file;
     LONG dlchg;                   /*   credits charged for dnloaded msgs  */
     CHAR flags;                   /*   packet formation flags             */
     SHORT cycdly;                 /*   cycle-based delay counter          */
     SHORT qwkstt;                 /*   current packet-formation state     */
     SHORT attnum;                 /*   attachment being processed         */
     SHORT uplding;
     struct ffblk fb;
     struct otscan *ots;
     CHAR qwkwork[GMEWRKSZ];
     struct message msg;
     CHAR text[1];
};

#define vd ((struct VOLATILE_DATA *)vdaptr)

                                   /* QWK packet formation flags           */
#define REQOPEN  0x01              /*   GME request is open                */
#define FIRSTATT 0x02              /*   first attachment to be processed   */

                                   /* QWK packet formation states          */
#define GETEML   1                 /*   getting E-mail                     */
#define MARKEML  2                 /*   marking an E-mail message read     */
#define STARTFOR 3                 /*   start getting Forum messages       */
#define GETFOR   4                 /*   getting Forum messages             */
#define MARKFOR  5                 /*   marking a Forum message read       */
#define DONEMSG  6                 /*   done getting msgs, start packing   */
#define INDEXING 7                 /*   copying index files into packet    */
#define NEXTATT  8                 /*   get next attachment to copy        */
#define COPYATT  9                 /*   copying an attachment into packet  */
#define DONEATT  10                /*   done with attachments              */
#define WRTFORS  11                /*   writing forums into packet         */
#define FINDNLD  12                /*   finish up packet formation         */

struct qwksid {                    /* a few QWK little items on the side   */
     CHAR protsl[5];               /*   download protocol selection (+ "!")*/
     CHAR gotbeg;                  /*   flag: 1=got TSHBEG, 2=got TSHSKP   */
     SHORT  fnewb4;                /*   ret val from ftgnew() b4 starting  */
     SHORT  wroteq;                /*   wrote quickscan data this session  */
} *qwksid;

/* options from GALQWK.MSG */
CHAR *qwk_name,                    /* name of the QWK packet               */
     *qdlkey,                      /* QWK-mail download permission key     */
     *qulkey,                      /* QWK-mail upload permission key       */
     *newsfl,                      /* QWK-mail "news" filename             */
     inetpfx[PFXSIZ+1];            /* prefix for "internet" exporter       */
INT inter_packet,                  /* time between processing packets      */
    intra_packet,                  /* time between proc'ing msgs in packet */
    frmlmt;                        /* max msgs per-forum in a packet       */

VOID EXPORT
init__galqwk(VOID)                 /* initialize the qwkmail module        */
{
     INT i;

     stzcpy(mmail.descrp,gmdnam("galqwk.mdf"),MNMSIZ);
     mjmstt=register_module(&mmail);
     numqwk=0;
     register_textvar("QWK_NAME",tvqname);
     register_textvar("QWK_TOGG",tvqtogg);
     txtbufsz=((txtlen()+ATINFSZ)/128+1)*128+1;
     dclvda(sizeof(struct VOLATILE_DATA)+txtbufsz-1);
     qwksid=(struct qwksid *)alczer(nterms*sizeof(struct qwksid));
     qwkmail_msgs=opnmsg("galqwk.mcv");
     qwk_name=strupr(stgopt(QWKNAME));
     if (strlen(qwk_name) > 8) {
          qwk_name[8]='\0';
     }
     stlcpy(inetpfx,rawmsg(INETPFX),PFXSIZ+1);
     i=strlen(inetpfx)-1;
     if (inetpfx[i] != ':') {
          i=min(i+1,PFXSIZ-1);
          inetpfx[i]=':';
          inetpfx[i+1]='\0';
     }
     qdlkey=stgopt(QDLKEY);
     qulkey=stgopt(QULKEY);
     mindly=numopt(MINDLY,0,32767);
     inter_packet=numopt(INTERPKT,1,300);
     intra_packet=numopt(INTRAPKT,1,5);
     newsfl=stgopt(NEWSFL);
     frmlmt=numopt(FRMLMT,1,30000);
     ul=(struct UPLOAD_DATA *)alczer(sizeof(struct UPLOAD_DATA));
     ul->text=(CHAR *)alcmem(txtbufsz);
     uz.input_buf=(CHAR *)alcmem(INPUT_BUFFER_SIZE);
     uz.output_buf=(CHAR *)alcmem(OUTPUT_BUFFER_SIZE);
     uz.prefix_of=(SHORT *)alcmem(ZIP_BUFFER_SIZE*sizeof(SHORT));
     uz.suffix_of=(CHAR *)alcmem(ZIP_BUFFER_SIZE);
     uz.stack=(CHAR *)alcmem(ZIP_BUFFER_SIZE);
     uz.literal_tree.tree=(struct sf_tree_entry *)
                         alcmem(256*sizeof(struct sf_tree_entry));
     uz.length_tree.tree=(struct sf_tree_entry *)
                         alcmem(64*sizeof(struct sf_tree_entry));
     uz.distance_tree.tree=(struct sf_tree_entry *)
                         alcmem(64*sizeof(struct sf_tree_entry));
     zip_central_record=(struct zipcrec *)
                         alcmem(sizeof(struct zipcrec));
     zip_end_central_record=(struct zipecrec *)
                         alcmem(sizeof(struct zipecrec));
     qwk_message_header=(struct qwkmhdr *)
                         alcmem(sizeof(struct qwkmhdr));
     ulhdr=(struct qwkmhdr *)
                         alcmem(sizeof(struct qwkmhdr));
     buffer=alcmem(BUFFER_SIZE);
     setcfl(qwkcfl);
     ul->state=QWKIDLE;
     rtkick(inter_packet,upload_check);
}

GBOOL
qwkcfl(                            /* QWK-mail conflict checker            */
VOID *work,                        /*   work area to check                 */
USHORT forum,                      /*   forum to check                     */
LONG msgid)                        /*   message ID to check                */
{
     INT tmpusn;
     struct user *tmpusp;
     struct VOLATILE_DATA *tmpvd;

     (VOID)work;
     if (msgid == 0L && ul->state != QWKIDLE && ul->forum == forum) {
          return(TRUE);
     }
     for (tmpusn=0 ; tmpusn < nterms ; tmpusn++) {
       tmpusp=usroff(tmpusn);
          if (tmpusp->usrcls > SUPIPG
           && tmpusp->state == mjmstt
           && tmpusp->substt == MAKEPCKT) {
               tmpvd=(struct VOLATILE_DATA *)vdaoff(tmpusn);
               if ((tmpvd->flags&REQOPEN)
                && chkmycfl(tmpvd->qwkwork,forum,msgid)) {
                    return(TRUE);
               }
          }
     }
     return(FALSE);
}

VOID
qwkmail_status(VOID)               /* status handler for cycled procs      */
{
     if (status == CYCLE) {
          qscptr=myqsptr();
          setmbk(qwkmail_msgs);
          clrprf();
          switch (usrptr->substt) {
          case MAKEPCKT:
               if (btuoba(usrnum) > outbsz-2-1024) {
                    if (cont_dl()) {
                         btuinj(usrnum,CYCLE);
                    }
               }
               else {
                    btuinj(usrnum,CYCLE);
               }
               break;
          }
          if (prfptr != prfbuf) {
               outprf(usrnum);
          }
     }
     else {
          dfsthn();
     }
}

GBOOL
qwkmail(VOID)                      /* module input handler                 */
{
     LONG tmpnum,newhi;
     INT i;

     qscptr=myqsptr();
     setmbk(qwkmail_msgs);
     clrprf();
     if (usrptr->flags&INJOIP) {
          switch (usrptr->substt) {
          case MAKEPCKT:
               break;
          case RESET:
               prfmsg(RESET,l2as(himsgid()));
               break;
          default:
               prfmsg(usrptr->substt);
               break;
          }
     }
     else if (margc == 0) {
          switch (usrptr->substt) {
          case MAKEPCKT:
               abort_dl();
               prfmsg(DLABORTD);
               restqs();
               prfmsg(PREVUS);
               prfmsg(usrptr->substt=MENU1);
               break;
          case RESET:
               prfmsg(NOTRESET);
               exit_qwkmail();
               prfmsg(usrptr->substt=MENU1);
               break;
          default:
               prfmsg(usrptr->substt);
               break;
          }
     }
     else {
          do {
               bgncnc();
               switch (usrptr->substt) {
               case 0:
                    chdmak();
                    MKDIR(qwkpath(NULL));
                    cncchr();
                    btuxnf(usrnum,0,19);
                    prfmsg(usrptr->substt=MENU1);
                    break;
               case MENU1:
                    if (!qwkmail_menu()) {
                         outprf(usrnum);
                         rstrxf();
                         return(FALSE);
                    }
                    break;
               case MAKEPCKT:
                    abort_dl();
                    prfmsg(DLABORTD);
                    restqs();
                    prfmsg(PREVUS);
                    prfmsg(usrptr->substt=MENU1);
                    break;
               case RESET:
                    if (isdigit(morcnc())) {
                         tmpnum=cnclon();
                         if (tmpnum > himsgid()) {
                              tmpnum=himsgid()+1L;
                         }
                         else if (tmpnum < 1L) {
                              tmpnum=1L;
                         }
                         newhi=tmpnum-1L;
                         if (tmpnum == 1L) {
                              ++newhi;
                         }
                         usaptr->emllim=newhi;
                         for (i=0 ; i < qscptr->nforums ; i++) {
                              if (igethi(qscptr,i) > 0L) {
                                   isethi(qscptr,i,newhi);
                              }
                         }
                         prfmsg(RESETTO,l2as(tmpnum));
                    }
                    else {
                         prfmsg(NOTRESET);
                    }
                    exit_qwkmail();
                    prfmsg(usrptr->substt=MENU1);
                    break;
               default:
                    catastro("Substate error in GALQWK: %d",usrptr->substt);
               }
          } while (!endcnc());
     }
     if (prfptr != prfbuf) {
          outprf(usrnum);
     }
     return(TRUE);
}

INT
qwkmail_menu(VOID)                 /* main menu handler                    */
{
     switch (cncchr()) {
     case 'D':
          if (haskey(qdlkey)) {
               morcnc();
               stzcpy(qwksid[usrnum].protsl,cncall(),5);
               prfmsg(usrptr->substt=MAKEPCKT);
               if (!start_dl()) {
                    abort_dl();
                    prfmsg(FILERR);
                    prfmsg(usrptr->substt=MENU1);
               }
          }
          else {
               prfmsg(LACKKEY,"download");
               exit_qwkmail();
               prfmsg(usrptr->substt=MENU1);
          }
          cncall();
          break;
     case 'U':
          if (!haskey(qulkey)) {
               cncall();
               prfmsg(LACKKEY,"upload");
               exit_qwkmail();
               prfmsg(usrptr->substt=MENU1);
               break;
          }
          setmem(&vd->msg,sizeof(struct message),0);
          strcpy(vd->msg.from,QWKMAIL_NAME);
          vd->msg.to[0]=HIDECHR;
          stlcpy(&vd->msg.to[1],usaptr->userid,UIDSIZ);
          vd->msg.flags=FILATT|FILAPV;
          vd->text[0]='\0';
          stlcpy(&vd->text[1],ulname(&vd->msg),GCMAXPTH);
          fileup(spr("%s.REP",qwk_name),cncall(),fupqwk);
          break;
     case 'A':
          cncall();
          qscptr->flags^=QWKATT;
          prfmsg(ATTONOFF,(qscptr->flags&QWKATT) ? "" : " NOT");
          exit_qwkmail();
          prfmsg(usrptr->substt=MENU1);
          break;
     case 'R':
          prfmsg(usrptr->substt=RESET,l2as(himsgid()));
          break;
     case '?':
          cncall();
          prfmsg(QWKHELP);
          prfmsg(UHELP1);
          exit_qwkmail();
          usrptr->substt=MENU1;
          prfmsg(PRESENTR);
          break;
     case 'X':
          return(0);
     default:
          prfmsg(NOTCMD);
          prfmsg(usrptr->substt=MENU1);
          cncall();
          break;
     }
     return(1);
}

INT
fupqwk(INT fupcod)       /* Handle uploads for the QWK application         */
{                        /* implicit inputs: usrnum, usrptr, usaptr, vdaptr*/
                         /* return value meaning depends on fupcod         */
                         /* implicit input/output in many cases: ftfbuf    */
                         /* expect caller to do outprf() if any            */
     INT rc=0;

     setmbk(qwkmail_msgs);
     switch (fupcod) {
     case FUPPTH:                 /* Find out what path the file would use */
     case FUPBEG:               /* Begin upload, check permission, reserve */
     case FUPEND:              /* End complete upload of a file, unreserve */
          strcpy(ftfbuf,&vd->text[1]);
          rc=1;
          break;
     case FUPSKP:                      /* Skip incomplete upload of a file */
          unlink(&vd->text[1]);
          break;
     case FUPFIN:                            /* Finish file upload session */
          upload_done(isfile(&vd->text[1]));
          rc=1;
          break;
     case FUPHUP:               /* Finish session because user logging off */
          break;
     }
     return(rc);
}

CHAR *
tvqname(VOID)                      /* QWK - name of system                 */
{
     return(qwk_name);
}

CHAR *
tvqtogg(VOID)                      /* QWK - attachment toggle on?          */
{
     return((myqsptr()->flags&QWKATT) ? "ON" : "OFF");
}

VOID
exit_qwkmail(VOID)                 /* exit module handler                  */
{
     cncall();
     if (usrptr->flags&CONCEX) {
          rstrxf();
          condex();
     }
}

GBOOL
start_dl(VOID)                     /* initialize the download procedure    */
{
     struct ffblk fb;
     INT i;
     FILE *fp,*fpo;

     numqwk++;
     restqs();
     i=fnd1st(&fb,qwkpath(STAR),0);
     while (i) {
          if (!sameas(fb.ff_name,"REQUEST.BAK")) {
               unlink(qwkpath(fb.ff_name));
          }
          i=fndnxt(&fb);
     }
     if ((fp=fopen(qwkpath("REQUEST.BAK"),FOPRB)) != NULL) {
          if ((fpo=fopen(qwkpath("request.lst"),FOPWB)) != NULL) {
               do {
                    i=fread(buffer,1,BUFFER_SIZE,fp);
                    if (i > 0) {
                         fwrite(buffer,1,i,fpo);
                    }
               } while (i > 0);
               fclose(fpo);
          }
          fclose(fp);
     }
     if (!saveqs()) {
          return(FALSE);
     }
     qwksid[usrnum].wroteq=1;
     vd->zip_file=NULL;
     vd->zip_central_file=NULL;
     if (!zip_init(qwkpath(spr("%s.QWK",qwk_name)))) {
          return(FALSE);
     }
     if (!zip_open("MESSAGES.DAT")) {
          return(FALSE);
     }
     strcpy(buffer,QMAIL_COPYRIGHT);
     if (!zip_write(buffer,128)) {
          return(FALSE);
     }
     vd->ots=(struct otscan *)malloc(sizeof(struct otscan)
                         +sizeof(USHORT)*(qscptr->nforums-1));
     if (vd->ots == NULL) {
          return(FALSE);
     }
     qsc2ots(qscptr,vd->ots);
     vd->curfor=EMLID;
     inigmerq(vd->qwkwork);
     vd->flags=REQOPEN;
     inormrd(vd->qwkwork,usaptr->userid,EMLID,firstnew(usaptr->userid,EMLID));
     setscan(vd->qwkwork,vd->ots);
     vd->current_block=2;
     vd->prop_index=0;
     vd->total_msgs=0;
     vd->personal_msgs=0;
     vd->sig_msgs=0;
     vd->personal_sig_msgs=0;
     vd->num_attachments=0;
     vd->index_file=NULL;
     vd->pindex_file=NULL;
     vd->attach_file=NULL;
     vd->cycdly=0;
     vd->qwkstt=GETEML;
     vd->attnum=0;
     vd->dlchg=0;
     prfmsg(SCNHDR);
     prfmsg(SCNLN1,"E-mail","Private messages");
     if (cont_dl()) {
          btuinj(usrnum,CYCLE);
     }
     return(TRUE);
}

GBOOL
cont_dl(VOID)                      /* continue download                    */
{                                  /* returns 1 if not done                */
     INT i,rc,n;
     LONG orgcrd;
     CHAR fname[GCMAXPTH],tag[TSLENG];
     FILE *fp;
     const struct message *tmpmsg;

     if (++vd->cycdly < mindly*numqwk) {
          return(TRUE);
     }
     vd->cycdly=0;
     switch (vd->qwkstt) {
     case GETEML:
          switch (rc=nextmsg(vd->qwkwork,&vd->msg,vd->text)) {
          case GMEAGAIN:
               return(TRUE);
          case GMEOK:
               if (vd->msg.forum == EMLID) {
                    prf("\b%c",prop[vd->prop_index]);
                    vd->prop_index=(vd->prop_index+1)&3;
                    vd->qwkstt=MARKEML;
               }
               break;
          case GMECRD:
          case GMENFND:
               prf("\b ");
               prfmsg(SCNLIN2,vd->sig_msgs,vd->sig_msgs);
               if (rc == GMECRD) {
                    prfmsg(OUTCRD);
                    prfmsg(CONTFOR);
               }
               vd->qwkstt=STARTFOR;
               break;
          default:
               dlerr();
               return(FALSE);
          }
          break;
     case MARKEML:
          orgcrd=usaptr->creds;
          rc=markread(vd->qwkwork,&vd->msg,vd->text);
          vd->dlchg+=(orgcrd-usaptr->creds);
          switch (rc) {
          case GMEAGAIN:
               return(TRUE);
          case GMEAFWD:
          case GMERRG:
          case GMEOK:
               if (vd->index_file == NULL
                && (vd->index_file=fopen(qwkpath("000.NDX"),FOPWB)) == NULL) {
                    dlerr();
                    return(FALSE);
               }
               if (!out_msg()) {
                    dlerr();
                    return(FALSE);
               }
               if (vd->sig_msgs < frmlmt) {
                    vd->qwkstt=GETEML;
               }
               else {
                    prf("\b ");
                    prfmsg(SCNLIN2,vd->sig_msgs,vd->sig_msgs);
                    vd->qwkstt=STARTFOR;
               }
               break;
          default:
               dlerr();
               return(FALSE);
          }
          break;
     case STARTFOR:
          vd->sig_msgs=0;
          vd->personal_sig_msgs=0;
          if (vd->index_file != NULL) {
               fclose(vd->index_file);
               vd->index_file=NULL;
          }
          vd->curfor=fstscnf(usaptr->userid,vd->ots);
          if (vd->curfor == EMLID) {
               vd->qwkstt=DONEMSG;
          }
          else {
               prfmsg(SCNLN1,getfnm(vd->curfor),getftpc(vd->curfor));
               inictx(vd->qwkwork,usaptr->userid,FSQSCN,vd->curfor,
                      fstscnm(usaptr->userid,vd->ots,vd->curfor),0L);
               setgmecb(vd->qwkwork,dlcbk);
               vd->qwkstt=GETFOR;
          }
          break;
     case GETFOR:
          switch (rc=nextmsg(vd->qwkwork,&vd->msg,vd->text)) {
          case GMEAGAIN:
               return(TRUE);
          case GMEOK:
               prf("\b%c",prop[vd->prop_index]);
               vd->prop_index=(vd->prop_index+1)&3;
               vd->qwkstt=MARKFOR;
               break;
          case GMECRD:
          case GMENFND:
               prf("\b ");
               prfmsg(SCNLIN2,vd->sig_msgs,vd->personal_sig_msgs);
               if (rc == GMECRD) {
                    prfmsg(OUTCRD);
               }
               vd->qwkstt=DONEMSG;
               break;
          default:
               dlerr();
               return(FALSE);
          }
          break;
     case MARKFOR:
          orgcrd=usaptr->creds;
          rc=markread(vd->qwkwork,&vd->msg,vd->text);
          vd->dlchg+=(orgcrd-usaptr->creds);
          switch (rc) {
          case GMEAGAIN:
               return(TRUE);
          case GMEOK:
               if (vd->index_file == NULL) {
                    vd->index_file=fopen(qwkpath(spr("%03u.NDX",
                                   vd->msg.forum)),FOPWB);
                    if (vd->index_file == NULL) {
                         dlerr();
                         return(FALSE);
                    }
               }
               if (!out_msg()) {
                    dlerr();
                    return(FALSE);
               }
               if (vd->sig_msgs < frmlmt) {
                    vd->qwkstt=GETFOR;
               }
               else {
                    for (i=0 ; i < vd->ots->nforums
                     && vd->ots->forlst[i] != vd->msg.forum ; ++i) {
                    }
                    ASSERT(i < vd->ots->nforums);
                    do {
                         ++i;
                    } while (i < vd->ots->nforums
                          && !faccok(vd->ots->forlst[i]));
                    if (i < vd->ots->nforums && faccok(vd->ots->forlst[i])) {
                         vd->curfor=vd->ots->forlst[i];
                         newfor();
                         inictx(vd->qwkwork,usaptr->userid,FSQSCN,vd->curfor,
                                fstscnm(usaptr->userid,vd->ots,vd->curfor),0L);
                         vd->qwkstt=GETFOR;
                    }
                    else {
                         prf("\b ");
                         prfmsg(SCNLIN2,vd->sig_msgs,vd->personal_sig_msgs);
                         vd->qwkstt=DONEMSG;
                    }
               }
               break;
          default:
               dlerr();
               return(FALSE);
          }
          break;
     case DONEMSG:
          clsgmerq(vd->qwkwork);
          vd->flags&=~REQOPEN;
          free(vd->ots);
          vd->ots=NULL;
          if (vd->index_file != NULL) {
               fclose(vd->index_file);
               vd->index_file=NULL;
          }
          if (vd->pindex_file != NULL) {
               fclose(vd->pindex_file);
               vd->pindex_file=NULL;
          }
          prfmsg(TOTALFND,vd->total_msgs);
          if (vd->personal_msgs > 0) {
               prfmsg(PRSNLFND,vd->personal_msgs,
                      (vd->personal_msgs == 1 ? "" : "s"));
          }
          if (vd->total_msgs == 0 && !exist(qwkpath("REQUEST.LST"))) {
               abort_dl();
               prfmsg(NOTHFND);
               qwksid[usrnum].wroteq=0;
               exit_qwkmail();
               prfmsg(usrptr->substt=MENU1);
               return(FALSE);
          }
          if (!zip_close()) {
               dlerr();
               return(FALSE);
          }
          vd->flags|=FIRSTATT;
          vd->qwkstt=fnd1st(&vd->fb,qwkpath("*.NDX"),0) ? INDEXING : NEXTATT;
          break;
     case INDEXING:
          stlcpy(fname,qwkpath(vd->fb.ff_name),GCMAXPTH);
          if (!zip_copy(fname,vd->fb.ff_name)) {
               dlerr();
               return(FALSE);
          }
          unlink(fname);
          if (!fndnxt(&vd->fb)) {
               vd->qwkstt=NEXTATT;
          }
          break;
     case NEXTATT:
          if ((fp=fopen(qwkpath("REQUEST.LST"),FOPRB)) != NULL
           && fseek(fp,(LONG)vd->attnum*(LONG)TSLENG,SEEK_SET) == 0
           && fread(tag,1,TSLENG,fp) == TSLENG) {
               if ((forintg(tag) == EMLID || fidxst(forintg(tag)))
                && (tmpmsg=tagmsg(tag)) != NULL) {
                    vd->attach_file=fopen(dlname(tmpmsg),FOPRB);
                    if (vd->attach_file != NULL) {
                         if (!zip_open(spr("%ld.ATT",msgintg(tag)))) {
                              dlerr();
                              return(FALSE);
                         }
                         orgcrd=usaptr->creds;
                         gdlatt(usaptr->userid,tag);
                         vd->dlchg+=(orgcrd-usaptr->creds);
                         if (vd->flags&FIRSTATT) {
                              prfmsg(ATTCOPY);
                              vd->flags&=~FIRSTATT;
                         }
                         else {
                              prf("\b\b\b\b, ");
                         }
                         prf("%s...%c",l2as(msgintg(tag)),
                                       prop[vd->prop_index]);
                         vd->prop_index=(vd->prop_index+1)&3;
                         vd->qwkstt=COPYATT;
                    }
               }
               vd->attnum++;
          }
          else {
               vd->qwkstt=DONEATT;
          }
          close_if_open(fp);
          break;
     case COPYATT:
          prf("\b%c",prop[vd->prop_index]);
          vd->prop_index=(vd->prop_index+1)&3;
          n=fread(buffer,1,BUFFER_SIZE,vd->attach_file);
          if (n > 0) {
               if (!zip_write(buffer,n)) {
                    dlerr();
                    return(FALSE);
               }
          }
          else {
               if (!zip_close()) {
                    dlerr();
                    return(FALSE);
               }
               fclose(vd->attach_file);
               vd->attach_file=NULL;
               vd->qwkstt=NEXTATT;
          }
          break;
     case DONEATT:
          if (vd->attnum > 0) {
               prf("\b\b\b\b    ");
          }
          if (vd->num_attachments > 1) {
               prfmsg(ATTNOTE,vd->num_attachments,
                              (vd->attnum > 0) ? " new" : "");
          }
          else if (vd->num_attachments > 0) {
               prfmsg(ATTNOTE1,(vd->attnum > 0) ? " new" : "");
          }
          if (!startctl()) {
               dlerr();
          }
          break;
     case WRTFORS:
          if (!wrtfors()) {
               dlerr();
          }
          break;
     case FINDNLD:
          if (!findl()) {
               dlerr();
          }
          return(FALSE);
     }
     return(TRUE);
}

GBOOL
out_msg(VOID)                      /* output the current msg to the qwk file*/
{
     SHORT i,mblocks,cvtsiz;
     CHAR tag[TSLENG],qwkbuf[sizeof(struct qwkmhdr)];
     FILE *fp;
     INT rc;

     setmem(qwk_message_header,sizeof(struct qwkmhdr),' ');
     if (vd->msg.forum == EMLID) {
          qwk_message_header->status=STATUS_RECEIVER_ONLY_READ;
     }
     else {
          qwk_message_header->status=STATUS_NORMAL;
     }
     cpylim(qwk_message_header->number,l2as(vd->msg.msgid),7);
     cpylim(qwk_message_header->date,spr("%02d-%02d-%02d",
                                         ddmon(vd->msg.crdate),
                                         ddday(vd->msg.crdate),
                                         ddyear(vd->msg.crdate)%100),8);
     cpylim(qwk_message_header->time,spr("%02d:%02d",
                                         dthour(vd->msg.crtime),
                                         dtmin(vd->msg.crtime)),5);
     cpylim(qwk_message_header->whoto,vd->msg.to,25);
     cpylim(qwk_message_header->from,vd->msg.from,25);
     cpylim(qwk_message_header->subject,vd->msg.topic,25);
     if (vd->msg.rplto.msgid != 0L) {
          while ((i=readpar(vd->qwkwork,(struct message *)vdatmp,
                         (CHAR *)vdatmp+sizeof(struct message))) == GMEAGAIN) {
               /* rather not do this */
          }
          msgctx(vd->qwkwork,vd->msg.msgid);
          if (i == GMEOK) {
               cpylim(qwk_message_header->reference,
                      l2as(((struct message *)vdatmp)->msgid),8);
          }
     }
     /* qwk_message_header->blocks is filled in after text created */
     qwk_message_header->active=MESSAGE_ACTIVE;
     qwk_message_header->conference=vd->curfor;
     i=0;
     if (vd->text[0] != '\0') {
          while (vd->text[i+1] != '\0') {
               if (vd->text[i+1] == '\r' || vd->text[i+1] == '\n') {
                    vd->text[i+1]=PCBEOL;
               }
               i++;
          }
     }
#ifdef UNIX
     if (i == 0) {
          if (vd->text[0] == '\r' || vd->text[0] == '\n') {
               vd->text[0]=PCBEOL;
               i=1;
          }
          else {
               vd->text[1]=PCBEOL;
               vd->text[2]='\0';
               i=2;
          }
     }
     else {
          vd->text[i+1]=PCBEOL;
     }
#else
     vd->text[i+1]=PCBEOL;
#endif
     i++;
     if ((vd->msg.flags&FILATT) && tagatt(vd->qwkwork,&vd->msg,tag) == GMEOK) {
          vd->text[i+1]='\0';
          stlcat(vd->text,getmsg(ATTDTEXT),txtbufsz);
          fnd1st(&vd->fb,dlname(&vd->msg),0);
          sprintf(buffer,getmsg(ATTTEXT),vd->fb.ff_fsize);
          stlcat(vd->text,buffer,txtbufsz);
          if (qscptr->flags&QWKATT) {
               if ((fp=fopen(qwkpath("REQUEST.LST"),FOPAB)) == NULL) {
                    return(FALSE);
               }
               if (fwrite(tag,1,TSLENG,fp) != TSLENG) {
                    fclose(fp);
                    return(FALSE);
               }
               fclose(fp);
               sprintf(buffer,getmsg(AINCTEXT),vd->msg.msgid);
               stlcat(vd->text,buffer,TXTLEN+ATINFSZ);
          }
          else {
               sprintf(buffer,getmsg(AREQTEXT),QWKMAIL_NAME,vd->msg.msgid);
               stlcat(vd->text,buffer,TXTLEN+ATINFSZ);
               vd->num_attachments++;
          }
          while (vd->text[i+1] != '\0') {
               i++;
          }
     }
     while ((i-1)%128 != 0) {
          vd->text[i+1]=' ';
          i++;
     }
     mblocks=(i-1)/128;
     cpylim(qwk_message_header->blocks,spr("%d",1+mblocks),6);
     if ((cvtsiz=cvtData(qwk_message_header,qwkbuf,sizeof(struct qwkmhdr),
                         qwkmhdrFDA,CVTSERVER,CVTPACKED,CHAN_NUL)) == 0) {
          return(FALSE);
     }
     if (!zip_write(qwkbuf,cvtsiz)) {
          return(FALSE);
     }
     if (!zip_write(&vd->text[1],mblocks*128)) {
          return(FALSE);
     }
     qwk_index_record.block_number=long_to_mks(vd->current_block);
     qwk_index_record.conference=(CHAR)qwk_message_header->conference;
     if ((cvtsiz=cvtData(&qwk_index_record,qwkbuf,sizeof(struct qwkirec),
                         qwkirecFDA,CVTSERVER,CVTPACKED,CHAN_NUL)) == 0) {
          return(FALSE);
     }
     rc=fwrite(qwkbuf,cvtsiz,1,vd->index_file);
     if (rc <= 0) {
          return(FALSE);
     }
     vd->total_msgs++;
     vd->sig_msgs++;
     if (vd->msg.forum != EMLID && sameas(vd->msg.to,usaptr->userid)) {
          if (vd->pindex_file == NULL) {
               if ((vd->pindex_file=fopen(qwkpath("PERSONAL.NDX"),FOPWB)) == NULL) {
                    return(FALSE);
               }
          }
          rc=fwrite(qwkbuf,cvtsiz,1,vd->pindex_file);
          if (rc <= 0) {
               return(FALSE);
          }
          vd->personal_msgs++;
          vd->personal_sig_msgs++;
     }
     vd->current_block+=1+mblocks;
     return(TRUE);
}

VOID
cpylim(                            /* copy limited # chars (not '\0')      */
CHAR *dst,                         /*   to this destination                */
CHAR *src,                         /*   from this source                   */
UINT lim)                          /*   to this limit                      */
{
     UINT i;

     for (i=0 ; i < lim && src[i] != '\0' ; ++i) {
          dst[i]=src[i];
     }
}

VOID
dlerr(VOID)                        /* error occurred while forming packet  */
{
     abort_dl();
     prfmsg(FILERR);
     prfmsg(usrptr->substt=MENU1);
}

VOID
dlcbk(                             /* scan callback for forming packet     */
INT evt,                           /*   event code                         */
INT rc)                            /*   result of event                    */
{
     (VOID)rc;
     if (evt == EVTNEWF) {
          vd->curfor=getfid(gmexinf());
          newfor();
          outprf(usrnum);
          clrprf();
     }
}

VOID
newfor(VOID)                       /* got a new forum                      */
{
     if (vd->index_file != NULL) {
          fclose(vd->index_file);
          vd->index_file=NULL;
     }
     prf("\b ");
     prfmsg(SCNLIN2,vd->sig_msgs,vd->personal_sig_msgs);
     vd->sig_msgs=0;
     vd->personal_sig_msgs=0;
     prfmsg(SCNLN1,getfnm(vd->curfor),getftpc(vd->curfor));
}

GBOOL
startctl(VOID)                     /* start writing CONTROL.DAT            */
{
     UINT i;

     if ((vd->index_file=fopen(qwkpath("CONTROL.DAT"),FOPWB)) == NULL) {
          return(FALSE);
     }
     fprintf(vd->index_file,"%s\r\n",bbsttl);
     fprintf(vd->index_file,"(%s)\r\n",SVR_NAME);
     fprintf(vd->index_file,"%s\r\n",dataph);
     fprintf(vd->index_file,"Sysop, Sysop\r\n");
     fprintf(vd->index_file,"30000,%s\r\n",qwk_name);
     fprintf(vd->index_file,"%02d-%02d-%04d,%02d:%02d:%02d\r\n",
                         ddmon(today()),
                         ddday(today()),
                         ddyear(today()),
                         dthour(now()),
                         dtmin(now()),
                         dtsec(now()));
     i=0;
     while (usaptr->userid[i] != '\0') {
          fprintf(vd->index_file,"%c",toupper(usaptr->userid[i]));
          i++;
     }
     fprintf(vd->index_file,"\r\n");
     fprintf(vd->index_file,"\r\n");
     fprintf(vd->index_file,"0\r\n");
     fprintf(vd->index_file,"0\r\n");
     vd->totfpos=ftell(vd->index_file);
     fprintf(vd->index_file,"%d\r\n",numforums());
     vd->totfors=0;
     vd->curfor=0;
     fprintf(vd->index_file,"0\r\n");
     fprintf(vd->index_file,"E-mail\r\n");
     vd->qwkstt=WRTFORS;
     return(TRUE);
}

GBOOL
wrtfors(VOID)                      /* write forums into packet             */
{
     SHORT i;
     USHORT nfors;
     LONG savpos;
     const struct fordef *tmpdef;

     nfors=numforums();
     for (i=0; vd->curfor < nfors && i < 100; ++i,++vd->curfor) {
          tmpdef=fiddefp(vd->curfor);
          if (faccok(tmpdef->forum)) {
               ++vd->totfors;
               fprintf(vd->index_file,"%u\r\n",tmpdef->forum);
               fprintf(vd->index_file,"%s\r\n",tmpdef->name);
          }
     }
     if (vd->curfor == nfors) {
          savpos=ftell(vd->index_file);
          fseek(vd->index_file,vd->totfpos,SEEK_SET);
          i=strlen(spr("%d",numforums()));
          fprintf(vd->index_file,"%-*.*d",i,i,vd->totfors);
          fseek(vd->index_file,savpos,SEEK_SET);
          vd->qwkstt=FINDNLD;
     }
     return(TRUE);
}

GBOOL
findl(VOID)                        /* finish up download                   */
{
     GBOOL sendnews;
     FILE *f;
     struct ffblk fb;

     sendnews=(newsfl[0] != '\0' && fndfile(&fb,newsfl,0)
                                 && usaptr->usedat <= fb.ff_fdate);
     fprintf(vd->index_file,"\r\n");
     fprintf(vd->index_file,"%s\r\n",sendnews ? newsfl : "");
     fprintf(vd->index_file,"\r\n");
     fprintf(vd->index_file,"0\r\n");
     fprintf(vd->index_file,"24\r\n");
     fclose(vd->index_file);
     if (sendnews) {
          if (!zip_copy(newsfl,newsfl)) {
               return(FALSE);
          }
     }
     if (!zip_copy(qwkpath("CONTROL.DAT"),"CONTROL.DAT")) {
          return(FALSE);
     }
     if ((f=fopen(qwkpath("DOOR.ID"),FOPWA)) == NULL) {
          return(FALSE);
     }
     fprintf(f,"DOOR=GALQWK\r\n");
     fprintf(f,"VERSION=%s\r\n",version);
     fprintf(f,"SYSTEM=" SVR_NAME "\r\n");
     fprintf(f,"RECEIPT\r\n");
     fprintf(f,"CONTROLNAME=GALQWK\r\n");
     fprintf(f,"CONTROLTYPE=REQUEST\r\n");
     fclose(f);
     if (!zip_copy(qwkpath("DOOR.ID"),"DOOR.ID")) {
          return(FALSE);
     }
     if (!zip_done()) {
          return(FALSE);
     }
     if ((f=fopen(qwkpath(QSCIMAGE),FOPRWB)) != NULL) {
          fwrite(&vd->dlchg,sizeof(LONG),1,f);
          fclose(f);
     }
     fndfile(&fb,qwkpath(spr("%s.QWK",qwk_name)),0);
     prfmsg(QWKRDY1,l2as(fb.ff_fsize));
     outprf(usrnum);
     sbmqwk(qwksid[usrnum].protsl);
     numqwk--;
     ASSERT(numqwk >= 0);
     return(TRUE);
}

VOID
sbmqwk(CHAR *protsl)               /* submit QWK packet for download       */
{
     if ((qwksid[usrnum].fnewb4=ftgnew()) != 0) {
          sprintf(ftgptr->tagspc,"%s.QWK",qwk_name);
          ftgptr->flags=FTGABL;
          ftgptr->tshndl=tshqwk;
     }
     qwksid[usrnum].gotbeg=0;
     ftgsbm(protsl);
     if (usrptr->state == mjmstt && sameto("T",protsl)) {
          prfmsg(usrptr->substt=MENU1);
     }
}

INT
tshqwk(INT tshcod)                /* Handle tagspecs for application       */
{                 /* implicit inputs: ftgptr,usrnum,usrptr,usaptr */
                   /* for TSHFIN and TSHHUP, vdaptr is also valid */
                        /* return value meaning depends on tshcod */
                   /* implicit input/output in many cases: tshmsg */
                           /* expect caller to do outprf() if any */
     INT rc=0;

     qscptr=myqsptr();
     setmbk(qwkmail_msgs);
     switch (tshcod) {
     case TSHDSC:                  /* Describe tagspec in English          */
          sprintf(tshmsg,"QWK-mail packet %s.QWK",qwk_name);
          break;
     case TSHVIS:                  /* Visible to this user?                */
          setmem(tshmsg,TSHLEN,0);      /*(suppress V option)              */
          rc=1;
          break;
     case TSHBEG:             /* Begin download, check permission, reserve */
          strcpy(tshmsg,qwkpath(spr("%s.QWK",qwk_name)));
          strcpy(ftfscb->fname,ftgptr->tagspc);
          qwksid[usrnum].gotbeg=1;
          rc=1;
          break;
     case TSHEND:            /* End complete download of a file, unreserve */
          unlink(qwkpath("REQUEST.BAK"));
          qwksid[usrnum].wroteq=0;
          break;
     case TSHSKP:                  /* Skip incomplete download of a file   */
          qwksid[usrnum].gotbeg=2;
          break;
     case TSHFIN:                  /* Finish file transfer session         */
          switch (qwksid[usrnum].gotbeg) {
          case 0:
               if (ftgnew() != qwksid[usrnum].fnewb4) {
                    break;         /* file was tagged for later            */
               }
          case 2:                  /* download was aborted                 */
               restqs();
               prfmsg(PREVUS);
          case 1:                  /* TSHEND took care of things           */
               break;
          }
          rc=0;
          break;
     case TSHHUP:               /* Finish session because user logging off */
          break;
     }
     return(rc);
}

VOID
abort_dl(VOID)                /* abort the download for whatever reason    */
{
     if (vd->flags&REQOPEN) {
          clsgmerq(vd->qwkwork);
          vd->flags&=~REQOPEN;
     }
     if (vd->ots != NULL) {
          free(vd->ots);
          vd->ots=NULL;
     }
     close_if_open(vd->zip_file);
     vd->zip_file=NULL;
     close_if_open(vd->zip_central_file);
     vd->zip_central_file=NULL;
     close_if_open(vd->index_file);
     vd->index_file=NULL;
     close_if_open(vd->pindex_file);
     vd->pindex_file=NULL;
     close_if_open(vd->attach_file);
     vd->attach_file=NULL;
     dedcrd(-vd->dlchg,TRUE);
     vd->dlchg=0;
     numqwk--;
     ASSERT(numqwk >= 0);
}

VOID
upload_done(INT ok)           /* routine to invoke when finished uploading */
{
     setmbk(qwkmail_msgs);
     if (ok) {
          inigmerq(vd->qwkwork);
          while (gsndmsg(vd->qwkwork,&vd->msg,vd->text,&vd->text[1])
              == GMEAGAIN){
               /* bad form */
          }
          prfmsg(UPLDONE);
     }
     usrptr->state=mjmstt;
     exit_qwkmail();
     prfmsg(usrptr->substt=MENU1);
}

VOID
upload_check(VOID)            /* check for newly uploaded reply packets    */
{
     INT wait;

     if (ul->state != QWKIDLE) {
          if (cont_ul()) {
               wait=intra_packet;
          }
          else {
               ul->state=QWKIDLE;
               wait=inter_packet;
          }
     }
     else if (start_ul()) {
          wait=intra_packet;
     }
     else {
          ul->state=QWKIDLE;
          wait=inter_packet;
     }
     rtkick(wait,upload_check);
}

GBOOL                              /*   returns FALSE if no more packets   */
start_ul(VOID)                     /* start uploading a packet             */
{
     INT i;

     inigmerq(ul->work);
     inictx(ul->work,QWKMAIL_NAME,ESQFRU,EMLID,FIRSTM,0L);
     while ((i=nextmsg(ul->work,&ul->msg,vdatmp)) == GMEAGAIN) {
          /* bad form */
     }
     clsgmerq(ul->work);
     if (i != GMEOK) {
          return(FALSE);
     }
     ul->cachemsg=ul->msg.msgid;
     stlcpy(ul->user_from,&ul->msg.to[1],UIDSIZ);
     ul->msgs_posted=0;
     if (!unzip_open(dlname(&ul->msg),spr("%s.MSG",qwk_name))) {
          finish_ul(NOTVALID);
          return(FALSE);
     }
     if (unzip_read(buffer,128) < 128) {
          finish_ul(NOTVALID);
          return(FALSE);
     }
     i=0;
     while (i < 128 && buffer[i] > ' ') {
          i++;
     }
     if (i < 128) {
          buffer[i]='\0';
     }
     else {
          finish_ul(NOTVALID);
          return(FALSE);
     }
     if (!sameas(buffer,qwk_name)) {
          finish_ul(NOTVALID);
          return(FALSE);
     }
     ul->state=QWKNEW;
     return(TRUE);
}

GBOOL                              /*   returns FALSE when done            */
cont_ul(VOID)                      /* continue processing a packet         */
{
     switch (ul->state) {
     case QWKNEW:
          return(rep2msg());
     case QWKSNDN:
          sndnew();
          break;
     case QWKSNDR:
          sndrpl();
          break;
     default:
          catastro("Corrupted packet upload state in GALQWK: %d",ul->state);
     }
     return(TRUE);
}

GBOOL                              /*   returns FALSE if no more messages  */
rep2msg(VOID)                      /* get msg from packet into send buffers*/
{
     INT rc;
     USHORT i,blocks,unread,dstfor,fortmp;
     LONG flags,rplto;
     CHAR *cp,hdrbuf[QWKMHDRSIZ];

     if (unzip_read(hdrbuf,QWKMHDRSIZ) < QWKMHDRSIZ
      || cvtData(hdrbuf,ulhdr,QWKMHDRSIZ,qwkmhdrFDA,CVTPACKED,CVTSERVER,
                 CHAN_NUL) == 0) {
          finish_ul(POSTED0);
          return(FALSE);
     }
     if (ulhdr->active == MESSAGE_INACTIVE) {
          return(TRUE);
     }
     ul->forum=atoi(grabfld(ulhdr->number,sizeof(ulhdr->number)));
     cvtData(&ulhdr->conference,&fortmp,sizeof(USHORT),shortFDA,
             CVTPACKED,CVTSERVER,CHAN_NUL);
     if (ul->forum != fortmp) {
          finish_ul(NOTVALID);
          return(FALSE);
     }
     if (ulhdr->date[2] != '-' || ulhdr->date[5] != '-'
      || ulhdr->time[2] != ':') {
          finish_ul(NOTVALID);
          return(FALSE);
     }
     blocks=atoi(grabfld(ulhdr->blocks,sizeof(ulhdr->blocks)));
     if (blocks == 0 || blocks >= 128) {
          finish_ul(NOTVALID);
          return(FALSE);
     }
     unread=(blocks-1)*128;
     i=min(unread,TXTLEN-1);
     unread-=i;
     ul->text[0]='\r';
     i=unzip_read(&ul->text[1],i);
     while (i > 0 && (ul->text[i] == ' ' || ul->text[i] == PCBEOL)) {
          --i;
     }
     ul->text[i+1]='\0';
     strrpl(ul->text,PCBEOL,'\r');
     while (unread > 0) {
          unread-=unzip_read(buffer,min(unread,BUFFER_SIZE));
     }
     if (ul->forum != EMLID && !fidxst(ul->forum)) {
          return(TRUE);
     }
     setmem(&ul->msg,sizeof(struct message),0);
     stlcpy(ul->msg.to,grabfld(ulhdr->whoto,sizeof(ulhdr->whoto)),26);
     if (sameas(ul->msg.to,QWKMAIL_NAME)) {
          hdlreq();
          return(TRUE);
     }
     if (ulhdr->status == STATUS_RECEIVER_ONLY
      || ulhdr->status == STATUS_RECEIVER_ONLY_READ) { /* (E)mail reply */
          dstfor=EMLID;
     }
     else {
          dstfor=ul->forum;
     }
     flags=0;
     stlcpy(ul->msg.topic,grabfld(ulhdr->subject,sizeof(ulhdr->subject)),26);
     if (ul->msg.topic[0] == 'R'
      && ul->msg.topic[1] == 'R'
      && ul->msg.topic[2] == 'R') {
          movmem(&ul->msg.topic[3],ul->msg.topic,strlen(&ul->msg.topic[3])+1);
          flags|=RECREQ;
     }
     if (pfnerr(ul->user_from,ul->text,dstfor)) {
          return(TRUE);
     }
     inigmerq(ul->work);
     rplto=atol(grabfld(ulhdr->reference,sizeof(ulhdr->reference)));
     if (rplto == 0L) {
          if (pfnerr(ul->user_from,ul->msg.topic,dstfor)) {
               clsgmerq(ul->work);
               return(TRUE);
          }
          rc=sameas(ul->msg.to,"internet");
          if (rc || sameas(ul->msg.to,"network")) {
               for (cp=ul->text ; *cp != '\0' ; cp++) {
                    if (*cp != ' ' && *cp != '\r') {
                         break;
                    }
               }
               if (sameto("to:",cp) && (cp=skpwht(cp+3)) != '\0') {
                    i=0;
                    while (cp[i] != '\r' && cp[i] != '\0') {
                         i++;
                    }
                    stlcpy(ul->msg.to,cp,min(i+1,MAXADR));
                    cp=&cp[i];
                    movmem(cp,ul->text,strlen(cp)+1);
                    if (rc && !sameto(inetpfx,ul->msg.to)) {
                         i=strlen(ul->msg.to)+1;
                         i=min(i,MAXADR-strlen(inetpfx));
                         ul->msg.to[i-1]='\0';
                         movmem(ul->msg.to,&ul->msg.to[strlen(inetpfx)],i);
                         movmem(inetpfx,ul->msg.to,strlen(inetpfx));
                    }
               }
          }
     }
     else {
          stlcpy((CHAR *)vdatmp,ul->msg.topic,TPCSIZ);
          inormrd(ul->work,ul->user_from,ul->forum,rplto);
          while ((rc=readmsg(ul->work,&ul->msg,(CHAR *)vdatmp+TPCSIZ))
              == GMEAGAIN) {
               /* bad form */
          }
          if (rc == GMEOK) {
               stlcpy(ul->msg.to,ul->msg.from,MAXADR);
               if (!sameto((CHAR *)vdatmp,ul->msg.topic)) {
                    stlcpy(ul->msg.topic,(CHAR *)vdatmp,TPCSIZ);
                    if (pfnerr(ul->user_from,ul->msg.topic,dstfor)) {
                         clsgmerq(ul->work);
                         return(TRUE);
                    }
               }
          }
     }
     ul->msg.forum=dstfor;
     if (ul->msg.forum != EMLID) {
          flags&=~RECREQ;
     }
     ul->msg.flags=flags;
     stlcpy(ul->msg.from,ul->user_from,UIDSIZ);
     if (ul->msg.forum != EMLID && sameas(ul->msg.to,"all")) {
          *ul->msg.to='\0';
     }
     if (valadr(ul->work,ul->user_from,ul->msg.to,ul->msg.forum) != VALYES) {
          clsgmerq(ul->work);
          return(TRUE);
     }
     if ((ul->msg.flags&RECREQ)
      && valrrr(ul->work,ul->user_from,ul->msg.to,ul->msg.forum) != VALYES) {
          ul->msg.flags&=~RECREQ;
     }
     if (rplto == 0L) {
          ul->state=QWKSNDN;
     }
     else {
          ul->state=QWKSNDR;
     }
     return(TRUE);
}

VOID
hdlreq(VOID)                       /* handle requests                      */
{
     INT rc;
     CHAR *cp,tag[TSLENG];
     FILE *f;
     struct message *tmpmsg;

     ASSERT(!gmerqopn(ul->work));
     cp=grabfld(ulhdr->subject,sizeof(ulhdr->subject));
     if (sameto("request ",cp)) {
          if (onsysn(ul->user_from,1)
           && (ul->forum == EMLID
            || gforac(ul->user_from,ul->forum) >= DLAXES)) {
               inigmerq(ul->work);
               inormrd(ul->work,ul->user_from,ul->forum,atol(cp+8));
               tmpmsg=(struct message *)vdatmp;
               cp=((CHAR *)vdatmp)+sizeof(struct message);
               while ((rc=readmsg(ul->work,tmpmsg,cp)) == GMEAGAIN) {
                    /* bad form */
               }
               if (rc == GMEOK && (tmpmsg->flags&FILATT)
                && tagatt(ul->work,tmpmsg,tag) == GMEOK
                && (f=fopen(uqwkpath(othusn,"REQUEST.BAK"),FOPAB)) != NULL) {
                    fwrite(tag,TSLENG,1,f);
                    fclose(f);
               }
               clsgmerq(ul->work);
          }
     }
}

CHAR *                             /*   ptr to temp buffer w/ ASCIIZ string*/
grabfld(                           /* get a string field out of qwk header */
CHAR *src,                         /*   space-padded field buffer          */
UINT len)                          /*   size of field buffer               */
{
     static CHAR retbuf[26];

     ASSERT(src != NULL);
     ASSERT(len < sizeof(retbuf));
     setmem(retbuf,sizeof(retbuf),0);
     movmem(src,retbuf,len);
     return(unpad(skpwht(retbuf)));
}

GBOOL
pfnerr(                            /* is string too profane?               */
CHAR *uid,                         /*   User-ID to check for               */
CHAR *str,                         /*   string to check                    */
USHORT forum)                      /*   forum to use for profanity level   */
{
     INT maxpfn;

     if (uhskey(uid,syskey)) {
          return(FALSE);
     }
     if (forum == EMLID) {
          maxpfn=3-pfceil;
     }
     else {
          maxpfn=getdefp(forum)->pfnlvl;
          if (maxpfn == DFTPFN) {
               maxpfn=3-pfceil;
          }
          else {
               maxpfn=3-maxpfn;
          }
     }
     return(profan(str) > maxpfn);
}

VOID
sndnew(VOID)                       /* send a new message                   */
{
     INT rc;

     switch (rc=gmeSendMsg(ul->work,&ul->msg,ul->text,NULL,NULL)) {
     case GMEAGAIN:
          return;
     case GMEAFWD:
     case GMEOK:
          wrtnot(rc == GMEOK ? ul->msg.to : gmexinf());
          ++ul->msgs_posted;
          break;
     }
     ul->state=QWKNEW;
}

VOID
sndrpl(VOID)                       /* send a reply                         */
{
     INT rc;

     switch (rc=reply(ul->work,&ul->msg,ul->text,NULL,NULL)) {
     case GMEAGAIN:
          return;
     case GMEAFWD:
     case GMEOK:
          wrtnot(rc == GMEOK ? ul->msg.to : gmexinf());
          ++ul->msgs_posted;
          break;
     }
     ul->state=QWKNEW;
}

static VOID
wrtnot(                            /* notify recipient of new mail to them */
const CHAR *to)                    /*   to whom the message was written    */
{
     setmbk(qwkmail_msgs);
     if (onsys(to) && !sameas(ul->user_from,to)) {
          if (ul->msg.forum == EMLID) {
               prfmlt(EWRITTEN,ul->user_from);
          }
          else {
               prfmlt(SWRITTEN,ul->user_from,getfnm(ul->msg.forum));
          }
          injoth();
     }
     rstmbk();
}

VOID
finish_ul(                         /* delete packet, confirm post          */
INT error_msg)                     /*   error message (if any)             */
{
     setmbk(qwkmail_msgs);
     if (onsysn(ul->user_from,1)) {
          prfmlt(FMMMGR);
          if (ul->msgs_posted > 0) {
               if (ul->msgs_posted == 1) {
                    prfmlt(POSTED1);
               }
               else {
                    prfmlt(POSTED2,ul->msgs_posted);
               }
          }
          else {
               prfmlt(error_msg);
          }
          if (exist(uqwkpath(othusn,"REQUEST.BAK"))) {
               prfmlt(POSTEDR);
          }
          injoth();
     }
     unzip_close();
     delete_ul();
     rstmbk();
}

VOID
delete_ul(VOID)               /* del attachment for msg number ul->msg_number*/
{
     inigmerq(ul->work);
     inormrd(ul->work,QWKMAIL_NAME,EMLID,ul->cachemsg);
     while (delmsg(ul->work) == GMEAGAIN) {
          /* bad form */
     }
     clsgmerq(ul->work);
     ul->cachemsg=0L;
}

/* zip file utility routines */
GBOOL
zip_init(
CHAR *fn)
{
     if ((vd->zip_file=fopen(fn,FOPWB"+")) == NULL) {
          return(FALSE);
     }
     if ((vd->zip_central_file=fopen(qwkpath("ZCENTRAL"),FOPWB"+")) == NULL) {
          return(FALSE);
     }
     vd->znum=0;

     return(TRUE);
}

GBOOL
zip_open(
CHAR *fn)
{
     INT rc;
     CHAR zipbuf[ZLH_SIZE+GCMAXFNM];

     vd->zoffset=ftell(vd->zip_file);

     vd->zlhdr.signature   =ZIP_LOCAL_HEADER_SIG;
     vd->zlhdr.ver_needed  =0;
     vd->zlhdr.gen_flags   =0;
     vd->zlhdr.compression =ZIP_STORED;
     vd->zlhdr.ftime       =now();
     vd->zlhdr.fdate       =today();
     vd->zlhdr.crc         =0xFFFFFFFFL;
     vd->zlhdr.compressed  =0;
     vd->zlhdr.uncompressed=0;
     vd->zlhdr.filename_len=strlen(fn);
     vd->zlhdr.extra_len   =0;
     strcpy(vd->zlhdr.filename,fn);
     cvtData(&vd->zlhdr,zipbuf,sizeof(struct ziplhdr),ziplhdrFDA,
             CVTSERVER,CVTPACKED,CHAN_NUL);
     rc=fwrite(zipbuf,ZLH_SIZE+strlen(fn),1,vd->zip_file);
     if (rc <= 0) {
          return(FALSE);
     }
     return(TRUE);
}

GBOOL
zip_write(
VOID *buf,
INT len)
{
     INT rc;

     vd->zlhdr.crc=crc32block(buf,len,vd->zlhdr.crc);

     rc=fwrite(buf,len,1,vd->zip_file);
     if (rc <= 0) {
          return(FALSE);
     }
     vd->zlhdr.uncompressed+=len;

     return(TRUE);
}

GBOOL
zip_close(VOID)
{
     INT rc,cvtsiz;
     CHAR zipbuf[max(sizeof(struct ziplhdr),sizeof(struct zipcrec))];

     vd->zlhdr.crc=~vd->zlhdr.crc;
     vd->zlhdr.compressed=vd->zlhdr.uncompressed;

     fseek(vd->zip_file,vd->zoffset+14,SEEK_SET);
     cvtData(&vd->zlhdr,zipbuf,sizeof(struct ziplhdr),ziplhdrFDA,
             CVTSERVER,CVTPACKED,CHAN_NUL);
     rc=fwrite(&zipbuf[14],12,1,vd->zip_file);
     if (rc <= 0) {
          return(FALSE);
     }
     fseek(vd->zip_file,0,SEEK_END);

     zip_central_record->signature          =ZIP_CENTRAL_RECORD_SIG;
     zip_central_record->ver_made_by        =0;
     zip_central_record->ver_needed         =vd->zlhdr.ver_needed;
     zip_central_record->gen_flags          =vd->zlhdr.gen_flags;
     zip_central_record->compression        =vd->zlhdr.compression;
     zip_central_record->ftime              =vd->zlhdr.ftime;
     zip_central_record->fdate              =vd->zlhdr.fdate;
     zip_central_record->crc                =vd->zlhdr.crc;
     zip_central_record->compressed         =vd->zlhdr.compressed;
     zip_central_record->uncompressed       =vd->zlhdr.uncompressed;
     zip_central_record->filename_len       =vd->zlhdr.filename_len;
     zip_central_record->extra_len          =vd->zlhdr.extra_len;
     zip_central_record->fcomment_len       =0;
     zip_central_record->disk_start         =0;
     zip_central_record->internal_attrs     =0;
     zip_central_record->external_attrs     =0;
     zip_central_record->local_header_offset=vd->zoffset;
     strcpy(zip_central_record->filename,vd->zlhdr.filename);

     if ((cvtsiz=cvtData(zip_central_record,zipbuf,sizeof(struct zipcrec),
                         zipcrecFDA,CVTSERVER,CVTPACKED,CHAN_NUL)) == 0) {
          return(0);
     }
     rc=fwrite(zipbuf,cvtsiz-13+strlen(vd->zlhdr.filename),1,
               vd->zip_central_file);
     if (rc <= 0) {
          return(FALSE);
     }
     vd->znum++;

     return(TRUE);
}

GBOOL
zip_copy(
CHAR *src,
CHAR *dest)
{
     FILE *srcf;
     INT n;

     if ((srcf=fopen(src,FOPRB)) == NULL) {
          return(FALSE);
     }
     if (!zip_open(dest)) {
          return(FALSE);
     }
     while (1) {
          n=fread(buffer,1,BUFFER_SIZE,srcf);
          if (n <= 0) {
               break;
          }
          if (!zip_write(buffer,n)) {
               return(FALSE);
          }
     }

     if (!zip_close()) {
          return(FALSE);
     }
     fclose(srcf);

     return(TRUE);
}

GBOOL
zip_done(VOID)
{
     INT n;
     LONG central_offset,central_size;
     INT rc,cvtsiz;
     CHAR zipbuf[sizeof(struct zipecrec)];

     central_offset=ftell(vd->zip_file);
     central_size=ftell(vd->zip_central_file);

     fseek(vd->zip_central_file,0,SEEK_SET);
     while (1) {
          n=fread(buffer,1,BUFFER_SIZE,vd->zip_central_file);
          if (n <= 0) {
               break;
          }
          rc=fwrite(buffer,n,1,vd->zip_file);
          if (rc <= 0) {
               return(FALSE);
          }
     }

     zip_end_central_record->signature           =ZIP_END_CENTRAL_RECORD_SIG;
     zip_end_central_record->disk_number         =0;
     zip_end_central_record->central_dir_disk    =0;
     zip_end_central_record->central_entries_disk=vd->znum;
     zip_end_central_record->central_entries     =vd->znum;
     zip_end_central_record->central_size        =central_size;
     zip_end_central_record->central_start_offset=central_offset;
     zip_end_central_record->zcomment_len        =0;
     if ((cvtsiz=cvtData(zip_end_central_record,zipbuf,sizeof(struct zipecrec),
                         zipecrecFDA,CVTSERVER,CVTPACKED,CHAN_NUL)) == 0) {
          return(FALSE);
     }
     rc=fwrite(zipbuf,cvtsiz,1,vd->zip_file);
     if (rc <= 0) {
          return(FALSE);
     }
     fclose(vd->zip_file);
     vd->zip_file=NULL;

     fclose(vd->zip_central_file);
     vd->zip_central_file=NULL;

     unlink(qwkpath("ZCENTRAL"));

     return(TRUE);
}

                              /* unzip utility routines                    */
GBOOL
unzip_open(
const CHAR *zn,
CHAR *fn)
{
     SHORT i;
     SHORT rc;
     CHAR zipbuf[ZLH_SIZE];

     if ((uz.zip_file=fopen(zn,FOPRB)) == NULL) {
          return(FALSE);
     }
     rc=fread(zipbuf,ZLH_SIZE,1,uz.zip_file);
     if (rc <= 0) {
          fclose(uz.zip_file);
          uz.zip_file=NULL;
          return(FALSE);
     }
     cvtData(zipbuf,&uz.zlhdr,ZLH_SIZE,ziplhdrFDA,CVTPACKED,CVTSERVER,CHAN_NUL);
     if (uz.zlhdr.signature != ZIP_LOCAL_HEADER_SIG) {
          fseek(uz.zip_file,0,SEEK_SET);
          uz.zlhdr.compression=ZIP_STORED;
          uz.zlhdr.compressed=0x7FFFFFFFL;
          return(TRUE);
     }

     setmem(uz.zlhdr.filename,13,0);
     rc=fread(uz.zlhdr.filename,min(uz.zlhdr.filename_len,12),1,uz.zip_file);
     if (rc <= 0) {
          fclose(uz.zip_file);
          uz.zip_file=NULL;
          return(FALSE);
     }
     if (fn != NULL && !sameas(fn,uz.zlhdr.filename)) {
          fclose(uz.zip_file);
          uz.zip_file=NULL;
          return(FALSE);
     }

     if (uz.zlhdr.extra_len) {
          fseek(uz.zip_file,uz.zlhdr.extra_len,SEEK_CUR);
     }

     uz.zip_eof=0;
     uz.crc=0xFFFFFFFFL;
     uz.input_index=0;
     uz.input_avail=0;
     uz.input_offset=0;
     uz.output_index=0;
     uz.output_avail=0;
     uz.output_offset=0;
     uz.bits_left=0;
     setmem(uz.output_buf,OUTPUT_BUFFER_SIZE,0);

     switch (uz.zlhdr.compression) {
     case ZIP_STORED:
          break;
     case ZIP_SHRUNK:
          uz.code_bits=INIT_BITS;
          uz.first_free=FIRST_ENTRY;
          setmem(uz.prefix_of,ZIP_BUFFER_SIZE*sizeof(SHORT),0xff);
          for (i=0; i < 256; i++) {
               uz.prefix_of[i]=0;
               uz.suffix_of[i]=i;
          }
          uz.last_code=read_bits(uz.code_bits);
          if (uz.zip_eof) {
               fclose(uz.zip_file);
               uz.zip_file=NULL;
               return(FALSE);
          }
          uz.final_char=uz.last_code;
          output_byte(uz.final_char);
          uz.stack_index=0;
          break;
     case ZIP_IMPLODED:
          if (uz.zlhdr.gen_flags&2) {
               uz.lower_dict_bits=7;
          }
          else {
               uz.lower_dict_bits=6;
          }
          if (uz.zlhdr.gen_flags&4) {
               uz.min_match_length=3;
               load_sf_tree(&uz.literal_tree,256);
               uz.literal_tree_present=1;
          }
          else {
               uz.min_match_length=2;
               uz.literal_tree_present=0;
          }
          load_sf_tree(&uz.length_tree,64);
          load_sf_tree(&uz.distance_tree,64);
          break;
     case ZIP_DEFLATED:
          zp.lbits=9;
          zp.dbits=6;
          zp.wp=0;
          zp.bk=0;
          zp.bb=0;
          zp.h2=0;
          zp.hufts=0;
          zp.defstate=INFLATE2;
          zp.csize=uz.zlhdr.compressed;
          break;
     default:
          fclose(uz.zip_file);
          uz.zip_file=NULL;
          return(FALSE);
     }
     return(TRUE);
}

SHORT
unzip_read(
CHAR *p,
INT n)
{
     SHORT start_output,i;

     start_output=uz.output_index-uz.output_avail;
     if (start_output < 0) {
          start_output+=OUTPUT_BUFFER_SIZE;
     }
     switch (uz.zlhdr.compression) {
     case ZIP_STORED:
          if (uz.zlhdr.compressed < n) {
               n=(INT)uz.zlhdr.compressed;
          }
          i=fread(p,1,n,uz.zip_file);
          if (i != 0 && i != -1) {
               uz.zlhdr.compressed-=i;
          }
          else {
               i=0;
          }
          return(i);
     case ZIP_SHRUNK:
          {
               SHORT this_code, code;
               SHORT prefix;

               while (uz.output_avail < n && !uz.zip_eof) {
read_again:
                    code=read_bits(uz.code_bits);
                    if (uz.zip_eof) {
                         break;
                    }
                    if (code == CLEAR_CODE) {
                         code=read_bits(uz.code_bits);
                         switch (code) {
                         case 1:
                              uz.code_bits++;
                              break;
                         case 2:
                              for (code=FIRST_ENTRY ; code < uz.first_free ; code++) {
                                   uz.prefix_of[code]|=0x8000;
                              }
                              for (code=FIRST_ENTRY ; code < uz.first_free ; code++) {
                                   prefix=uz.prefix_of[code]&0x7fff;
                                   if (prefix >= FIRST_ENTRY) {
                                        uz.prefix_of[prefix]&=0x7fff;
                                   }
                              }
                              for (code=FIRST_ENTRY ; code < uz.first_free ; code++) {
                                   if (uz.prefix_of[code]&0x8000) {
                                        uz.prefix_of[code]=-1;
                                   }
                              }
                              uz.first_free=FIRST_ENTRY;
                              while (uz.first_free < MAX_CODE && uz.prefix_of[uz.first_free] != -1) {
                                   uz.first_free++;
                              }
                              break;
                         }
                         goto read_again;
                    }
                    this_code=code;
                    if (uz.prefix_of[code] == -1) {
                         uz.stack[uz.stack_index++]=uz.final_char;
                         code=uz.last_code;
                    }
                    while (code >= FIRST_ENTRY) {
                         uz.stack[uz.stack_index++]=uz.suffix_of[code];
                         code=uz.prefix_of[code];
                    }
                    uz.final_char=uz.suffix_of[code];
                    uz.stack[uz.stack_index++]=uz.final_char;
                    while (uz.stack_index) {
                         output_byte(uz.stack[--uz.stack_index]);
                    }
                    code=uz.first_free;
                    if (code < MAX_CODE) {
                         uz.prefix_of[code]=uz.last_code;
                         uz.suffix_of[code]=uz.final_char;
                         while (uz.first_free < MAX_CODE && uz.prefix_of[uz.first_free] != -1) {
                              uz.first_free++;
                         }
                    }
                    uz.last_code=this_code;
               }
          }
          break;
     case ZIP_IMPLODED:
          {
               SHORT distance, length;
            SHORT back_index;

               while (uz.output_avail < n && !uz.zip_eof && uz.output_offset < uz.zlhdr.uncompressed) {
                    if (read_bits(1)) {
                         if (uz.literal_tree_present) {
                              output_byte(read_via_tree(&uz.literal_tree));
                              if (uz.zip_eof) {
                                   break;
                              }
                         }
                         else {
                              output_byte(read_bits(8));
                              if (uz.zip_eof) {
                                   break;
                              }
                         }
                    }
                    else {
                         if (uz.zip_eof) {
                              break;
                         }
                         distance=read_bits(uz.lower_dict_bits);
                         distance+=(read_via_tree(&uz.distance_tree)<<uz.lower_dict_bits);
                         if (uz.zip_eof) {
                              break;
                         }
                         length=read_via_tree(&uz.length_tree) + uz.min_match_length;
                         if (uz.zip_eof) {
                              break;
                         }
                         if (length == 63 + uz.min_match_length) {
                              length += read_bits(8);
                         }
                         back_index=uz.output_index - (distance+1);
                         if (back_index < 0) {
                              /* uz.output_buf is initialized to zero so we */
                              /* deal with the output wrap below 0 properly */
                              back_index += OUTPUT_BUFFER_SIZE;
                         }
                         while (length) {
                              output_byte(uz.output_buf[back_index]);
                              back_index=(back_index + 1) % OUTPUT_BUFFER_SIZE;
                              length--;
                         }
                    }
               }
          }
          break;
     case ZIP_DEFLATED:
          {
               while (outcnt < n && zp.defstate != INFOK
                && zp.defstate != INFNOK) {
                    inflate();
               }
               if (zp.defstate == INFNOK) {
                    return(0);
               }
               i=0;
               while (i < n && outcnt) {
                    *p++=uz.output_buf[i];
                    i++;
                    outcnt--;
               }
               movmem(&uz.output_buf[i],uz.output_buf,outcnt);
               return(i);
          }
     }
     i=0;
     while (i < n && uz.output_avail) {
          *p++=uz.output_buf[start_output];
          start_output=(start_output+1)%OUTPUT_BUFFER_SIZE;
          i++;
          uz.output_avail--;
     }
     return(i);
}

static GBOOL
initfixd(VOID)                     /* init inflate of fixed code block     */
{
     SHORT i;

     for (i=0 ; i < 144 ; i++) {
          zp.ll4[i]=8;
     }
     for (; i < 256 ; i++) {
          zp.ll4[i]=9;
     }
     for (; i < 280 ; i++) {
          zp.ll4[i]=7;
     }
     for (; i < 288 ; i++) {
          zp.ll4[i]=8;
     }
     zp.bl4=7;
     if ((i=huft_build(zp.ll4,288,257,cplens,cplext,&zp.tl4,&zp.bl4)) != 0) {
          return(FALSE);
     }
     for (i=0 ; i < 30 ; i++) {
          zp.ll4[i]=5;
     }
     zp.bd4=5;
     if ((i=huft_build(zp.ll4,30,0,cpdist,cpdext,&zp.td4,&zp.bd4)) > 1) {
          huft_free(zp.tl4);
          return(FALSE);
     }
     return(TRUE);
}

VOID
inflate(VOID)                      /* Routine to INFLATE deflated .ZIPs    */
{
     USHORT i;

     switch (zp.defstate) {
     case INFLATE2:
          zp.b3=zp.bb;
          zp.k3=zp.bk;
          NEEDBITS4(1)
          zp.e2=(SHORT)zp.b3&1;
          DUMPBITS4(1)
          NEEDBITS4(2)
          zp.t3=(USHORT)zp.b3&3;
          DUMPBITS4(2)
          zp.bb=zp.b3;
          zp.bk=zp.k3;
          switch (zp.t3) {
          case 2:
               zp.defstate=INFLATE3;
               break;
          case 1:
               if (initfixd()) {
                    zp.defstate=INFLATE4;
                    break;
               }
          default:
               zp.defstate=INFNOK;
          }
          return;
     case INFLATE3:
          zp.b4=zp.bb;
          zp.k4=zp.bk;
          NEEDBITS3(5)
          zp.nl4=257+((USHORT)zp.b4&0x1f);
          DUMPBITS3(5)
          NEEDBITS3(5)
          zp.nd4=1+((USHORT)zp.b4&0x1f);
          DUMPBITS3(5)
          NEEDBITS3(4)
          zp.nb4=4+((USHORT)zp.b4&0xf);
          DUMPBITS3(4)
          if (zp.nl4 > 286 || zp.nd4 > 30) {
               zp.defstate=INFNOK;
               return;
          }
          for (zp.j4=0 ; zp.j4 < zp.nb4 ; zp.j4++) {
               NEEDBITS3(3)
               zp.ll4[border[zp.j4]]=(USHORT)zp.b4&7;
               DUMPBITS3(3)
          }
          for ( ; zp.j4 < 19 ; zp.j4++) {
               zp.ll4[border[zp.j4]]=0;
          }
          zp.bl4=7;
          if ((zp.i4=huft_build(zp.ll4,19,19,NULL,NULL,
              &zp.tl4,&zp.bl4)) != 0) {
               if (zp.i4 == 1) {
                    huft_free(zp.tl4);
               }
               zp.defstate=INFNOK;
               return;
          }
          zp.n4=zp.nl4+zp.nd4;
          zp.m4=mask_bits[zp.bl4];
          zp.i4=zp.l4=0;
          while ((USHORT)zp.i4 < zp.n4) {
               NEEDBITS3((USHORT)zp.bl4)
               zp.j4=(zp.td4=zp.tl4+((USHORT)zp.b4&zp.m4))->b;
               DUMPBITS3(zp.j4)
               zp.j4=zp.td4->v.n;
               if (zp.j4 < 16) {
                    zp.ll4[zp.i4++]=zp.l4=zp.j4;
               }
               else if (zp.j4 == 16) {
                    NEEDBITS3(2)
                    zp.j4=3+((USHORT)zp.b4&3);
                    DUMPBITS3(2)
                    if ((USHORT)zp.i4+zp.j4 > zp.n4) {
                         zp.defstate=INFNOK;
                         return;
                    }
                    while (zp.j4--)
                         zp.ll4[zp.i4++]=zp.l4;
               }
               else if (zp.j4 == 17) {
                    NEEDBITS3(3)
                    zp.j4=3+((USHORT)zp.b4&7);
                    DUMPBITS3(3)
                    if ((USHORT)zp.i4+zp.j4 > zp.n4) {
                         zp.defstate=INFNOK;
                         return;
                    }
                    while (zp.j4--) {
                         zp.ll4[zp.i4++]=0;
                    }
                    zp.l4=0;
               }
               else {
                    NEEDBITS3(7)
                    zp.j4=11+((USHORT)zp.b4&0x7f);
                    DUMPBITS3(7)
                    if ((USHORT)zp.i4+zp.j4 > zp.n4) {
                         zp.defstate=INFNOK;
                         return;
                    }
                    while (zp.j4--) {
                         zp.ll4[zp.i4++]=0;
                    }
                    zp.l4=0;
               }
          }
          huft_free(zp.tl4);
          zp.bb=zp.b4;
          zp.bk=zp.k4;
          zp.bl4=zp.lbits;
          if ((zp.i4=huft_build(zp.ll4,zp.nl4,257,cplens,cplext,
               &zp.tl4,&zp.bl4)) != 0) {
               if (zp.i4 == 1) {
                    huft_free(zp.tl4);
               }
               zp.defstate=INFNOK;
               return;
          }
          zp.bd4=zp.dbits;
          if ((zp.i4=huft_build(zp.ll4+zp.nl4,zp.nd4,0,
              cpdist,cpdext,&zp.td4,&zp.bd4)) != 0) {
               if (zp.i4 == 1) {
                    huft_free(zp.td4);
               }
               huft_free(zp.tl4);
               zp.defstate=INFNOK;
               return;
          }
          zp.defstate=INFLATE4;
          return;
     case INFLATE4:
          zp.b5=zp.bb;
          zp.k5=zp.bk;
          zp.w5=zp.wp;
          zp.ml5=mask_bits[zp.bl4];
          zp.md5=mask_bits[zp.bd4];
          zp.defstate=INFLATE5;
          return;
     case INFLATE5:
          NEEDBITS5((USHORT)zp.bl4)
          if ((zp.e5=(zp.t5=zp.tl4+
              ((USHORT)zp.b5&zp.ml5))->e) > 16) {
               do {
                    if (zp.e5 == 99) {
                         zp.defstate=INFNOK;
                    }
                    DUMPBITS5(zp.t5->b)
                    zp.e5-=16;
                    NEEDBITS5(zp.e5)
               } while ((zp.e5=(zp.t5=zp.t5->v.t+
                 ((USHORT)zp.b5&mask_bits[zp.e5]))->e) > 16);
          }
          DUMPBITS5(zp.t5->b)
          if (zp.e5 == 16) {
               slide[zp.w5++]=outbyte((CHAR)zp.t5->v.n);
               if (zp.w5 == WSIZE) {
                    zp.w5=0;
               }
          }
          else {
               /* exit if end of block */
               if (zp.e5 == 15) {
                    zp.defstate=INFLATE6;
                    return;
               }
               NEEDBITS5(zp.e5)
               zp.n5=zp.t5->v.n+((USHORT)zp.b5&mask_bits[zp.e5]);
               DUMPBITS5(zp.e5);
               NEEDBITS5((USHORT)zp.bd4)
               if ((zp.e5=(zp.t5=zp.td4+
                   ((USHORT)zp.b5&zp.md5))->e) > 16) {
                    do {
                         if (zp.e5 == 99) {
                              zp.defstate=INFNOK;
                         }
                         DUMPBITS5(zp.t5->b)
                         zp.e5-=16;
                         NEEDBITS5(zp.e5)
                    } while ((zp.e5=(zp.t5=zp.t5->v.t+
                       ((USHORT)zp.b5&mask_bits[zp.e5]))->e)
                        > 16);
               }
               DUMPBITS5(zp.t5->b)
               NEEDBITS5(zp.e5)
               zp.d5=zp.w5-zp.t5->v.n-((USHORT)zp.b5&mask_bits[zp.e5]);
               DUMPBITS5(zp.e5)
               zp.defstate=INFLATE7;
               return;
          }
          return;
     case INFLATE6:
          zp.wp=zp.w5;
          zp.bb=zp.b5;
          zp.bk=zp.k5;
          huft_free(zp.tl4);
          huft_free(zp.td4);
          if (zp.hufts > zp.h2) {
               zp.h2=zp.hufts;
          }
          if (!zp.e2) {
               zp.hufts=0;
               zp.defstate=INFLATE2;
               return;
          }
          zp.defstate=INFOK;
          return;
     case INFLATE7:
          zp.n5-=(zp.e5=(zp.e5=WSIZE-((zp.d5&=WSIZE-1)
           > zp.w5 ? zp.d5 : zp.w5)) > zp.n5 ? zp.n5 :
            zp.e5);
          if (zp.w5-zp.d5 >= zp.e5) {
               for (i=0 ; i < zp.e5 ; i++) {
                    slide[zp.w5+i]=outbyte(slide[zp.d5+i]);
               }
               zp.w5+=zp.e5;
               zp.d5+=zp.e5;
          }
          else {
               do {
                    slide[zp.w5++]=outbyte(slide[zp.d5++]);
               } while (--zp.e5);
          }
          if (zp.w5 == WSIZE) {
               zp.w5=0;
          }
          if (!zp.n5) { /* 3 */
               zp.defstate=INFLATE5;
          }
          return;
     }
}

SHORT                              /* build huftman tables                 */
huft_build(
USHORT *b,
USHORT n,
USHORT s,
USHORT *d,
USHORT *e,
struct huft **t,
SHORT *m)
{
     USHORT a;                     /* counter for codes of length k        */
     USHORT c[BMAX+1];             /* bit length count table               */
     USHORT f;                     /* i repeats in table every f entries   */
     SHORT g;                      /* maximum code length                  */
     SHORT h;                      /* table level                          */
     USHORT i;                     /* counter, current code                */
     USHORT j;                     /* counter                              */
     SHORT k;                      /* number of bits in current code       */
     SHORT l;                      /* bits per table (returned in m)       */
     USHORT *p;                    /* pointer into c[], b[], or v[]        */
     struct huft *q;               /* points to current table              */
     struct huft r;                /* table entry for structure assignment */
     struct huft *u[BMAX];         /* table stack                          */
     USHORT v[N_MAX];              /* values in order of bit length        */
     SHORT w;                      /* bits before this table == (l * h)    */
     USHORT x[BMAX+1];             /* bit offsets, then code stack         */
     USHORT *xp;                   /* pointer into x                       */
     SHORT y;                      /* number of dummy codes added          */
     USHORT z;                     /* number of entries in current table   */

     memset(c,0,sizeof(c));
     p=b;
     i=n;
     do {
          c[*p++]++;
     } while (--i);
     if (c[0] == n) {
          *t=(struct huft *)NULL;
          *m=0;
          return(0);
     }
     l=*m;
     for (j=1 ; j <= BMAX ; j++) {
          if (c[j]) {
               break;
          }
     }
     k=j;
     if ((USHORT)l < j) {
          l=j;
     }
     for (i=BMAX ; i ; i--) {
          if (c[i]) {
               break;
          }
     }
     g=i;
     if ((USHORT)l > i) {
          l=i;
     }
     *m=l;
     for (y=1<<j ; j < i ; j++,y<<=1) {
          if ((y-=c[j]) < 0) {
               return(2);
          }
     }
     if ((y-=c[i]) < 0) {
          return(2);
     }
     c[i]+=y;
     x[1]=j=0;
     p=c+1;
     xp=x+2;
     while (--i) {
          *xp++=(j+=*p++);
     }
     p=b;
     i=0;
     do {
          if ((j=*p++) != 0) {
               v[x[j]++]=i;
          }
     } while (++i < n);

     x[0]=i=0;
     p=v;
     h=-1;
     w=-l;
     u[0]=(struct huft *)NULL;
     q=(struct huft *)NULL;
     z=0;

     for ( ; k <= g ; k++) { /* 1 */
          a=c[k];
          while (a--) { /* 2 */
               while (k > w+l) { /* 3 */
                    h++;
                    w+=l;
                    z=(z=g-w) > (USHORT)l ? l : z;
                    if ((f=1 << (j=k-w)) > a+1) {
                         f-=a+1;
                         xp=c+k;
                         while (++j < z) {
                              if ((f <<= 1) <= *++xp) {
                                   break;
                              }
                              f-=*xp;
                         }
                    }
                    z=1<<j;

                    if ((q=(struct huft *)malloc((z+1)*sizeof(struct huft))) ==
                        (struct huft *)NULL) {
                         if (h) {
                              huft_free(u[0]);
                         }
                         return(3);
                    }
                    zp.hufts+=z+1;
                    *t=q+1;
                    *(t=&(q->v.t))=(struct huft *)NULL;
                    u[h]=++q;
                    if (h) {
                         x[h]=i;
                         r.b=(CHAR)l;
                         r.e = (CHAR)(16 + j);
                         r.v.t=q;
                         j=i>>(w-l);
                         u[h-1][j]=r;
                    }
               }
               r.b=(CHAR)(k-w);
               if (p >= v+n) {
                    r.e=99;
               }
               else if (*p < s) {
                    r.e=(CHAR)(*p < 256 ? 16 : 15);
                    r.v.n=*p++;
               }
               else {
                    r.e=(CHAR)e[*p-s];
                    r.v.n=d[*p++-s];
               }
               f=1 << (k-w);
               for (j=i>>w ; j < z ; j+=f) {
                    q[j] = r;
               }
               for (j=1<<(k-1) ; i&j ; j>>=1) {
                    i^=j;
               }
               i^=j;
               while ((i&((1<<w)-1)) != x[h]) {
                    h--;
                    w-=l;
               }
          }
     }
     return(y != 0 && g != 1);
}

SHORT
huft_free(                         /* "Release" Huftman tables             */
struct huft *t)
{
     struct huft *p, *q;

     p=t;
     while (p != (struct huft *)NULL) {
          q=(--p)->v.t;
          free(p);
          p=q;
     }
     return(0);
}

SHORT
readbyte(                          /* Read in next byte from .ZIP file     */
USHORT *x)
{
     if (zp.csize-- <= 0) {
          return(0);
     }

     if (uz.input_avail == 0) {
          if ((uz.input_avail=fread((CHAR *)uz.input_buf,1,INPUT_BUFFER_SIZE,
                                            uz.zip_file)) <= 0) {
               uz.input_avail=0;
               return(0);
          }
          /* buffer ALWAYS starts on a block boundary:  */
          zp.cur_zip_bs+=INPUT_BUFFER_SIZE;
          zp.inptr=uz.input_buf;
     }
     *x=*zp.inptr;
     zp.inptr++;
     --(uz.input_avail);
     return(8);
}

SHORT
outbyte(
SHORT intc)                /* Output inflated byte                 */
{
     if (outcnt == OUTPUT_BUFFER_SIZE) {
          catastro("QWKMAIL: OUTPUT BUFFER OVERFLOW!");
     }
     uz.output_buf[outcnt++]=(byte)intc;
     return(intc);
}

VOID
unzip_close(VOID)
{
     close_if_open(uz.zip_file);
     uz.zip_file=NULL;
}

VOID
load_sf_tree(
struct sf_tree *t,
SHORT tree_size)
{
     t->entries=tree_size;

     {
          SHORT compressed_tree_bytes;
          SHORT i,num,len;

          compressed_tree_bytes=read_bits(8)+1;
          i=0;
          t->max_length=0;
          while (compressed_tree_bytes) {
               len=read_bits(4)+1;
               num=read_bits(4)+1;
               while (num != 0) {
                    if (len > t->max_length) {
                         t->max_length=len;
                    }
                    t->tree[i].bit_length=len;
                    t->tree[i].value=i;
                    i++;
                    num--;
               }
               compressed_tree_bytes--;
          }
     }
     {
          CHAR swapped;
          SHORT i;
          CHAR x, y;
          struct sf_tree_entry temp;

          do {
               swapped=0;
               for (i=0; i < t->entries-1; i++) {
                    x=t->tree[i].bit_length;
                    y=t->tree[i+1].bit_length;
                    if (x > y
               || (x == y && t->tree[i].value > t->tree[i+1].value)) {
                         temp=t->tree[i];
                         t->tree[i]=t->tree[i+1];
                         t->tree[i+1]=temp;
                         swapped=1;
                    }
               }
          } while (swapped);
     }
     {
          SHORT code, code_increment;
          SHORT last_bit_length;
          SHORT i;

          code=0;
          code_increment=0;
          last_bit_length=0;
          i=t->entries-1;
          while (i >= 0) {
               code+=code_increment;
               if (t->tree[i].bit_length != last_bit_length) {
                    last_bit_length=t->tree[i].bit_length;
                    code_increment=1<<(16-last_bit_length);
               }
               t->tree[i].code=reverse_bits(code);
               i--;
          }
     }
}

SHORT
reverse_bits(
SHORT x)
{
     register SHORT in;
     register SHORT out;
     SHORT b;

     in=x;
     out=0;
     for (b=0; b < sizeof(x); b++) {
          out<<=1;
          if (in&1) {
               out|=1;
          }
          in>>=1;
     }
     return(out);
}

SHORT
read_via_tree(
struct sf_tree *t)
{
     SHORT code,bits,index;

     code=0;
     bits=0;
     index=0;
     for (;;) {
          code|=read_bits(1)<<bits;
          bits++;
          while (t->tree[index].bit_length < bits) {
               index++;
               if (index >= t->entries) {
                    uz.zip_eof=1;
                    return(0);
               }
          }
          while (t->tree[index].bit_length == bits) {
               if (t->tree[index].code == code) {
                    return(t->tree[index].value);
               }
               index++;
               if (index >= t->entries) {
                    uz.zip_eof=1;
                    return(0);
               }
          }
     }
}

SHORT
read_bits(
SHORT b)
{
     register SHORT x;
     register SHORT mask;
     SHORT n;

     x=0;
     mask=1;
     while (b) {
          if (!uz.bits_left) {
               uz.input_index++;
               if (uz.input_index >= uz.input_avail) {
                    n=INPUT_BUFFER_SIZE;
                    if (uz.zlhdr.compressed-uz.input_offset < n) {
                         n=(SHORT)(uz.zlhdr.compressed-uz.input_offset);
                    }
                    uz.input_avail=fread(uz.input_buf,1,n,uz.zip_file);
                    if (uz.input_avail == 0 || uz.input_avail == -1) {
                         uz.zip_eof=1;
                         uz.input_avail=0;
                         return(0);
                    }
                    uz.input_offset+=uz.input_avail;
                    uz.input_index=0;
               }
               uz.bits_left=8;
          }
          if (uz.input_buf[uz.input_index]&1) {
               x|=mask;
          }
          uz.input_buf[uz.input_index] >>= 1;
          uz.bits_left--;
          mask<<=1;
          b--;
     }
     return(x);
}

VOID
output_byte(
CHAR b)
{
     uz.output_buf[uz.output_index]=b;
     uz.output_index=(uz.output_index+1)%OUTPUT_BUFFER_SIZE;
     uz.crc=crc32byte(b,uz.crc);
     uz.output_avail++;
     uz.output_offset++;
}

                    /* utility routines for converting MKS$ reals <-> longs*/
ULONG
mks_to_long(
ULONG x)
{
     return(((x&~0xFF000000UL)|0x00800000UL)>>(INT)(24-((x>>24)&0x7F)));
}

ULONG
long_to_mks(
ULONG x)
{
     INT exp;

     exp=0;
     if (x == 0) {
          exp=0x80+24;
     }
     else if (x >= 0x01000000L) {
          while (x&0xff000000L) {
               x>>=1;
               exp--;
          }
     }
     else {
          while ((x&0x00800000L) == 0) {
               x<<=1;
               exp++;
          }
     }
     return((x&~0x00800000L)|((24L-exp+0x80)<<24));
}

ULONG
crc32block(                        /* compute the crc32                    */
VOID *buf,
INT len,
ULONG crc)
{
     CHAR *p=buf;

     while (len--) {
          crc=crc32byte(*p,crc);
          p++;
     }
     return(crc);
}

ULONG                              /*   returns updated crc32              */
crc32byte(                         /* compute the crc32 of a byte          */
CHAR c,                            /*   byte to compute for                */
ULONG crc)                         /*   crc32 to base on                   */
{
     SHORT i;

     for (i=0; i < 8; i++) {
          if (c&1) {
               crc^=1;
          }
          if (crc&1) {
               crc=(crc>>1)^0xedb88320L;
          }
          else {
               crc>>=1;
          }
          c>>=1;
     }
     return(crc);
}

VOID
close_if_open(FILE *f)        /* close a file if f != NULL                 */
{
     if (f != NULL) {
          fclose(f);
     }
}

GBOOL
exist(CHAR *fn)               /* check to see if a file exists             */
{
     return(!access(fn,GCF_OK));
}

VOID
qwkmail_hangup(VOID)          /* hangup, close open files and clean up     */
{
     GBOOL i;
     GBOOL reload;
     struct ffblk fb;

     qscptr=myqsptr();
     reload=!sameas(qscptr->userid,usaptr->userid);
     if (reload) {
          inigmeu();
     }
     if (usrptr->state == mjmstt) {
          switch (usrptr->substt) {
          case MAKEPCKT:
               abort_dl();
               break;
          }
          clrprf();
     }
     restqs();
     i=fnd1st(&fb,qwkpath(STAR),0);
     while (i) {
          unlink(qwkpath(fb.ff_name));
          i=fndnxt(&fb);
     }
     if (reload) {
          clsgmeu();
     }
}

GBOOL
saveqs(VOID)                       /* save quickscan info                  */
{
     SHORT i;
     USHORT forum;
     LONG himid;
     CHAR *fname;
     FILE *fp;
     INT rc;

     fname=qwkpath(QSCIMAGE);
     if ((fp=fopen(fname,FOPWB)) == NULL) {
          return(FALSE);
     }
     himid=0L;
     rc=fwrite(&himid,sizeof(LONG),1,fp);
     if (rc <= 0) {                /* message read charge   */
          fclose(fp);
          unlink(fname);
          return(FALSE);
     }
     rc=fwrite(&usaptr->emllim,sizeof(LONG),1,fp);
     if (rc <= 0) {
          fclose(fp);
          unlink(fname);
          return(FALSE);
     }
     for (i=0 ; i < qscptr->nforums ; ++i) {
          if ((himid=igethi(qscptr,i)) > 0L) {
               forum=igetfid(qscptr,i);
               if (fwrite(&forum,1,sizeof(USHORT),fp) != sizeof(USHORT)
                || fwrite(&himid,1,sizeof(LONG),fp) != sizeof(LONG)) {
                    fclose(fp);
                    unlink(fname);
                    return(FALSE);
               }
          }
     }
     fclose(fp);
     return(TRUE);
}

VOID
restqs(VOID)                       /* restore quickscan data if present    */
{
     USHORT forum;
     LONG oldhi;
     FILE *fp;
     INT rc;

     if (qwksid[usrnum].wroteq
      && (fp=fopen(qwkpath(QSCIMAGE),FOPRB)) != NULL) {
          rc=fread(&oldhi,sizeof(LONG),1,fp);
          if (rc <= 0) {  /* msg read charge  */
               fclose(fp);
               qwksid[usrnum].wroteq=0;
               return;
          }
          dedcrd(-oldhi,TRUE);
          rc=fread(&usaptr->emllim,sizeof(LONG),1,fp);
          if (rc <= 0) {
               fclose(fp);
               qwksid[usrnum].wroteq=0;
               return;
          }
          while (fread(&forum,1,sizeof(USHORT),fp) == sizeof(USHORT)) {
               if (fread(&oldhi,1,sizeof(LONG),fp) != sizeof(LONG)) {
                    break;
               }
               if (fidxst(forum)) {
                    sethi(qscptr,forum,oldhi);
               }
          }
          fclose(fp);
          qwksid[usrnum].wroteq=0;
     }
}

CHAR *                             /* path of file in current QWK dir      */
qwkpath(                           /* file name, wildcard, or NULL=dir name*/
CHAR *filespec)                    /* (usrnum implicit input)              */
{
     return(uqwkpath(usrnum,filespec));
}

CHAR *
uqwkpath(                          /* path of file in a QWK directory      */
INT unum,                          /* user number, 0 to nterms-1           */
CHAR *filespec)                    /* file name, wildcard, or NULL=dir name*/
{
     CHAR buffer[40];

     if (filespec == NULL) {
          return(uchdpath(unum,QWKMAIL_SUB));
     }
     else {
          sprintf(buffer,"%s"SLS"%s",QWKMAIL_SUB,filespec);
          return(uchdpath(unum,buffer));
     }
}

VOID
qwksht(VOID)                  /* shut down qwkmail                         */
{
     if (ul->state != QWKIDLE) {
          while (cont_ul()) {
          }
     }
     clsmsg(qwkmail_msgs);
}

VOID
qwkmail_cleanup(VOID)         /* process any rep packets, del outstanding qwk*/
{
     INT rc;

     setmbk(qwkmail_msgs);
     if (ul->state != QWKIDLE) {
          while (cont_ul()) {
          }
     }
     while (start_ul()) {
          while (cont_ul()) {
          }
     }
     inigmerq(ul->work);
     inictx(ul->work,QWKMAIL_NAME,ESQFRU,EMLID,FIRSTM,0L);
     do {
          while ((rc=nextmsg(ul->work,&ul->msg,ul->text)) == GMEAGAIN) {
          }
          if (rc == GMEOK) {
               while (delmsg(ul->work) == GMEAGAIN) {
               }
          }
     } while (rc == GMEOK);
}
