/*
   crypt.c (full version) by Info-ZIP.        Last revised:  23 Oct 92

   This code is not copyrighted and is put in the public domain.  The
   encryption/decryption parts (as opposed to the non-echoing password
   parts) were originally written in Europe; the whole file can there-
   fore be freely distributed from any country except the USA.  If this
   code is imported into the USA, it cannot be re-exported from from
   there to another country.  (This restriction might seem curious, but
   this is what US law requires.)
 */

/* This encryption code is a direct transcription of the algorithm from
   Roger Schlafly, described by Phil Katz in the file appnote.txt.  This
   file (appnote.txt) is distributed with the PKZIP program (even in the
   version without encryption capabilities).
 */

#include "zip.h"
#include "crypt.h"

#ifndef UNZIP         /* time.h already included in unzip's zip.h */
#  include <time.h>
#endif

#if ((defined(DOS_OS2) || defined(WIN32)) && !defined(__GO32__))
#  include <process.h>
#else
#  if !defined(__386BSD__) && !defined(SYSV)  /* getpid() in unistd.h */
     int  getpid OF((void));
#  endif
#endif

#ifndef __GNUC__
void srand  OF((unsigned int));
#endif
int  rand   OF((void));

/* For now, assume DIRENT implies System V implies TERMIO */
#if (defined(DIRENT) && !defined(NO_TERMIO) && !defined(TERMIO))
#  define TERMIO
#endif

#if (defined(DOS_OS2) || defined(VMS))
#  ifndef MSVMS
#    define MSVMS
#  endif
#  ifdef DOS_OS2
#    ifdef __EMX__
#      define getch() _read_kbd(0, 1, 0)
#    else
#      ifdef __GO32__
#        include <pc.h>
#        define getch() getkey()
#      else /* !__GO32__ */
#        include <conio.h>
#      endif /* ?__GO32__ */
#    endif
#  else /* !DOS_OS2 */
#    define getch() getc(stderr)
#    include <descrip.h>
#    include <iodef.h>
#    include <ttdef.h>
#    if !defined(SS$_NORMAL)
#      define SS$_NORMAL 1   /* only thing we need from <ssdef.h> */
#    endif
#  endif /* ?DOS_OS2 */
#else /* !(DOS_OS2 || VMS) */
#  ifdef TERMIO       /* Amdahl, Cray, all SysV? */
#    ifdef COHERENT
#      include <termio.h>
#    else
#     ifdef LINUX
#      include <termios.h>
#     else
#      include <sys/termio.h>
#     endif
#    endif /* COHERENT */
#    define sgttyb termio
#    define sg_flags c_lflag
#    if !defined(__386BSD__) && !defined(SYSV)
       int ioctl OF((int, int, voidp *)); /* already in unistd.h */
#    endif
#    define GTTY(f,s) ioctl(f,TCGETA,(voidp *)s)
#    define STTY(f,s) ioctl(f,TCSETAW,(voidp *)s)
#  else /* !TERMIO */
#    if (!defined(MINIX) && !defined(__386BSD__))
#      include <sys/ioctl.h>
#    endif /* !MINIX && !__386BSD__ */
#    include <sgtty.h>
#    ifdef __386BSD__
#      define GTTY(f, s) ioctl(f, TIOCGETP, (voidp *) s)
#      define STTY(f, s) ioctl(f, TIOCSETP, (voidp *) s)
#    else /* !__386BSD__ */
#      define GTTY gtty
#      define STTY stty
       int gtty OF((int, struct sgttyb *));
       int stty OF((int, struct sgttyb *));
#    endif /* ?__386BSD__ */
#  endif /* ?TERMIO */
#  if defined(__386BSD__) || defined(SYSV)
#    if !defined(UNZIP)
#      include <fcntl.h>
#    endif
#  else
     int isatty OF((int));
     char *ttyname OF((int));
     /* int open OF((char *, int));  works in zipup.c but not here. Strange. */
     int close OF((int));
     int read OF((int, voidp *, int));
#  endif
#endif /* ?(DOS_OS2 || VMS) */

#if (defined(UNZIP) && !defined(FUNZIP))
  local int testp OF((uch *h));
#endif

local ulg keys[3]; /* keys defining the pseudo-random sequence */

#ifdef CRYPT_DEBUG
#  define Tracecr(x) printf x
#else
#  define Tracecr(x)
#endif

/***********************************************************************
 * Return the next byte in the pseudo-random sequence
 */
