/*
 * zip2john processes input ZIP files into a format suitable for use with JtR.
 *
 * This software is
 * Copyright (c) 2011-2018 Dhiru Kholia <dhiru.kholia at gmail.com>,
 * Copyright (c) 2011-2018 JimF, Copyright (c) 2020 Simon Rettberg,
 * Copyright (c) 2013-2021 magnum,
 * and it is hereby released to the general public under the following terms:
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted.
 *
 * References:
 *
 * 1. http://www.winzip.com/aes_info.htm
 * 2. http://www.winzip.com/aes_tips.htm
 * 4. ftp://ftp.info-zip.org/pub/infozip/doc/appnote-iz-latest.zip
 * 5. Nathan Moinvaziri's work in extending minizip to support AES.
 * 6. http://oldhome.schmorp.de/marc/fcrackzip.html (coding hints)
 * 7. http://www.pkware.com/documents/casestudies/APPNOTE.TXT
 * 8. http://gladman.plushost.co.uk/oldsite/cryptography_technology/fileencrypt/index.php
 *   (borrowed files have "gladman_" prepended to them). This gladman code has been removed from JtR source tree.
 *
 * Usage:
 *
 * 1. Run zip2john on zip file(s) as "zip2john [zip files]".
 *    Output is written to standard output.
 * 2. Run JtR on the output generated by zip2john as "john [output file]".
 *
 * Output Line Format:
 *
 * For type = 0, for ZIP files encrypted using AES
 * filename:$zip$*type*hex(CRC)*encryption_strength*hex(salt)*hex(password_verfication_value):hex(authentication_code)
 *
 * Original $pkzip$ only had CS, fixed to be part of CRC, which was found out sometimes inappropriate.
 * filename:$pkzip$C*B*[DT*MT{CL*UL*CR*OF*OX}*CT*DL*CS*DA]*$/pkzip$
 *   CS  2 bytes of checksum data.
 *
 * The newer $pkzip2$ addressed the problem by adding TC (as in timestamp), but still neither zip2john or
 * the format really knew when to use which (resulting in suboptimal early rejection).
 * filename:$pkzip2$C*B*[DT*MT{CL*UL*CR*OF*OX}*CT*DL*CS*TC*DA]*$/pkzip2$ (with 2 checksums: CS & TC)
 *   CS  2 bytes of checksum data.
 *   TC  2 bytes of checksum data (from timestamp)
 *
 * Current version (Feb 2021) reverted to original $pkzip$ but now with the one correct value put in CS: Sometimes
 * it's taken from timestamp, sometimes part of the CRC.
 * filename:$pkzip$C*B*[DT*MT{CL*UL*CR*OF*OX}*CT*DL*CS*DA]*$/pkzip$
 *   CS  Depending on archive, 2 bytes of either checksum data or timestamp.
 *
 * All numeric and 'binary data' fields are stored in hex.
 *
 * C   is the count of hashes present (the array of items, inside the []  C can be 1 to 8.).
 * B   is number of valid bytes in the CS.  For ZIP version [needed to extract] < 2.0, this is 2, otherwise it's 1.
 *     The B value should actually be defined within the ARRAY below, not per archive. For now we set it to 1 if any
 *     of the files limits it.
 * ARRAY of data starts here
 *   DT  is a "Data Type enum".  This will be 1 2 or 3.  1 is 'partial'. 2 and 3 are full file data (2 is inline, 3 is load from file).
 *   MT  Magic Type enum.  0 is no 'type'.  255 is 'text'. Other types (like MS Doc, GIF, etc), see source.
 *     NOTE, CL, DL, CRC, OFF are only present if DT != 1
 *     CL  Compressed length of file blob data (includes 12 byte IV).
 *     UL  Uncompressed length of the file.
 *     CR  CRC32 of the 'final' file.
 *     OF  Offset to the PK\x3\x4 record for this file data. If DT == 2, then this will be a 0, as it is not needed, all of the data is already included in the line.
 *     OX  Additional offset (past OF), to get to the zip data within the file.
 *     END OF 'optional' fields.
 *   CT  Compression type  (0 or 8)  0 is stored, 8 is imploded.
 *   DL  Length of the DA data.
 *   CS  2 bytes of checksum data, from CRC, *or* from either CRC or timestamp (see above).
 *   TC  2 bytes of checksum data, from timestamp (only $pkzip2$, see above).
 *   DA  This is the 'data'.  It will be hex data if DT == 1 or 2. If DT == 3, then it is a filename (name of the .zip file).
 * END of array item.  There will be C (count) array items.
 * The format string will end with $/pkzip$
 *
 * The AES-zip format redone by JimF, Summer 2014.  Spent some time to understand the AES authentication code,
 * and now have placed code to do this. However, this required format change.  The old AES format was:
 *
 *    For type = 0, for ZIP files encrypted using AES
 *    filename:$zip$*type*hex(CRC)*encryption_strength*hex(salt)*hex(password_verfication_value):hex(authentication_code)
 *     NOTE, the authentication code was NOT part of this, even though documented in this file. nor is hex(CRC) a part.
 *
 * The new format is:  (and the $zip$ is deprecated)
 *
 *    filename:$zip2$*Ty*Mo*Ma*Sa*Va*Le*DF*Au*$/zip2$
 *    Ty = type (0) and ignored.
 *    Mo = mode (1 2 3 for 128/192/256 bit)
 *    Ma = magic (file magic).  This is reservered for now.  See pkzip_fmt_plug.c or zip2john.c for information.
 *         For now, this must be a '0'
 *    Sa = salt(hex).   8, 12 or 16 bytes of salt (depends on mode)
 *    Va = Verification bytes(hex) (2 byte quick checker)
 *    Le = real compr len (hex) length of compressed/encrypted data (field DF)
 *    DF = compressed data DF can be Le*2 hex bytes, and if so, then it is the ENTIRE file blob written 'inline'.
 *         However, if the data blob is too long, then a .zip ZIPDATA_FILE_PTR_RECORD structure will be the 'contents' of DF
 *    Au = Authentication code (hex) a 10 byte hex value that is the hmac-sha1 of data over DF. This is the binary() value
 *
 *  ZIPDATA_FILE_PTR_RECORD  (this can be the 'DF' of this above hash line).
 *      *ZFILE*Fn*Oh*Ob*  (Note, the leading and trailing * are the * that 'wrap' the DF object.
 *  ZFILE This is the literal string ZFILE
 *  Fn    This is the name of the .zip file.  NOTE the user will need to keep the .zip file in proper locations (same as
 *        was seen when running zip2john. If the file is removed, this hash line will no longer be valid.
 *  Oh    Offset to the zip local header record for this blob.
 *  Ob    Offset to the start of the blob data
 *
 *
 * The new format for PKWARE's Strong Encryption Specification is:
 *
 *    filename:$zip3$*Ty*Al*Bi*Ma*Sa*Erd*Le*DF*Au*Fn
 *    Ty = type (0) and ignored.
 *    Al = algorithm (1 for AES)
 *    Bi = bit length (128/192/256 bit)
 *    Ma = magic (file magic), reserved, must be '0' now
 *    Sa = salt(hex), 12 or 16 bytes of IV data
 *    Erd = encrypted random data (max. 256 bytes)
 *    Le = real compr len (hex) length of compressed/encrypted data (field DF), unused currently
 *    DF = compressed data DF can be Le*2 hex bytes, and if so, then it is the ENTIRE file blob written 'inline', unused currently
 *    Au = authentication code, a 8 byte hex value that contains a CRC32 checksum, unused currently
 *    Fn = filename within zip file
 */

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "arch.h"
#if !AC_BUILT || HAVE_LIMITS_H
#include <limits.h>
#endif
#include <errno.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#if (!AC_BUILT || HAVE_UNISTD_H) && !_MSC_VER
#include <unistd.h>
#endif

