1/*
2  Copyright (c) 1990-2007 Info-ZIP.  All rights reserved.
3
4  See the accompanying file LICENSE, version 2000-Apr-09 or later
5  (the contents of which are also included in unzip.h) for terms of use.
6  If, for some reason, these files are missing, the Info-ZIP license
7  also may be found at:  ftp://ftp.info-zip.org/pub/infozip/license.html
8*/
9/*---------------------------------------------------------------------------
10
11  qdos.c
12
13  QDOS-specific routines for use with Info-ZIP's UnZip 5.3 and later.
14
15  Contains:  Qstrfix()
16             QFilename()
17             QMatch()
18             chowner()
19             Qgetch()
20             QReturn()
21             LastDir()
22             screensize()
23             do_wild()           <-- generic enough to put in file_io.c?
24             mapattr()
25             mapname()
26             checkdir()
27             qfix()
28             close_outfile()
29             stamp_file()
30             getp()
31             version()
32
33  ---------------------------------------------------------------------------*/
34
35#define UNZIP_INTERNAL
36
37#include "unzip.h"
38#include "crypt.h"
39#include "ttyio.h"
40#include <dirent.h>
41#include "izqdos.h"
42#include "unzvers.h"
43
44#ifndef SFX
45char _prog_name[] = "UnZip";
46#else
47char _prog_name[] = "??Special Flag for unzipsfx hack  ??";
48#endif
49/* sorrid hack at request of GRR follows; hope the compiler stays kind to us */
50char _version[] = {UZ_MAJORVER+'0','.',UZ_MINORVER+'0',UZ_PATCHLEVEL+'0'};
51char _extra[] = " " UZ_BETALEVEL;
52char _copyright[] = "(c) Info-ZIP Group";
53char *  _endmsg = NULL;
54long _stack = 16*1024;         /* huge stack (for qdos) */
55
56extern void consetup_title(chanid_t,struct WINDOWDEF *);
57void (*_consetup)(chanid_t,struct WINDOWDEF *) = consetup_title;
58
59struct WINDOWDEF _condetails =
60{
61    2,
62    1,
63    0,
64    7,
65    500,
66    220,
67    2,
68    30
69};
70
71
72static jobid_t chowner(chanid_t chan)
73{
74    extern char *_sys_var;
75    char *scht;
76    long *cdb;
77    long jid;
78
79    scht = *((char **)(_sys_var + 0x78));
80    cdb = *(long **)((long *)scht  + (chan & 0xffff));
81    jid = *(cdb + 2);
82    return jid;
83}
84
85int QReturn(int err)
86{
87    jobid_t me,you;
88
89    me = getpid();
90    you = chowner(getchid(0));
91
92    if((me == you) && ((qlflag & 4) == 0))
93    {
94        if(isatty(0) && isatty(2) && qlwait)
95        {
96            char c = 0;
97            fputs("Press a key to exit", stderr);
98            if((io_fbyte(getchid(0), qlwait, &c) == 0) && c == 27)
99            {
100                io_fbyte(getchid(0), -1, &c);
101            }
102        }
103    }
104    if(err > 0) err = -err;     /* We like -ve err nos (exclusively, alas) */
105    exit(err);
106}
107
108#ifndef FUNZIP
109
110static int created_dir;        /* used in mapname(), checkdir() */
111static int renamed_fullpath;   /* ditto */
112
113char *Qstrfix (char *p)
114{
115    char *q;
116    for (q = p; (q = strstr(q, ".zip"));)
117    {
118        *q = '_';
119        q += 4;
120    }
121    return p;
122}
123
124void QFilename(char *f)
125{
126    char *o,*p,*q = strdup(f);
127    p = q;
128
129    if(*q == '.' && *(q+1) == '/') q += 2;
130    o = q;
131
132    for(;*q;q++)
133    {
134        if(*q == '/') *q = '_';
135        if((qlflag & 1) == 0)
136        {
137            if(*q == '.') *q = '_';
138        }
139    }
140    strcpy(f,o);
141    free(p);
142}
143
144int QMatch(uch c1, uch c2)
145{
146    int m =0;
147
148    if(c1 != c2)
149    {
150        if(c1 == '_' && (c2 == '.' || c2 == '/'))
151        {
152            m = 1;
153        }
154    }
155    else
156    {
157        m = 1;
158    }
159    return m;
160}
161
162
163int Qgetch(void)
164{
165    char ch;
166
167    if(io_fbyte(getchid(0), -1, &ch) < 0)
168    {
169        return EOF;
170    }
171    else
172    {
173        return (int) ch;
174    }
175}
176
177int screensize(int *tt_rows, int *tt_cols)
178{
179    QLRECT_t rect;
180
181    if(0 == sd_chenq(getchid(1), -1, &rect))
182    {
183        if(tt_cols)
184            *tt_cols = rect.q_width;
185        if(tt_rows)
186            *tt_rows = rect.q_height;
187    }
188    else
189    {
190        if(tt_cols)
191            *tt_cols = 80;
192        if(tt_rows)
193            *tt_rows = 24;
194    }
195    return 0;
196}
197
198
199
200#ifndef SFX
201char *LastDir(char *ws)
202{
203    char *p;
204    char *q = ws;
205    struct stat s;
206
207    for(p = ws; *p; p++)
208    {
209        if(*p == '_')
210        {
211            char c;
212
213            p++;
214            c = *p;
215            *p = 0;
216            if(stat(ws, &s) == 0 && S_ISDIR(s.st_mode))
217            {
218                q = p;
219            }
220            *p = c;
221        }
222    }
223    return q;
224}
225
226
227/**********************/
228/* Function do_wild() */   /* for porting: dir separator; match(ignore_case) */
229/**********************/
230
231char *do_wild(__G__ wildspec)
232    __GDEF
233    ZCONST char *wildspec;  /* only used first time on a given dir */
234{
235    static DIR *wild_dir = (DIR *)NULL;
236    static ZCONST char *wildname;
237    static char *dirname, matchname[FILNAMSIZ];
238    static int notfirstcall=FALSE, have_dirname, dirnamelen;
239    struct dirent *file;
240    char basedir[40];
241
242    /* Even when we're just returning wildspec, we *always* do so in
243     * matchname[]--calling routine is allowed to append four characters
244     * to the returned string, and wildspec may be a pointer to argv[].
245     */
246    if (!notfirstcall) {    /* first call:  must initialize everything */
247        char *ws = NULL, *us = NULL;
248
249        notfirstcall = TRUE;
250
251        /* break the wildspec into a directory part and a wildcard filename */
252
253        ws = (char *) iswild(wildspec);
254
255        if(ws == NULL)
256        {
257            strncpy(matchname, wildspec, FILNAMSIZ);
258            matchname[FILNAMSIZ-1] = '\0';
259            return matchname;
260        }
261
262        us = LastDir(wildspec);
263
264        if(us == wildspec)
265        {
266            dirname = basedir;
267            getcwd(basedir, sizeof(basedir)-1);
268            dirnamelen = strlen(basedir);
269            have_dirname = FALSE;
270            wildname = wildspec;
271        } else {
272            wildname = us;     /* point at character after '/' */
273            dirnamelen = wildname - wildspec;
274            if ((dirname = (char *)malloc(dirnamelen+1)) == (char *)NULL) {
275                Info(slide, 0x201, ((char *)slide,
276                  "warning:  cannot allocate wildcard buffers\n"));
277                strncpy(matchname, wildspec, FILNAMSIZ);
278                matchname[FILNAMSIZ-1] = '\0';
279                return matchname;   /* but maybe filespec was not a wildcard */
280            }
281            strncpy(dirname, wildspec, dirnamelen);
282            dirname[dirnamelen] = '\0';   /* terminate for strcpy below */
283            have_dirname = TRUE;
284        }
285
286        if ((wild_dir = opendir(dirname)) != (DIR *)NULL) {
287            while ((file = readdir(wild_dir)) != (struct dirent *)NULL) {
288                if (match(file->d_name, wildname, 0 WISEP)) { /* 0=case sens.*/
289                    if (have_dirname) {
290                        strcpy(matchname, dirname);
291                        strcpy(matchname+dirnamelen, file->d_name);
292                    } else
293                        strcpy(matchname, file->d_name);
294                    return matchname;
295                }
296            }
297            /* if we get to here directory is exhausted, so close it */
298            closedir(wild_dir);
299            wild_dir = (DIR *)NULL;
300        }
301
302        /* return the raw wildspec in case that works (e.g., directory not
303         * searchable, but filespec was not wild and file is readable) */
304        strncpy(matchname, wildspec, FILNAMSIZ);
305        matchname[FILNAMSIZ-1] = '\0';
306        return matchname;
307    }
308
309    /* last time through, might have failed opendir but returned raw wildspec */
310    if (wild_dir == (DIR *)NULL) {
311        notfirstcall = FALSE; /* nothing left to try--reset for new wildspec */
312        if (have_dirname)
313            free(dirname);
314        return (char *)NULL;
315    }
316
317    /* If we've gotten this far, we've read and matched at least one entry
318     * successfully (in a previous call), so dirname has been copied into
319     * matchname already.
320     */
321    while ((file = readdir(wild_dir)) != (struct dirent *)NULL) {
322        if (match(file->d_name, wildname, 0 WISEP)) {   /* 0 == case sens. */
323            if (have_dirname) {
324                /* strcpy(matchname, dirname); */
325                strcpy(matchname+dirnamelen, file->d_name);
326            } else
327                strcpy(matchname, file->d_name);
328            return matchname;
329        }
330    }
331
332    closedir(wild_dir);     /* have read at least one entry; nothing left */
333    wild_dir = (DIR *)NULL;
334    notfirstcall = FALSE;   /* reset for new wildspec */
335    if (have_dirname)
336        free(dirname);
337    return (char *)NULL;
338
339} /* end function do_wild() */
340
341#endif /* !SFX */
342
343
344
345
346
347/**********************/
348/* Function mapattr() */
349/**********************/
350
351int mapattr(__G)
352    __GDEF
353{
354    ulg tmp = G.crec.external_file_attributes;
355
356    switch (G.pInfo->hostnum) {
357        case AMIGA_:
358            tmp = (unsigned)(tmp>>17 & 7);   /* Amiga RWE bits */
359            G.pInfo->file_attr = (unsigned)(tmp<<6 | tmp<<3 | tmp);
360            break;
361        case THEOS_:
362            tmp &= 0xF1FFFFFFL;
363            if ((tmp & 0xF0000000L) != 0x40000000L)
364                tmp &= 0x01FFFFFFL;     /* not a dir, mask all ftype bits */
365            else
366                tmp &= 0x41FFFFFFL;     /* leave directory bit as set */
367            /* fall through! */
368        case QDOS_:
369        case UNIX_:
370        case VMS_:
371        case ACORN_:
372        case ATARI_:
373        case ATHEOS_:
374        case BEOS_:
375        case TANDEM_:
376            G.pInfo->file_attr = (unsigned)(tmp >> 16);
377            if (G.pInfo->file_attr != 0 || !G.extra_field) {
378                return 0;
379            } else {
380                /* Some (non-Info-ZIP) implementations of Zip for Unix and
381                   VMS (and probably others ??) leave 0 in the upper 16-bit
382                   part of the external_file_attributes field. Instead, they
383                   store file permission attributes in some extra field.
384                   As a work-around, we search for the presence of one of
385                   these extra fields and fall back to the MSDOS compatible
386                   part of external_file_attributes if one of the known
387                   e.f. types has been detected.
388                   Later, we might implement extraction of the permission
389                   bits from the VMS extra field. But for now, the work-around
390                   should be sufficient to provide "readable" extracted files.
391                   (For ASI Unix e.f., an experimental remap of the e.f.
392                   mode value IS already provided!)
393                 */
394                ush ebID;
395                unsigned ebLen;
396                uch *ef = G.extra_field;
397                unsigned ef_len = G.crec.extra_field_length;
398                int r = FALSE;
399
400                while (!r && ef_len >= EB_HEADSIZE) {
401                    ebID = makeword(ef);
402                    ebLen = (unsigned)makeword(ef+EB_LEN);
403                    if (ebLen > (ef_len - EB_HEADSIZE))
404                        /* discoverd some e.f. inconsistency! */
405                        break;
406                    switch (ebID) {
407                      case EF_ASIUNIX:
408                        if (ebLen >= (EB_ASI_MODE+2)) {
409                            G.pInfo->file_attr =
410                              (unsigned)makeword(ef+(EB_HEADSIZE+EB_ASI_MODE));
411                            /* force stop of loop: */
412                            ef_len = (ebLen + EB_HEADSIZE);
413                            break;
414                        }
415                        /* else: fall through! */
416                      case EF_PKVMS:
417                        /* "found nondecypherable e.f. with perm. attr" */
418                        r = TRUE;
419                      default:
420                        break;
421                    }
422                    ef_len -= (ebLen + EB_HEADSIZE);
423                    ef += (ebLen + EB_HEADSIZE);
424                }
425                if (!r)
426                    return 0;
427            }
428            /* fall through! */
429        /* all remaining cases:  expand MSDOS read-only bit into write perms */
430        case FS_FAT_:
431            /* PKWARE's PKZip for Unix marks entries as FS_FAT_, but stores the
432             * Unix attributes in the upper 16 bits of the external attributes
433             * field, just like Info-ZIP's Zip for Unix.  We try to use that
434             * value, after a check for consistency with the MSDOS attribute
435             * bits (see below).
436             */
437            G.pInfo->file_attr = (unsigned)(tmp >> 16);
438            /* fall through! */
439        case FS_HPFS_:
440        case FS_NTFS_:
441        case MAC_:
442        case TOPS20_:
443        default:
444            /* Ensure that DOS subdir bit is set when the entry's name ends
445             * in a '/'.  Some third-party Zip programs fail to set the subdir
446             * bit for directory entries.
447             */
448            if ((tmp & 0x10) == 0) {
449                extent fnlen = strlen(G.filename);
450                if (fnlen > 0 && G.filename[fnlen-1] == '/')
451                    tmp |= 0x10;
452            }
453            /* read-only bit --> write perms; subdir bit --> dir exec bit */
454            tmp = !(tmp & 1) << 1  |  (tmp & 0x10) >> 4;
455            if ((G.pInfo->file_attr & 0700) == (unsigned)(0400 | tmp<<6))
456                /* keep previous G.pInfo->file_attr setting, when its "owner"
457                 * part appears to be consistent with DOS attribute flags!
458                 */
459                return 0;
460            G.pInfo->file_attr = (unsigned)(0444 | tmp<<6 | tmp<<3 | tmp);
461            break;
462    } /* end switch (host-OS-created-by) */
463
464    /* for originating systems with no concept of "group," "other," "system": */
465    umask( (int)(tmp=umask(0)) );    /* apply mask to expanded r/w(/x) perms */
466    G.pInfo->file_attr &= ~tmp;
467
468    return 0;
469
470} /* end function mapattr() */
471
472
473
474/************************/
475/*  Function mapname()  */
476/************************/
477int mapname(__G__ renamed)
478    __GDEF
479    int renamed;
480/*
481 * returns:
482 *  MPN_OK          - no problem detected
483 *  MPN_INF_TRUNC   - caution (truncated filename)
484 *  MPN_INF_SKIP    - info "skip entry" (dir doesn't exist)
485 *  MPN_ERR_SKIP    - error -> skip entry
486 *  MPN_ERR_TOOLONG - error -> path is too long
487 *  MPN_NOMEM       - error (memory allocation failed) -> skip entry
488 *  [also MPN_VOL_LABEL, MPN_CREATED_DIR]
489 */
490{
491    char pathcomp[FILNAMSIZ];      /* path-component buffer */
492    char *pp, *cp=(char *)NULL;    /* character pointers */
493    char *lastsemi=(char *)NULL;   /* pointer to last semi-colon in pathcomp */
494    int killed_ddot = FALSE;       /* is set when skipping "../" pathcomp */
495    int error = MPN_OK;
496    register unsigned workch;      /* hold the character being tested */
497
498
499/*---------------------------------------------------------------------------
500    Initialize various pointers and counters and stuff.
501  ---------------------------------------------------------------------------*/
502
503    if (G.pInfo->vollabel)
504        return MPN_VOL_LABEL;   /* can't set disk volume labels in SMS/QDOS */
505
506    /* can create path as long as not just freshening, or if user told us */
507    G.create_dirs = (!uO.fflag || renamed);
508
509    created_dir = FALSE;        /* not yet */
510
511    /* user gave full pathname:  don't prepend rootpath */
512    renamed_fullpath = (renamed && (*G.filename == '/'));
513
514    if (checkdir(__G__ (char *)NULL, INIT) == MPN_NOMEM)
515        return MPN_NOMEM;       /* initialize path buffer, unless no memory */
516
517    *pathcomp = '\0';           /* initialize translation buffer */
518    pp = pathcomp;              /* point to translation buffer */
519    if (uO.jflag)               /* junking directories */
520        cp = (char *)strrchr(G.filename, '/');
521    if (cp == (char *)NULL)     /* no '/' or not junking dirs */
522        cp = G.filename;        /* point to internal zipfile-member pathname */
523    else
524        ++cp;                   /* point to start of last component of path */
525
526/*---------------------------------------------------------------------------
527    Begin main loop through characters in filename.
528  ---------------------------------------------------------------------------*/
529
530    while ((workch = (uch)*cp++) != 0) {
531
532        switch (workch) {
533            case '/':             /* can assume -j flag not given */
534                *pp = '\0';
535                if (((error = checkdir(__G__ pathcomp, APPEND_DIR))
536                     & MPN_MASK) > MPN_INF_TRUNC)
537                    return error;
538                pp = pathcomp;    /* reset conversion buffer for next piece */
539                lastsemi = (char *)NULL; /* leave direct. semi-colons alone */
540                break;
541
542            case '.':
543                if (pp == pathcomp) {   /* nothing appended yet... */
544                    if (*cp == '/') {   /* don't bother appending "./" to */
545                        ++cp;           /*  the path: skip behind the '/' */
546                        break;
547                    } else if (!uO.ddotflag && *cp == '.' && cp[1] == '/') {
548                        /* "../" dir traversal detected */
549                        cp += 2;        /*  skip over behind the '/' */
550                        killed_ddot = TRUE; /*  set "show message" flag */
551                        break;
552                    }
553                }
554                *pp++ = (((qlflag & 1) == 0) ? '_' : '.');
555                break;
556
557            case ';':             /* VMS version (or DEC-20 attrib?) */
558                lastsemi = pp;
559                *pp++ = ';';      /* keep for now; remove VMS ";##" */
560                break;            /*  later, if requested */
561
562            default:
563                /* allow European characters in filenames: */
564                if (isprint(workch) || (128 <= workch && workch <= 254))
565                    *pp++ = (char)workch;
566        } /* end switch */
567
568    } /* end while loop */
569
570    /* Show warning when stripping insecure "parent dir" path components */
571    if (killed_ddot && QCOND2) {
572        Info(slide, 0, ((char *)slide,
573          "warning:  skipped \"../\" path component(s) in %s\n",
574          FnFilter1(G.filename)));
575        if (!(error & ~MPN_MASK))
576            error = (error & MPN_MASK) | PK_WARN;
577    }
578
579/*---------------------------------------------------------------------------
580    Report if directory was created (and no file to create:  filename ended
581    in '/'), check name to be sure it exists, and combine path and name be-
582    fore exiting.
583  ---------------------------------------------------------------------------*/
584
585    if (G.filename[strlen(G.filename) - 1] == '/') {
586        G.filename[strlen(G.filename) - 1] = '_';
587        checkdir(__G__ G.filename, GETPATH);
588        if (created_dir) {
589            if (QCOND2) {
590                Info(slide, 0, ((char *)slide, "   creating: %s\n",
591                  FnFilter1(G.filename)));
592            }
593            /* set dir time (note trailing '/') */
594            return (error & ~MPN_MASK) | MPN_CREATED_DIR;
595        }
596        /* dir existed already; don't look for data to extract */
597        return (error & ~MPN_MASK) | MPN_INF_SKIP;
598    }
599
600    *pp = '\0';                   /* done with pathcomp:  terminate it */
601
602    /* if not saving them, remove VMS version numbers (appended ";###") */
603    if (!uO.V_flag && lastsemi) {
604        pp = lastsemi + 1;
605        while (isdigit((uch)(*pp)))
606            ++pp;
607        if (*pp == '\0')          /* only digits between ';' and end:  nuke */
608            *lastsemi = '\0';
609    }
610
611    if (*pathcomp == '\0') {
612        Info(slide, 1, ((char *)slide, "mapname:  conversion of %s failed\n",
613          FnFilter1(G.filename)));
614        return (error & ~MPN_MASK) | MPN_ERR_SKIP;
615    }
616
617    checkdir(__G__ pathcomp, APPEND_NAME);  /* returns 1 if truncated: care? */
618    checkdir(__G__ G.filename, GETPATH);
619
620    return error;
621
622} /* end function mapname() */
623
624
625
626
627/***********************/
628/* Function checkdir() */
629/***********************/
630
631int checkdir(__G__ pathcomp, flag)
632    __GDEF
633    char *pathcomp;
634    int flag;
635/*
636 * returns:
637 *  MPN_OK          - no problem detected
638 *  MPN_INF_TRUNC   - (on APPEND_NAME) truncated filename
639 *  MPN_INF_SKIP    - path doesn't exist, not allowed to create
640 *  MPN_ERR_SKIP    - path doesn't exist, tried to create and failed; or path
641 *                    exists and is not a directory, but is supposed to be
642 *  MPN_ERR_TOOLONG - path is too long
643 *  MPN_NOMEM       - can't allocate memory for filename buffers
644 */
645{
646    static int rootlen = 0;   /* length of rootpath */
647    static char *rootpath;    /* user's "extract-to" directory */
648    static char *buildpath;   /* full path (so far) to extracted file */
649    static char *end;         /* pointer to end of buildpath ('\0') */
650
651#   define FN_MASK   7
652#   define FUNCTION  (flag & FN_MASK)
653
654
655/*---------------------------------------------------------------------------
656    APPEND_DIR:  append the path component to the path being built and check
657    for its existence.  If doesn't exist and we are creating directories, do
658    so for this one; else signal success or error as appropriate.
659  ---------------------------------------------------------------------------*/
660
661    if (FUNCTION == APPEND_DIR) {
662        int too_long = FALSE;
663#ifdef SHORT_NAMES
664        char *old_end = end;
665#endif
666
667        Trace((stderr, "appending dir segment [%s]\n", FnFilter1(pathcomp)));
668        while ((*end = *pathcomp++) != '\0')
669            ++end;
670#ifdef SHORT_NAMES   /* path components restricted to 14 chars, typically */
671        if ((end-old_end) > FILENAME_MAX)  /* GRR:  proper constant? */
672            *(end = old_end + FILENAME_MAX) = '\0';
673#endif
674
675        /* GRR:  could do better check, see if overrunning buffer as we go:
676         * check end-buildpath after each append, set warning variable if
677         * within 20 of FILNAMSIZ; then if var set, do careful check when
678         * appending.  Clear variable when begin new path. */
679
680        if ((end-buildpath) > FILNAMSIZ-2)  /* need '/', one-char name, '\0' */
681            too_long = TRUE;                /* check if extracting directory? */
682        if (stat(buildpath, &G.statbuf)) {  /* path doesn't exist */
683            if (!G.create_dirs) { /* told not to create (freshening) */
684                free(buildpath);
685                return MPN_INF_SKIP;    /* path doesn't exist: nothing to do */
686            }
687            if (too_long) {
688                Info(slide, 1, ((char *)slide,
689                  "checkdir error:  path too long: %s\n",
690                  FnFilter1(buildpath)));
691                free(buildpath);
692                /* no room for filenames:  fatal */
693                return MPN_ERR_TOOLONG;
694            }
695            if (mkdir(buildpath, 0777) == -1) {   /* create the directory */
696                Info(slide, 1, ((char *)slide,
697                  "checkdir error:  cannot create %s\n\
698                 unable to process %s.\n",
699                  FnFilter2(buildpath), FnFilter1(G.filename)));
700                free(buildpath);
701                /* path didn't exist, tried to create, failed */
702                return MPN_ERR_SKIP;
703            }
704            created_dir = TRUE;
705        } else if (!S_ISDIR(G.statbuf.st_mode)) {
706            Info(slide, 1, ((char *)slide,
707              "checkdir error:  %s exists but is not directory\n\
708                 unable to process %s.\n",
709              FnFilter2(buildpath), FnFilter1(G.filename)));
710            free(buildpath);
711            /* path existed but wasn't dir */
712            return MPN_ERR_SKIP;
713        }
714        if (too_long) {
715            Info(slide, 1, ((char *)slide,
716              "checkdir error:  path too long: %s\n", FnFilter1(buildpath)));
717            free(buildpath);
718            /* no room for filenames:  fatal */
719            return MPN_ERR_TOOLONG;
720        }
721        *end++ = '_';
722        *end = '\0';
723        Trace((stderr, "buildpath now = [%s]\n", FnFilter1(buildpath)));
724        return MPN_OK;
725
726    } /* end if (FUNCTION == APPEND_DIR) */
727
728/*---------------------------------------------------------------------------
729    GETPATH:  copy full path to the string pointed at by pathcomp, and free
730    buildpath.
731  ---------------------------------------------------------------------------*/
732
733    if (FUNCTION == GETPATH) {
734        strcpy(pathcomp, buildpath);
735        Trace((stderr, "getting and freeing path [%s]\n",
736          FnFilter1(pathcomp)));
737        free(buildpath);
738        buildpath = end = (char *)NULL;
739        return MPN_OK;
740    }
741
742/*---------------------------------------------------------------------------
743    APPEND_NAME:  assume the path component is the filename; append it and
744    return without checking for existence.
745  ---------------------------------------------------------------------------*/
746
747    if (FUNCTION == APPEND_NAME) {
748#ifdef SHORT_NAMES
749        char *old_end = end;
750#endif
751        short dlen;
752
753        Trace((stderr, "appending filename [%s]\n", FnFilter1(pathcomp)));
754        while ((*end = *pathcomp++) != '\0') {
755            ++end;
756#ifdef SHORT_NAMES  /* truncate name at 14 characters, typically */
757            if ((end-old_end) > FILENAME_MAX)      /* GRR:  proper constant? */
758                *(end = old_end + FILENAME_MAX) = '\0';
759#endif
760            if (isdirdev(buildpath))
761            {
762                dlen = 5;
763            }
764            else
765            {
766                dlen = 0;
767            }
768
769            if ((end-buildpath-dlen) >= FILNAMSIZ) {
770                *--end = '\0';
771                Info(slide, 0x201, ((char *)slide,
772                  "checkdir warning:  path too long; truncating\n\
773                   %s\n                -> %s\n",
774                  FnFilter1(G.filename), FnFilter2(buildpath)));
775                return MPN_INF_TRUNC;   /* filename truncated */
776            }
777        }
778        Trace((stderr, "buildpath now = [%s]\n", FnFilter1(buildpath)));
779        /* could check for existence here, prompt for new name... */
780        return MPN_OK;
781    }
782
783/*---------------------------------------------------------------------------
784    INIT:  allocate and initialize buffer space for the file currently being
785    extracted.  If file was renamed with an absolute path, don't prepend the
786    extract-to path.
787  ---------------------------------------------------------------------------*/
788
789/* GRR:  for VMS and TOPS-20, add up to 13 to strlen */
790
791    if (FUNCTION == INIT) {
792        Trace((stderr, "initializing buildpath to "));
793        if ((buildpath = (char *)malloc(strlen(G.filename)+rootlen+1))
794            == (char *)NULL)
795            return MPN_NOMEM;
796        if ((rootlen > 0) && !renamed_fullpath) {
797            strcpy(buildpath, rootpath);
798            end = buildpath + rootlen;
799        } else {
800            *buildpath = '\0';
801            end = buildpath;
802        }
803        Trace((stderr, "[%s]\n", FnFilter1(buildpath)));
804        return MPN_OK;
805    }
806
807/*---------------------------------------------------------------------------
808    ROOT:  if appropriate, store the path in rootpath and create it if
809    necessary; else assume it's a zipfile member and return.  This path
810    segment gets used in extracting all members from every zipfile specified
811    on the command line.
812  ---------------------------------------------------------------------------*/
813
814#if (!defined(SFX) || defined(SFX_EXDIR))
815    if (FUNCTION == ROOT) {
816        Trace((stderr, "initializing root path to [%s]\n",
817          FnFilter1(pathcomp)));
818        if (pathcomp == (char *)NULL) {
819            rootlen = 0;
820            return MPN_OK;
821        }
822        if (rootlen > 0)        /* rootpath was already set, nothing to do */
823            return MPN_OK;
824        if ((rootlen = strlen(pathcomp)) > 0) {
825            char *tmproot;
826
827            if ((tmproot = (char *)malloc(rootlen+2)) == (char *)NULL) {
828                rootlen = 0;
829                return MPN_NOMEM;
830            }
831            strcpy(tmproot, pathcomp);
832            if ((stat(tmproot, &G.statbuf) ||
833                !S_ISDIR(G.statbuf.st_mode)))
834            {   /* path does not exist */
835                if (!G.create_dirs /* || iswild(tmproot) */ ) {
836                    free(tmproot);
837                    rootlen = 0;
838                    /* skip (or treat as stored file) */
839                    return MPN_INF_SKIP;
840                }
841                /* create the directory (could add loop here scanning tmproot
842                 * to create more than one level, but why really necessary?) */
843                if (mkdir(tmproot, 0777) == -1) {
844                    Info(slide, 1, ((char *)slide,
845                      "checkdir:  cannot create extraction directory: %s\n",
846                      FnFilter1(tmproot)));
847                    free(tmproot);
848                    rootlen = 0;
849                    /* path didn't exist, tried to create, and failed: */
850                    /* file exists, or 2+ subdir levels required */
851                    return MPN_ERR_SKIP;
852                }
853            }
854            if (tmproot[rootlen-1] == '/' || tmproot[rootlen-1] == '_') {
855                tmproot[--rootlen] = '\0';
856            }
857            tmproot[rootlen++] = '_';
858            tmproot[rootlen] = '\0';
859            if ((rootpath = (char *)realloc(tmproot, rootlen+1)) == NULL) {
860                free(tmproot);
861                rootlen = 0;
862                return MPN_NOMEM;
863            }
864        }
865        Trace((stderr, "rootpath now = [%s]\n", FnFilter1(rootpath)));
866        return MPN_OK;
867    }
868#endif /* !SFX || SFX_EXDIR */
869
870/*---------------------------------------------------------------------------
871    END:  free rootpath, immediately prior to program exit.
872  ---------------------------------------------------------------------------*/
873
874    if (FUNCTION == END) {
875        Trace((stderr, "freeing rootpath\n"));
876        if (rootlen > 0) {
877            free(rootpath);
878            rootlen = 0;
879        }
880        return MPN_OK;
881    }
882
883    return MPN_INVALID; /* should never reach */
884
885} /* end function checkdir() */
886
887
888static void qfix(__G__ ef_ptr, ef_len)
889    __GDEF
890    uch *ef_ptr;
891    unsigned ef_len;
892{
893    qdosextra qextra;
894
895    while (ef_len >= EB_HEADSIZE)
896    {
897        qdosextra   *extra = &qextra;
898        jbextra     *jbp   = (jbextra *)&qextra;
899        unsigned    eb_len = makeword(EB_LEN + ef_ptr);
900
901        if (eb_len > (ef_len - EB_HEADSIZE)) {
902            /* discovered some extra field inconsistency! */
903            Trace((stderr,
904              "qfix: block length %u > rest ef_size %u\n", eb_len,
905              ef_len - EB_HEADSIZE));
906            break;
907        }
908
909        /* Must ensure that we don't use ODD addresses here */
910
911        memcpy(&qextra, ef_ptr, sizeof(qdosextra));
912        switch (extra->shortid) {
913          case SHORTID:
914            if (!strncmp(extra->longid, LONGID, strlen(LONGID)))
915            {
916                if (eb_len != EXTRALEN)
917                    fputs("warning: invalid length in Qdos field", stderr);
918                if (extra->header.d_type)
919                {
920                    fs_heads(fgetchid(G.outfile), (timeout_t)-1,
921                             &extra->header, 14);
922                    G.pInfo->file_attr |= S_IXUSR;
923                }
924            }
925
926            if (!strncmp(jbp->longid, JBLONGID, strlen(JBLONGID)))
927            {
928                if (eb_len != JBEXTRALEN)
929                    fputs("warning: invalid length in QZ field", stderr);
930                if (jbp->header.d_type)
931                {
932                    fs_heads(fgetchid(G.outfile), (timeout_t)-1,
933                             &jbp->header, 14);
934                    G.pInfo->file_attr |= S_IXUSR;
935                }
936            }
937            break;
938
939          default:
940            Trace((stderr,"qfix: unknown extra field block, ID=%d\n",
941               extra->shortid));
942            break;
943        }
944
945        /* Skip this extra field block */
946        ef_ptr += (eb_len + EB_HEADSIZE);
947        ef_len -= (eb_len + EB_HEADSIZE);
948    }
949}
950
951
952#ifdef QDOS
953#  include <utime.h>
954   long timezone = 0;
955#endif
956
957
958/****************************/
959/* Function close_outfile() */
960/****************************/
961
962void close_outfile(__G)
963    __GDEF
964{
965    union {
966        iztimes t3;             /* mtime, atime, ctime */
967        struct utimbuf t2;      /* modtime, actime */
968    } zt;
969#ifdef USE_EF_UT_TIME
970    unsigned eb_izux_flg;
971#endif
972
973    if (G.extra_field) {
974        qfix(__G__ G.extra_field, G.lrec.extra_field_length);
975    }
976
977    fclose(G.outfile);
978
979/*---------------------------------------------------------------------------
980    Change the file permissions from default ones to those stored in the
981    zipfile.
982  ---------------------------------------------------------------------------*/
983
984#ifndef NO_CHMOD
985    if (chmod(G.filename, 0xffff & G.pInfo->file_attr))
986        perror("chmod (file attributes) error");
987#endif
988
989/*---------------------------------------------------------------------------
990    Convert from MSDOS-format local time and date to Unix-format 32-bit GMT
991    time:  adjust base year from 1980 to 1970, do usual conversions from
992    yy/mm/dd hh:mm:ss to elapsed seconds, and account for timezone and day-
993    light savings time differences.  If we have a Unix extra field, however,
994    we're laughing:  both mtime and atime are ours.
995  ---------------------------------------------------------------------------*/
996
997    /* skip restoring time stamps on user's request */
998    if (uO.D_flag <= 1) {
999#ifdef USE_EF_UT_TIME
1000        eb_izux_flg = (G.extra_field ? ef_scan_for_izux(G.extra_field,
1001                       G.lrec.extra_field_length, 0,
1002                       G.lrec.last_mod_dos_datetime,
1003#ifdef IZ_CHECK_TZ
1004                       (G.tz_is_valid ? &(zt.t3) : NULL),
1005#else
1006                       &(zt.t3),
1007#endif
1008                       NULL) : 0);
1009        if (eb_izux_flg & EB_UT_FL_MTIME) {
1010            TTrace((stderr,
1011              "\nclose_outfile:  Unix e.f. modif. time = %ld\n",
1012              zt.t3.mtime));
1013        } else {
1014            zt.t3.mtime = dos_to_unix_time(G.lrec.last_mod_dos_datetime);
1015        }
1016        if (eb_izux_flg & EB_UT_FL_ATIME) {
1017            TTrace((stderr,
1018              "close_outfile:  Unix e.f. access time = %ld\n",
1019              zt.t3.atime));
1020        } else {
1021            zt.t3.atime = zt.t3.mtime;
1022            TTrace((stderr,
1023              "\nclose_outfile:  modification/access times = %ld\n",
1024              zt.t3.mtime));
1025        }
1026#else
1027        zt.t3.atime = zt.t3.mtime
1028          = dos_to_unix_time(G.lrec.last_mod_dos_datetime);
1029#endif
1030
1031        /* set the file's access and modification times */
1032        if (utime(G.filename, &(zt.t2))) {
1033            Info(slide, 0x201, ((char *)slide,
1034              "warning:  cannot set the time for %s\n",
1035              FnFilter1(G.filename)));
1036        }
1037    }
1038
1039} /* end function close_outfile() */
1040
1041
1042
1043
1044#ifdef TIMESTAMP
1045
1046/***************************/
1047/*  Function stamp_file()  */
1048/***************************/
1049
1050int stamp_file(fname, modtime)
1051    ZCONST char *fname;
1052    time_t modtime;
1053{
1054    struct utimbuf tp;
1055
1056    tp.modtime = tp.actime = modtime;
1057    return (utime(fname, &tp));
1058
1059} /* end function stamp_file() */
1060
1061#endif /* TIMESTAMP */
1062
1063
1064
1065
1066#ifndef SFX
1067
1068/************************/
1069/*  Function version()  */
1070/************************/
1071
1072void version(__G)
1073    __GDEF
1074{
1075
1076    sprintf((char *)slide, LoadFarString(CompiledWith),
1077           "c68", " v4.2x", "SMS/QDOS",
1078            " on ", __DATE__, "","");
1079    (*G.message)((zvoid *)&G, slide, (ulg)strlen((char *)slide), 0);
1080
1081} /* end function version() */
1082
1083#endif /* !SFX */
1084#endif /* !FUNZIP */
1085
1086#if CRYPT
1087
1088char *getp(__G__ m, p, n)
1089    __GDEF
1090    const char *m;              /* prompt for password */
1091    char *p;                    /* return value: line input */
1092    int n;                      /* bytes available in p[] */
1093{
1094    int c;                      /* one-byte buffer for read() to use */
1095    int i;                      /* number of characters input */
1096    char *w;                    /* warning on retry */
1097
1098    /* get password */
1099    w = "";
1100    sd_cure(getchid(0), -1);    /* enable cursor */
1101    do {
1102        fputs(w, stderr);       /* warning if back again */
1103        fputs(m, stderr);       /* display prompt and flush */
1104        fflush(stderr);
1105        i = 0;
1106        do {
1107            c = getch();
1108            if (c == 0xc2) {
1109                if (i > 0) {
1110                    i--; /* the `del' keys works */
1111                    fputs("\b \b", stderr);
1112                }
1113            }
1114            else if (i < n) {
1115                p[i++] = c;     /* truncate past n */
1116                if(c != '\n') putc('*', stderr);
1117            }
1118        } while (c != '\n');
1119
1120        putc('\n', stderr);  fflush(stderr);
1121        w = "(line too long--try again)\n";
1122    } while (p[i-1] != '\n');
1123
1124    p[i-1] = 0;                 /* terminate at newline */
1125    sd_curs(getchid(0), -1);    /* suppress cursor */
1126    return p;                   /* return pointer to password */
1127
1128} /* end function getp() */
1129
1130#endif /* CRYPT */
1131