int decrypt_byte()
{
   ush temp;

   temp = (ush)keys[2] | 2;
   return (int)(((ush)(temp * (temp ^ 1)) >> 8) & 0xff);
}

/***********************************************************************
 * Update the encryption keys with the next byte of plain text
 */
void update_keys(c)
    int c;                  /* byte of plain text */
{
    keys[0] = CRC32(keys[0], c);
    keys[1] += keys[0] & 0xff;
    keys[1] = keys[1] * 134775813L + 1;
    keys[2] = CRC32(keys[2], (int)(keys[1] >> 24));
}


/***********************************************************************
 * Initialize the encryption keys and the random header according to
 * the given password.
 */
void init_keys(passwd)
    char *passwd;             /* password string with which to modify keys */
{
    keys[0] = 305419896L;
    keys[1] = 591751049L;
    keys[2] = 878082192L;
    while (*passwd != '\0') {
        update_keys((int)*passwd);
        passwd++;
    }
}

/***********************************************************************
 * Write encryption header to file zfile using the password passwd
 * and the cyclic redundancy check crc.
 */
void crypthead(passwd, crc, zfile)
    char *passwd;                /* password string */
    ulg crc;                     /* crc of file being encrypted */
    FILE *zfile;                 /* where to write header */
{
    int n;                       /* index in random header */
    int t;                       /* temporary */
    int c;                       /* random byte */
    uch header[RAND_HEAD_LEN-2]; /* random header */
    static unsigned calls = 0;   /* ensure different random header each time */

    /* First generate RAND_HEAD_LEN-2 random bytes. We encrypt the
     * output of rand() to get less predictability, since rand() is
     * often poorly implemented.
     */
    if (++calls == 1) {
        srand((unsigned)time(NULL) ^ getpid());
    }
    init_keys(passwd);
    for (n = 0; n < RAND_HEAD_LEN-2; n++) {
        c = (rand() >> 7) & 0xff;
        header[n] = (uch)zencode(c, t);
    }
    /* Encrypt random header (last two bytes is high word of crc) */
    init_keys(passwd);
    for (n = 0; n < RAND_HEAD_LEN-2; n++) {
        putc(zencode(header[n], t), zfile);
    }
    putc(zencode((int)(crc >> 16) & 0xff, t), zfile);
    putc(zencode((int)(crc >> 24), t), zfile);
}


#ifdef UTIL

/***********************************************************************
 * Encrypt the zip entry described by z from file source to file dest
 * using the password passwd.  Return an error code in the ZE_ class.
 */
int zipcloak(z, source, dest, passwd)
    struct zlist far *z;    /* zip entry to encrypt */
    FILE *source, *dest;    /* source and destination files */
    char *passwd;           /* password string */
{
    int c;                  /* input byte */
    int res;                /* result code */
    ulg n;                  /* holds offset and counts size */
    ush flag;               /* previous flags */
    int t;                  /* temporary */

    /* Set encrypted bit, clear extended local header bit and write local
       header to output file */
    if ((n = ftell(dest)) == -1L) return ZE_TEMP;
    z->off = n;
    flag = z->flg;
    z->flg |= 1,  z->flg &= ~8;
    z->lflg |= 1, z->lflg &= ~8;
    z->siz += RAND_HEAD_LEN;
    if ((res = putlocal(z, dest)) != ZE_OK) return res;

    /* Initialize keys with password and write random header */
    crypthead(passwd, z->crc, dest);

    /* Skip local header in input file */
    if (fseek(source, (long)(4 + LOCHEAD + (ulg)z->nam + (ulg)z->ext),
              SEEK_CUR)) {
        return ferror(source) ? ZE_READ : ZE_EOF;
    }

    /* Encrypt data */
    for (n = z->siz - RAND_HEAD_LEN; n; n--) {
        if ((c = getc(source)) == EOF) {
            return ferror(source) ? ZE_READ : ZE_EOF;
        }
        putc(zencode(c, t), dest);
    }
    /* Skip extended local header in input file if there is one */
    if ((flag & 8) != 0 && fseek(source, 16L, SEEK_CUR)) {
        return ferror(source) ? ZE_READ : ZE_EOF;
    }
    if (fflush(dest) == EOF) return ZE_TEMP;
    return ZE_OK;
}

/***********************************************************************
 * Decrypt the zip entry described by z from file source to file dest
 * using the password passwd.  Return an error code in the ZE_ class.
 */