#include "common.h"
#include "jumbo.h"
#include "formats.h"
#include "memory.h"
#include "pkzip.h"
#ifdef _MSC_VER
#include "missing_getopt.h"
#endif
#include "johnswap.h"

#define _STR_VALUE(arg) #arg
#define STR_MACRO(n)    _STR_VALUE(n)
#define MAX_FILES STR_MACRO(MAX_PKZ_FILES)

#define MAX_BLOB_INLINE_SIZE 0x400000000ULL // 16 GB

#define FLAG_ENCRYPTED            1
#define FLAG_LOCAL_SIZE_UNKNOWN   8
#define FLAG_STRONG_ENCRYPTION   64

#define AES_EXTRA_DATA_LENGTH 7  // https://www.winzip.com/aes_info.html#extra-data
#define AES_AUTHENTICATION_CODE_LENGTH 10 // http://www.winzip.com/aes_info.htm#authentication-code
#define AES_PASSWORD_VERIFICATION_LENGTH 2 // https://www.winzip.com/aes_info.html#pwd-verify

// TODO: Add macro for fprintf(stderr, ...) and make DEBUG a command line switch

// Simple wrappers
static void xfseek(FILE *stream, long offset, int whence)
{
	if (fseek(stream, offset, whence) == -1) {
		error(1, errno, "fseek");
	}
}

static size_t xfread(void *ptr, size_t size, size_t nmemb, FILE *stream)
{
	size_t ret = fread(ptr, size, nmemb, stream);
	if (ret != nmemb) {
		if (feof(stream)) {
			error(1, 0, "Reached end of stream on fread");
		} else if (ferror(stream)) {
			error(1, 0, "ferror triggered on fread");
		} else {
			error(1, 0, "No reason for short read on fread (%"PRIu64"/%"PRIu64" records)",
			      (uint64_t)ret, (uint64_t)nmemb);
		}
	}
	return ret;
}

/**
 * Read a char from stream, push it back if it doesn't have the expected value.
 * This is so we can properly look for header magic (32bit).
 */
static int fexpect(FILE *stream, int c)
{
	int d = fgetc(stream);
	if (d == EOF)
		return 0;
	if (d != c) {
		ungetc(d, stream);
	}
	return d == c;
}

static int checksum_only, use_magic;
static char *ascii_fname, *only_fname;

static char *MagicTypes[] = { "", "DOC", "XLS", "DOT", "XLT", "EXE", "DLL", "ZIP", "BMP", "DIB", "GIF", "PDF", "GZ", "TGZ", "BZ2", "TZ2", "FLV", "SWF", "MP3", "PST", NULL };
static int  MagicToEnum[] = {  0,   1,     1,     1,     1,     2,     2,     3,     4,     4,     5,     6,     7,    7,     8,     8,     9,     10,    11,  12, 0 };

static void print_hex_inline(unsigned char *str, int len)
{
	int i;
	for (i = 0; i < len; ++i)
		printf("%02x", str[i]);
}

// Represents a file in the archive
typedef struct _zip_ptr
{
	uint16_t      version, flags;
	uint16_t      magic_type, cmptype;
	char         *hash_data;
	char         *file_name;
	uint64_t      offset, offex;
	uint64_t      cmp_len, decomp_len;
	uint32_t      crc;
	char          cs[5]; // High-order word of either crc or timestamp
	int           zip64; // Has extended header with 64bit data
	uint16_t      lastmod_date, lastmod_time;
	uint16_t      extrafield_length;
	struct {
		uint16_t vendor_version, vendor_id, cmptype;
		char strength;
		int found;
	} aes;
} zip_ptr;

// Represents a zip archive
typedef struct _zip_file
{
	FILE *fp;
	const char *fname;
	int check_bytes;  // number of valid bytes in checksum
	int zip64; // Is ZIP64 format (has ZIP64 EOCD)
} zip_file;

// Wrapper struct so we don't have to pass three pointers all the time
typedef struct _zip_context {
	zip_ptr best_files[MAX_PKZ_FILES]; // Up to 8 candidates for handling old encryption type archives
	zip_ptr curzip;         // Meta data of file in archive we're currently processing
	zip_file archive;       // The zip file being processed
	int num_candidates;     // Number of candidates in best_files array
} zip_context;

/**
 * Read 64bit fields from zip64 extended file header.
 * fp is expected to point right after the tag and size of the efh
 */
static void handle_zip64_ef(FILE *fp, zip_ptr *p, uint16_t len, int is_local)
{
	uint64_t decomp_len = UINT64_MAX, cmp_len = UINT64_MAX, offset = UINT64_MAX;
	long rem_len = len;
#ifdef DEBUG
	fprintf(stderr, "Handling ZIP64 ef (local = %d)\n", is_local);
#endif
	// Only read this if we didn't get it from the Central Directory yet, otherwise
	// we need to store both, local and central values in the zip_ptr struct, as the
	// handle_zip64_ef function needs to know whether the fields of the header this
	// extra field belongs to were 0xff..ff, because only in that case the according
	// 64bit fields will be present here.
	// See "4.5.3 -Zip64 Extended Information Extra Field (0x0001)" in APPNOTE.TXT
	// The additional checks that reset the read values to UINT64_MAX (=not present)
	// is a workaround for archives that have valid values in their normal headers
	// (ie. fitting into 32bit) but additionally supply a bogus ZIP64 extended field
	// in their local header, where the according fields are 0.
	if (p->zip64) {
		xfseek(fp, len, SEEK_CUR);
		return;
	}
	if (p->decomp_len == UINT32_MAX || is_local) {
		decomp_len = fget64LE(fp);
		if (p->decomp_len != UINT32_MAX && decomp_len == 0) {
			decomp_len = UINT64_MAX;
		}
		rem_len -= 8;
	}
	if (p->cmp_len == UINT32_MAX || is_local) {
		cmp_len = fget64LE(fp);
		if (p->cmp_len != UINT32_MAX && cmp_len == 0) {
			cmp_len = UINT64_MAX;
		}
		rem_len -= 8;
	}
	if (p->offset == UINT32_MAX) {
		offset = fget64LE(fp);
		if (p->offset != UINT32_MAX && offset == 0) {
			offset = UINT64_MAX;
		}
		rem_len -= 8;
	}
	// Other fields we don't care about follow...
	// Now only apply what we read if there was enough bytes in the field
	if (rem_len >= 0) {
		p->zip64 = 1;
		if (decomp_len != UINT64_MAX) {
			p->decomp_len = decomp_len;
		}
		if (cmp_len != UINT64_MAX) {
			p->cmp_len = cmp_len;
		}
		if (offset != UINT64_MAX) {
			p->offset = offset;
		}
	} else {
		fprintf(stderr, "Ignoring short zip64 extended field for %s\n", p->file_name);
	}
	xfseek(fp, rem_len, SEEK_CUR);
#if DEBUG
	fprintf(stderr,
			"ZIP64 EXTRA FIELD; NEW: decomp_len=%"PRIu64" cmp_len=%"PRIu64" offset=%"PRIu64"\n",
			p->decomp_len, p->cmp_len, p->offset);
#endif
}

/**
 * Handle the extended field for AES encrypted files (99)
 */
static void handle_aes_ef(FILE *fp, zip_ptr *p, uint16_t len)
{
	// Data size: this value is currently 7, but because it is possible that this
	// specification will be modified in the future to store additional data in
	// this extra field, vendors should not assume that it will always remain 7.
	if (len != AES_EXTRA_DATA_LENGTH) {
		fprintf(stderr, "AES_EXTRA_DATA_LENGTH is not 7 for %s, please report this to us!\n", p->file_name);
		xfseek(fp, len, SEEK_CUR);
		return;
	}
	p->aes.found = 1;
	p->aes.vendor_version = fget16LE(fp);
	p->aes.vendor_id = fget16LE(fp);
	p->aes.strength = fgetc(fp);
	p->aes.cmptype = fget16LE(fp);
}

static void scan_for_data_descriptor(zip_file *zip, zip_ptr *p);

/**
 * Process AES encrypted file in zip and produce according hash.
 * file pointer in archive is assumed to be at the beginning of
 * the local extra fields.
 */