int zipbare(z, source, dest, passwd)
    struct zlist far *z;  /* zip entry to encrypt */
    FILE *source, *dest;  /* source and destination files */
    char *passwd;         /* password string */
{
    int c0, c1;           /* last two input bytes */
    ulg offset;           /* used for file offsets */
    ulg size;             /* size of input data */
    int r;                /* size of encryption header */
    int res;              /* return code */
    ush flag;             /* previous flags */

    /* Save position and skip local header in input file */
    if ((offset = ftell(source)) == -1L ||
        fseek(source, (long)(4 + LOCHEAD + (ulg)z->nam + (ulg)z->ext),
              SEEK_CUR)) {
        return ferror(source) ? ZE_READ : ZE_EOF;
    }
    /* Initialize keys with password */
    init_keys(passwd);

    /* Decrypt encryption header, save last two bytes */
    c1 = 0;
    for (r = RAND_HEAD_LEN; r; r--) {
        c0 = c1;
        if ((c1 = getc(source)) == EOF) {
            return ferror(source) ? ZE_READ : ZE_EOF;
        }
        Tracecr((" (%02x)", c1));
        zdecode(c1);
        Tracecr((" %02x", c1));
    }
    Tracecr(("\n"));

    /* If last two bytes of header don't match crc (or file time in the
     * case of an extended local header), back up and just copy. For
     * pkzip 2.0, the check has been reduced to one byte only.
     */
#ifdef ZIP10
    if ((ush)(c0 | (c1<<8)) !=
        (z->flg & 8 ? (ush) z->tim & 0xffff : (ush)(z->crc >> 16))) {
#else
    c0++; /* avoid warning on unused variable */
    if ((ush)c1 != (z->flg & 8 ? (ush) z->tim >> 8 : (ush)(z->crc >> 24))) {
#endif
        if (fseek(source, offset, SEEK_SET)) {
            return ferror(source) ? ZE_READ : ZE_EOF;
        }
        if ((res = zipcopy(z, source, dest)) != ZE_OK) return res;
        return ZE_MISS;
    }

    /* Clear encrypted bit and local header bit, and write local header to
       output file */
    if ((offset = ftell(dest)) == -1L) return ZE_TEMP;
    z->off = offset;
    flag = z->flg;
    z->flg &= ~9;
    z->lflg &= ~9;
    z->siz -= RAND_HEAD_LEN;
    if ((res = putlocal(z, dest)) != ZE_OK) return res;

    /* Decrypt data */
    for (size = z->siz; size; size--) {
        if ((c1 = getc(source)) == EOF) {
            return ferror(source) ? ZE_READ : ZE_EOF;
        }
        zdecode(c1);
        putc(c1, dest);
    }
    /* Skip extended local header in input file if there is one */
    if ((flag & 8) != 0 && fseek(source, 16L, SEEK_CUR)) {
        return ferror(source) ? ZE_READ : ZE_EOF;
    }
    if (fflush(dest) == EOF) return ZE_TEMP;

    return ZE_OK;
}


#else /* !UTIL */

/***********************************************************************
 * If requested, encrypt the data in buf, and in any case call fwrite()
 * with the arguments to zfwrite().  Return what fwrite() returns.
 */
unsigned zfwrite(buf, item_size, nb, f)
    voidp *buf;                /* data buffer */
    extent item_size;          /* size of each item in bytes */
    extent nb;                 /* number of items */
    FILE *f;                   /* file to write to */
{
    int t;                    /* temporary */

    if (key != (char *)NULL) { /* key is the global password pointer */
        ulg size;              /* buffer size */
        char *p = (char*)buf;  /* steps through buffer */

        /* Encrypt data in buffer */
        for (size = item_size*(ulg)nb; size != 0; p++, size--) {
            *p = (char)zencode(*p, t);
        }
    }
    /* Write the buffer out */
    return fwrite(buf, item_size, nb, f);
}

#endif /* ?UTIL */


#ifndef DOS_OS2
#ifdef VMS

/***********************************************************************
 * Turn keyboard echoing on or off (VMS).  Loosely based on VMSmunch.c
 * and hence on Joe Meadows' file.c code.
 */
int echo(opt)
    int opt;
{
    /*
     * For VMS v5.x:
     *   IO$_SENSEMODE/SETMODE info:  Programming, Vol. 7A, System Programming,
     *     I/O User's: Part I, sec. 8.4.1.1, 8.4.3, 8.4.5, 8.6
     *   sys$assign(), sys$qio() info:  Programming, Vol. 4B, System Services,
     *     System Services Reference Manual, pp. sys-23, sys-379
     *   fixed-length descriptor info:  Programming, Vol. 3, System Services,
     *     Intro to System Routines, sec. 2.9.2
     * GRR, 15 Aug 91
     */

    static struct dsc$descriptor_s DevDesc =
        {9, DSC$K_DTYPE_T, DSC$K_CLASS_S, "SYS$INPUT"};
     /* {dsc$w_length, dsc$b_dtype, dsc$b_class, dsc$a_pointer}; */
    static short           DevChan, iosb[4];
    static long            i, status;
    static unsigned long   oldmode[2], newmode[2];   /* each = 8 bytes */
  

    /* assign a channel to standard input */
    status = sys$assign(&DevDesc, &DevChan, 0, 0);
    if (!(status & 1))
        return status;

    /* use sys$qio and the IO$_SENSEMODE function to determine the current
     * tty status (for password reading, could use IO$_READVBLK function
     * instead, but echo on/off will be more general)
     */
    status = sys$qio(0, DevChan, IO$_SENSEMODE, &iosb, 0, 0,
                     oldmode, 8, 0, 0, 0, 0);
    if (!(status & 1))
        return status;
    status = iosb[0];
    if (!(status & 1))
        return status;

    /* copy old mode into new-mode buffer, then modify to be either NOECHO or
     * ECHO (depending on function argument opt)
     */
    newmode[0] = oldmode[0];
    newmode[1] = oldmode[1];
    if (opt == 0)   /* off */
        newmode[1] |= TT$M_NOECHO;                      /* set NOECHO bit */
    else
        newmode[1] &= ~((unsigned long) TT$M_NOECHO);   /* clear NOECHO bit */

    /* use the IO$_SETMODE function to change the tty status */
    status = sys$qio(0, DevChan, IO$_SETMODE, &iosb, 0, 0,
                     newmode, 8, 0, 0, 0, 0);
    if (!(status & 1))
        return status;
    status = iosb[0];
    if (!(status & 1))
        return status;

    /* deassign the sys$input channel by way of clean-up */
    status = sys$dassgn(DevChan);
    if (!(status & 1))
        return status;

    return SS$_NORMAL;   /* we be happy */

} /* end function echo() */


#else /* !VMS */

static int echofd=(-1);       /* file descriptor whose echo is off */

/***********************************************************************
 * Turn echo off for file descriptor f.  Assumes that f is a tty device.
 */
void echoff(f)
    int f;                    /* file descriptor for which to turn echo off */
{
    struct sgttyb sg;         /* tty device structure */

    echofd = f;
    GTTY(f, &sg);             /* get settings */
    sg.sg_flags &= ~ECHO;     /* turn echo off */
    STTY(f, &sg);
}

/***********************************************************************
 * Turn echo back on for file descriptor echofd.
 */
void echon()
{
    struct sgttyb sg;         /* tty device structure */

    if (echofd != -1) {
        GTTY(echofd, &sg);    /* get settings */
        sg.sg_flags |= ECHO;  /* turn echo on */
        STTY(echofd, &sg);
        echofd = -1;
    }
}

#endif /* ?VMS */
#endif /* !DOS_OS2 */


/***********************************************************************
 * Get a password of length n-1 or less into *p using the prompt *m.
 * The entered password is not echoed.  Return p on success, NULL on
 * failure (can't get controlling tty).
 */
char *getp(m, p, n)
    char *m;                  /* prompt for password */
    char *p;                  /* return value: line input */
    int n;                    /* bytes available in p[] */
{
    char c;                   /* one-byte buffer for read() to use */
    int i;                    /* number of characters input */
    char *w;                  /* warning on retry */

#ifndef DOS_OS2
#ifndef VMS
    int f;                    /* file decsriptor for tty device */

    /* turn off echo on tty */
    if (!isatty(2))
        return NULL;          /* error if not tty */
    if ((f = open(ttyname(2), 0)) == -1)
        return NULL;
#endif /* !VMS */
    echoff(f);                /* turn echo off */
#endif /* !DOS_OS2 */

    /* get password */
    w = "";
    do {
#ifdef VMS   /* bug:  VMS adds '\n' to NULL fputs (apparently) */
        if (*w)
#endif /* VMS */
            fputs(w, stderr); /* warning if back again */
        fputs(m, stderr);     /* prompt */
        fflush(stderr);
        i = 0;
        do {                  /* read line, keeping n */
#ifdef MSVMS
            if ((c = (char)getch()) == '\r')
                c = '\n';
#else /* !MSVMS */
            read(f, &c, 1);
#endif /* ?MSVMS */
            if (i < n)
                p[i++] = c;
        } while (c != '\n');
        putc('\n', stderr);  fflush(stderr);
        w = "(line too long--try again)\n";
    } while (p[i-1] != '\n');
    p[i-1] = 0;               /* terminate at newline */

#ifndef DOS_OS2
    echon();                  /* turn echo back on */
#ifndef VMS
    close(f);
#endif /* !VMS */
#endif /* !DOS_OS2 */

    /* return pointer to password */
    return p;
}


#if (defined(UNZIP) && !defined(FUNZIP))

/***********************************************************************
 * Get the password and set up keys for current zipfile member.  Return
 * PK_ class error.
 */
int decrypt_member()
{
    ush b;
    int n, r;
    static int nopwd=FALSE;
    char *m, *prompt;
    uch h[RAND_HEAD_LEN];

    Tracecr(("\n[incnt = %d]: ", incnt));

    /* get header once (turn off "encrypted" flag temporarily so we don't
     * try to decrypt the same data twice) */
    pInfo->encrypted = FALSE;
    for (n = 0; n < RAND_HEAD_LEN; n++) {
        ReadByte(&b);
        h[n] = (uch)b;
        Tracecr((" (%02x)", h[n]));
    }
    pInfo->encrypted = TRUE;

    /* if have key already, test it; else allocate memory for it */
    if (key) {
        if (!testp(h))
            return PK_COOL;   /* existing password OK (else prompt for new) */
        else if (nopwd)
            return PK_WARN;   /* user indicated no more prompting */
    } else if ((key = (char *)malloc(PWLEN+1)) == (char *)NULL)
        return PK_MEM2;

    if ((prompt = (char *)malloc(FILNAMSIZ+15)) != (char *)NULL) {
        sprintf(prompt, "%s password: ", filename);
        m = prompt;
    } else
        m = "Enter password: ";

    /* try a few keys */
    for (r = 0;  r < 3;  ++r) {
        m = getp(m, key, PWLEN+1);
        if (prompt != (char *)NULL) {
            free(prompt);
            prompt = (char *)NULL;
        }
        if (m == (char *)NULL)
            return PK_MEM2;
        if (!testp(h))
            return PK_COOL;
        if (*key == '\0') {
            nopwd = TRUE;
            return PK_WARN;
        }
        m = "password incorrect--reenter: ";
    }
    return PK_WARN;
} /* end function decrypt_member() */

/***********************************************************************
 * Test the password.  Return -1 if bad, 0 if OK.
 */
local int testp(h)
    uch *h;
{
    ush b, c;
    int n;
    uch *p;
    uch hh[RAND_HEAD_LEN]; /* decrypted header */

    /* set keys and save the encrypted header */
    init_keys(key);
    memcpy(hh, h, RAND_HEAD_LEN);

    /* check password */
    for (n = 0; n < RAND_HEAD_LEN; n++) {
        zdecode(hh[n]);
        Tracecr((" %02x", hh[n]));
    }
    c = hh[RAND_HEAD_LEN-2], b = hh[RAND_HEAD_LEN-1];

    Tracecr(("\n  lrec.crc= %08lx  crec.crc= %08lx  pInfo->ExtLocHdr= %s\n",
      lrec.crc32, pInfo->crc, pInfo->ExtLocHdr? "true":"false"));
    Tracecr(("  incnt = %d  unzip offset into zipfile = %ld\n", incnt,
      cur_zipfile_bufstart+(inptr-inbuf)));
    Tracecr(("  (c | (b<<8)) = %04x  (crc >> 16) = %04x  lrec.time = %04x\n",
      (ush)(c | (b<<8)), (ush)(lrec.crc32 >> 16), lrec.last_mod_file_time));

    /* same test as in zipbare(): */

#ifdef ZIP10 /* check two bytes */
    if ((ush)(c | (b<<8)) != (pInfo->ExtLocHdr? lrec.last_mod_file_time :
        (ush)(lrec.crc32 >> 16)))
        return -1;  /* bad */
#else
    c++; /* avoid warning on unused variable */
    if (b != (pInfo->ExtLocHdr? lrec.last_mod_file_time >> 8 :
        (ush)(lrec.crc32 >> 24)))
        return -1;  /* bad */
#endif
    /* password OK:  decrypt current buffer contents before leaving */
    for (n = (longint)incnt > csize ? (int)csize : incnt, p = inptr; n--; p++)
        zdecode(*p);
    return 0;       /* OK */

} /* end function testp() */

#endif /* UNZIP && !FUNZIP */