static int process_aes(zip_file *zip, zip_ptr *p)
{
	FILE *fp = zip->fp;

	if (p->cmptype == 99) {	/* AES encryption */
		uint64_t real_cmpr_len;
		uint16_t efh_id;
		uint16_t efh_datasize;
		unsigned char salt[16];
		char *bname;
		int d;
		int magic_enum = 0;  // reserved at 0 for now, we are not computing this (yet).
		uint64_t i;
		uint32_t salt_length;
		uint16_t ef_remaining = p->extrafield_length;

		// There could be multiple extra fields, so need to process them all.
		while (!ferror(fp) && !feof(fp) && ef_remaining > 0) {
			efh_id = fget16LE(fp);
			efh_datasize = fget16LE(fp);

			// Adjust the bytes processed for id, size and acutal data so the
			// file pointer is moved on correctly,
			// - 2 bytes for the efh_id
			// - 2 bytes for the efh_datasize
			ef_remaining = ef_remaining - 2 - 2 - efh_datasize;
			if (efh_id == 0x9901) {
				handle_aes_ef(fp, p, efh_datasize);
			} else if (efh_id == 0x0001) {
				handle_zip64_ef(fp, p, efh_datasize, 1);
			} else {
				xfseek(fp, efh_datasize, SEEK_CUR);
			}
		}
		if (!p->aes.found) {
			fprintf(stderr, "Couldn't find (valid) extra header for type 99 AES entry for %s in %s.\n", p->file_name, zip->fname);
			return 0;
		}

		bname = jtr_basename(zip->fname);

#if DEBUG
		fprintf(stderr,
			 "%s/%s is using AES encryption (AE-%"PRIu16"), extrafield_length is %d\n",
			 bname, p->file_name, p->aes.vendor_version, p->extrafield_length);
#endif
		if (p->aes.vendor_version < 1 || p->aes.vendor_version > 2) {
			fprintf(stderr, "! Unknown AES encryption version: %"PRIu16"\n", p->aes.vendor_version);
			return 0;
		}
		if (p->aes.strength < 1 || p->aes.strength > 3) {
			fprintf(stderr, "! Unknown AES encryption strength: %u\n", p->aes.strength);
			return 0;
		}
#if DEBUG
		if (p->aes.vendor_version == 2 && p->crc != 0) {
			fprintf(stderr, "Odd: crc is not zero for AE-2 encrypted file.\n");
		}
#endif

		if (p->cmp_len == 0 && (p->flags & FLAG_LOCAL_SIZE_UNKNOWN)) {
			scan_for_data_descriptor(zip, p);
		}
		salt_length = 4 + 4 * p->aes.strength; // 128 -> 8, 192 -> 12, 256 -> 16
		if (sizeof(salt) < salt_length ||
			fread(salt, 1, salt_length, fp) != salt_length) {
				fprintf(stderr, "Error, in fread of salt!\n");
				return 0;
		}

		printf("%s/%s:$zip2$*0*%x*%x*",
				bname, p->file_name, p->aes.strength,
				magic_enum);

		// Print salt value
		for (i = 0; i < salt_length; i++) {
			printf("%c%c",
					itoa16[ARCH_INDEX(salt[i] >> 4)],
					itoa16[ARCH_INDEX(salt[i] & 0x0f)]);
		}
		printf("*");
		// Password verification value
		// since in the format we read/compare this one, we do it char by
		// char, so there is no endianity swapping needed. (validator)
		for (i = 0; i < 2; i++) {
			d = fgetc(fp);
			if (d == EOF)
				break;
			printf("%c%c",
					itoa16[ARCH_INDEX(d >> 4)],
					itoa16[ARCH_INDEX(d & 0x0f)]);
		}
		if (p->cmp_len <= AES_PASSWORD_VERIFICATION_LENGTH + salt_length + AES_AUTHENTICATION_CODE_LENGTH) {
			real_cmpr_len = 0;
			fprintf(stderr, "!? compressed length of AES entry too short.\n");
		} else {
			// Password verification value -> 2 bytes
			// Salt value -> salt_length
			// Authentication code -> 10 bytes
			real_cmpr_len = p->cmp_len - AES_PASSWORD_VERIFICATION_LENGTH
				- salt_length - AES_AUTHENTICATION_CODE_LENGTH;
		}
		printf("*%"PRIx64"*", real_cmpr_len);

		// Actual encrypted data
		if (real_cmpr_len > MAX_BLOB_INLINE_SIZE) {
			printf("ZFILE*%s*%"PRIx64"*%lx", zip->fname, p->offset, ftell(fp));
			xfseek(fp, real_cmpr_len, SEEK_CUR);
		} else {
			for (i = 0; i < real_cmpr_len; i++) {
				d = fgetc(fp);
				if (d == EOF)
					break;
				printf("%c%c",
						itoa16[ARCH_INDEX(d >> 4)],
						itoa16[ARCH_INDEX(d & 0x0f)]);
			}
		}
		printf("*");
		// Authentication code
		for (i = 0; i < AES_AUTHENTICATION_CODE_LENGTH; i++) {
			d = fgetc(fp);
			if (d == EOF)
				break;
			printf("%c%c",
					itoa16[ARCH_INDEX(d >> 4)],
					itoa16[ARCH_INDEX(d & 0x0f)]);
		}
		printf("*$/zip2$:%s:%s:%s\n",
				p->file_name, bname, zip->fname);
		return 1;
	}
	if ((p->flags & FLAG_ENCRYPTED) /* Try to detect Strong Encryption, bit 6 is sometimes not set */
			&& ((p->flags & FLAG_STRONG_ENCRYPTION) || p->version == 51 || p->version == 52 || p->version >= 61)) {
		// fseek(fp, filename_length, SEEK_CUR);
		// fseek(fp, extrafield_length, SEEK_CUR);
		// continue;
		unsigned char iv[16];
		unsigned char Erd[256];
		uint32_t Format;
		uint16_t AlgId;
		uint16_t Bitlen;
		uint16_t ErdSize;
		uint32_t Reserved1;
		uint16_t VSize;
		uint16_t IVSize;
		char *bname;

		fprintf(stderr, "Poking at %s in %s to see if it's indeed strong encryption...\n", p->file_name, zip->fname);
		bname = jtr_basename(zip->fname);
		IVSize = fget16LE(fp);
		if (IVSize > sizeof(iv)) {
			fprintf(stderr, "No (IV too long: %"PRIu16")\n", IVSize);
			return 0;
		}
		if (fread(iv, 1, IVSize, fp) != IVSize) {
			fprintf(stderr, "No (Error reading IV)\n");
			return 0;
		}
		(void) fget32LE(fp); /* Size */
		Format = fget16LE(fp);
		if (Format != 3) {
			fprintf(stderr, "No (Format == %"PRIu32")\n", Format);
			return 0;
		}
		AlgId = fget16LE(fp);
		if (AlgId == 0x660E || AlgId == 0x660F || AlgId ==  0x6610) {
			AlgId = 1;
		} else if (AlgId == 0x6603 || AlgId == 0x6609 || AlgId == 0x6720 || AlgId == 0x6721 || AlgId == 0x6801) {
			fprintf(stderr, "AlgId (%x) is currently unsupported, please report this to us!\n", AlgId);
			return 0;
		} else {
			fprintf(stderr, "No (Unknown AlgId)\n");
			return 0;
		}
		if (IVSize == 0) {
			uint32_t crc = p->crc;
			uint64_t decomp_len = p->decomp_len;
#if DEBUG
			fprintf(stderr, "IV size is 0, assuming it is crc + decomp_len...\n");
#endif
			memset(iv, 0, 16);
#if !ARCH_LITTLE_ENDIAN
			crc = JOHNSWAP(crc);
			decomp_len = JOHNSWAP64(decomp_len);
#endif
			memcpy(iv, &crc, 4);
			memcpy(iv + 4, &decomp_len, 8);
			IVSize = 12;
		}
		Bitlen = fget16LE(fp);
		(void) fget16LE(fp); /* Flags */
		ErdSize = fget16LE(fp);
		if (ErdSize > sizeof(Erd)) {
			fprintf(stderr, "No (ErdSize too large: %"PRIu16")\n", ErdSize);
			return 0;
		}
		if (fread(Erd, 1, ErdSize, fp) != ErdSize) {
			fprintf(stderr, "! Could not fread Erd from file\n");
			return 0;
		}
		Reserved1 = fget32LE(fp);
		if (Reserved1 != 0) {
			fprintf(stderr, "Reserved1 is %u (non-zero), please report this bug to us!\n", Reserved1);
			return 0;
		}
		VSize = fget16LE(fp);
		xfseek(fp, VSize, SEEK_CUR);

		printf("%s:$zip3$*%d*%d*%d*%d*", bname, 0, AlgId, Bitlen, 0);
		print_hex_inline(iv, IVSize);  // getting this right isn't important ;)
		printf("*");
		print_hex_inline(Erd, ErdSize);
		printf("*0*0*0*%s\n", p->file_name);
		return 1;
	}
	return 0;
}

static int magic_type(const char *filename) {
	char *Buf = str_alloc_copy((char*)filename), *cp;
	int i;

	if (!use_magic)
		return 0;

	strupr(Buf);
	if (ascii_fname && !strcasecmp(Buf, ascii_fname))
		return 255;

	cp = strrchr(Buf, '.');
	if (!cp)
		return 0;
	++cp;
	for (i = 1; MagicTypes[i]; ++i)
		if (!strcmp(cp, MagicTypes[i]))
			return MagicToEnum[i];
	return 0;
}

static void print_hex(unsigned char *p, uint64_t len) {
	while (len--)
		printf("%02x", *p++);
	printf("*");
}

// If archive was created from a non-seekable stream, we need to find CRC and
// sizes AFTER file data which means we're in a hen-and-egg situation since we
// don't know the size... I think the below is enough but there may be edge
// cases where we need to also recognize some other kind of start-of-whatever
// and seek back 16 bytes.
// This would also fail if there is some garbage bytes between the end of the
// current file and the next one, but you can't cover everything I guess.
static void scan_for_data_descriptor(zip_file *zip, zip_ptr *p)
{
	FILE *fp = zip->fp;
	long saved_pos = ftell(fp);
	uint32_t crc = 0;
	uint64_t cmp_len = 0, decomp_len = 0;

	// All values known? Nothing to do.
	if (p->cmp_len && p->decomp_len && p->crc)
		return;
	// Flag not set, and both sizes are 0: accept as valid
	if (p->cmp_len == 0 && p->decomp_len == 0 && !(p->flags & FLAG_LOCAL_SIZE_UNKNOWN))
		return;
	// Likewise, no flag set and at least one field filled: accept
	if ((p->cmp_len || p->decomp_len || p->crc) && !(p->flags & FLAG_LOCAL_SIZE_UNKNOWN))
		return;

	fprintf(stderr, "Scanning for EOD... ");
	while (!feof(fp)) {
		if (fgetc(fp) != 0x50 || !fexpect(fp, 0x4b))
			continue;
		if (fexpect(fp, 0x07) && fexpect(fp, 0x08)) {
			// Best case: Found the optional header
			fprintf(stderr, "FOUND Extended local header\n");
			crc = fget32LE(fp);
			if (zip->zip64) {
				cmp_len = fget64LE(fp);
				decomp_len = fget64LE(fp);
			} else {
				cmp_len = fget32LE(fp);
				decomp_len = fget32LE(fp);
			}
			break;
		}
		if ((fexpect(fp, 0x03) && fexpect(fp, 0x04)
				&& fprintf(stderr, "FOUND next Local file header\n"))
			|| (fexpect(fp, 0x01) && fexpect(fp, 0x02)
				&& fprintf(stderr, "FOUND Central directory\n"))
		) {
			// No optional header, but we found something that looks like the
			// start of another file, or the central directory. Assume that
			// the data we're interested in lies directly before this header.
			if (zip->zip64)
				xfseek(fp, -24, SEEK_CUR);
			else
				xfseek(fp, -16, SEEK_CUR);
			crc = fget32LE(fp);
			if (zip->zip64) {
				cmp_len = fget64LE(fp);
				decomp_len = fget64LE(fp);
			} else {
				cmp_len = fget32LE(fp);
				decomp_len = fget32LE(fp);
			}
			break;
		}
	}
	if (feof(fp)) {
		fprintf(stderr, "Nothing found.\n");
	} else {
		if (cmp_len > ftell(fp) - p->offset) {
			fprintf(stderr, "!? Compressed len stored after compessed data (%"PRIu64") is"
					" larger than offset between current and next file (%"PRIu64")\n",
					cmp_len, ftell(fp) - p->offset);
		}
		//fprintf(stderr, "cmp_len: %"PRIu64", decomp_len: %"PRIu64", crc32: %"PRIu32"\n",
		//		cmp_len, decomp_len, crc);
		if (p->cmp_len == 0) {
			p->cmp_len = cmp_len;
		}
		if (p->decomp_len == 0) {
			p->decomp_len = decomp_len;
		}
		if (p->crc == 0) {
			p->crc = crc;
		}
	}

	xfseek(fp, saved_pos, SEEK_SET);
}

static int load_local_header(zip_file *zfp, zip_ptr *p)
{
	FILE *fp = zfp->fp;
	uint16_t filename_length;

	p->offset = ftell(fp)-4;
	// Ignore mismatches for these for now and trust local over central, but we warn about crc/size below
	p->version = fget16LE(fp) & 0xff; // Remove OS compatibility byte, don't care
	p->flags = fget16LE(fp);
	p->cmptype = fget16LE(fp);
	p->lastmod_time = fget16LE(fp);
	p->lastmod_date = fget16LE(fp);
	if (!(p->flags & FLAG_LOCAL_SIZE_UNKNOWN)) {
		uint32_t crc, cmp_len, decomp_len;
		crc = fget32LE(fp);
		cmp_len = fget32LE(fp);
		decomp_len = fget32LE(fp);
		if (!p->zip64) {
			// If we already got 64bit values from the central directory, don't overwrite them again with 32bit ones
			if (crc == 0 && p->crc != 0) {
				fprintf(stderr, "Local CRC field is 0, but central one is %"PRIx32". Weird.\n", p->crc);
			} else {
				p->crc = crc;
			}
			if (cmp_len == 0 && p->cmp_len != 0) {
				fprintf(stderr, "Local cmp_len field is 0, but central one is %"PRIx64". Weird.\n", p->cmp_len);
			} else {
				p->cmp_len = cmp_len;
			}
			if (decomp_len == 0 && p->decomp_len != 0) {
				fprintf(stderr, "Local decomp_len field is 0, but central one is %"PRIx64". Weird.\n", p->decomp_len);
			} else {
				p->decomp_len = decomp_len;
			}
		}
	} else { // Local size unknown, skip according fields
		xfseek(fp, 12, SEEK_CUR);
	}
	filename_length = fget16LE(fp);
	p->extrafield_length = fget16LE(fp);

	if (p->file_name != NULL) {
		xfseek(fp, filename_length, SEEK_CUR);
	} else {
		p->file_name = mem_alloc(filename_length + 1);
		xfread(p->file_name, filename_length, 1, fp);
		p->file_name[filename_length] = 0;
	}
	if (only_fname != NULL && strcmp(only_fname, (char*)p->file_name) != 0)
		return 0; // Not interested in this one

	p->magic_type = magic_type(p->file_name);
	p->offex = 30 + filename_length + p->extrafield_length;
	return 1;
}

/**
 * Process file in archive with legacy encryption. File pointer
 * is assumed to be at the start of local extra fields.
 */
static int process_legacy(zip_file *zfp, zip_ptr *p)
{
	FILE *fp = zfp->fp;

	fprintf(stderr, "ver %d.%d ", p->version / 10, p->version % 10);

	if ( (p->flags & FLAG_ENCRYPTED) &&
	     (p->version == 10 || p->version == 20 || p->version == 45)) {
		uint16_t extra_len_used = 0;

		while (extra_len_used < p->extrafield_length) {
			uint16_t efh_id = fget16LE(fp);
			uint16_t efh_datasize = fget16LE(fp);

			fprintf(stderr, "efh %04x ", efh_id);

			if (efh_id == 0x0001) {
				handle_zip64_ef(fp, p, efh_datasize, 1);
				extra_len_used += efh_datasize;
				efh_datasize = 0;
			}
			xfseek(fp, efh_datasize, SEEK_CUR);

			extra_len_used += 4 + efh_datasize;
		}

		if (p->version >= 20)
			zfp->check_bytes = 1;
		else if (zfp->check_bytes == 1)
			fprintf(stderr, "** 2b ** ");

		scan_for_data_descriptor(zfp, p);

		if (p->cmptype != 0 && p->cmptype != 8) {
			fprintf(stderr, "%s/%s is not encrypted, or stored with non-handled compression type=%"PRIu16"\n",
			        zfp->fname, p->file_name, p->cmptype);
			return 0;
		}

		// Ok, now set checksum bytes.  This will depend upon if from crc, or from timestamp
		if (p->flags & FLAG_LOCAL_SIZE_UNKNOWN)
			sprintf(p->cs, "%02x%02x", p->lastmod_time >> 8, p->lastmod_time & 0xFF);
		else
			sprintf(p->cs, "%02x%02x", (p->crc >> 24) & 0xFF, (p->crc >> 16) & 0xFF);

		fprintf(stderr,
		        "%s/%s PKZIP%s Encr: %s%scmplen=%"PRIu64", decmplen=%"PRIu64", crc=%08X ts=%04X cs=%s type=%"PRIu16"\n",
		        jtr_basename(zfp->fname), p->file_name,
		        zfp->zip64 ? "64" : "",
		        zfp->check_bytes == 2 ? "2b chk, " : "",
		        p->flags & FLAG_LOCAL_SIZE_UNKNOWN ? "TS_chk, " : "",
		        p->cmp_len, p->decomp_len, p->crc, p->lastmod_time, p->cs, p->cmptype);

		MEM_FREE(p->hash_data);
		p->hash_data = mem_alloc(p->cmp_len + 1);
		if (fread(p->hash_data, 1, p->cmp_len, fp) != p->cmp_len) {
			fprintf(stderr, "Error, fread could not read the data from the file: %s\n", zfp->fname);
			return 0;
		}

		return 1;
	}

	scan_for_data_descriptor(zfp, p);

	fprintf(stderr, "%s/%s is not encrypted, or stored with non-handled compression type\n", zfp->fname, p->file_name);
	xfseek(fp, p->extrafield_length, SEEK_CUR);
	xfseek(fp, p->cmp_len, SEEK_CUR);

	return 0;
}

static void move_entry(zip_ptr *dst, zip_ptr *src)
{
	MEM_FREE(dst->file_name);
	MEM_FREE(dst->hash_data);
	memcpy(dst, src, sizeof(zip_ptr));
	src->file_name = NULL;
	src->hash_data = NULL;
}

/**
 * Process next file in zip archive. ctx->curzip is assumed to
 * be empty or populated with data from the central directory,
 * and the position in the zip file is at the beginning of the
 * local header of the according file.
 */
static void handle_file_entry(zip_context *ctx)
{
	if (!load_local_header(&ctx->archive, &ctx->curzip)) {
		//fprintf(stderr, "Skipping bad entry\n");
		return;
	}
	if (ctx->curzip.cmptype == 99) {
		// AES
		if (process_aes(&ctx->archive, &ctx->curzip))
			return;
		fprintf(stderr, "Skipping bad AES entry\n");
		return;
	}

	// Legacy
	if (!process_legacy(&ctx->archive, &ctx->curzip))
		return;

	if (ctx->curzip.decomp_len < 4) {
		fprintf(stderr, "Skipping short file %s\n", ctx->curzip.file_name);
		return;
	}

	// Suitable file with legacy encryption
	int i, j;

	for (i = 0; i < ctx->num_candidates; i++) {
		if (ctx->curzip.cmp_len < ctx->best_files[i].cmp_len) {
			for (j = ctx->num_candidates; j > i; j--)
				if (j < MAX_PKZ_FILES)
					move_entry(&(ctx->best_files[j]), &(ctx->best_files[j - 1]));
			break;
		}
	}
	move_entry(&(ctx->best_files[i]), &ctx->curzip);
	if (ctx->num_candidates < MAX_PKZ_FILES)
		ctx->num_candidates++;
}

static void print_and_cleanup(zip_context *ctx);

static void init_zip_context(zip_context *ctx, const char *fname, FILE *fp)
{
	memset(ctx, 0, sizeof(*ctx));
	ctx->archive.fname = fname;
	ctx->archive.check_bytes = 2;
	ctx->archive.fp = fp;
}

/**
 * Forward scan file for local file header signatures and try to
 * process each of these entries. Might yield false positives, or
 * have minor trouble with entries that don't have the size field
 * of the header filled out (see handle_file_entry for details).
 */
static void scan_from_start(const char *fname)
{
	FILE *fp;
	zip_context ctx;

	if (!(fp = fopen(fname, "rb"))) {
		fprintf(stderr, "! %s : %s\n", fname, strerror(errno));
		return;
	}
	init_zip_context(&ctx, fname, fp);

	while (!feof(fp)) {
		if (fgetc(fp) != 0x50 || !fexpect(fp, 0x4b))
				continue;

		if (fexpect(fp, 0x03) && fexpect(fp, 0x04)) { /* local header */
			memset(&ctx.curzip, 0, sizeof(ctx.curzip));
			handle_file_entry(&ctx);
#if 0 // Maybe not worth it - best case we skip over uninteresting parts, but might bail out too early on a false positive
		} else if (fexpect(fp, 0x07) && fexpect(fp, 0x08)) { /* data descriptor */
			xfseek(fp, 12, SEEK_CUR);
		} else if ((fexpect(fp, 0x01) && fexpect(fp, 0x02))
				|| (fexpect(fp, 0x05) && fexpect(fp, 0x06))) { /* central directory structures */
			break;
#endif
		}
	}
	fclose(fp);

	// This will only print stuff if we found at least one old type encrypted file
	print_and_cleanup(&ctx);
}

static void print_and_cleanup(zip_context *ctx)
{
	int i = 1;
	char *bname;
	static int once;
	char *filenames;
	if (ctx->num_candidates == 0)
		return;

	filenames = xstrdup(ctx->best_files[0].file_name);
	bname = jtr_basename(ctx->archive.fname);

	printf("%s%s%s:$pkzip$%x*%x*", bname,
			 ctx->num_candidates == 1 ? "/" : "",
			 ctx->num_candidates == 1 ? ctx->best_files[0].file_name : "",
			 ctx->num_candidates, ctx->archive.check_bytes);
	if (checksum_only)
		i = 0;
	for (; i < ctx->num_candidates; ++i) {
		uint64_t len = 12+24;

		if (i) {
			filenames = mem_realloc(filenames,
											strlen(filenames) +
											strlen(ctx->best_files[i].file_name) + 3);
			strcat(filenames, ", ");
			strcat(filenames, ctx->best_files[i].file_name);
		}
		if (ctx->best_files[i].magic_type)
			len = 12+180;
		if (len > ctx->best_files[i].cmp_len)
			len = ctx->best_files[i].cmp_len; // even though we 'could' output a '2', we do not.  We only need one full inflate CRC check file.
		printf("1*%x*%x*%"PRIx64"*%s*", ctx->best_files[i].magic_type, ctx->best_files[i].cmptype, (uint64_t)len, ctx->best_files[i].cs);
		print_hex((unsigned char*)ctx->best_files[i].hash_data, len);
	}
	// Ok, now output the 'little' one (the first).
	if (!checksum_only) {
		printf("%x*%x*%"PRIx64"*%"PRIx64"*%x*%"PRIx64"*%"PRIx64"*%x*", 2, ctx->best_files[0].magic_type, ctx->best_files[0].cmp_len, ctx->best_files[0].decomp_len, ctx->best_files[0].crc, ctx->best_files[0].offset, ctx->best_files[0].offex, ctx->best_files[0].cmptype);
		printf("%"PRIx64"*%s*", ctx->best_files[0].cmp_len, ctx->best_files[0].cs);
		print_hex((unsigned char*)ctx->best_files[0].hash_data, ctx->best_files[0].cmp_len);
	}
	/* Don't allow our delimiter in there! */
	replace(filenames, ':', ' ');
	if (ctx->num_candidates > 1)
		printf("$/pkzip$::%s:%s:%s\n", bname, filenames, ctx->archive.fname);
	else
		printf("$/pkzip$:%s:%s::%s\n", filenames, bname, ctx->archive.fname);

	if (ctx->num_candidates > 1 && !once++)
		fprintf(stderr,
			"NOTE: It is assumed that all files in each archive have the same password.\n"
			"If that is not the case, the hash may be uncrackable. To avoid this, use\n"
			"option -o to pick a file at a time.\n");

	for (i = 0; i < ctx->num_candidates; ++i) {
		MEM_FREE(ctx->best_files[i].hash_data);
		MEM_FREE(ctx->best_files[i].file_name);
	}
	MEM_FREE(filenames);
}

/**
 * Open given file and scan for the central directory. From there,
 * process every file in the archive that has a central directory
 * entry.
 */
static void scan_central_index(const char *fname)
{
	FILE *fp;
	long filesize;
	uint32_t this_disk, cd_start_disk;
	uint64_t num_records, num_records_total;
	uint64_t cd_size, cd_start_offset;
	zip_context ctx = {};

	if (!(fp = fopen(fname, "rb"))) {
		fprintf(stderr, "! %s : %s\n", fname, strerror(errno));
		return;
	}
	// File is now open, jump to cleanup on error
	xfseek(fp, 0, SEEK_END);
	filesize = ftell(fp);
	if (filesize == -1) {
		perror("ftell (trying to get filesize)");
		goto cleanup;
	}
	// First, scan for end of central directory. Its length is 22+n bytes, where n can be up to 64k
	if (filesize > 22 + 65535) {
		xfseek(fp, -(22 + 65535), SEEK_END);
	} else {
		xfseek(fp, 0, SEEK_SET);
	}
	while (!feof(fp)) {
		if (fgetc(fp) == 0x50 && fexpect(fp, 0x4b)) {
			long found = 0;
			int zip64 = 0;
			if (fexpect(fp, 0x05) && fexpect(fp, 0x06)) {
				// Old EOCD header
				found = ftell(fp);
				this_disk = fget16LE(fp);
				cd_start_disk = fget16LE(fp);
			}
			else if (fexpect(fp, 0x06) && fexpect(fp, 0x06)) {
				// ZIP64 EOCD
				found = ftell(fp);
				zip64 = 1;
				(void) fget64LE(fp); // size of this record
				(void) fget16LE(fp); // version made by
				(void) fget16LE(fp); // needed to extract
				this_disk = fget32LE(fp);
				cd_start_disk = fget32LE(fp);
			}
			if (found) {
				if (this_disk != 0 || cd_start_disk != 0) {
					fprintf(stderr, "Found EOCD header, but this is either a multipart archive (which are not supported), or false positive. Will keep scanning...\n");
#if DEBUG
				fprintf(stderr, "This disk: %"PRIu32", cd start disk: %"PRIu32"\n", this_disk, cd_start_disk);
#endif
				xfseek(fp, found, SEEK_SET);
				} else {
					ctx.archive.zip64 = zip64;
					break;
				}
			}
		}
		if (ferror(fp)) {
			perror("fgetc");
			goto cleanup;
		}
	}
	if (feof(fp)) {
		fprintf(stderr, "Did not find End Of Central Directory.\n");
		goto cleanup;
	}
	// Read some info about CD
	if (ctx.archive.zip64) {
		num_records = fget64LE(fp);
		num_records_total = fget64LE(fp);
		cd_size = fget64LE(fp);
		cd_start_offset = fget64LE(fp);
		// Followed by: zip64 extensible data sector
	} else {
		num_records = fget16LE(fp);
		num_records_total = fget16LE(fp);
		cd_size = fget32LE(fp);
		cd_start_offset = fget32LE(fp);
		// Followed by: 2 byte comment length, n bytes comment...
	}
#if DEBUG
	fprintf(stderr, "Central Directory has %"PRIu64"/%"PRIu64" files. ZIP64: %d\n", num_records, num_records_total, ctx.archive.zip64);
#else
	if (num_records != num_records_total) {
		fprintf(stderr, "!? num_records(%"PRIu64") != num_records_total(%"PRIu64") for single-file archive. Continuing anyways...\n", num_records, num_records_total);
	}
#endif
	// Go back to start of CD and process files from there
	xfseek(fp, cd_start_offset, SEEK_SET);
	if (fget32LE(fp) != 0x02014b50UL) {
		fprintf(stderr, "Did not find a Central Directory File Header at expected file offset %"PRIu64"\n", cd_start_offset);
		goto cleanup;
	}
	init_zip_context(&ctx, fname, fp);
	while (num_records > 0) {
		uint32_t id;
		uint16_t fn_len, extra_len, comment_len;
		long old_pos;
		memset(&ctx.curzip, 0, sizeof(ctx.curzip));
		num_records--;
		fget16LE(fp); // version made by
		// Upper byte tells OS compatibility, not actual version, mask it
		ctx.curzip.version = fget16LE(fp) & 0xff; // version needed
		ctx.curzip.flags = fget16LE(fp);
		ctx.curzip.cmptype = fget16LE(fp);
		fget16LE(fp); // filemtime
		fget16LE(fp); // filemdate
		ctx.curzip.crc = fget32LE(fp);
		ctx.curzip.cmp_len = fget32LE(fp);
		ctx.curzip.decomp_len = fget32LE(fp); // uncompressed_size
		fn_len = fget16LE(fp); // fname_len
		extra_len = fget16LE(fp); // extrafield_len
		comment_len = fget16LE(fp); // comment_length
		fget16LE(fp); // disk number where file starts
		fget16LE(fp); // internal file attributes
		fget32LE(fp); // external file attributes
		ctx.curzip.offset = fget32LE(fp); // relative offset of local file header
		ctx.curzip.file_name = mem_alloc(fn_len + 1);
		xfread(ctx.curzip.file_name, fn_len, 1, fp);
		ctx.curzip.file_name[fn_len] = '\0';
		// Handle extra fields
		while (extra_len >= 4) {
			uint16_t efh_id = fget16LE(fp);
			uint16_t efh_len = fget16LE(fp);
			extra_len -= 4;
			if (efh_id == 0x0001) {
				// ZIP64
				handle_zip64_ef(fp, &ctx.curzip, efh_len, 0);
			} else if (efh_id == 0x9901) {
				handle_aes_ef(fp, &ctx.curzip, efh_len);
			} else {
				xfseek(fp, efh_len, SEEK_CUR);
			}
			extra_len -= efh_len;
		}
		old_pos = ftell(fp);
		xfseek(fp, ctx.curzip.offset, SEEK_SET);
		id = fget32LE(fp);
		if (id != 0x04034b50) {
			fprintf(stderr, "! Did not find local file header for %s at %"PRIu64"\n", ctx.curzip.file_name, ctx.curzip.offset);
		} else {
			handle_file_entry(&ctx);
		}
		MEM_FREE(ctx.curzip.file_name);
		xfseek(fp, old_pos + extra_len + comment_len, SEEK_SET);
		if (num_records > 0) {
			if (fget32LE(fp) != 0x02014b50UL) {
				fprintf(stderr, "No more Central Directory File Headers, but expected %"PRIu64" more.\n", num_records);
				goto cleanup;
			}
		}
	}
	(void) cd_size;
	print_and_cleanup(&ctx);

cleanup:
	fclose(fp);
}

static int usage(char *name)
{
	fprintf(stderr, "Usage: %s [options] [zip file(s)]\n", name);
	fprintf(stderr, " -s Scan archive from the beginning, looking for local file headers. This\n");
	fprintf(stderr, "    is less reliable than going by the central index, but might work better\n");
	fprintf(stderr, "    with corrupted or split archives.\n");
	fprintf(stderr, "Options for 'old' PKZIP encrypted files only:\n");
	fprintf(stderr, " -a <filename>   This is a 'known' ASCII file. This can be faster, IF all\n");
	fprintf(stderr, "    files are larger, and you KNOW that at least one of them starts out as\n");
	fprintf(stderr, "    'pure' ASCII data.\n");
	fprintf(stderr, " -o <filename>   Only use this file from the .zip file.\n");
	fprintf(stderr, " -c This will create a 'checksum only' hash.  If there are many encrypted\n");
	fprintf(stderr, "    files in the .zip file, then this may be an option, and there will be\n");
	fprintf(stderr, "    enough data that false positives will not be seen.  Up to " MAX_FILES " files are\n");
	fprintf(stderr, "    supported. These hashes do not reveal actual file data.\n");
	fprintf(stderr, " -m Use \"file magic\" as known-plain if applicable. This can be faster but\n");
	fprintf(stderr, "    not 100%% safe in all situations.\n");
	fprintf(stderr, "\nNOTE: By default it is assumed that all files in each archive have the same\n");
	fprintf(stderr, "password. If that's not the case, the produced hash may be uncrackable.\n");
	fprintf(stderr, "To avoid this, use -o option to pick a file at a time.\n");

	return EXIT_FAILURE;
}

int zip2john(int argc, char **argv)
{
	int c;
	int do_scan = 0;

	/* Parse command line */
	while ((c = getopt(argc, argv, "a:o:cm2s")) != -1) {
		switch (c) {
		case 'a':
			ascii_fname = optarg;
			fprintf(stderr, "Using file %s as an 'ASCII' quick check file\n", ascii_fname);
			break;
		case 'o':
			only_fname = optarg;
			fprintf(stderr, "Using file %s as only file to check\n", only_fname);
			break;
		case 'c':
			checksum_only = 1;
			fprintf(stderr, "Outputing hashes that are 'checksum ONLY' hashes\n");
			break;
		case 'm':
			use_magic = 1;
			fprintf(stderr, "Using file 'magic' signatures if applicable (not 100%% safe)\n");
			break;
		case 's':
			do_scan = 1;
			fprintf(stderr, "Scanning archive for local file headers\n");
			break;
		case '?':
		default:
			return usage(argv[0]);
		}
	}
	argc -= optind;
	if (argc == 0)
		return usage(argv[0]);
	argv += optind;

	while(argc--) {
		if (do_scan) {
			scan_from_start(*argv++);
		} else {
			scan_central_index(*argv++);
		}
	}

	cleanup_tiny_memory();

	return EXIT_SUCCESS;
}
